Merge branch 'development'

This commit is contained in:
Patrick Jentsch 2022-11-24 12:29:28 +01:00
commit 903310c17f
21 changed files with 209 additions and 168 deletions

View File

@ -11,8 +11,3 @@ class AdminEditUserForm(FlaskForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
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()]
def prefill(self, user):
''' Pre-fill the form with data of an exististing user '''
self.confirmed.data = user.confirmed
self.role.data = user.role.hashid

View File

@ -30,7 +30,7 @@ def index():
@bp.route('/users') @bp.route('/users')
def users(): def users():
json_users = [x.to_json(backrefs=True) for x in User.query.all()] json_users = [x.to_json_serializeable(backrefs=True) for x in User.query.all()]
return render_template( return render_template(
'admin/users.html.j2', 'admin/users.html.j2',
json_users=json_users, json_users=json_users,
@ -48,15 +48,16 @@ def user(user_id):
def edit_user(user_id): def edit_user(user_id):
user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
admin_edit_user_form = AdminEditUserForm( admin_edit_user_form = AdminEditUserForm(
obj=user, data={'confirmed': user.confirmed, 'role': user.role.hashid},
prefix='admin-edit-user-form' prefix='admin-edit-user-form'
) )
edit_general_settings_form = EditGeneralSettingsForm( edit_general_settings_form = EditGeneralSettingsForm(
user, user,
obj=user, data=user.to_json_serializeable(),
prefix='edit-general-settings-form' prefix='edit-general-settings-form'
) )
edit_notification_settings_form = EditNotificationSettingsForm( edit_notification_settings_form = EditNotificationSettingsForm(
data=user.to_json_serializeable(),
prefix='edit-notification-settings-form' prefix='edit-notification-settings-form'
) )
if (admin_edit_user_form.submit.data if (admin_edit_user_form.submit.data
@ -83,7 +84,6 @@ def edit_user(user_id):
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.edit_user', user_id=user.id)) return redirect(url_for('.edit_user', user_id=user.id))
edit_notification_settings_form.prefill(user)
return render_template( return render_template(
'admin/edit_user.html.j2', 'admin/edit_user.html.j2',
admin_edit_user_form=admin_edit_user_form, admin_edit_user_form=admin_edit_user_form,

View File

@ -51,7 +51,7 @@ def tesseract_ocr_pipeline_models():
def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id): def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id) tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
form = EditTesseractOCRPipelineModelForm( form = EditTesseractOCRPipelineModelForm(
obj=tesseract_ocr_pipeline_model, data=tesseract_ocr_pipeline_model.to_json_serializeable(),
prefix='edit-tesseract-ocr-pipeline-model-form' prefix='edit-tesseract-ocr-pipeline-model-form'
) )
if form.validate_on_submit(): if form.validate_on_submit():
@ -148,7 +148,7 @@ def spacy_nlp_pipeline_models():
def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id): def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id):
spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id) spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
form = EditSpaCyNLPPipelineModelForm( form = EditSpaCyNLPPipelineModelForm(
obj=spacy_nlp_pipeline_model, data=spacy_nlp_pipeline_model.to_json_serializeable(),
prefix='edit-spacy-nlp-pipeline-model-form' prefix='edit-spacy-nlp-pipeline-model-form'
) )
if form.validate_on_submit(): if form.validate_on_submit():

View File

@ -54,7 +54,6 @@ def convert_corpus(json_corpus, user, corpus_dir):
user=user, user=user,
creation_date=datetime.fromtimestamp(json_corpus['creation_date']), creation_date=datetime.fromtimestamp(json_corpus['creation_date']),
description=json_corpus['description'], description=json_corpus['description'],
last_edited_date=datetime.fromtimestamp(json_corpus['last_edited_date']),
title=json_corpus['title'] title=json_corpus['title']
) )
db.session.add(corpus) db.session.add(corpus)

View File

