diff --git a/app/SpaCyNLPPipelineModel.defaults.yml b/app/SpaCyNLPPipelineModel.defaults.yml index 576f85e460d8e4b9770781b157dc527f9584dccc..ed4ea3bd854fcf96a0473f1197d487d737a3386e 100644 --- a/app/SpaCyNLPPipelineModel.defaults.yml +++ b/app/SpaCyNLPPipelineModel.defaults.yml @@ -5,6 +5,62 @@ publisher_url: 'https://github.com/explosion' publishing_url: 'https://github.com/explosion/spacy-models/releases/tag/de_core_news_md-3.4.0' publishing_year: 2022 + pipeline_name: 'de_core_news_md' version: '3.4.0' compatible_service_versions: - '0.1.0' +- title: 'en_core_web_md-3.4.1' + description: 'English pipeline optimized for CPU. Components: tok2vec, tagger, parser, senter, ner, attribute_ruler, lemmatizer.' + url: 'https://github.com/explosion/spacy-models/releases/download/en_core_web_md-3.4.1/en_core_web_md-3.4.1.tar.gz' + publisher: 'Explosion' + publisher_url: 'https://github.com/explosion' + publishing_url: 'https://github.com/explosion/spacy-models/releases/tag/en_core_web_md-3.4.1' + publishing_year: 2022 + pipeline_name: 'en_core_web_md' + version: '3.4.1' + compatible_service_versions: + - '0.1.0' +- title: 'uk_core_news_md-3.4.0' + description: 'Ukrainian pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.' + url: 'https://github.com/explosion/spacy-models/releases/download/uk_core_news_md-3.4.0/uk_core_news_md-3.4.0.tar.gz' + publisher: 'Explosion' + publisher_url: 'https://github.com/explosion' + publishing_url: 'https://github.com/explosion/spacy-models/releases/tag/uk_core_news_md-3.4.0' + publishing_year: 2022 + pipeline_name: 'uk_core_news_md' + version: '3.4.0' + compatible_service_versions: + - '0.1.0' +- title: 'zh_core_web_md-3.4.0' + description: 'Chinese pipeline optimized for CPU. Components: tok2vec, tagger, parser, senter, ner, attribute_ruler.' + url: 'https://github.com/explosion/spacy-models/releases/download/zh_core_web_md-3.4.0/zh_core_web_md-3.4.0.tar.gz' + publisher: 'Explosion' + publisher_url: 'https://github.com/explosion' + publishing_url: 'https://github.com/explosion/spacy-models/releases/tag/zh_core_web_md-3.4.0' + publishing_year: 2022 + pipeline_name: 'zh_core_web_md' + version: '3.4.0' + compatible_service_versions: + - '0.1.0' +- title: 'ru_core_news_md-3.4.0' + description: 'Russian pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.' + url: 'https://github.com/explosion/spacy-models/releases/download/ru_core_news_md-3.4.0/ru_core_news_md-3.4.0.tar.gz' + publisher: 'Explosion' + publisher_url: 'https://github.com/explosion' + publishing_url: 'https://github.com/explosion/spacy-models/releases/tag/ru_core_news_md-3.4.0' + publishing_year: 2022 + pipeline_name: 'ru_core_news_md' + version: '3.4.0' + compatible_service_versions: + - '0.1.0' +- title: 'la_core_cltk_sm-0.1.0' + description: 'Latin pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.' + url: 'https://github.com/diyclassics/latin-spacy-models/raw/main/la_core_cltk_sm/la_core_cltk_sm-0.1.0.tar.gz' + publisher: 'DIY Classics' + publisher_url: 'https://github.com/diyclassics/' + publishing_url: 'https://github.com/diyclassics/latin-spacy-models/tree/main/la_core_cltk_sm' + publishing_year: 2022 + pipeline_name: 'la_core_cltk_sm' + version: '0.1.0' + compatible_service_versions: + - '0.1.0' diff --git a/app/contributions/forms.py b/app/contributions/forms.py index 8577ee979b6583c53a9a1b0115430ba93ebf144a..dcdfaea85a8694057ed4c6276485a57187b4d902 100644 --- a/app/contributions/forms.py +++ b/app/contributions/forms.py @@ -46,6 +46,18 @@ class CreateContributionBaseForm(FlaskForm): ) submit = SubmitField() +class EditForm(CreateContributionBaseForm): + def prefill(self, model_file): + ''' Pre-fill the form with data of an exististing corpus file ''' + self.title.data = model_file.title + self.description.data = model_file.description + self.publisher.data = model_file.publisher + self.publishing_year.data = model_file.publishing_year + self.publisher_url.data = model_file.publisher_url + self.publishing_url.data = model_file.publishing_url + self.version.data = model_file.version + self.shared.data = model_file.shared + class TesseractOCRModelContributionForm(CreateContributionBaseForm): tesseract_model_file = FileField( 'File', @@ -67,16 +79,23 @@ class TesseractOCRModelContributionForm(CreateContributionBaseForm): ] self.compatible_service_versions.default = '' -class TesseractOCRModelEditForm(CreateContributionBaseForm): - def prefill(self, model_file): - ''' Pre-fill the form with data of an exististing corpus file ''' - self.title.data = model_file.title - self.description.data = model_file.description - self.publisher.data = model_file.publisher - self.publishing_year.data = model_file.publishing_year - self.publisher_url.data = model_file.publisher_url - self.publishing_url.data = model_file.publishing_url - self.version.data = model_file.version - self.shared.data = model_file.shared - +class SpacyNLPModelContributionForm(CreateContributionBaseForm): + spacy_model_file = FileField( + 'File', + validators=[FileRequired()] + ) + compatible_service_versions = SelectMultipleField( + 'Compatible service versions' + ) + def validate_spacy(self, field): + if field.data.mimetype != '.tar.gz': + raise ValidationError('.tar.gz files only!') + def __init__(self, *args, **kwargs): + service_manifest = SERVICES['spacy-nlp-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/routes.py b/app/contributions/routes.py index 385e1eec028c6a70f32ffae4baefca4835d22b0d..b63a43ec9c88b38977205bffc0ce637ff0cb6ae1 100644 --- a/app/contributions/routes.py +++ b/app/contributions/routes.py @@ -1,11 +1,11 @@ -from flask import abort, current_app, flash, Markup, redirect, render_template, url_for +from flask import abort, current_app, flash, Markup, render_template, url_for from flask_login import login_required, current_user from threading import Thread from app import db from app.decorators import admin_required, permission_required -from app.models import TesseractOCRPipelineModel, Permission +from app.models import Permission, SpaCyNLPPipelineModel, TesseractOCRPipelineModel from . import bp -from .forms import TesseractOCRModelContributionForm, TesseractOCRModelEditForm +from .forms import TesseractOCRModelContributionForm, EditForm, SpacyNLPModelContributionForm @bp.before_request @@ -22,20 +22,26 @@ def contributions(): tesseract_ocr_user_models = [ x for x in current_user.tesseract_ocr_pipeline_models ] + spacy_nlp_user_models = [ + x for x in current_user.spacy_nlp_pipeline_models + ] + spacy_models = SpaCyNLPPipelineModel.query.all() + print(spacy_models) return render_template( 'contributions/contribution_overview.html.j2', - tesseractOCRUserModels=tesseract_ocr_user_models, + tesseract_ocr_user_models=tesseract_ocr_user_models, + spacy_nlp_user_models=spacy_nlp_user_models, userId = current_user.hashid, title='Contribution Overview' ) -@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>', methods=['GET', 'POST']) +@bp.route('/edit-tesseract-model/<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 = TesseractOCRModelEditForm(prefix='tesseract-ocr-model-edit-form') + form = EditForm(prefix='tesseract-ocr-model-edit-form') if form.validate_on_submit(): if tesseract_ocr_pipeline_model.title != form.title.data: tesseract_ocr_pipeline_model.title = form.title.data @@ -65,7 +71,7 @@ def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id): title='Edit your Tesseract OCR model' ) -@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>', methods=['DELETE']) +@bp.route('/edit-tesseract-model/<hashid:tesseract_ocr_pipeline_model_id>', methods=['DELETE']) @login_required def delete_tesseract_model(tesseract_ocr_pipeline_model_id): def _delete_tesseract_model(app, tesseract_ocr_pipeline_model_id): @@ -123,3 +129,95 @@ def add_tesseract_ocr_pipeline_model(): tesseract_ocr_pipeline_models=tesseract_ocr_pipeline_models, title='Tesseract OCR Model Contribution' ) + +@bp.route('/edit-spacy-model//<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 = EditForm(prefix='spacy-nlp-model-edit-form') + if form.validate_on_submit(): + if spacy_nlp_pipeline_model.title != form.title.data: + spacy_nlp_pipeline_model.title = form.title.data + if spacy_nlp_pipeline_model.description != form.description.data: + spacy_nlp_pipeline_model.description = form.description.data + if spacy_nlp_pipeline_model.publisher != form.publisher.data: + spacy_nlp_pipeline_model.publisher = form.publisher.data + if spacy_nlp_pipeline_model.publishing_year != form.publishing_year.data: + spacy_nlp_pipeline_model.publishing_year = form.publishing_year.data + if spacy_nlp_pipeline_model.publisher_url != form.publisher_url.data: + spacy_nlp_pipeline_model.publisher_url = form.publisher_url.data + if spacy_nlp_pipeline_model.publishing_url != form.publishing_url.data: + spacy_nlp_pipeline_model.publishing_url = form.publishing_url.data + if spacy_nlp_pipeline_model.version != form.version.data: + spacy_nlp_pipeline_model.version = form.version.data + if spacy_nlp_pipeline_model.shared != form.shared.data: + spacy_nlp_pipeline_model.shared = form.shared.data + db.session.commit() + message = Markup(f'Model "<a href="contribute/{spacy_nlp_pipeline_model.hashid}">{spacy_nlp_pipeline_model.title}</a>" updated') + flash(message, category='corpus') + return {}, 201, {'Location': url_for('contributions.contributions')} + form.prefill(spacy_nlp_pipeline_model) + return render_template( + 'contributions/spacy_nlp_pipeline_model.html.j2', + spacy_nlp_pipeline_model=spacy_nlp_pipeline_model, + form=form, + title='Edit your spaCy NLP model' + ) + +@bp.route('/edit-spacy-model/<hashid:spacy_nlp_pipeline_model_id>', methods=['DELETE']) +@login_required +def delete_spacy_model(spacy_nlp_pipeline_model_id): + def _delete_spacy_model(app, spacy_nlp_pipeline_model_id): + with app.app_context(): + model = SpaCyNLPPipelineModel.query.get(spacy_nlp_pipeline_model_id) + model.delete() + db.session.commit() + + model = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id) + if not (model.user == current_user or current_user.is_administrator()): + abort(403) + thread = Thread( + target=_delete_spacy_model, + args=(current_app._get_current_object(), spacy_nlp_pipeline_model_id) + ) + thread.start() + return {}, 202 + +@bp.route('/add-spacy-nlp-pipeline-model', methods=['GET', 'POST']) +def add_spacy_nlp_pipeline_model(): + form = SpacyNLPModelContributionForm(prefix='contribute-spacy-nlp-pipeline-model-form') + if form.is_submitted(): + if not form.validate(): + response = {'errors': form.errors} + return response, 400 + try: + spacy_nlp_model = SpaCyNLPPipelineModel.create( + form.spacy_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, + shared=form.shared.data, + title=form.title.data, + version=form.version.data, + user=current_user + ) + except OSError: + abort(500) + db.session.commit() + message = Markup(f'Model "{spacy_nlp_model.title}" created') + flash(message) + return {}, 201, {'Location': url_for('contributions.contributions')} + spacy_nlp_pipeline_models = [ + x for x in SpaCyNLPPipelineModel.query.all() + ] + return render_template( + 'contributions/contribute_spacy_nlp_models.html.j2', + form=form, + spacy_nlp_pipeline_models=spacy_nlp_pipeline_models, + title='spaCy NLP Model Contribution' + ) diff --git a/app/models.py b/app/models.py index e1acf6de0b3d74175c67031f8a94e55d77b8158d..90d16f485372bc264e53937356d6392751c00e33 100644 --- a/app/models.py +++ b/app/models.py @@ -520,6 +520,10 @@ class User(HashidMixin, UserMixin, db.Model): x.hashid: x.to_json(relationships=True) for x in self.tesseract_ocr_pipeline_models } + _json['spacy_nlp_pipeline_models'] = { + x.hashid: x.to_json(relationships=True) + for x in self.spacy_nlp_pipeline_models + } return _json class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model): @@ -643,6 +647,7 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model): publisher_url = db.Column(db.String(512)) publishing_url = db.Column(db.String(512)) publishing_year = db.Column(db.Integer) + pipeline_name = db.Column(db.String(64)) shared = db.Column(db.Boolean, default=False) # Backrefs: user: User @@ -675,6 +680,7 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model): model.shared = True model.title = m['title'] model.version = m['version'] + model.pipeline_name = m['pipeline_name'] continue model = SpaCyNLPPipelineModel( compatible_service_versions=m['compatible_service_versions'], @@ -686,7 +692,8 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model): shared=True, title=m['title'], user=nopaque_user, - version=m['version'] + version=m['version'], + pipeline_name=m['pipeline_name'] ) db.session.add(model) db.session.flush(objects=[model]) @@ -708,6 +715,13 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model): f.write(chunk) pbar.close() db.session.commit() + + def delete(self): + try: + os.remove(self.path) + except OSError as e: + current_app.logger.error(e) + db.session.delete(self) def to_json(self, backrefs=False, relationships=False): _json = { @@ -718,6 +732,7 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model): 'publisher_url': self.publisher_url, 'publishing_url': self.publishing_url, 'publishing_year': self.publishing_year, + 'pipeline_name': self.pipeline_name, 'shared': self.shared, 'title': self.title, **self.file_mixin_to_json() diff --git a/app/static/js/RessourceLists/SpacyNLPModelList.js b/app/static/js/RessourceLists/SpacyNLPModelList.js new file mode 100644 index 0000000000000000000000000000000000000000..0e20191b0d35694a2d378ab44ff4cd927aa7e5c6 --- /dev/null +++ b/app/static/js/RessourceLists/SpacyNLPModelList.js @@ -0,0 +1,76 @@ +class SpacyNLPModelList { + constructor () { + + this.elements = { + spacyNLPModelList: document.querySelector('#spacy-nlp-model-list'), + deleteButtons: document.querySelectorAll('.delete-spacy-model-button'), + editButtons: document.querySelectorAll('.edit-spacy-model-button'), + + } + } + + init () { + let userId = this.elements.spacyNLPModelList.dataset.userId; + + for (let deleteButton of this.elements.deleteButtons) { + deleteButton.addEventListener('click', () => {this.deleteModel(deleteButton, userId);}); + } + + for (let editButton of this.elements.editButtons) { + editButton.addEventListener('click', () => {this.editModel(editButton);}); + } + } + + deleteModel(deleteButton, userId) { + return new Promise((resolve, reject) => { + let modelId = deleteButton.dataset.modelId; + let model = app.data.users[userId].spacy_nlp_pipeline_models[modelId]; + let modalElement = Utils.elementFromString( + ` + <div class="modal"> + <div class="modal-content"> + <h4>Confirm job deletion</h4> + <p>Do you really want to delete <b>${model.title}</b>? All files will be permanently deleted!</p> + </div> + <div class="modal-footer"> + <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a> + <a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a> + </div> + </div> + ` + ); + document.querySelector('#modals').appendChild(modalElement); + let modal = M.Modal.init( + modalElement, + { + dismissible: false, + onCloseEnd: () => { + modal.destroy(); + modalElement.remove(); + } + } + ); + let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); + confirmElement.addEventListener('click', (event) => { + let modelTitle = model.title; + fetch(`/contributions/edit-spacy-model/${modelId}`, {method: 'DELETE'}) + .then( + (response) => { + app.flash(`Model "${modelTitle}" marked for deletion`, 'corpus'); + resolve(response); + }, + (response) => { + if (response.status === 403) {app.flash('Forbidden', 'error');} + if (response.status === 404) {app.flash('Not Found', 'error');} + reject(response); + } + ); + }); + modal.open(); + }); + } + + editModel(editButton) { + window.location.href = `/contributions/edit-spacy-model/${editButton.dataset.modelId}`; + } +} diff --git a/app/static/js/RessourceLists/TesseractOCRModelList.js b/app/static/js/RessourceLists/TesseractOCRModelList.js index 9080447e361a688ec7dd06595744c2a5f6a33d06..782f5d7eed368bb2aa1c9becda74df9c304f8796 100644 --- a/app/static/js/RessourceLists/TesseractOCRModelList.js +++ b/app/static/js/RessourceLists/TesseractOCRModelList.js @@ -25,7 +25,6 @@ class TesseractOCRModelList { return new Promise((resolve, reject) => { let modelId = deleteButton.dataset.modelId; let model = app.data.users[userId].tesseract_ocr_pipeline_models[modelId]; - let modalElement = Utils.elementFromString( ` <div class="modal"> @@ -54,7 +53,7 @@ class TesseractOCRModelList { let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); confirmElement.addEventListener('click', (event) => { let modelTitle = model.title; - fetch(`/contributions/${modelId}`, {method: 'DELETE'}) + fetch(`/contributions/edit-tesseract-model/${modelId}`, {method: 'DELETE'}) .then( (response) => { app.flash(`Model "${modelTitle}" marked for deletion`, 'corpus'); @@ -72,6 +71,6 @@ class TesseractOCRModelList { } editModel(editButton) { - window.location.href = `/contributions/${editButton.dataset.modelId}`; + window.location.href = `/contributions/edit-tesseract-model/${editButton.dataset.modelId}`; } } diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 3b93ef668553148b6a0ccc24f67d1b11af3d2fac..798d2848ea1f83f74b9de9086364aa22eaa579f3 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -25,6 +25,7 @@ 'js/RessourceLists/JobInputList.js', 'js/RessourceLists/JobResultList.js', 'js/RessourceLists/QueryResultList.js', + 'js/RessourceLists/SpacyNLPModelList.js', 'js/RessourceLists/TesseractOCRModelList.js', 'js/RessourceLists/UserList.js' %} diff --git a/app/templates/contributions/_breadcrumbs.html.j2 b/app/templates/contributions/_breadcrumbs.html.j2 index 9d49da6868d6292d7caac41dee7d80262e3f25c3..4ccfad3ba57b1874a23879b07fb17b8fc6115042 100644 --- a/app/templates/contributions/_breadcrumbs.html.j2 +++ b/app/templates/contributions/_breadcrumbs.html.j2 @@ -14,5 +14,18 @@ <li class="tab"><a href="{{ url_for('.contributions', tesseract_ocr_pipeline_model_id=nn) }}" target="_self">Contributions Overview</a></li> <li class="tab disabled"><i class="material-icons">navigate_next</i></li> <li class="tab"><a class="active" href="{{ url_for('.add_tesseract_ocr_pipeline_model') }}" target="_self">{{ title }}</a></li> + +{% elif request.path == url_for('.spacy_nlp_pipeline_model', spacy_nlp_pipeline_model_id=spacy_nlp_pipeline_model.id) %} +<li class="tab"><a href="{{ url_for('.contributions') }}" target="_self">Contributions Overview</a></li> +<li class="tab disabled"><i class="material-icons">navigate_next</i></li> +<li class="tab"> + <a class="active" href="{{ url_for('.spacy_nlp_pipeline_model', spacy_nlp_pipeline_model_id=spacy_nlp_pipeline_model.hashid) }}" target="_self"> + Edit {{ spacy_nlp_pipeline_model.title }} + </a> +</li> +{% elif request.path == url_for('.add_spacy_nlp_pipeline_model, spacy_nlp_pipeline_model=nn') %} +<li class="tab"><a href="{{ url_for('.contributions', spacy_nlp_pipeline_model_id=nn) }}" target="_self">Contributions Overview</a></li> +<li class="tab disabled"><i class="material-icons">navigate_next</i></li> +<li class="tab"><a class="active" href="{{ url_for('.add_spacy_nlp_pipeline_model') }}" target="_self">{{ title }}</a></li> {% endif %} {% endset %} diff --git a/app/templates/contributions/contribute.html.j2 b/app/templates/contributions/contribute.html.j2 deleted file mode 100644 index 6789e1f85a7c8215c2b4d53ee21f0f5b33d059c2..0000000000000000000000000000000000000000 --- a/app/templates/contributions/contribute.html.j2 +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "base.html.j2" %} -{% import "materialize/wtf.html.j2" as wtf %} - - -{% block page_content %} -<div class="container"> - <div class="row"> - <div class="col s12 m8 offset-m2"> - <h1 id="title">{{ title }}</h1> - <p> - In order to add a new model, please fill in the form below. - </p> - - <form method="POST"> - <div class="card-panel"> - {{ form.hidden_tag() }} - {{ wtf.render_field(form.title) }} - {{ wtf.render_field(form.description) }} - {{ wtf.render_field(form.publisher) }} - {{ wtf.render_field(form.publisher_url) }} - {{ wtf.render_field(form.publishing_url) }} - {{ wtf.render_field(form.publishing_year) }} - {{ wtf.render_field(form.shared) }} - {{ wtf.render_field(form.version) }} - {{ wtf.render_field(form.compatible_service_versions) }} - {{ wtf.render_field(form.submit, class_='width-100', material_icon='send') }} - - </div> - </form> - </div> -</div> -{% endblock page_content %} \ No newline at end of file diff --git a/app/templates/contributions/contribute_spacy_nlp_models.html.j2 b/app/templates/contributions/contribute_spacy_nlp_models.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..97475c400cdc97fc56390dacd6cba4d97202fcc0 --- /dev/null +++ b/app/templates/contributions/contribute_spacy_nlp_models.html.j2 @@ -0,0 +1,124 @@ +{% 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 %} + +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> + + <div class="col s12 m3 push-m9"> + <div class="center-align"> + <p class="hide-on-small-only"> </p> + <p class="hide-on-small-only"> </p> + <a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light"> + <i class="nopaque-icons service-color darken service-icon" data-service="spacy-nlp-pipeline"></i> + </a> + </div> + </div> + + <div class="col s12 m9 pull-m3"> + <div class="card service-color-border border-darken" data-service="spacy-nlp-pipeline" style="border-top: 10px solid;"> + <div class="card-content"> + <div class="row"> + <div class="col s12"> + <div class="card-panel z-depth-0"> + <span class="card-title"><i class="left material-icons">layers</i>spaCy NLP Models</span> + <p>You can add more Tesseract OCR models using the form below. They will automatically appear in the list of usable models.</p> + <p><a href="">Edit already uploaded models</a></p> + <p><a class="modal-trigger" href="#models-modal">Information about the already existing models.</a></p> + </div> + </div> + </div> + </div> + </div> + </div> + + <div class="col s12"> + <h2>Add a model</h2> + <div class="card"> + <form class="create-contribution-form" enctype="multipart/form-data" method="POST"> + <div class="card-content"> + {{ form.hidden_tag() }} + <div class="row"> + <div class="col s12 l5"> + {{ wtf.render_field(form.spacy_model_file, accept='.tar.gz', placeholder='Choose a .tar.gz file') }} + </div> + <div class="col s12 l7"> + {{ wtf.render_field(form.title, material_icon='title') }} + </div> + <div class="col s12"> + {{ wtf.render_field(form.description, material_icon='description') }} + </div> + <div class="col s12 l6"> + {{ wtf.render_field(form.publisher, material_icon='account_balance') }} + </div> + <div class="col s12 l6"> + {{ wtf.render_field(form.publishing_year, material_icon='calendar_month') }} + </div> + <div class="col s12"> + {{ wtf.render_field(form.publisher_url, material_icon='link') }} + </div> + <div class="col s12"> + {{ wtf.render_field(form.publishing_url, material_icon='link') }} + </div> + <div class="col s12 l10"> + {{ wtf.render_field(form.version, material_icon='apps') }} + </div> + <div class="col s12 l6"> + {{ wtf.render_field(form.compatible_service_versions) }} + </div> + <div class="col s12 l6 right-align" style="padding-right:20px;"> + <p></p> + <br> + {{ wtf.render_field(form.shared) }} + </div> + </div> + </div> + <div class="card-action right-align"> + {{ wtf.render_field(form.submit, material_icon='send') }} + </div> + </form> + </div> + </div> + </div> +</div> +{% endblock page_content %} + +{% block modals %} +{{ super() }} +<div id="models-modal" class="modal"> + <div class="modal-content"> + <h4>spaCy NLP Pipeline models</h4> + <table> + <thead> + <tr> + <th>Title</th> + <th>Description</th> + <th>Biblio</th> + </tr> + </thead> + <tbody> + {% for m in spacy_nlp_pipeline_models %} + <tr id="spacy-nlp-pipeline-model-{{ m.hashid }}"> + <td>{{ m.title }}</td> + {% if m.description == '' %} + <td>Description is not available.</td> + {% else %} + <td>{{ m.description }}</td> + {% endif %} + <td><a href="{{ m.publisher_url }}">{{ m.publisher }}</a> ({{ m.publishing_year }}), {{ m.title }} {{ m.version}}, <a href="{{ m.publishing_url }}">{{ m.publishing_url }}</a></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + <div class="modal-footer"> + <a href="#!" class="modal-close waves-effect waves-light btn">Close</a> + </div> +</div> +{% endblock modals %} diff --git a/app/templates/contributions/contribution_overview.html.j2 b/app/templates/contributions/contribution_overview.html.j2 index 6a1ebb1ed3e5d18046480aecb276a2a1dd8873cb..c6facf9d7c813915444b87497a0c591cec413815 100644 --- a/app/templates/contributions/contribution_overview.html.j2 +++ b/app/templates/contributions/contribution_overview.html.j2 @@ -28,8 +28,8 @@ </tr> </thead> <tbody> - {% if tesseractOCRUserModels|length > 0 %} - {% for m in tesseractOCRUserModels %} + {% if tesseract_ocr_user_models|length > 0 %} + {% for m in tesseract_ocr_user_models %} <tr id="tesseract-ocr-pipeline-model-{{ m.hashid }}"> <td>{{ m.title }}</td> {% if m.description == '' %} @@ -61,6 +61,58 @@ </div> </div> + {# spaCy NLP Models #} + <div> + <h3>My spaCy NLP Pipeline Models</h3> + <p>Here you can see and edit the models that you have created. You can also create new models.</p> + + <div class="row"> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <div id="spacy-nlp-model-list" data-user-id="{{ userId }}" data-user-models="{{ spacy_nlp_user_models }}"> + <table> + <thead> + <tr> + <th>Title</th> + <th>Description</th> + <th>Biblio</th> + <th></th> + </tr> + </thead> + <tbody> + {% if spacy_nlp_user_models|length > 0 %} + {% for m in spacy_nlp_user_models %} + <tr id="spacy_nlp-pipeline-model-{{ m.hashid }}"> + <td>{{ m.title }}</td> + {% if m.description == '' %} + <td>Description is not available.</td> + {% else %} + <td>{{ m.description }}</td> + {% endif %} + <td><a href="{{ m.publisher_url }}">{{ m.publisher }}</a> ({{ m.publishing_year }}), {{ m.title }} {{ m.version}}, <a href="{{ m.publishing_url }}">{{ m.publishing_url }}</a></td> + <td class="right-align"> + <a class="delete-spacy-model-button btn-floating red waves-effect waves-light" data-model-id="{{ m.hashid }}"><i class="material-icons">delete</i></a> + <a class="edit-spacy-model-button btn-floating service-color darken waves-effect waves-light" data-model-id="{{ m.hashid }}"><i class="material-icons">edit</i></a> + </td> + </tr> + {% endfor %} + {% else %} + <tr> + <td colspan="4">No models available.</td> + </tr> + {% endif %} + </tbody> + </table> + </div> + </div> + <div class="card-action right-align"> + <a href="{{ url_for('contributions.add_spacy_nlp_pipeline_model') }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Add model file</a> + </div> + </div> + </div> + </div> + </div> </div> </div> </div> @@ -71,5 +123,7 @@ <script> const tesseractOCRModelList = new TesseractOCRModelList(); tesseractOCRModelList.init(); +const spacyNLPModelList = new SpacyNLPModelList(); +spacyNLPModelList.init(); </script> {% endblock scripts %} diff --git a/app/templates/contributions/spacy_nlp_pipeline_model.html.j2 b/app/templates/contributions/spacy_nlp_pipeline_model.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..04d7506fbfaaa3b21c36271004ed3289b67498a9 --- /dev/null +++ b/app/templates/contributions/spacy_nlp_pipeline_model.html.j2 @@ -0,0 +1,56 @@ +{% 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 %} + +{% block page_content %} +<div class="container"> + <div class="row"> + <div class="col s12"> + <h1 id="title">{{ title }}</h1> + </div> + + <div class="col s12"> + <div class="card"> + <form class="create-contribution-form" enctype="multipart/form-data" method="POST"> + <div class="card-content"> + {{ form.hidden_tag() }} + <div class="row"> + <div class="col s12 l7"> + {{ wtf.render_field(form.title, material_icon='title') }} + </div> + <div class="col s12"> + {{ wtf.render_field(form.description, material_icon='description') }} + </div> + <div class="col s12 l6"> + {{ wtf.render_field(form.publisher, material_icon='account_balance') }} + </div> + <div class="col s12 l6"> + {{ wtf.render_field(form.publishing_year, material_icon='calendar_month') }} + </div> + <div class="col s12"> + {{ wtf.render_field(form.publisher_url, material_icon='link') }} + </div> + <div class="col s12"> + {{ wtf.render_field(form.publishing_url, material_icon='link') }} + </div> + <div class="col s12 l10"> + {{ wtf.render_field(form.version, material_icon='apps') }} + </div> + <div class="col s12 l6 right-align" style="padding-right:20px;"> + <p></p> + <br> + {{ wtf.render_field(form.shared) }} + </div> + </div> + </div> + <div class="card-action right-align"> + {{ wtf.render_field(form.submit, material_icon='send') }} + </div> + </form> + </div> + </div> + </div> +</div> +{% endblock page_content %} diff --git a/migrations/versions/721829b5dd25_.py b/migrations/versions/721829b5dd25_.py new file mode 100644 index 0000000000000000000000000000000000000000..124ca07d15f2ccd126a4993bd05fc7e9969fb1ad --- /dev/null +++ b/migrations/versions/721829b5dd25_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 721829b5dd25 +Revises: 31dd42e5ea6f +Create Date: 2022-11-04 13:58:13.008301 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '721829b5dd25' +down_revision = '31dd42e5ea6f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('spacy_nlp_pipeline_models', sa.Column('pipeline_name', sa.String(length=64), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('spacy_nlp_pipeline_models', 'pipeline_name') + # ### end Alembic commands ###