diff --git a/app/contributions/__init__.py b/app/contributions/__init__.py index af9747a67b39250b7dd0a857948fe915462af504..5175c0ce7d5c6b907a1cd0fe1a6f3df140f580b4 100644 --- a/app/contributions/__init__.py +++ b/app/contributions/__init__.py @@ -3,3 +3,15 @@ from flask import Blueprint bp = Blueprint('contributions', __name__) from . import routes + +from .spacy_nlp_pipeline_models import bp as spacy_nlp_pipeline_models_bp +bp.register_blueprint( + spacy_nlp_pipeline_models_bp, + url_prefix='/spacy-nlp-pipeline-models' +) + +from .tesseract_ocr_pipeline_models import bp as tesseract_ocr_pipeline_models_bp +bp.register_blueprint( + tesseract_ocr_pipeline_models_bp, + url_prefix='/tesseract-ocr-pipeline-models' +) diff --git a/app/contributions/forms.py b/app/contributions/forms.py index eb25babbe74d6161c71d1154f5eca9c311199782..acec307f00b4e409ff962cc7e0021973a442f2fe 100644 --- a/app/contributions/forms.py +++ b/app/contributions/forms.py @@ -1,16 +1,11 @@ -from flask import current_app from flask_wtf import FlaskForm -from flask_wtf.file import FileField, FileRequired from wtforms import ( - BooleanField, StringField, SubmitField, SelectMultipleField, - IntegerField, - ValidationError + IntegerField ) from wtforms.validators import InputRequired, Length -from app.services import SERVICES class ContributionBaseForm(FlaskForm): @@ -48,74 +43,5 @@ class ContributionBaseForm(FlaskForm): submit = SubmitField() -class CreateTesseractOCRPipelineModelForm(ContributionBaseForm): - tesseract_model_file = FileField( - 'File', - validators=[FileRequired()] - ) - - def validate_tesseract_model_file(self, field): - if not field.data.filename.lower().endswith('.traineddata'): - raise ValidationError('traineddata files only!') - - def __init__(self, *args, **kwargs): - service_manifest = SERVICES['tesseract-ocr-pipeline'] - super().__init__(*args, **kwargs) - self.compatible_service_versions.choices = [('', 'Choose your option')] - self.compatible_service_versions.choices += [ - (x, x) for x in service_manifest['versions'].keys() - ] - self.compatible_service_versions.default = '' - - -class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm): - spacy_model_file = FileField( - 'File', - validators=[FileRequired()] - ) - pipeline_name = StringField( - 'Pipeline name', - validators=[InputRequired(), Length(max=64)] - ) - - def validate_spacy_model_file(self, field): - if not field.data.filename.lower().endswith('.tar.gz'): - raise ValidationError('.tar.gz files only!') - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - service_manifest = SERVICES['spacy-nlp-pipeline'] - self.compatible_service_versions.choices = [('', 'Choose your option')] - self.compatible_service_versions.choices += [ - (x, x) for x in service_manifest['versions'].keys() - ] - self.compatible_service_versions.default = '' - - class EditContributionBaseForm(ContributionBaseForm): pass - -class EditTesseractOCRPipelineModelForm(EditContributionBaseForm): - def __init__(self, *args, **kwargs): - service_manifest = SERVICES['tesseract-ocr-pipeline'] - super().__init__(*args, **kwargs) - self.compatible_service_versions.choices = [('', 'Choose your option')] - self.compatible_service_versions.choices += [ - (x, x) for x in service_manifest['versions'].keys() - ] - self.compatible_service_versions.default = '' - - -class EditSpaCyNLPPipelineModelForm(EditContributionBaseForm): - pipeline_name = StringField( - 'Pipeline name', - validators=[InputRequired(), Length(max=64)] - ) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - service_manifest = SERVICES['spacy-nlp-pipeline'] - self.compatible_service_versions.choices = [('', 'Choose your option')] - self.compatible_service_versions.choices += [ - (x, x) for x in service_manifest['versions'].keys() - ] - self.compatible_service_versions.default = '' diff --git a/app/contributions/routes.py b/app/contributions/routes.py index f9e4d2c5a0d428c822bf66506dece49b027e5955..6d8b9cc3ab62b318b9c9a61fe4442e2698c9ec87 100644 --- a/app/contributions/routes.py +++ b/app/contributions/routes.py @@ -1,369 +1,12 @@ -from flask import ( - abort, - current_app, - flash, - jsonify, - Markup, - redirect, - render_template, - request, - url_for -) -from flask_login import login_required, current_user -from threading import Thread -from app import db -from app.decorators import content_negotiation, permission_required -from app.models import SpaCyNLPPipelineModel, TesseractOCRPipelineModel +from flask import render_template +from flask_login import login_required from . import bp -from .forms import ( - CreateSpaCyNLPPipelineModelForm, - CreateTesseractOCRPipelineModelForm, - EditSpaCyNLPPipelineModelForm, - EditTesseractOCRPipelineModelForm -) - - -@bp.before_request -@login_required -def before_request(): - pass @bp.route('/') +@login_required def contributions(): return render_template( 'contributions/contributions.html.j2', title='Contributions' ) - - -############################################################################## -# SpaCy NLP Pipeline Models # -############################################################################## -#region spacy-nlp-pipeline-models -@bp.route('/spacy-nlp-pipeline-models') -def spacy_nlp_pipeline_models(): - return render_template( - 'contributions/spacy_nlp_pipeline_models.html.j2', - title='SpaCy NLP Pipeline Models' - ) - - -@bp.route('/spacy-nlp-pipeline-models/create', methods=['GET', 'POST']) -def create_spacy_nlp_pipeline_model(): - form = CreateSpaCyNLPPipelineModelForm(prefix='create-spacy-nlp-pipeline-model-form') - if form.is_submitted(): - if not form.validate(): - response = {'errors': form.errors} - return response, 400 - try: - spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.create( - form.spacy_model_file.data, - compatible_service_versions=form.compatible_service_versions.data, - description=form.description.data, - pipeline_name=form.pipeline_name.data, - publisher=form.publisher.data, - publisher_url=form.publisher_url.data, - publishing_url=form.publishing_url.data, - publishing_year=form.publishing_year.data, - is_public=False, - title=form.title.data, - version=form.version.data, - user=current_user - ) - except OSError: - abort(500) - db.session.commit() - spacy_nlp_pipeline_model_url = url_for( - '.spacy_nlp_pipeline_model', - spacy_nlp_pipeline_model_id=spacy_nlp_pipeline_model.id - ) - message = Markup(f'SpaCy NLP Pipeline model "<a href="{spacy_nlp_pipeline_model_url}">{spacy_nlp_pipeline_model.title}</a>" created') - flash(message) - return {}, 201, {'Location': spacy_nlp_pipeline_model_url} - return render_template( - 'contributions/create_spacy_nlp_pipeline_model.html.j2', - form=form, - title='Create SpaCy NLP Pipeline Model' - ) - - -@bp.route('/spacy-nlp-pipeline-models/<hashid:spacy_nlp_pipeline_model_id>', methods=['GET', 'POST']) -def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id): - spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id) - form = EditSpaCyNLPPipelineModelForm( - data=spacy_nlp_pipeline_model.to_json_serializeable(), - prefix='edit-spacy-nlp-pipeline-model-form' - ) - if form.validate_on_submit(): - form.populate_obj(spacy_nlp_pipeline_model) - if db.session.is_modified(spacy_nlp_pipeline_model): - message = Markup(f'SpaCy NLP Pipeline model "<a href="{spacy_nlp_pipeline_model.url}">{spacy_nlp_pipeline_model.title}</a>" updated') - flash(message) - db.session.commit() - return redirect(url_for('.spacy_nlp_pipeline_models')) - return render_template( - 'contributions/spacy_nlp_pipeline_model.html.j2', - form=form, - spacy_nlp_pipeline_model=spacy_nlp_pipeline_model, - title=f'{spacy_nlp_pipeline_model.title} {spacy_nlp_pipeline_model.version}' - ) - - -#region json-routes -@bp.route('/spacy-nlp-pipeline-models/<hashid:spacy_nlp_pipeline_model_id>', methods=['DELETE']) -@login_required -@content_negotiation(produces='application/json') -def delete_spacy_model(spacy_nlp_pipeline_model_id): - def _delete_spacy_model(app, spacy_nlp_pipeline_model_id): - with app.app_context(): - snpm = SpaCyNLPPipelineModel.query.get(spacy_nlp_pipeline_model_id) - snpm.delete() - db.session.commit() - - snpm = SpaCyNLPPipelineModel.query.get(spacy_nlp_pipeline_model_id) - if snpm is None: - resonse_data = { - 'message': ( - 'SpaCy NLP Pipeline Model with id' - f' "{spacy_nlp_pipeline_model_id}" not found' - ), - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 404 - return response - if not (snpm.user == current_user or current_user.is_administrator()): - resonse_data = { - 'message': f'You are not allowed to delete "{snpm.title}"', - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 403 - return response - thread = Thread( - target=_delete_spacy_model, - args=(current_app._get_current_object(), snpm.id) - ) - thread.start() - resonse_data = { - 'message': \ - f'SpaCy NLP Pipeline Model "{snpm.title}" marked for deletion' - } - response = jsonify(resonse_data) - response.status_code = 202 - return response - - -@bp.route('/spacy-nlp-pipeline-models/<hashid:spacy_nlp_pipeline_model_id>/is_public', methods=['PUT']) -@login_required -@permission_required('CONTRIBUTE') -@content_negotiation(consumes='application/json', produces='application/json') -def update_spacy_nlp_pipeline_model_is_public(spacy_nlp_pipeline_model_id): - is_public = request.json - if not isinstance(is_public, bool): - resonse_data = { - 'message': 'Request body must be a boolean', - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 400 - return response - snpm = SpaCyNLPPipelineModel.query.get(spacy_nlp_pipeline_model_id) - if snpm is None: - resonse_data = { - 'message': ( - 'SpaCy NLP Pipeline Model with id' - f' "{spacy_nlp_pipeline_model_id}" not found' - ), - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 404 - return response - if not (snpm.user == current_user or current_user.is_administrator()): - resonse_data = { - 'message': f'You are not allowed to delete "{snpm.title}"', - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 403 - return response - snpm.is_public = is_public - db.session.commit() - response_data = { - 'message': ( - f'SpaCy NLP Pipeline Model "{snpm.title}"' - f' is now {"public" if is_public else "private"}' - ) - } - response = jsonify(response_data) - response.status_code = 200 - return response -#endregion json-routes -#endregion spacy-nlp-pipeline-models - -############################################################################## -# Tesseract OCR Pipeline Models # -############################################################################## -#region tesseract-ocr-pipeline-models -@bp.route('/tesseract-ocr-pipeline-models') -def tesseract_ocr_pipeline_models(): - return render_template( - 'contributions/tesseract_ocr_pipeline_models.html.j2', - title='Tesseract OCR Pipeline Models' - ) - - -@bp.route('/tesseract-ocr-pipeline-models/create', methods=['GET', 'POST']) -def create_tesseract_ocr_pipeline_model(): - form = CreateTesseractOCRPipelineModelForm(prefix='create-tesseract-ocr-pipeline-model-form') - if form.is_submitted(): - if not form.validate(): - response = {'errors': form.errors} - return response, 400 - try: - tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.create( - form.tesseract_model_file.data, - compatible_service_versions=form.compatible_service_versions.data, - description=form.description.data, - publisher=form.publisher.data, - publisher_url=form.publisher_url.data, - publishing_url=form.publishing_url.data, - publishing_year=form.publishing_year.data, - is_public=False, - title=form.title.data, - version=form.version.data, - user=current_user - ) - except OSError: - abort(500) - db.session.commit() - tesseract_ocr_pipeline_model_url = url_for( - '.tesseract_ocr_pipeline_model', - tesseract_ocr_pipeline_model_id=tesseract_ocr_pipeline_model.id - ) - message = Markup(f'Tesseract OCR Pipeline model "<a href="{tesseract_ocr_pipeline_model_url}">{tesseract_ocr_pipeline_model.title}</a>" created') - flash(message) - return {}, 201, {'Location': tesseract_ocr_pipeline_model_url} - return render_template( - 'contributions/create_tesseract_ocr_pipeline_model.html.j2', - form=form, - title='Create Tesseract OCR Pipeline Model' - ) - - -@bp.route('/tesseract-ocr-pipeline-models/<hashid:tesseract_ocr_pipeline_model_id>', methods=['GET', 'POST']) -def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id): - tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id) - form = EditTesseractOCRPipelineModelForm( - data=tesseract_ocr_pipeline_model.to_json_serializeable(), - prefix='edit-tesseract-ocr-pipeline-model-form' - ) - if form.validate_on_submit(): - form.populate_obj(tesseract_ocr_pipeline_model) - if db.session.is_modified(tesseract_ocr_pipeline_model): - message = Markup(f'Tesseract OCR Pipeline model "<a href="{tesseract_ocr_pipeline_model.url}">{tesseract_ocr_pipeline_model.title}</a>" updated') - flash(message) - db.session.commit() - return redirect(url_for('.tesseract_ocr_pipeline_models')) - return render_template( - 'contributions/tesseract_ocr_pipeline_model.html.j2', - form=form, - tesseract_ocr_pipeline_model=tesseract_ocr_pipeline_model, - title=f'{tesseract_ocr_pipeline_model.title} {tesseract_ocr_pipeline_model.version}' - ) - - -#region json-routes -@bp.route('/tesseract-ocr-pipeline-models/<hashid:tesseract_ocr_pipeline_model_id>', methods=['DELETE']) -@login_required -@content_negotiation(produces='application/json') -def delete_tesseract_model(tesseract_ocr_pipeline_model_id): - def _delete_tesseract_ocr_pipeline_model(app, tesseract_ocr_pipeline_model_id): - with app.app_context(): - topm = TesseractOCRPipelineModel.query.get(tesseract_ocr_pipeline_model_id) - topm.delete() - db.session.commit() - - topm = TesseractOCRPipelineModel.query.get(tesseract_ocr_pipeline_model_id) - if topm is None: - resonse_data = { - 'message': ( - 'Tesseract OCR Pipeline Model with id' - f' "{tesseract_ocr_pipeline_model_id}" not found' - ), - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 404 - return response - if not (topm.user == current_user or current_user.is_administrator()): - resonse_data = { - 'message': f'You are not allowed to delete "{topm.title}"', - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 403 - return response - thread = Thread( - target=_delete_tesseract_ocr_pipeline_model, - args=(current_app._get_current_object(), topm.id) - ) - thread.start() - resonse_data = { - 'message': \ - f'Tesseract OCR Pipeline Model "{topm.title}" marked for deletion' - } - response = jsonify(resonse_data) - response.status_code = 202 - return response - - -@bp.route('/tesseract-ocr-pipeline-models/<hashid:tesseract_ocr_pipeline_model_id>/is_public', methods=['PUT']) -@login_required -@permission_required('CONTRIBUTE') -@content_negotiation(consumes='application/json', produces='application/json') -def update_tesseract_ocr_pipeline_model_is_public(tesseract_ocr_pipeline_model_id): - is_public = request.json - if not isinstance(is_public, bool): - resonse_data = { - 'message': 'Request body must be a boolean', - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 400 - return response - topm = TesseractOCRPipelineModel.query.get(tesseract_ocr_pipeline_model_id) - if topm is None: - resonse_data = { - 'message': ( - 'Tesseract OCR Pipeline Model with id' - f' "{tesseract_ocr_pipeline_model_id}" not found' - ), - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 404 - return response - if not (topm.user == current_user or current_user.is_administrator()): - resonse_data = { - 'message': f'You are not allowed to delete "{topm.title}"', - 'category': 'error' - } - response = jsonify(resonse_data) - response.status_code = 403 - return response - topm.is_public = is_public - db.session.commit() - response_data = { - 'message': ( - f'Tesseract OCR Pipeline Model "{topm.title}"' - f' is now {"public" if is_public else "private"}' - ) - } - response = jsonify(response_data) - response.status_code = 200 - return response -#endregion json-routes -#endregion tesseract-ocr-pipeline-models diff --git a/app/contributions/spacy_nlp_pipeline_models/__init__.py b/app/contributions/spacy_nlp_pipeline_models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6b73681a167c86724af39c199069732b0c7e56ac --- /dev/null +++ b/app/contributions/spacy_nlp_pipeline_models/__init__.py @@ -0,0 +1,8 @@ +from flask import Blueprint + + +TEMPLATE_FOLDER = 'contributions/spacy_nlp_pipeline_models' + + +bp = Blueprint('spacy_nlp_pipeline_models', __name__) +from . import routes, json_routes diff --git a/app/contributions/spacy_nlp_pipeline_models/forms.py b/app/contributions/spacy_nlp_pipeline_models/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..2670c1d1c0ba126c1eabf911b09475eda53c3799 --- /dev/null +++ b/app/contributions/spacy_nlp_pipeline_models/forms.py @@ -0,0 +1,44 @@ +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 + + +class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm): + spacy_model_file = FileField( + 'File', + validators=[FileRequired()] + ) + pipeline_name = StringField( + 'Pipeline name', + validators=[InputRequired(), Length(max=64)] + ) + + def validate_spacy_model_file(self, field): + if not field.data.filename.lower().endswith('.tar.gz'): + raise ValidationError('.tar.gz files only!') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + service_manifest = SERVICES['spacy-nlp-pipeline'] + self.compatible_service_versions.choices = [('', 'Choose your option')] + self.compatible_service_versions.choices += [ + (x, x) for x in service_manifest['versions'].keys() + ] + self.compatible_service_versions.default = '' + + +class EditSpaCyNLPPipelineModelForm(EditContributionBaseForm): + pipeline_name = StringField( + 'Pipeline name', + validators=[InputRequired(), Length(max=64)] + ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + service_manifest = SERVICES['spacy-nlp-pipeline'] + self.compatible_service_versions.choices = [('', 'Choose your option')] + self.compatible_service_versions.choices += [ + (x, x) for x in service_manifest['versions'].keys() + ] + self.compatible_service_versions.default = '' diff --git a/app/contributions/spacy_nlp_pipeline_models/json_routes.py b/app/contributions/spacy_nlp_pipeline_models/json_routes.py new file mode 100644 index 0000000000000000000000000000000000000000..9247f85b27f0f8b543933b726edafea074c36f6d --- /dev/null +++ b/app/contributions/spacy_nlp_pipeline_models/json_routes.py @@ -0,0 +1,58 @@ +from flask import abort, current_app, jsonify, request +from flask_login import login_required, current_user +from threading import Thread +from app import db +from app.decorators import content_negotiation, permission_required +from app.models import SpaCyNLPPipelineModel +from . import bp + + +@bp.route('/<hashid:spacy_nlp_pipeline_model_id>', methods=['DELETE']) +@login_required +@content_negotiation(produces='application/json') +def delete_spacy_model(spacy_nlp_pipeline_model_id): + def _delete_spacy_model(app, spacy_nlp_pipeline_model_id): + with app.app_context(): + snpm = SpaCyNLPPipelineModel.query.get(spacy_nlp_pipeline_model_id) + snpm.delete() + db.session.commit() + + snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id) + if not (snpm.user == current_user or current_user.is_administrator()): + abort(403) + thread = Thread( + target=_delete_spacy_model, + args=(current_app._get_current_object(), snpm.id) + ) + thread.start() + resonse_data = { + 'message': \ + f'SpaCy NLP Pipeline Model "{snpm.title}" marked for deletion' + } + response = jsonify(resonse_data) + response.status_code = 202 + return response + + +@bp.route('/<hashid:spacy_nlp_pipeline_model_id>/is_public', methods=['PUT']) +@login_required +@permission_required('CONTRIBUTE') +@content_negotiation(consumes='application/json', produces='application/json') +def update_spacy_nlp_pipeline_model_is_public(spacy_nlp_pipeline_model_id): + is_public = request.json + if not isinstance(is_public, bool): + abort(400) + snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id) + if not (snpm.user == current_user or current_user.is_administrator()): + abort(403) + snpm.is_public = is_public + db.session.commit() + response_data = { + 'message': ( + f'SpaCy NLP Pipeline Model "{snpm.title}"' + f' is now {"public" if is_public else "private"}' + ) + } + response = jsonify(response_data) + response.status_code = 200 + return response diff --git a/app/contributions/spacy_nlp_pipeline_models/routes.py b/app/contributions/spacy_nlp_pipeline_models/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..2e416c478c70deb42254ab8fcac42ac5edd0a941 --- /dev/null +++ b/app/contributions/spacy_nlp_pipeline_models/routes.py @@ -0,0 +1,87 @@ +from flask import abort, flash, Markup, redirect, render_template, url_for +from flask_login import login_required, current_user +from app import db +from app.models import SpaCyNLPPipelineModel +from . import bp, TEMPLATE_FOLDER +from .forms import ( + CreateSpaCyNLPPipelineModelForm, + EditSpaCyNLPPipelineModelForm +) + + +@bp.route('') +@login_required +def spacy_nlp_pipeline_models(): + return render_template( + f'{TEMPLATE_FOLDER}/spacy_nlp_pipeline_models.html.j2', + title='SpaCy NLP Pipeline Models' + ) + + +@bp.route('/create', methods=['GET', 'POST']) +@login_required +def create_spacy_nlp_pipeline_model(): + form = CreateSpaCyNLPPipelineModelForm(prefix='create-spacy-nlp-pipeline-model-form') + if form.is_submitted(): + if not form.validate(): + response = {'errors': form.errors} + return response, 400 + try: + spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.create( + form.spacy_model_file.data, + compatible_service_versions=form.compatible_service_versions.data, + description=form.description.data, + pipeline_name=form.pipeline_name.data, + publisher=form.publisher.data, + publisher_url=form.publisher_url.data, + publishing_url=form.publishing_url.data, + publishing_year=form.publishing_year.data, + is_public=False, + title=form.title.data, + version=form.version.data, + user=current_user + ) + except OSError: + abort(500) + db.session.commit() + spacy_nlp_pipeline_model_url = url_for( + '.spacy_nlp_pipeline_model', + spacy_nlp_pipeline_model_id=spacy_nlp_pipeline_model.id + ) + message = Markup( + f'SpaCy NLP Pipeline model "{spacy_nlp_pipeline_model.title}" ' + 'created' + ) + flash(message) + return '', 201, {'Location': spacy_nlp_pipeline_model_url} + return render_template( + f'{TEMPLATE_FOLDER}/create_spacy_nlp_pipeline_model.html.j2', + form=form, + title='Create SpaCy NLP Pipeline Model' + ) + + +@bp.route('/<hashid:spacy_nlp_pipeline_model_id>', methods=['GET', 'POST']) +@login_required +def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id): + spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id) + form = EditSpaCyNLPPipelineModelForm( + data=spacy_nlp_pipeline_model.to_json_serializeable(), + prefix='edit-spacy-nlp-pipeline-model-form' + ) + if form.validate_on_submit(): + form.populate_obj(spacy_nlp_pipeline_model) + if db.session.is_modified(spacy_nlp_pipeline_model): + message = Markup( + f'SpaCy NLP Pipeline model "{spacy_nlp_pipeline_model.title}" ' + 'updated' + ) + flash(message) + db.session.commit() + return redirect(url_for('.spacy_nlp_pipeline_models')) + return render_template( + f'{TEMPLATE_FOLDER}/spacy_nlp_pipeline_model.html.j2', + form=form, + spacy_nlp_pipeline_model=spacy_nlp_pipeline_model, + title=f'{spacy_nlp_pipeline_model.title} {spacy_nlp_pipeline_model.version}' + ) diff --git a/app/contributions/tesseract_ocr_pipeline_models/__init__.py b/app/contributions/tesseract_ocr_pipeline_models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c60e0915d6c8029742183ea37f155799ce37bce0 --- /dev/null +++ b/app/contributions/tesseract_ocr_pipeline_models/__init__.py @@ -0,0 +1,8 @@ +from flask import Blueprint + + +TEMPLATE_FOLDER = 'contributions/tesseract_ocr_pipeline_models' + + +bp = Blueprint('tesseract_ocr_pipeline_models', __name__) +from . import routes, json_routes diff --git a/app/contributions/tesseract_ocr_pipeline_models/forms.py b/app/contributions/tesseract_ocr_pipeline_models/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..51f0d76c2017029c46691719d3131ee779d5c39c --- /dev/null +++ b/app/contributions/tesseract_ocr_pipeline_models/forms.py @@ -0,0 +1,35 @@ +from flask_wtf.file import FileField, FileRequired +from wtforms import ValidationError +from app.services import SERVICES +from ..forms import ContributionBaseForm, EditContributionBaseForm + + +class CreateTesseractOCRPipelineModelForm(ContributionBaseForm): + tesseract_model_file = FileField( + 'File', + validators=[FileRequired()] + ) + + def validate_tesseract_model_file(self, field): + if not field.data.filename.lower().endswith('.traineddata'): + raise ValidationError('traineddata files only!') + + def __init__(self, *args, **kwargs): + service_manifest = SERVICES['tesseract-ocr-pipeline'] + super().__init__(*args, **kwargs) + self.compatible_service_versions.choices = [('', 'Choose your option')] + self.compatible_service_versions.choices += [ + (x, x) for x in service_manifest['versions'].keys() + ] + self.compatible_service_versions.default = '' + + +class EditTesseractOCRPipelineModelForm(EditContributionBaseForm): + def __init__(self, *args, **kwargs): + service_manifest = SERVICES['tesseract-ocr-pipeline'] + super().__init__(*args, **kwargs) + self.compatible_service_versions.choices = [('', 'Choose your option')] + self.compatible_service_versions.choices += [ + (x, x) for x in service_manifest['versions'].keys() + ] + self.compatible_service_versions.default = '' diff --git a/app/contributions/tesseract_ocr_pipeline_models/json_routes.py b/app/contributions/tesseract_ocr_pipeline_models/json_routes.py new file mode 100644 index 0000000000000000000000000000000000000000..f90a971ff1888dc5cb839a88abf2d9ad718db54a --- /dev/null +++ b/app/contributions/tesseract_ocr_pipeline_models/json_routes.py @@ -0,0 +1,58 @@ +from flask import abort, current_app, jsonify, request +from flask_login import login_required, current_user +from threading import Thread +from app import db +from app.decorators import content_negotiation, permission_required +from app.models import TesseractOCRPipelineModel +from . import bp + + +@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>', methods=['DELETE']) +@login_required +@content_negotiation(produces='application/json') +def delete_tesseract_model(tesseract_ocr_pipeline_model_id): + def _delete_tesseract_ocr_pipeline_model(app, tesseract_ocr_pipeline_model_id): + with app.app_context(): + topm = TesseractOCRPipelineModel.query.get(tesseract_ocr_pipeline_model_id) + topm.delete() + db.session.commit() + + topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id) + if not (topm.user == current_user or current_user.is_administrator()): + abort(403) + thread = Thread( + target=_delete_tesseract_ocr_pipeline_model, + args=(current_app._get_current_object(), topm.id) + ) + thread.start() + resonse_data = { + 'message': \ + f'Tesseract OCR Pipeline Model "{topm.title}" marked for deletion' + } + response = jsonify(resonse_data) + response.status_code = 202 + return response + + +@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>/is_public', methods=['PUT']) +@login_required +@permission_required('CONTRIBUTE') +@content_negotiation(consumes='application/json', produces='application/json') +def update_tesseract_ocr_pipeline_model_is_public(tesseract_ocr_pipeline_model_id): + is_public = request.json + if not isinstance(is_public, bool): + abort(400) + topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id) + if not (topm.user == current_user or current_user.is_administrator()): + abort(403) + topm.is_public = is_public + db.session.commit() + response_data = { + 'message': ( + f'Tesseract OCR Pipeline Model "{topm.title}"' + f' is now {"public" if is_public else "private"}' + ) + } + response = jsonify(response_data) + response.status_code = 200 + return response diff --git a/app/contributions/tesseract_ocr_pipeline_models/routes.py b/app/contributions/tesseract_ocr_pipeline_models/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..823e54d9d62d47c164a554fbb3b0834222bb9f5b --- /dev/null +++ b/app/contributions/tesseract_ocr_pipeline_models/routes.py @@ -0,0 +1,80 @@ +from flask import abort, flash, Markup, redirect, render_template, url_for +from flask_login import login_required, current_user +from app import db +from app.models import TesseractOCRPipelineModel +from . import bp, TEMPLATE_FOLDER +from .forms import ( + CreateTesseractOCRPipelineModelForm, + EditTesseractOCRPipelineModelForm +) + + +@bp.route('') +@login_required +def tesseract_ocr_pipeline_models(): + return render_template( + f'{TEMPLATE_FOLDER}/tesseract_ocr_pipeline_models.html.j2', + title='Tesseract OCR Pipeline Models' + ) + + +@bp.route('/create', methods=['GET', 'POST']) +@login_required +def create_tesseract_ocr_pipeline_model(): + form = CreateTesseractOCRPipelineModelForm(prefix='create-tesseract-ocr-pipeline-model-form') + if form.is_submitted(): + if not form.validate(): + response = {'errors': form.errors} + return response, 400 + try: + tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.create( + form.tesseract_model_file.data, + compatible_service_versions=form.compatible_service_versions.data, + description=form.description.data, + publisher=form.publisher.data, + publisher_url=form.publisher_url.data, + publishing_url=form.publishing_url.data, + publishing_year=form.publishing_year.data, + is_public=False, + title=form.title.data, + version=form.version.data, + user=current_user + ) + except OSError: + abort(500) + db.session.commit() + tesseract_ocr_pipeline_model_url = url_for( + '.tesseract_ocr_pipeline_model', + tesseract_ocr_pipeline_model_id=tesseract_ocr_pipeline_model.id + ) + message = Markup(f'Tesseract OCR Pipeline model "<a href="{tesseract_ocr_pipeline_model_url}">{tesseract_ocr_pipeline_model.title}</a>" created') + flash(message) + return {}, 201, {'Location': tesseract_ocr_pipeline_model_url} + return render_template( + f'{TEMPLATE_FOLDER}/create_tesseract_ocr_pipeline_model.html.j2', + form=form, + title='Create Tesseract OCR Pipeline Model' + ) + + +@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>', methods=['GET', 'POST']) +@login_required +def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id): + tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id) + form = EditTesseractOCRPipelineModelForm( + data=tesseract_ocr_pipeline_model.to_json_serializeable(), + prefix='edit-tesseract-ocr-pipeline-model-form' + ) + if form.validate_on_submit(): + form.populate_obj(tesseract_ocr_pipeline_model) + if db.session.is_modified(tesseract_ocr_pipeline_model): + message = Markup(f'Tesseract OCR Pipeline model "<a href="{tesseract_ocr_pipeline_model.url}">{tesseract_ocr_pipeline_model.title}</a>" updated') + flash(message) + db.session.commit() + return redirect(url_for('.tesseract_ocr_pipeline_models')) + return render_template( + f'{TEMPLATE_FOLDER}/tesseract_ocr_pipeline_model.html.j2', + form=form, + tesseract_ocr_pipeline_model=tesseract_ocr_pipeline_model, + title=f'{tesseract_ocr_pipeline_model.title} {tesseract_ocr_pipeline_model.version}' + ) diff --git a/app/templates/contributions/contributions.html.j2 b/app/templates/contributions/contributions.html.j2 index bdbcb10c42addf50fa31f220f682fb104dc12b68..99147b56f247447160b2af8dae8ca2c02f886262 100644 --- a/app/templates/contributions/contributions.html.j2 +++ b/app/templates/contributions/contributions.html.j2 @@ -1,6 +1,5 @@ {% extends "base.html.j2" %} {% import "materialize/wtf.html.j2" as wtf %} -{% from "contributions/_breadcrumbs.html.j2" import breadcrumbs with context %} {% block page_content %} <div class="container"> @@ -11,7 +10,7 @@ <div class="col s4"> <div class="card extension-selector hoverable service-color" data-service="tesseract-ocr-pipeline"> - <a href="{{ url_for('.tesseract_ocr_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a> + <a href="{{ url_for('.tesseract_ocr_pipeline_models.tesseract_ocr_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a> <div class="card-content"> <span class="card-title" data-service="tesseract-ocr-pipeline"><i class="nopaque-icons service-icons" data-service="tesseract-ocr-pipeline"></i>Tesseract OCR Pipeline Models</span> <p>Here you can see and edit the models that you have created. You can also create new models.</p> @@ -21,7 +20,7 @@ <div class="col s4"> <div class="card extension-selector hoverable service-color" data-service="spacy-nlp-pipeline"> - <a href="{{ url_for('.spacy_nlp_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a> + <a href="{{ url_for('.spacy_nlp_pipeline_models.spacy_nlp_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a> <div class="card-content"> <span class="card-title"><i class="nopaque-icons service-icons" data-service="spacy-nlp-pipeline"></i>SpaCy NLP Pipeline Models</span> <p>Here you can see and edit the models that you have created. You can also create new models.</p> diff --git a/app/templates/contributions/create_spacy_nlp_pipeline_model.html.j2 b/app/templates/contributions/spacy_nlp_pipeline_models/create_spacy_nlp_pipeline_model.html.j2 similarity index 97% rename from app/templates/contributions/create_spacy_nlp_pipeline_model.html.j2 rename to app/templates/contributions/spacy_nlp_pipeline_models/create_spacy_nlp_pipeline_model.html.j2 index e17ac9e58cec63afebbb6a307774724f0d72cdbe..091c61ad22a8df5e0ffd522cb46b67eaa04090ae 100644 --- a/app/templates/contributions/create_spacy_nlp_pipeline_model.html.j2 +++ b/app/templates/contributions/spacy_nlp_pipeline_models/create_spacy_nlp_pipeline_model.html.j2 @@ -1,6 +1,5 @@ {% extends "base.html.j2" %} {% import "materialize/wtf.html.j2" as wtf %} -{% from "contributions/_breadcrumbs.html.j2" import breadcrumbs with context %} {% block main_attribs %} class="service-scheme" data-service="spacy-nlp-pipeline"{% endblock main_attribs %} diff --git a/app/templates/contributions/spacy_nlp_pipeline_model.html.j2 b/app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_model.html.j2 similarity index 96% rename from app/templates/contributions/spacy_nlp_pipeline_model.html.j2 rename to app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_model.html.j2 index 32f27303455c9fbde91dbbb10dbf9d73d1c963b0..467effc95ac9b1b71f6ab23c192802a77f9ea4ad 100644 --- a/app/templates/contributions/spacy_nlp_pipeline_model.html.j2 +++ b/app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_model.html.j2 @@ -1,6 +1,5 @@ {% extends "base.html.j2" %} {% import "materialize/wtf.html.j2" as wtf %} -{% from "contributions/_breadcrumbs.html.j2" import breadcrumbs with context %} {% block main_attribs %} class="service-scheme" data-service="spacy-nlp-pipeline"{% endblock main_attribs %} diff --git a/app/templates/contributions/spacy_nlp_pipeline_models.html.j2 b/app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_models.html.j2 similarity index 91% rename from app/templates/contributions/spacy_nlp_pipeline_models.html.j2 rename to app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_models.html.j2 index f3c7a40ee113b0c49893f6fedc0d8b5312082a6f..b57507beb4dc39e4486534ce78fd69a4c7d9a7e1 100644 --- a/app/templates/contributions/spacy_nlp_pipeline_models.html.j2 +++ b/app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_models.html.j2 @@ -1,6 +1,5 @@ {% extends "base.html.j2" %} {% import "materialize/wtf.html.j2" as wtf %} -{% from "contributions/_breadcrumbs.html.j2" import breadcrumbs with context %} {% block page_content %} <div class="container"> diff --git a/app/templates/contributions/create_tesseract_ocr_pipeline_model.html.j2 b/app/templates/contributions/tesseract_ocr_pipeline_models/create_tesseract_ocr_pipeline_model.html.j2 similarity index 98% rename from app/templates/contributions/create_tesseract_ocr_pipeline_model.html.j2 rename to app/templates/contributions/tesseract_ocr_pipeline_models/create_tesseract_ocr_pipeline_model.html.j2 index ecede20a08d2a8ce5cc1b46e9023808c55191747..43ad0a136437deaae1ac299c0b13a2f8ca64b2df 100644 --- a/app/templates/contributions/create_tesseract_ocr_pipeline_model.html.j2 +++ b/app/templates/contributions/tesseract_ocr_pipeline_models/create_tesseract_ocr_pipeline_model.html.j2 @@ -1,6 +1,5 @@ {% extends "base.html.j2" %} {% import "materialize/wtf.html.j2" as wtf %} -{% from "contributions/_breadcrumbs.html.j2" import breadcrumbs with context %} {% block main_attribs %} class="service-scheme" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %} diff --git a/app/templates/contributions/tesseract_ocr_pipeline_model.html.j2 b/app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_model.html.j2 similarity index 95% rename from app/templates/contributions/tesseract_ocr_pipeline_model.html.j2 rename to app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_model.html.j2 index 02322d8a9b884e30274a1b40adf0f482f0118112..c7cc2d5c8cf9fe1566f768556a01cc17d59b1802 100644 --- a/app/templates/contributions/tesseract_ocr_pipeline_model.html.j2 +++ b/app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_model.html.j2 @@ -1,6 +1,5 @@ {% extends "base.html.j2" %} {% import "materialize/wtf.html.j2" as wtf %} -{% from "contributions/_breadcrumbs.html.j2" import breadcrumbs with context %} {% block main_attribs %} class="service-scheme" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %} diff --git a/app/templates/contributions/tesseract_ocr_pipeline_models.html.j2 b/app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_models.html.j2 similarity index 91% rename from app/templates/contributions/tesseract_ocr_pipeline_models.html.j2 rename to app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_models.html.j2 index 3d43d72769ba5204c528102294648ec5e06524bf..a2f18c2a4b4a4f3896e0a1c17d325433c4436e3f 100644 --- a/app/templates/contributions/tesseract_ocr_pipeline_models.html.j2 +++ b/app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_models.html.j2 @@ -1,6 +1,5 @@ {% extends "base.html.j2" %} {% import "materialize/wtf.html.j2" as wtf %} -{% from "contributions/_breadcrumbs.html.j2" import breadcrumbs with context %} {% block page_content %} <div class="container">