@ -48,16 +48,6 @@ def corpus(corpus_id):
corpus = Corpus.query.get_or_404(corpus_id) corpus = Corpus.query.get_or_404(corpus_id)
if not (corpus.user == current_user or current_user.is_administrator()): if not (corpus.user == current_user or current_user.is_administrator()):
abort(403) abort(403)
# cool = False
# if corpus.is_public:
# cool = True
# elif current_user.is_authenticated:
# if corpus.user == current_user or current_user.is_administrator:
# cool = True
# else:
# abort(403)
# else:
# return current_app.login_manager.unauthorized()
return render_template( return render_template(
'corpora/corpus.html.j2', 'corpora/corpus.html.j2',
corpus=corpus, corpus=corpus,
@ -176,7 +166,10 @@ def corpus_file(corpus_id, corpus_file_id):
abort(404) abort(404)
if not (corpus_file.corpus.user == current_user or current_user.is_administrator()): if not (corpus_file.corpus.user == current_user or current_user.is_administrator()):
abort(403) abort(403)
form = EditCorpusFileForm(obj=corpus_file, prefix='edit-corpus-file-form') form = EditCorpusFileForm(
data=corpus_file.to_json_serializeable(),
prefix='edit-corpus-file-form'
)
if form.validate_on_submit(): if form.validate_on_submit():
form.populate_obj(corpus_file) form.populate_obj(corpus_file)
if db.session.is_modified(corpus_file): if db.session.is_modified(corpus_file):

View File

@ -109,7 +109,7 @@ def _create_job_service(job):
job.status = JobStatus.FAILED job.status = JobStatus.FAILED
return return
models_mount_source = model.path models_mount_source = model.path
models_mount_target = f'/usr/local/share/tessdata/{model.filename}' models_mount_target = f'/usr/local/share/tessdata/{model.id}.traineddata'
models_mount = f'{models_mount_source}:{models_mount_target}:ro' models_mount = f'{models_mount_source}:{models_mount_target}:ro'
mounts.append(models_mount) mounts.append(models_mount)
elif job.service == 'spacy-nlp-pipeline': elif job.service == 'spacy-nlp-pipeline':

View File

@ -74,17 +74,12 @@ class FileMixin:
''' '''
creation_date = db.Column(db.DateTime, default=datetime.utcnow) creation_date = db.Column(db.DateTime, default=datetime.utcnow)
filename = db.Column(db.String(255)) filename = db.Column(db.String(255))
last_edited_date = db.Column(db.DateTime)
mimetype = db.Column(db.String(255)) mimetype = db.Column(db.String(255))
def file_mixin_to_json(self, backrefs=False, relationships=False): def file_mixin_to_json_serializeable(self, backrefs=False, relationships=False):
return { return {
'creation_date': f'{self.creation_date.isoformat()}Z', 'creation_date': f'{self.creation_date.isoformat()}Z',
'filename': self.filename, 'filename': self.filename,
'last_edited_date': (
None if self.last_edited_date is None
else f'{self.last_edited_date.isoformat()}Z'
),
'mimetype': self.mimetype 'mimetype': self.mimetype
} }
@ -186,19 +181,19 @@ class Role(HashidMixin, db.Model):
def reset_permissions(self): def reset_permissions(self):
self.permissions = 0 self.permissions = 0
def to_json(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
_json = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
'default': self.default, 'default': self.default,
'name': self.name, 'name': self.name,
'permissions': self.permissions 'permissions': self.permissions
} }
if relationships: if relationships:
_json['users'] = { json_serializeable['users'] = {
x.hashid: x.to_json(relationships=True) x.hashid: x.to_json_serializeable(relationships=True)
for x in self.users for x in self.users
} }
return _json return json_serializeable
@staticmethod @staticmethod
def insert_defaults(): def insert_defaults():
@ -486,8 +481,8 @@ class User(HashidMixin, UserMixin, db.Model):
return False return False
return check_password_hash(self.password_hash, password) return check_password_hash(self.password_hash, password)
def to_json(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
_json = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
'confirmed': self.confirmed, 'confirmed': self.confirmed,
'email': self.email, 'email': self.email,
@ -497,31 +492,30 @@ class User(HashidMixin, UserMixin, db.Model):
), ),
'member_since': f'{self.member_since.isoformat()}Z', 'member_since': f'{self.member_since.isoformat()}Z',
'username': self.username, 'username': self.username,
'settings': {
'job_status_mail_notification_level': \ 'job_status_mail_notification_level': \
self.setting_job_status_mail_notification_level.name self.setting_job_status_mail_notification_level.name
} }
}
if backrefs: if backrefs:
_json['role'] = self.role.to_json(backrefs=True) json_serializeable['role'] = \
self.role.to_json_serializeable(backrefs=True)
if relationships: if relationships:
_json['corpora'] = { json_serializeable['corpora'] = {
x.hashid: x.to_json(relationships=True) x.hashid: x.to_json_serializeable(relationships=True)
for x in self.corpora for x in self.corpora
} }
_json['jobs'] = { json_serializeable['jobs'] = {
x.hashid: x.to_json(relationships=True) x.hashid: x.to_json_serializeable(relationships=True)
for x in self.jobs for x in self.jobs
} }
_json['tesseract_ocr_pipeline_models'] = { json_serializeable['tesseract_ocr_pipeline_models'] = {
x.hashid: x.to_json(relationships=True) x.hashid: x.to_json_serializeable(relationships=True)
for x in self.tesseract_ocr_pipeline_models for x in self.tesseract_ocr_pipeline_models
} }
_json['spacy_nlp_pipeline_models'] = { json_serializeable['spacy_nlp_pipeline_models'] = {
x.hashid: x.to_json(relationships=True) x.hashid: x.to_json_serializeable(relationships=True)
for x in self.spacy_nlp_pipeline_models for x in self.spacy_nlp_pipeline_models
} }
return _json return json_serializeable
class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model): class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model):
__tablename__ = 'tesseract_ocr_pipeline_models' __tablename__ = 'tesseract_ocr_pipeline_models'
@ -626,8 +620,8 @@ class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model):
current_app.logger.error(e) current_app.logger.error(e)
db.session.delete(self) db.session.delete(self)
def to_json(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
_json = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
'compatible_service_versions': self.compatible_service_versions, 'compatible_service_versions': self.compatible_service_versions,
'description': self.description, 'description': self.description,
@ -638,11 +632,12 @@ class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model):
'shared': self.shared, 'shared': self.shared,
'title': self.title, 'title': self.title,
'version': self.version, 'version': self.version,
**self.file_mixin_to_json() **self.file_mixin_to_json_serializeable()
} }
if backrefs: if backrefs:
_json['user'] = self.user.to_json(backrefs=True) json_serializeable['user'] = \
return _json self.user.to_json_serializeable(backrefs=True)
return json_serializeable
class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model): class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model):
@ -751,8 +746,8 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model):
current_app.logger.error(e) current_app.logger.error(e)
db.session.delete(self) db.session.delete(self)
def to_json(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
_json = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
'compatible_service_versions': self.compatible_service_versions, 'compatible_service_versions': self.compatible_service_versions,
'description': self.description, 'description': self.description,
@ -764,11 +759,11 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model):
'shared': self.shared, 'shared': self.shared,
'title': self.title, 'title': self.title,
'version': self.version, 'version': self.version,
**self.file_mixin_to_json() **self.file_mixin_to_json_serializeable()
} }
if backrefs: if backrefs:
_json['user'] = self.user.to_json(backrefs=True) json_serializeable['user'] = self.user.to_json_serializeable(backrefs=True)
return _json return json_serializeable
class JobInput(FileMixin, HashidMixin, db.Model): class JobInput(FileMixin, HashidMixin, db.Model):
@ -814,14 +809,15 @@ class JobInput(FileMixin, HashidMixin, db.Model):
def user_id(self): def user_id(self):
return self.job.user_id return self.job.user_id
def to_json(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
_json = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
**self.file_mixin_to_json() **self.file_mixin_to_json_serializeable()
} }
if backrefs: if backrefs:
_json['job'] = self.job.to_json(backrefs=True) json_serializeable['job'] = \
return _json self.job.to_json_serializeable(backrefs=True)
return json_serializeable
class JobResult(FileMixin, HashidMixin, db.Model): class JobResult(FileMixin, HashidMixin, db.Model):
@ -869,18 +865,19 @@ class JobResult(FileMixin, HashidMixin, db.Model):
def user_id(self): def user_id(self):
return self.job.user_id return self.job.user_id
def to_json(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
_json = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
'description': self.description, 'description': self.description,
**self.file_mixin_to_json( **self.file_mixin_to_json_serializeable(
backrefs=backrefs, backrefs=backrefs,
relationships=relationships relationships=relationships
) )
} }
if backrefs: if backrefs:
_json['job'] = self.job.to_json(backrefs=True) json_serializeable['job'] = \
return _json self.job.to_json_serializeable(backrefs=True)
return json_serializeable
class Job(HashidMixin, db.Model): class Job(HashidMixin, db.Model):
@ -988,8 +985,8 @@ class Job(HashidMixin, db.Model):
self.end_date = None self.end_date = None
self.status = JobStatus.SUBMITTED self.status = JobStatus.SUBMITTED
def to_json(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
_json = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
'creation_date': f'{self.creation_date.isoformat()}Z', 'creation_date': f'{self.creation_date.isoformat()}Z',
'description': self.description, 'description': self.description,
@ -1005,17 +1002,18 @@ class Job(HashidMixin, db.Model):
'url': self.url 'url': self.url
} }
if backrefs: if backrefs:
_json['user'] = self.user.to_json(backrefs=True) json_serializeable['user'] = \
self.user.to_json_serializeable(backrefs=True)
if relationships: if relationships:
_json['inputs'] = { json_serializeable['inputs'] = {
x.hashid: x.to_json(relationships=True) x.hashid: x.to_json_serializeable(relationships=True)
for x in self.inputs for x in self.inputs
} }
_json['results'] = { json_serializeable['results'] = {
x.hashid: x.to_json(relationships=True) x.hashid: x.to_json_serializeable(relationships=True)
for x in self.results for x in self.results
} }
return _json return json_serializeable
class CorpusFile(FileMixin, HashidMixin, db.Model): class CorpusFile(FileMixin, HashidMixin, db.Model):
@ -1079,8 +1077,8 @@ class CorpusFile(FileMixin, HashidMixin, db.Model):
db.session.delete(self) db.session.delete(self)
self.corpus.status = CorpusStatus.UNPREPARED self.corpus.status = CorpusStatus.UNPREPARED
def to_json(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
_json = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
'url': self.url, 'url': self.url,
'address': self.address, 'address': self.address,
@ -1095,14 +1093,15 @@ class CorpusFile(FileMixin, HashidMixin, db.Model):
'publishing_year': self.publishing_year, 'publishing_year': self.publishing_year,
'school': self.school, 'school': self.school,
'title': self.title, 'title': self.title,
**self.file_mixin_to_json( **self.file_mixin_to_json_serializeable(
backrefs=backrefs, backrefs=backrefs,
relationships=relationships relationships=relationships
) )
} }
if backrefs: if backrefs:
_json['corpus'] = self.corpus.to_json(backrefs=True) json_serializeable['corpus'] = \
return _json self.corpus.to_json_serializeable(backrefs=True)
return json_serializeable
class Corpus(HashidMixin, db.Model): class Corpus(HashidMixin, db.Model):
''' '''
@ -1116,7 +1115,6 @@ class Corpus(HashidMixin, db.Model):
# Fields # Fields
creation_date = db.Column(db.DateTime(), default=datetime.utcnow) creation_date = db.Column(db.DateTime(), default=datetime.utcnow)
description = db.Column(db.String(255)) description = db.Column(db.String(255))
last_edited_date = db.Column(db.DateTime())
status = db.Column( status = db.Column(
IntEnumColumn(CorpusStatus), IntEnumColumn(CorpusStatus),
default=CorpusStatus.UNPREPARED default=CorpusStatus.UNPREPARED
@ -1210,15 +1208,14 @@ class Corpus(HashidMixin, db.Model):
os.path.join(self.path, 'cwb', 'corpus.vrt'), os.path.join(self.path, 'cwb', 'corpus.vrt'),
encoding='utf-8' encoding='utf-8'
) )
self.last_edited_date = datetime.utcnow()
self.status = CorpusStatus.SUBMITTED self.status = CorpusStatus.SUBMITTED
def delete(self): def delete(self):
shutil.rmtree(self.path, ignore_errors=True) shutil.rmtree(self.path, ignore_errors=True)
db.session.delete(self) db.session.delete(self)
def to_json(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
_json = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
'creation_date': f'{self.creation_date.isoformat()}Z', 'creation_date': f'{self.creation_date.isoformat()}Z',
'description': self.description, 'description': self.description,
@ -1226,21 +1223,17 @@ class Corpus(HashidMixin, db.Model):
'num_analysis_sessions': self.num_analysis_sessions, 'num_analysis_sessions': self.num_analysis_sessions,
'num_tokens': self.num_tokens, 'num_tokens': self.num_tokens,
'status': self.status.name, 'status': self.status.name,
'last_edited_date': (
None if self.last_edited_date is None
else f'{self.last_edited_date.isoformat()}Z'
),
'title': self.title, 'title': self.title,
'is_public': self.is_public 'is_public': self.is_public
} }
if backrefs: if backrefs:
_json['user'] = self.user.to_json(backrefs=True) json_serializeable['user'] = self.user.to_json_serializeable(backrefs=True)
if relationships: if relationships:
_json['files'] = { json_serializeable['files'] = {
x.hashid: x.to_json(relationships=True) x.hashid: x.to_json_serializeable(relationships=True)
for x in self.files for x in self.files
} }
return _json return json_serializeable
# endregion models # endregion models
@ -1248,8 +1241,6 @@ class Corpus(HashidMixin, db.Model):
# event_handlers # # event_handlers #
############################################################################## ##############################################################################
# region event_handlers # region event_handlers
@db.event.listens_for(Corpus, 'after_delete') @db.event.listens_for(Corpus, 'after_delete')
@db.event.listens_for(CorpusFile, 'after_delete') @db.event.listens_for(CorpusFile, 'after_delete')
@db.event.listens_for(Job, 'after_delete') @db.event.listens_for(Job, 'after_delete')
@ -1269,7 +1260,7 @@ def ressource_after_delete(mapper, connection, ressource):
@db.event.listens_for(JobInput, 'after_insert') @db.event.listens_for(JobInput, 'after_insert')
@db.event.listens_for(JobResult, 'after_insert') @db.event.listens_for(JobResult, 'after_insert')
def ressource_after_insert_handler(mapper, connection, ressource): def ressource_after_insert_handler(mapper, connection, ressource):
value = ressource.to_json() value = ressource.to_json_serializeable()
for attr in mapper.relationships: for attr in mapper.relationships:
value[attr.key] = {} value[attr.key] = {}
jsonpatch = [ jsonpatch = [

View File

@ -96,7 +96,3 @@ class EditNotificationSettingsForm(FlaskForm):
(x.name, x.name.capitalize()) (x.name, x.name.capitalize())
for x in UserSettingJobStatusMailNotificationLevel for x in UserSettingJobStatusMailNotificationLevel
] ]
def prefill(self, user):
self.job_status_mail_notification_level.data = \
user.setting_job_status_mail_notification_level.name

