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]": { "[jinja-js]": {
"editor.tabSize": 2 "editor.tabSize": 2
}, }
} }

View File

@ -1,14 +1,16 @@
from wtforms import BooleanField, SelectField, SubmitField from flask_wtf import FlaskForm
from app.forms import NopaqueForm from wtforms import SelectField, SubmitField
from app.models import Role from app.models import Role
class UpdateUserForm(NopaqueForm): class UpdateUserForm(FlaskForm):
role = SelectField('Role') role = SelectField('Role')
submit = SubmitField() submit = SubmitField()
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
if 'data' not in kwargs: if 'data' not in kwargs:
kwargs['data'] = {'role': user.role.hashid} kwargs['data'] = {'role': user.role.hashid}
if 'prefix' not in kwargs:
kwargs['prefix'] = 'update-user-form'
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.role.choices = [(x.hashid, x.name) for x in Role.query.all()] 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 ( from wtforms import (
BooleanField, BooleanField,
PasswordField, PasswordField,
@ -6,11 +7,10 @@ from wtforms import (
ValidationError ValidationError
) )
from wtforms.validators import InputRequired, Email, EqualTo, Length, Regexp from wtforms.validators import InputRequired, Email, EqualTo, Length, Regexp
from app.forms import NopaqueForm
from app.models import User from app.models import User
class RegistrationForm(NopaqueForm): class RegistrationForm(FlaskForm):
email = StringField( email = StringField(
'Email', 'Email',
validators=[InputRequired(), Email(), Length(max=254)] validators=[InputRequired(), Email(), Length(max=254)]
@ -45,6 +45,11 @@ class RegistrationForm(NopaqueForm):
) )
submit = SubmitField() 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): def validate_email(self, field):
if User.query.filter_by(email=field.data.lower()).first(): if User.query.filter_by(email=field.data.lower()).first():
raise ValidationError('Email already registered') raise ValidationError('Email already registered')
@ -54,19 +59,29 @@ class RegistrationForm(NopaqueForm):
raise ValidationError('Username already in use') raise ValidationError('Username already in use')
class LoginForm(NopaqueForm): class LoginForm(FlaskForm):
user = StringField('Email or username', validators=[InputRequired()]) user = StringField('Email or username', validators=[InputRequired()])
password = PasswordField('Password', validators=[InputRequired()]) password = PasswordField('Password', validators=[InputRequired()])
remember_me = BooleanField('Keep me logged in') remember_me = BooleanField('Keep me logged in')
submit = SubmitField() 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()]) email = StringField('Email', validators=[InputRequired(), Email()])
submit = SubmitField() 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( password = PasswordField(
'New password', 'New password',
validators=[ validators=[
@ -82,3 +97,8 @@ class ResetPasswordForm(NopaqueForm):
] ]
) )
submit = SubmitField() 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 ( from wtforms import (
StringField, StringField,
SubmitField, SubmitField,
@ -5,10 +6,9 @@ from wtforms import (
IntegerField IntegerField
) )
from wtforms.validators import InputRequired, Length from wtforms.validators import InputRequired, Length
from app.forms import NopaqueForm
class ContributionBaseForm(NopaqueForm): class ContributionBaseForm(FlaskForm):
title = StringField( title = StringField(
'Title', 'Title',
validators=[InputRequired(), Length(max=64)] validators=[InputRequired(), Length(max=64)]
@ -43,5 +43,5 @@ class ContributionBaseForm(NopaqueForm):
submit = SubmitField() submit = SubmitField()
class EditContributionBaseForm(ContributionBaseForm): class UpdateContributionBaseForm(ContributionBaseForm):
pass pass

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ from app.models import TesseractOCRPipelineModel
from . import bp from . import bp
from .forms import ( from .forms import (
CreateTesseractOCRPipelineModelForm, CreateTesseractOCRPipelineModelForm,
EditTesseractOCRPipelineModelForm UpdateTesseractOCRPipelineModelForm
) )
from .utils import ( from .utils import (
tesseract_ocr_pipeline_model_dlc as tesseract_ocr_pipeline_model_dlc tesseract_ocr_pipeline_model_dlc as tesseract_ocr_pipeline_model_dlc
@ -62,7 +62,7 @@ def create_tesseract_ocr_pipeline_model():
@login_required @login_required
def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id): def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
topm = TesseractOCRPipelineModel.query.get_or_404(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(): if form.validate_on_submit():
form.populate_obj(topm) form.populate_obj(topm)
if db.session.is_modified(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 flask_wtf.file import FileField, FileRequired
from wtforms import ( from wtforms import (
StringField, StringField,
@ -6,10 +7,9 @@ from wtforms import (
IntegerField IntegerField
) )
from wtforms.validators import InputRequired, Length from wtforms.validators import InputRequired, Length
from app.forms import NopaqueForm
class CorpusFileBaseForm(NopaqueForm): class CorpusFileBaseForm(FlaskForm):
author = StringField( author = StringField(
'Author', 'Author',
validators=[InputRequired(), Length(max=255)] validators=[InputRequired(), Length(max=255)]
@ -41,6 +41,14 @@ class CreateCorpusFileForm(CorpusFileBaseForm):
if not field.data.filename.lower().endswith('.vrt'): if not field.data.filename.lower().endswith('.vrt'):
raise ValidationError('VRT files only!') 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): 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 import StringField, SubmitField, TextAreaField
from wtforms.validators import InputRequired, Length from wtforms.validators import InputRequired, Length
from app.forms import NopaqueForm
class CorpusBaseForm(NopaqueForm): class CorpusBaseForm(FlaskForm):
description = TextAreaField( description = TextAreaField(
'Description', 'Description',
validators=[InputRequired(), Length(max=255)] validators=[InputRequired(), Length(max=255)]
@ -13,12 +13,21 @@ class CorpusBaseForm(NopaqueForm):
class CreateCorpusForm(CorpusBaseForm): 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): 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): class ImportCorpusForm(FlaskForm):
pass 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_login import current_user
from flask_wtf.file import FileField, FileRequired from flask_wtf.file import FileField, FileRequired
from wtforms import ( from wtforms import (
@ -10,12 +11,11 @@ from wtforms import (
ValidationError ValidationError
) )
from wtforms.validators import InputRequired, Length from wtforms.validators import InputRequired, Length
from app.forms import NopaqueForm
from app.models import SpaCyNLPPipelineModel, TesseractOCRPipelineModel from app.models import SpaCyNLPPipelineModel, TesseractOCRPipelineModel
from . import SERVICES from . import SERVICES
class CreateJobBaseForm(NopaqueForm): class CreateJobBaseForm(FlaskForm):
description = StringField( description = StringField(
'Description', 'Description',
validators=[InputRequired(), Length(max=255)] validators=[InputRequired(), Length(max=255)]
@ -38,6 +38,8 @@ class CreateFileSetupPipelineJobForm(CreateJobBaseForm):
raise ValidationError('JPEG, PNG and TIFF files only!') raise ValidationError('JPEG, PNG and TIFF files only!')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-file-setup-pipeline-job-form'
service_manifest = SERVICES['file-setup-pipeline'] service_manifest = SERVICES['file-setup-pipeline']
version = kwargs.pop('version', service_manifest['latest_version']) version = kwargs.pop('version', service_manifest['latest_version'])
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -65,6 +67,8 @@ class CreateTesseractOCRPipelineJobForm(CreateJobBaseForm):
raise ValidationError('PDF files only!') raise ValidationError('PDF files only!')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-tesseract-ocr-pipeline-job-form'
service_manifest = SERVICES['tesseract-ocr-pipeline'] service_manifest = SERVICES['tesseract-ocr-pipeline']
version = kwargs.pop('version', service_manifest['latest_version']) version = kwargs.pop('version', service_manifest['latest_version'])
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -111,6 +115,8 @@ class CreateTranskribusHTRPipelineJobForm(CreateJobBaseForm):
raise ValidationError('PDF files only!') raise ValidationError('PDF files only!')
def __init__(self, *args, **kwargs): 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', []) transkribus_htr_pipeline_models = kwargs.pop('transkribus_htr_pipeline_models', [])
service_manifest = SERVICES['transkribus-htr-pipeline'] service_manifest = SERVICES['transkribus-htr-pipeline']
version = kwargs.pop('version', service_manifest['latest_version']) version = kwargs.pop('version', service_manifest['latest_version'])
@ -149,6 +155,8 @@ class CreateSpacyNLPPipelineJobForm(CreateJobBaseForm):
raise ValidationError('Plain text files only!') raise ValidationError('Plain text files only!')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = 'create-spacy-nlp-pipeline-job-form'
service_manifest = SERVICES['spacy-nlp-pipeline'] service_manifest = SERVICES['spacy-nlp-pipeline']
version = kwargs.pop('version', service_manifest['latest_version']) version = kwargs.pop('version', service_manifest['latest_version'])
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

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

View File

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

View File

@ -8,32 +8,6 @@ from app.models import Avatar, User, ProfilePrivacySettings
from . import bp 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']) @bp.route('/<hashid:user_id>/avatar', methods=['DELETE'])
@content_negotiation(produces='application/json') @content_negotiation(produces='application/json')
def delete_profile_avatar(user_id): def delete_profile_avatar(user_id):

View File

@ -91,7 +91,7 @@ class App {
.filter((operation) => {return subRegExp.test(operation.path);}); .filter((operation) => {return subRegExp.test(operation.path);});
for (let operation of subFilteredPatch) { for (let operation of subFilteredPatch) {
let [match, userId, jobId] = operation.path.match(subRegExp); 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 // Apply Patch

View File

@ -1,6 +1,6 @@
/***************************************************************************** /*****************************************************************************
* Users * * Settings *
* Fetch requests for /users routes * * Fetch requests for /settings routes *
*****************************************************************************/ *****************************************************************************/
Requests.settings = {}; 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.userId = listContainerElement.dataset.userId;
this.jobId = listContainerElement.dataset.jobId; this.jobId = listContainerElement.dataset.jobId;
if (this.userId === undefined || this.jobId === undefined) {return;} if (this.userId === undefined || this.jobId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId);
app.socket.on('PATCH', (patch) => {
if (this.isInitialized) {this.onPatch(patch);}
});
});
app.getUser(this.userId).then((user) => { app.getUser(this.userId).then((user) => {
this.add(Object.values(user.jobs[this.jobId].inputs)); this.add(Object.values(user.jobs[this.jobId].inputs));
this.isInitialized = true; this.isInitialized = true;

View File

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

View File

@ -28,38 +28,11 @@
<div class="corpus-list" data-user-id="{{ current_user.hashid }}"></div> <div class="corpus-list" data-user-id="{{ current_user.hashid }}"></div>
</div> </div>
<div class="card-action right-align"> <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> <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>
</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>
</div> </div>
{% endblock page_content %} {% endblock page_content %}

View File

@ -221,7 +221,7 @@ deleteAvatarButtonElement.addEventListener('click', () => {
}); });
document.querySelector('#delete-user').addEventListener('click', (event) => { 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 = '/';}); .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