Merge branch 'public-corpus' of gitlab.ub.uni-bielefeld.de:sfb1288inf/nopaque into public-corpus

This commit is contained in:
Inga Kirschnick 2023-03-31 11:47:52 +02:00
commit 451a0a3955
25 changed files with 182 additions and 132 deletions

View File

@ -18,5 +18,5 @@
},
"[jinja-js]": {
"editor.tabSize": 2
},
}
}

View File

@ -1,14 +1,16 @@
from wtforms import BooleanField, SelectField, SubmitField
from app.forms import NopaqueForm
from flask_wtf import FlaskForm
from wtforms import SelectField, SubmitField
from app.models import Role
class UpdateUserForm(NopaqueForm):
class UpdateUserForm(FlaskForm):
role = SelectField('Role')
submit = SubmitField()
def __init__(self, user, *args, **kwargs):
if 'data' not in kwargs:
kwargs['data'] = {'role': user.role.hashid}
if 'prefix' not in kwargs:
kwargs['prefix'] = 'update-user-form'
super().__init__(*args, **kwargs)
self.role.choices = [(x.hashid, x.name) for x in Role.query.all()]

View File

@ -1,3 +1,4 @@
from flask_wtf import FlaskForm
from wtforms import (
BooleanField,
PasswordField,
@ -6,11 +7,10 @@ from wtforms import (
ValidationError
)
from wtforms.validators import InputRequired, Email, EqualTo, Length, Regexp
from app.forms import NopaqueForm
from app.models import User
class RegistrationForm(NopaqueForm):
class RegistrationForm(FlaskForm):
email = StringField(
'Email',
validators=[InputRequired(), Email(), Length(max=254)]
@ -45,6 +45,11 @@ class RegistrationForm(NopaqueForm):
)
submit = SubmitField()
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'registration-form'
super().__init__(*args, **kwargs)
def validate_email(self, field):
if User.query.filter_by(email=field.data.lower()).first():
raise ValidationError('Email already registered')
@ -54,19 +59,29 @@ class RegistrationForm(NopaqueForm):
raise ValidationError('Username already in use')
class LoginForm(NopaqueForm):
class LoginForm(FlaskForm):
user = StringField('Email or username', validators=[InputRequired()])
password = PasswordField('Password', validators=[InputRequired()])
remember_me = BooleanField('Keep me logged in')
submit = SubmitField()
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'login-form'
super().__init__(*args, **kwargs)
class ResetPasswordRequestForm(NopaqueForm):
class ResetPasswordRequestForm(FlaskForm):
email = StringField('Email', validators=[InputRequired(), Email()])
submit = SubmitField()
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'reset-password-request-form'
super().__init__(*args, **kwargs)
class ResetPasswordForm(NopaqueForm):
class ResetPasswordForm(FlaskForm):
password = PasswordField(
'New password',
validators=[
@ -82,3 +97,8 @@ class ResetPasswordForm(NopaqueForm):
]
)
submit = SubmitField()
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'reset-password-form'
super().__init__(*args, **kwargs)

View File

@ -1,3 +1,4 @@
from flask_wtf import FlaskForm
from wtforms import (
StringField,
SubmitField,
@ -5,10 +6,9 @@ from wtforms import (
IntegerField
)
from wtforms.validators import InputRequired, Length
from app.forms import NopaqueForm
class ContributionBaseForm(NopaqueForm):
class ContributionBaseForm(FlaskForm):
title = StringField(
'Title',
validators=[InputRequired(), Length(max=64)]
@ -43,5 +43,5 @@ class ContributionBaseForm(NopaqueForm):
submit = SubmitField()
class EditContributionBaseForm(ContributionBaseForm):
class UpdateContributionBaseForm(ContributionBaseForm):
pass

View File

@ -2,7 +2,7 @@ from flask_wtf.file import FileField, FileRequired
from wtforms import StringField, ValidationError
from wtforms.validators import InputRequired, Length
from app.services import SERVICES
from ..forms import ContributionBaseForm, EditContributionBaseForm
from ..forms import ContributionBaseForm, UpdateContributionBaseForm
class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm):
@ -20,6 +20,8 @@ class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm):
raise ValidationError('.tar.gz files only!')
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-spacy-nlp-pipeline-model-form'
super().__init__(*args, **kwargs)
service_manifest = SERVICES['spacy-nlp-pipeline']
self.compatible_service_versions.choices = [('', 'Choose your option')]
@ -29,12 +31,14 @@ class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm):
self.compatible_service_versions.default = ''
class EditSpaCyNLPPipelineModelForm(EditContributionBaseForm):
class UpdateSpaCyNLPPipelineModelForm(UpdateContributionBaseForm):
pipeline_name = StringField(
'Pipeline name',
validators=[InputRequired(), Length(max=64)]
)
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'edit-spacy-nlp-pipeline-model-form'
super().__init__(*args, **kwargs)
service_manifest = SERVICES['spacy-nlp-pipeline']
self.compatible_service_versions.choices = [('', 'Choose your option')]

