diff --git a/app/contributions/__init__.py b/app/contributions/__init__.py index af9747a6..5175c0ce 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 eb25babb..acec307f 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 f9e4d2c5..6d8b9cc3 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 "{spacy_nlp_pipeline_model.title}" 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/', 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 "{spacy_nlp_pipeline_model.title}" 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/', 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//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 "{tesseract_ocr_pipeline_model.title}" 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/', 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 "{tesseract_ocr_pipeline_model.title}" 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/', 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//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 00000000..6b73681a --- /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 00000000..2670c1d1 --- /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 00000000..9247f85b --- /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('/', 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('//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 00000000..2e416c47 --- /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('/', 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 00000000..c60e0915 --- /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 00000000..51f0d76c --- /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 00000000..f90a971f --- /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('/', 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('//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 00000000..823e54d9 --- /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 "{tesseract_ocr_pipeline_model.title}" 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('/', 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 "{tesseract_ocr_pipeline_model.title}" 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 bdbcb10c..99147b56 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 %}
@@ -11,7 +10,7 @@
- +
Tesseract OCR Pipeline Models

Here you can see and edit the models that you have created. You can also create new models.

@@ -21,7 +20,7 @@
- +
SpaCy NLP Pipeline Models

Here you can see and edit the models that you have created. You can also create new models.

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 e17ac9e5..091c61ad 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 32f27303..467effc9 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 f3c7a40e..b57507be 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 %}
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 ecede20a..43ad0a13 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 02322d8a..c7cc2d5c 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 3d43d727..a2f18c2a 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 %}