View File

@ -19,10 +19,11 @@ def settings():
) )
edit_general_settings_form = EditGeneralSettingsForm( edit_general_settings_form = EditGeneralSettingsForm(
current_user, current_user,
obj=current_user, data=current_user.to_json_serializeable(),
prefix='edit-general-settings-form' prefix='edit-general-settings-form'
) )
edit_notification_settings_form = EditNotificationSettingsForm( edit_notification_settings_form = EditNotificationSettingsForm(
data=current_user.to_json_serializeable(),
prefix='edit-notification-settings-form' prefix='edit-notification-settings-form'
) )
@ -48,7 +49,6 @@ def settings():
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.settings')) return redirect(url_for('.settings'))
edit_notification_settings_form.prefill(current_user)
return render_template( return render_template(
'settings/settings.html.j2', 'settings/settings.html.j2',
change_password_form=change_password_form, change_password_form=change_password_form,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -16,10 +16,12 @@ class App {
this.data.promises.getUser[userId] = new Promise((resolve, reject) => { this.data.promises.getUser[userId] = new Promise((resolve, reject) => {
fetch(`/users/${userId}?backrefs=true&relationships=true`, {headers: {Accept: 'application/json'}}) fetch(`/users/${userId}?backrefs=true&relationships=true`, {headers: {Accept: 'application/json'}})
.then( .then(
(response) => {return response.json();},
(response) => { (response) => {
if (response.status === 403) {this.flash('Forbidden', 'error');} if (response.status === 403) {this.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {this.flash('Not Found', 'error');} return response.json();
},
(response) => {
this.flash('Something went wrong', 'error');
reject(response); reject(response);
} }
) )

View File

@ -18,7 +18,6 @@ class CorpusDisplay extends RessourceDisplay {
let corpus = user.corpora[this.corpusId]; let corpus = user.corpora[this.corpusId];
this.setCreationDate(corpus.creation_date); this.setCreationDate(corpus.creation_date);
this.setDescription(corpus.description); this.setDescription(corpus.description);
this.setLastEditedDate(corpus.last_edited_date);
this.setStatus(corpus.status); this.setStatus(corpus.status);
this.setTitle(corpus.title); this.setTitle(corpus.title);
this.setNumTokens(corpus.num_tokens); this.setNumTokens(corpus.num_tokens);
@ -37,11 +36,6 @@ class CorpusDisplay extends RessourceDisplay {
break; break;
} }
case 'replace': { case 'replace': {
let re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/last_edited_date$`);
if (re.test(operation.path)) {
this.setLastEditedDate(operation.value);
break;
}
re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/num_tokens`); re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/num_tokens`);
if (re.test(operation.path)) { if (re.test(operation.path)) {
this.setNumTokens(operation.value); this.setNumTokens(operation.value);
@ -113,11 +107,4 @@ class CorpusDisplay extends RessourceDisplay {
new Date(creationDate).toLocaleString("en-US") new Date(creationDate).toLocaleString("en-US")
); );
} }
setLastEditedDate(lastEditedDate) {
this.setElements(
this.displayElement.querySelectorAll('.corpus-end-date'),
new Date(lastEditedDate).toLocaleString("en-US")
);
}
} }

View File

@ -86,6 +86,11 @@ class SpaCyNLPPipelineModelList extends RessourceList {
init(user) { init(user) {
this._init(user.spacy_nlp_pipeline_models); this._init(user.spacy_nlp_pipeline_models);
if (user.role.name !== ('Administrator' || 'Contributor')) {
for (let switchElement of this.listjs.list.querySelectorAll('.shared')) {
switchElement.setAttribute('disabled', '');
}
}
} }
_init(ressources) { _init(ressources) {
@ -96,7 +101,13 @@ class SpaCyNLPPipelineModelList extends RessourceList {
} }
onClick(event) { onClick(event) {
if (event.target.closest('.action-switch')) {return;} if (event.target.closest('.action-switch')) {
let userRole = app.data.users[this.userId].role.name;
if (userRole !== ('Administrator' || 'Contributor')) {
app.flash('You need the "Contributor" or "Administrator" role to perform this action.', 'error');
}
return;
}
let actionButtonElement = event.target.closest('.action-button'); let actionButtonElement = event.target.closest('.action-button');
let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action; let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
let spaCyNLPPipelineModelElement = event.target.closest('tr'); let spaCyNLPPipelineModelElement = event.target.closest('tr');

View File

@ -86,6 +86,11 @@ class TesseractOCRPipelineModelList extends RessourceList {
init (user) { init (user) {
this._init(user.tesseract_ocr_pipeline_models); this._init(user.tesseract_ocr_pipeline_models);
if (user.role.name !== ('Administrator' || 'Contributor')) {
for (let switchElement of this.listjs.list.querySelectorAll('.shared')) {
switchElement.setAttribute('disabled', '');
}
}
} }
_init(ressources) { _init(ressources) {
@ -96,7 +101,13 @@ class TesseractOCRPipelineModelList extends RessourceList {
} }
onClick(event) { onClick(event) {
if (event.target.closest('.action-switch')) {return;} if (event.target.closest('.action-switch')) {
let userRole = app.data.users[this.userId].role.name;
if (userRole !== ('Administrator' || 'Contributor')) {
app.flash('You need the "Contributor" or "Administrator" role to perform this action.', 'error');
}
return;
}
let actionButtonElement = event.target.closest('.action-button'); let actionButtonElement = event.target.closest('.action-button');
let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action; let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.action;
let tesseractOCRPipelineModelElement = event.target.closest('tr'); let tesseractOCRPipelineModelElement = event.target.closest('tr');

View File

@ -12,13 +12,14 @@ class Utils {
fetch(`/corpora/${corpus.id}/build`, {method: 'POST', headers: {Accept: 'application/json'}}) fetch(`/corpora/${corpus.id}/build`, {method: 'POST', headers: {Accept: 'application/json'}})
.then( .then(
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);}
app.flash(`Corpus "${corpus.title}" marked for building`, 'corpus'); app.flash(`Corpus "${corpus.title}" marked for building`, 'corpus');
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
if (response.status === 404) {app.flash('Not Found', 'error');}
if (response.status === 409) {app.flash('Conflict', 'error');}
reject(response); reject(response);
} }
); );
@ -61,12 +62,13 @@ class Utils {
fetch(`/corpora/${corpus.id}`, {method: 'DELETE', headers: {Accept: 'application/json'}}) fetch(`/corpora/${corpus.id}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then( .then(
(response) => { (response) => {
app.flash(`Corpus "${corpusTitle}" marked for deletion`, 'corpus'); if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Corpus "${corpus.title}" marked for deletion`, 'corpus');
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
if (response.status === 404) {app.flash('Not Found', 'error');}
reject(response); reject(response);
} }
); );
@ -112,12 +114,13 @@ class Utils {
fetch(`/corpora/${corpusId}/files/${corpusFileId}`, {method: 'DELETE', headers: {Accept: 'application/json'}}) fetch(`/corpora/${corpusId}/files/${corpusFileId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then( .then(
(response) => { (response) => {
app.flash(`Corpus file "${corpusFileTitle}" marked for deletion`, 'corpus'); if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Corpus File "${corpusFileTitle}" deleted`, 'corpus');
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
if (response.status === 404) {app.flash('Not Found', 'error');}
reject(response); reject(response);
} }
); );
@ -160,12 +163,13 @@ class Utils {
fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}`, {method: 'DELETE'}) fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}`, {method: 'DELETE'})
.then( .then(
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`SpaCy NLP Pipeline Model "${spaCyNLPPipelineModelTitle}" marked for deletion`); app.flash(`SpaCy NLP Pipeline Model "${spaCyNLPPipelineModelTitle}" marked for deletion`);
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
if (response.status === 404) {app.flash('Not Found', 'error');}
reject(response); reject(response);
} }
); );
@ -208,12 +212,13 @@ class Utils {
fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}`, {method: 'DELETE'}) fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}`, {method: 'DELETE'})
.then( .then(
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Tesseract OCR Pipeline Model "${tesseractOCRPipelineModelTitle}" marked for deletion`); app.flash(`Tesseract OCR Pipeline Model "${tesseractOCRPipelineModelTitle}" marked for deletion`);
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
if (response.status === 404) {app.flash('Not Found', 'error');}
reject(response); reject(response);
} }
); );
@ -258,12 +263,13 @@ class Utils {
fetch(`/jobs/${job.id}`, {method: 'DELETE', headers: {Accept: 'application/json'}}) fetch(`/jobs/${job.id}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then( .then(
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Job "${jobTitle}" marked for deletion`, 'job'); app.flash(`Job "${jobTitle}" marked for deletion`, 'job');
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
if (response.status === 404) {app.flash('Not Found', 'error');}
reject(response); reject(response);
} }
); );
@ -279,12 +285,12 @@ class Utils {
fetch(`/jobs/${job.id}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}}) fetch(`/jobs/${job.id}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}})
.then( .then(
(response) => { (response) => {
resolve(response); if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
return response.text(); return response.text();
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
if (response.status === 404) {app.flash('Not Found', 'error');}
reject(response); reject(response);
} }
) )
@ -314,6 +320,7 @@ class Utils {
} }
); );
modal.open(); modal.open();
resolve(text);
} }
); );
}); });
@ -355,13 +362,14 @@ class Utils {
fetch(`/jobs/${job.id}/restart`, {method: 'POST', headers: {Accept: 'application/json'}}) fetch(`/jobs/${job.id}/restart`, {method: 'POST', headers: {Accept: 'application/json'}})
.then( .then(
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);}
app.flash(`Job "${jobTitle}" restarted.`, 'job'); app.flash(`Job "${jobTitle}" restarted.`, 'job');
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
if (response.status === 404) {app.flash('Not Found', 'error');}
if (response.status === 409) {app.flash('Conflict', 'error');}
reject(response); reject(response);
} }
); );
@ -406,12 +414,13 @@ class Utils {
fetch(`/users/${user.id}`, {method: 'DELETE', headers: {Accept: 'application/json'}}) fetch(`/users/${user.id}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then( .then(
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`User "${userName}" marked for deletion`); app.flash(`User "${userName}" marked for deletion`);
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
if (response.status === 404) {app.flash('Not Found', 'error');}
reject(response); reject(response);
} }
); );
@ -432,11 +441,12 @@ class Utils {
fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModel.id}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}}) fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModel.id}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}})
.then( .then(
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
app.flash(msg); app.flash(msg);
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
reject(response); reject(response);
} }
); );
@ -455,11 +465,12 @@ class Utils {
fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModel.id}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}}) fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModel.id}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}})
.then( .then(
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
app.flash(msg); app.flash(msg);
resolve(response); resolve(response);
}, },
(response) => { (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error');} app.flash('Something went wrong', 'error');
reject(response); reject(response);
} }
); );

View File

@ -48,13 +48,6 @@
</div> </div>
</div> </div>
<div class="col s12 m6">
<div class="input-field">
<input class="corpus-last-edited-date validate" disabled id="corpus-last-edited-date" type="text">
<label for="corpus-last-edited-date">Last edited</label>
</div>
</div>
<div class="col s12 m6"> <div class="col s12 m6">
<div class="input-field"> <div class="input-field">
<input class="corpus-token-ratio validate" disabled id="corpus-token-ratio" type="text"> <input class="corpus-token-ratio validate" disabled id="corpus-token-ratio" type="text">

View File

@ -15,7 +15,7 @@ def user(user_id):
backrefs = request.args.get('backrefs', 'false').lower() == 'true' backrefs = request.args.get('backrefs', 'false').lower() == 'true'
relationships = ( relationships = (
request.args.get('relationships', 'false').lower() == 'true') request.args.get('relationships', 'false').lower() == 'true')
return user.to_json(backrefs=backrefs, relationships=relationships), 200 return user.to_json_serializeable(backrefs=backrefs, relationships=relationships), 200
@bp.route('/<hashid:user_id>', methods=['DELETE']) @bp.route('/<hashid:user_id>', methods=['DELETE'])

View File

@ -21,6 +21,7 @@ def upgrade():
def downgrade(): def downgrade():
op.add_column('transkribus_htr_models', op.add_column(
'transkribus_htr_models',
sa.Column('transkribus_name', sa.String(length=64), autoincrement=False, nullable=True) sa.Column('transkribus_name', sa.String(length=64), autoincrement=False, nullable=True)
) )

View File

@ -0,0 +1,51 @@
"""Remove last_edited_date column from all tables
Revision ID: 31b9c0259e6b
Revises: 89e9526089bf
Create Date: 2022-11-24 09:53:21.025531
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '31b9c0259e6b'
down_revision = '89e9526089bf'
branch_labels = None
depends_on = None
def upgrade():
op.drop_column('corpora', 'last_edited_date')
op.drop_column('corpus_files', 'last_edited_date')
op.drop_column('job_inputs', 'last_edited_date')
op.drop_column('job_results', 'last_edited_date')
op.drop_column('spacy_nlp_pipeline_models', 'last_edited_date')
op.drop_column('tesseract_ocr_pipeline_models', 'last_edited_date')
def downgrade():
op.add_column(
'tesseract_ocr_pipeline_models',
sa.Column('last_edited_date', sa.DateTime(), nullable=True)
)
op.add_column(
'spacy_nlp_pipeline_models',
sa.Column('last_edited_date', sa.DateTime(), nullable=True)
)
op.add_column(
'job_results',
sa.Column('last_edited_date', sa.DateTime(), nullable=True)
)
op.add_column(
'job_inputs',
sa.Column('last_edited_date', sa.DateTime(), nullable=True)
)
op.add_column(
'corpus_files',
sa.Column('last_edited_date', sa.DateTime(), nullable=True)
)
op.add_column(
'corpora',
sa.Column('last_edited_date', sa.DateTime(), nullable=True)
)

View File

@ -5,7 +5,6 @@ Revises: 260b57d5f4e7
Create Date: 2022-10-11 14:32:13.227364 Create Date: 2022-10-11 14:32:13.227364
""" """
from genericpath import isdir
from alembic import op from alembic import op
import os import os
from app.models import User from app.models import User

View File

@ -21,6 +21,7 @@ def upgrade():
def downgrade(): def downgrade():
op.add_column('users', op.add_column(
'users',
sa.Column('setting_dark_mode', sa.Boolean(), nullable=True) sa.Column('setting_dark_mode', sa.Boolean(), nullable=True)
) )