View File

@ -6,7 +6,7 @@ from app.models import SpaCyNLPPipelineModel
from . import bp
from .forms import (
CreateSpaCyNLPPipelineModelForm,
EditSpaCyNLPPipelineModelForm
UpdateSpaCyNLPPipelineModelForm
)
from .utils import (
spacy_nlp_pipeline_model_dlc as spacy_nlp_pipeline_model_dlc
@ -63,7 +63,7 @@ def create_spacy_nlp_pipeline_model():
@login_required
def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id):
snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
form = EditSpaCyNLPPipelineModelForm(data=snpm.to_json_serializeable())
form = UpdateSpaCyNLPPipelineModelForm(data=snpm.to_json_serializeable())
if form.validate_on_submit():
form.populate_obj(snpm)
if db.session.is_modified(snpm):

View File

@ -1,7 +1,7 @@
from flask_wtf.file import FileField, FileRequired
from wtforms import ValidationError
from app.services import SERVICES
from ..forms import ContributionBaseForm, EditContributionBaseForm
from ..forms import ContributionBaseForm, UpdateContributionBaseForm
class CreateTesseractOCRPipelineModelForm(ContributionBaseForm):
@ -15,6 +15,8 @@ class CreateTesseractOCRPipelineModelForm(ContributionBaseForm):
raise ValidationError('traineddata files only!')
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-tesseract-ocr-pipeline-model-form'
service_manifest = SERVICES['tesseract-ocr-pipeline']
super().__init__(*args, **kwargs)
self.compatible_service_versions.choices = [('', 'Choose your option')]
@ -24,8 +26,10 @@ class CreateTesseractOCRPipelineModelForm(ContributionBaseForm):
self.compatible_service_versions.default = ''
class EditTesseractOCRPipelineModelForm(EditContributionBaseForm):
class UpdateTesseractOCRPipelineModelForm(UpdateContributionBaseForm):
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'edit-tesseract-ocr-pipeline-model-form'
service_manifest = SERVICES['tesseract-ocr-pipeline']
super().__init__(*args, **kwargs)
self.compatible_service_versions.choices = [('', 'Choose your option')]

View File

@ -6,7 +6,7 @@ from app.models import TesseractOCRPipelineModel
from . import bp
from .forms import (
CreateTesseractOCRPipelineModelForm,
EditTesseractOCRPipelineModelForm
UpdateTesseractOCRPipelineModelForm
)
from .utils import (
tesseract_ocr_pipeline_model_dlc as tesseract_ocr_pipeline_model_dlc
@ -62,7 +62,7 @@ def create_tesseract_ocr_pipeline_model():
@login_required
def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
form = EditTesseractOCRPipelineModelForm(data=topm.to_json_serializeable())
form = UpdateTesseractOCRPipelineModelForm(data=topm.to_json_serializeable())
if form.validate_on_submit():
form.populate_obj(topm)
if db.session.is_modified(topm):

View File

@ -1,3 +1,4 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired
from wtforms import (
StringField,
@ -6,10 +7,9 @@ from wtforms import (
IntegerField
)
from wtforms.validators import InputRequired, Length
from app.forms import NopaqueForm
class CorpusFileBaseForm(NopaqueForm):
class CorpusFileBaseForm(FlaskForm):
author = StringField(
'Author',
validators=[InputRequired(), Length(max=255)]
@ -41,6 +41,14 @@ class CreateCorpusFileForm(CorpusFileBaseForm):
if not field.data.filename.lower().endswith('.vrt'):
raise ValidationError('VRT files only!')
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-corpus-file-form'
super().__init__(*args, **kwargs)
class UpdateCorpusFileForm(CorpusFileBaseForm):
pass
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'update-corpus-file-form'
super().__init__(*args, **kwargs)

View File

@ -1,9 +1,9 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField
from wtforms.validators import InputRequired, Length
from app.forms import NopaqueForm
class CorpusBaseForm(NopaqueForm):
class CorpusBaseForm(FlaskForm):
description = TextAreaField(
'Description',
validators=[InputRequired(), Length(max=255)]
@ -13,12 +13,21 @@ class CorpusBaseForm(NopaqueForm):
class CreateCorpusForm(CorpusBaseForm):
pass
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-corpus-form'
super().__init__(*args, **kwargs)
class UpdateCorpusForm(CorpusBaseForm):
pass
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'update-corpus-form'
super().__init__(*args, **kwargs)
class ImportCorpusForm(NopaqueForm):
pass
class ImportCorpusForm(FlaskForm):
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'import-corpus-form'
super().__init__(*args, **kwargs)

View File

@ -1,26 +0,0 @@
from flask_wtf import FlaskForm
from wtforms.validators import ValidationError
import re
form_prefix_pattern = re.compile(r'(?<!^)(?=[A-Z])')
def LimitFileSize(max_size_mb):
max_size_b = max_size_mb * 1024 * 1024
def file_length_check(form, field):
if len(field.data.read()) >= max_size_b:
raise ValidationError(
f'File size must be less or equal than {max_size_mb} MB'
)
field.data.seek(0)
return file_length_check
class NopaqueForm(FlaskForm):
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = \
form_prefix_pattern.sub('-', self.__class__.__name__).lower()
super().__init__(*args, **kwargs)

View File

@ -1,3 +1,4 @@
from flask_wtf import FlaskForm
from flask_login import current_user
from flask_wtf.file import FileField, FileRequired
from wtforms import (
@ -10,12 +11,11 @@ from wtforms import (
ValidationError
)
from wtforms.validators import InputRequired, Length
from app.forms import NopaqueForm
from app.models import SpaCyNLPPipelineModel, TesseractOCRPipelineModel
from . import SERVICES
class CreateJobBaseForm(NopaqueForm):
class CreateJobBaseForm(FlaskForm):
description = StringField(
'Description',
validators=[InputRequired(), Length(max=255)]
@ -38,6 +38,8 @@ class CreateFileSetupPipelineJobForm(CreateJobBaseForm):
raise ValidationError('JPEG, PNG and TIFF files only!')
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-file-setup-pipeline-job-form'
service_manifest = SERVICES['file-setup-pipeline']
version = kwargs.pop('version', service_manifest['latest_version'])
super().__init__(*args, **kwargs)
@ -65,6 +67,8 @@ class CreateTesseractOCRPipelineJobForm(CreateJobBaseForm):
raise ValidationError('PDF files only!')
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-tesseract-ocr-pipeline-job-form'
service_manifest = SERVICES['tesseract-ocr-pipeline']
version = kwargs.pop('version', service_manifest['latest_version'])
super().__init__(*args, **kwargs)
@ -111,6 +115,8 @@ class CreateTranskribusHTRPipelineJobForm(CreateJobBaseForm):
raise ValidationError('PDF files only!')
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-transkribus-htr-pipeline-job-form'
transkribus_htr_pipeline_models = kwargs.pop('transkribus_htr_pipeline_models', [])
service_manifest = SERVICES['transkribus-htr-pipeline']
version = kwargs.pop('version', service_manifest['latest_version'])
@ -149,6 +155,8 @@ class CreateSpacyNLPPipelineJobForm(CreateJobBaseForm):
raise ValidationError('Plain text files only!')
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-spacy-nlp-pipeline-job-form'
service_manifest = SERVICES['spacy-nlp-pipeline']
version = kwargs.pop('version', service_manifest['latest_version'])
super().__init__(*args, **kwargs)

View File

@ -35,7 +35,7 @@ def file_setup_pipeline():
version = request.args.get('version', service_manifest['latest_version'])
if version not in service_manifest['versions']:
abort(404)
form = CreateFileSetupPipelineJobForm(version=version)
form = CreateFileSetupPipelineJobForm(prefix='create-job-form', version=version)
if form.is_submitted():
if not form.validate():
response = {'errors': form.errors}
@ -77,7 +77,7 @@ def tesseract_ocr_pipeline():
version = request.args.get('version', service_manifest['latest_version'])
if version not in service_manifest['versions']:
abort(404)
form = CreateTesseractOCRPipelineJobForm(version=version)
form = CreateTesseractOCRPipelineJobForm(prefix='create-job-form', version=version)
if form.is_submitted():
if not form.validate():
response = {'errors': form.errors}
@ -137,8 +137,8 @@ def transkribus_htr_pipeline():
abort(500)
transkribus_htr_pipeline_models = r.json()['trpModelMetadata']
transkribus_htr_pipeline_models.append({'modelId': 48513, 'name': 'Caroline Minuscle', 'language': 'lat', 'isoLanguages': ['lat']})
print(transkribus_htr_pipeline_models[len(transkribus_htr_pipeline_models)-1])
form = CreateTranskribusHTRPipelineJobForm(
prefix='create-job-form',
transkribus_htr_pipeline_models=transkribus_htr_pipeline_models,
version=version
)
@ -186,7 +186,7 @@ def spacy_nlp_pipeline():
version = request.args.get('version', SERVICES[service]['latest_version'])
if version not in service_manifest['versions']:
abort(404)
form = CreateSpacyNLPPipelineJobForm(version=version)
form = CreateSpacyNLPPipelineJobForm(prefix='create-job-form', version=version)
spacy_nlp_pipeline_models = SpaCyNLPPipelineModel.query.all()
if form.is_submitted():
if not form.validate():

View File

@ -1,4 +1,5 @@
from flask_login import current_user
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired
from wtforms import (
PasswordField,
@ -15,11 +16,11 @@ from wtforms.validators import (
Length,
Regexp
)
from app.forms import NopaqueForm, LimitFileSize
from app.models import User, UserSettingJobStatusMailNotificationLevel
from app.wtforms.validators import FileSize
class UpdateAccountInformationForm(NopaqueForm):
class UpdateAccountInformationForm(FlaskForm):
email = StringField(
'E-Mail',
validators=[DataRequired(), Length(max=254), Email()]
@ -43,6 +44,8 @@ class UpdateAccountInformationForm(NopaqueForm):
def __init__(self, *args, user=current_user, **kwargs):
if 'data' not in kwargs:
kwargs['data'] = user.to_json_serializeable()
if 'prefix' not in kwargs:
kwargs['prefix'] = 'update-account-information-form'
super().__init__(*args, **kwargs)
self.user = user
@ -57,7 +60,7 @@ class UpdateAccountInformationForm(NopaqueForm):
raise ValidationError('Username already in use')
class UpdateProfileInformationForm(NopaqueForm):
class UpdateProfileInformationForm(FlaskForm):
full_name = StringField(
'Full name',
validators=[Length(max=128)]
@ -91,11 +94,13 @@ class UpdateProfileInformationForm(NopaqueForm):
def __init__(self, *args, user=current_user, **kwargs):
if 'data' not in kwargs:
kwargs['data'] = user.to_json_serializeable()
if 'prefix' not in kwargs:
kwargs['prefix'] = 'update-profile-information-form'
super().__init__(*args, **kwargs)
class UpdateAvatarForm(NopaqueForm):
avatar = FileField('File', validators=[LimitFileSize(2)])
class UpdateAvatarForm(FlaskForm):
avatar = FileField('File', validators=[FileRequired(), FileSize(2)])
submit = SubmitField()
def validate_avatar(self, field):
@ -103,7 +108,13 @@ class UpdateAvatarForm(NopaqueForm):
if field.data.mimetype not in valid_mimetypes:
raise ValidationError('JPEG and PNG files only!')
class UpdatePasswordForm(NopaqueForm):
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'update-avatar-form'
super().__init__(*args, **kwargs)
class UpdatePasswordForm(FlaskForm):
password = PasswordField('Old password', validators=[DataRequired()])
new_password = PasswordField(
'New password',
@ -122,6 +133,8 @@ class UpdatePasswordForm(NopaqueForm):
submit = SubmitField()
def __init__(self, *args, user=current_user, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'update-password-form'
super().__init__(*args, **kwargs)
self.user = user
@ -130,7 +143,7 @@ class UpdatePasswordForm(NopaqueForm):
raise ValidationError('Invalid password')
class UpdateNotificationsForm(NopaqueForm):
class UpdateNotificationsForm(FlaskForm):
job_status_mail_notification_level = SelectField(
'Job status mail notification level',
choices=[
@ -144,4 +157,6 @@ class UpdateNotificationsForm(NopaqueForm):
def __init__(self, *args, user=current_user, **kwargs):
if 'data' not in kwargs:
kwargs['data'] = user.to_json_serializeable()
if 'prefix' not in kwargs:
kwargs['prefix'] = 'update-notifications-form'
super().__init__(*args, **kwargs)

View File

@ -8,32 +8,6 @@ from app.models import Avatar, User, ProfilePrivacySettings
from . import bp
@bp.route('/<hashid:user_id>', methods=['DELETE'])
@login_required
@content_negotiation(produces='application/json')
def delete_user(user_id):
def _delete_user(app, user_id):
with app.app_context():
user = User.query.get(user_id)
user.delete()
db.session.commit()
user = User.query.get_or_404(user_id)
if not (user == current_user or current_user.is_administrator()):
abort(403)
thread = Thread(
target=_delete_user,
args=(current_app._get_current_object(), user_id)
)
if user == current_user:
logout_user()
thread.start()
response_data = {
'message': f'User "{user.username}" marked for deletion'
}
return response_data, 202
@bp.route('/<hashid:user_id>/avatar', methods=['DELETE'])
@content_negotiation(produces='application/json')
def delete_profile_avatar(user_id):

View File

@ -91,7 +91,7 @@ class App {
.filter((operation) => {return subRegExp.test(operation.path);});
for (let operation of subFilteredPatch) {
let [match, userId, jobId] = operation.path.match(subRegExp);
this.flash(`[<a href="/jobs/${jobId}">${this.data.users[userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
this.flash(`[<a href="/jobs/${jobId}">${this.data.users[userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-status="${operation.value}"></span>`, 'job');
}
// Apply Patch

View File

@ -1,6 +1,6 @@
/*****************************************************************************
* Users *
* Fetch requests for /users routes *
* Settings *
* Fetch requests for /settings routes *
*****************************************************************************/
Requests.settings = {};

View File

@ -0,0 +1,15 @@
/*****************************************************************************
* Users *
* Fetch requests for /users routes *
*****************************************************************************/
Requests.users = {};
Requests.users.entity = {};
Requests.settings.entity.delete = (userId) => {
let input = `/users/${userId}`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
};

View File

@ -12,11 +12,7 @@ class JobInputList extends ResourceList {
this.userId = listContainerElement.dataset.userId;
this.jobId = listContainerElement.dataset.jobId;
if (this.userId === undefined || this.jobId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => {
app.socket.on('PATCH', (patch) => {
if (this.isInitialized) {this.onPatch(patch);}
});
});
app.subscribeUser(this.userId);
app.getUser(this.userId).then((user) => {
this.add(Object.values(user.jobs[this.jobId].inputs));
this.isInitialized = true;

View File

@ -66,7 +66,8 @@
'js/Requests/corpora/files.js',
'js/Requests/corpora/followers.js',
'js/Requests/jobs/jobs.js',
'js/Requests/settings/settings.js'
'js/Requests/settings/settings.js',
'js/Requests/users/users.js'
%}
<script src="{{ ASSET_URL }}"></script>
{%- endassets %}

View File

@ -28,38 +28,11 @@
<div class="corpus-list" data-user-id="{{ current_user.hashid }}"></div>
</div>
<div class="card-action right-align">
<a class="waves-effect waves-light btn" href="{{ url_for('corpora.import_corpus') }}">Import Corpus<i class="material-icons right">import_export</i></a>
<a class="btn disabled waves-effect waves-light" href="{{ url_for('corpora.import_corpus') }}">Import Corpus<i class="material-icons right">import_export</i></a>
<a class="btn waves-effect waves-light" href="{{ url_for('corpora.create_corpus') }}">Create corpus<i class="material-icons right">add</i></a>
</div>
</div>
</div>
<div class="col s12 query-result-list" data-user-id="{{ current_user.hashid }}" id="query-results">
<h2>My query results</h2>
<div class="card">
<div class="card-content">
<div class="input-field">
<i class="material-icons prefix">search</i>
<input id="search-query-results" class="search" type="search"></input>
<label for="search-query-results">Search query result</label>
</div>
<table class="highlight">
<thead>
<tr>
<th>Title and Description</th>
<th>Corpus and Query</th>
<th></th>
</tr>
</thead>
<tbody class="list"></tbody>
</table>
<ul class="pagination"></ul>
</div>
<div class="card-action right-align">
<a class="btn waves-effect waves-light disabled">Add query result<i class="material-icons right">file_upload</i></a>
</div>
</div>
</div>
</div>
</div>
{% endblock page_content %}

View File

@ -221,7 +221,7 @@ deleteAvatarButtonElement.addEventListener('click', () => {
});
document.querySelector('#delete-user').addEventListener('click', (event) => {
Requests.settings.entity.delete({{ user.hashid|tojson }})
Requests.users.entity.delete({{ user.hashid|tojson }})
.then((response) => {window.location.href = '/';});
});

33
app/users/json_routes.py Normal file
View File

@ -0,0 +1,33 @@
from flask import abort, current_app
from flask_login import current_user, login_required, logout_user
from threading import Thread
from app import db
from app.decorators import content_negotiation
from app.models import User
from . import bp
@bp.route('/<hashid:user_id>', methods=['DELETE'])
@login_required
@content_negotiation(produces='application/json')
def delete_user(user_id):
def _delete_user(app, user_id):
with app.app_context():
user = User.query.get(user_id)
user.delete()
db.session.commit()
user = User.query.get_or_404(user_id)
if not (user == current_user or current_user.is_administrator()):
abort(403)
thread = Thread(
target=_delete_user,
args=(current_app._get_current_object(), user_id)
)
if user == current_user:
logout_user()
thread.start()
response_data = {
'message': f'User "{user.username}" marked for deletion'
}
return response_data, 202

0
app/wtforms/__init__.py Normal file
View File

14
app/wtforms/validators.py Normal file
View File

@ -0,0 +1,14 @@
from wtforms.validators import ValidationError
def FileSize(max_size_mb):
max_size_b = max_size_mb * 1024 * 1024
def file_length_check(form, field):
if len(field.data.read()) >= max_size_b:
raise ValidationError(
f'File size must be less or equal than {max_size_mb} MB'
)
field.data.seek(0)
return file_length_check