mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-11-03 20:02:47 +00:00 
			
		
		
		
	Merge branch 'public-corpus' of gitlab.ub.uni-bielefeld.de:sfb1288inf/nopaque into public-corpus
This commit is contained in:
		@@ -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'
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = ''
 | 
			
		||||
 
 | 
			
		||||
@@ -1,233 +1,12 @@
 | 
			
		||||
from flask import (
 | 
			
		||||
    abort,
 | 
			
		||||
    current_app,
 | 
			
		||||
    flash,
 | 
			
		||||
    Markup,
 | 
			
		||||
    redirect,
 | 
			
		||||
    render_template,
 | 
			
		||||
    url_for
 | 
			
		||||
)
 | 
			
		||||
from flask_login import login_required, current_user
 | 
			
		||||
from threading import Thread
 | 
			
		||||
from app import db
 | 
			
		||||
from app.decorators import permission_required 
 | 
			
		||||
from app.models import (
 | 
			
		||||
    Permission,
 | 
			
		||||
    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'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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/<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}'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/tesseract-ocr-pipeline-models/<hashid:tesseract_ocr_pipeline_model_id>', methods=['DELETE'])
 | 
			
		||||
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():
 | 
			
		||||
            tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.query.get(tesseract_ocr_pipeline_model_id)
 | 
			
		||||
            tesseract_ocr_pipeline_model.delete()
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
 | 
			
		||||
    tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
 | 
			
		||||
    if not (tesseract_ocr_pipeline_model.user == current_user or current_user.is_administrator()):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    thread = Thread(
 | 
			
		||||
        target=_delete_tesseract_ocr_pipeline_model,
 | 
			
		||||
        args=(current_app._get_current_object(), tesseract_ocr_pipeline_model_id)
 | 
			
		||||
    )
 | 
			
		||||
    thread.start()
 | 
			
		||||
    return {}, 202
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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>/toggle-public-status', methods=['POST'])
 | 
			
		||||
@permission_required(Permission.CONTRIBUTE)
 | 
			
		||||
def toggle_tesseract_ocr_pipeline_model_public_status(tesseract_ocr_pipeline_model_id):
 | 
			
		||||
    tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
 | 
			
		||||
    if not (tesseract_ocr_pipeline_model.user == current_user or current_user.is_administrator()):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    tesseract_ocr_pipeline_model.is_public = not tesseract_ocr_pipeline_model.is_public
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return {}, 201
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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/<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}'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@bp.route('/spacy-nlp-pipeline-models/<hashid:spacy_nlp_pipeline_model_id>', methods=['DELETE'])
 | 
			
		||||
def delete_spacy_model(spacy_nlp_pipeline_model_id):
 | 
			
		||||
    def _delete_spacy_model(app, spacy_nlp_pipeline_model_id):
 | 
			
		||||
        with app.app_context():
 | 
			
		||||
            spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.query.get(spacy_nlp_pipeline_model_id)
 | 
			
		||||
            spacy_nlp_pipeline_model.delete()
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
    
 | 
			
		||||
    spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
 | 
			
		||||
    if not (spacy_nlp_pipeline_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('/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>/toggle-public-status', methods=['POST'])
 | 
			
		||||
@permission_required(Permission.CONTRIBUTE)
 | 
			
		||||
def toggle_spacy_nlp_pipeline_model_public_status(spacy_nlp_pipeline_model_id):
 | 
			
		||||
    spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
 | 
			
		||||
    if not (spacy_nlp_pipeline_model.user == current_user or current_user.is_administrator()):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    spacy_nlp_pipeline_model.is_public = not spacy_nlp_pipeline_model.is_public
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return {}, 201
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								app/contributions/spacy_nlp_pipeline_models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/contributions/spacy_nlp_pipeline_models/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										44
									
								
								app/contributions/spacy_nlp_pipeline_models/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/contributions/spacy_nlp_pipeline_models/forms.py
									
									
									
									
									
										Normal file
									
								
							@@ -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 = ''
 | 
			
		||||
							
								
								
									
										58
									
								
								app/contributions/spacy_nlp_pipeline_models/json_routes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								app/contributions/spacy_nlp_pipeline_models/json_routes.py
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										87
									
								
								app/contributions/spacy_nlp_pipeline_models/routes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								app/contributions/spacy_nlp_pipeline_models/routes.py
									
									
									
									
									
										Normal file
									
								
							@@ -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}'
 | 
			
		||||
    )
 | 
			
		||||
@@ -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
 | 
			
		||||
							
								
								
									
										35
									
								
								app/contributions/tesseract_ocr_pipeline_models/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/contributions/tesseract_ocr_pipeline_models/forms.py
									
									
									
									
									
										Normal file
									
								
							@@ -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 = ''
 | 
			
		||||
@@ -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
 | 
			
		||||
							
								
								
									
										80
									
								
								app/contributions/tesseract_ocr_pipeline_models/routes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								app/contributions/tesseract_ocr_pipeline_models/routes.py
									
									
									
									
									
										Normal file
									
								
							@@ -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}'
 | 
			
		||||
    )
 | 
			
		||||
@@ -16,6 +16,7 @@ from threading import Thread
 | 
			
		||||
import os
 | 
			
		||||
from .decorators import corpus_follower_permission_required, corpus_owner_or_admin_required
 | 
			
		||||
from app import db, hashids
 | 
			
		||||
from app.decorators import content_negotiation
 | 
			
		||||
from app.models import (
 | 
			
		||||
    Corpus,
 | 
			
		||||
    CorpusFile,
 | 
			
		||||
@@ -31,102 +32,6 @@ from .forms import (
 | 
			
		||||
    UpdateCorpusFileForm
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@bp.route('/fake-add')
 | 
			
		||||
@login_required
 | 
			
		||||
def fake_add():
 | 
			
		||||
    pjentsch = User.query.filter_by(username='pjentsch').first()
 | 
			
		||||
    alice = Corpus.query.filter_by(title='Alice in Wonderland').first()
 | 
			
		||||
    pjentsch.follow_corpus(alice)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return ''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/is_public', methods=['POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_owner_or_admin_required
 | 
			
		||||
def update_corpus_is_public(corpus_id):
 | 
			
		||||
    is_public = request.json
 | 
			
		||||
    if not isinstance(is_public, bool):
 | 
			
		||||
        response = jsonify('The request body must be a boolean')
 | 
			
		||||
        response.status_code = 400
 | 
			
		||||
        abort(response)
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    corpus.is_public = is_public
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return '', 204
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/followers/add', methods=['POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_owner_or_admin_required
 | 
			
		||||
def add_corpus_followers(corpus_id):
 | 
			
		||||
    usernames = request.json
 | 
			
		||||
    if not (isinstance(usernames, list) or all(isinstance(u, str) for u in usernames)):
 | 
			
		||||
        response = jsonify('The request body must be a list of strings')
 | 
			
		||||
        response.status_code = 400
 | 
			
		||||
        abort(response)
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    for username in usernames:
 | 
			
		||||
        user = User.query.filter_by(username=username, is_public=True).first_or_404()
 | 
			
		||||
        user.follow_corpus(corpus)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return '', 204
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/follow/<token>')
 | 
			
		||||
@login_required
 | 
			
		||||
def follow_corpus(corpus_id, token):
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    if current_user.follow_corpus_by_token(token):
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
        flash(f'You are following {corpus.title} now', category='corpus')
 | 
			
		||||
        return redirect(url_for('corpora.corpus', corpus_id=corpus_id))
 | 
			
		||||
    abort(403)    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/unfollow', methods=['POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
def unfollow_corpus(corpus_id, follower_id):
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    follower = User.query.get_or_404(follower_id)
 | 
			
		||||
    if not (corpus.user == current_user or follower == current_user or current_user.is_administrator()):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    if not follower.is_following_corpus(corpus):
 | 
			
		||||
        abort(409)  # 'User is not following the corpus'
 | 
			
		||||
    follower.unfollow_corpus(corpus)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    flash(f'{follower.username} is not following {corpus.title} anymore', category='corpus')
 | 
			
		||||
    return '', 204
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['POST'])
 | 
			
		||||
@corpus_follower_permission_required('UPDATE_FOLLOWER')
 | 
			
		||||
def add_permission(corpus_id, follower_id):
 | 
			
		||||
    corpus_follower_association = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404()
 | 
			
		||||
    if not (corpus_follower_association.corpus.user == current_user or current_user.is_administrator()):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    role_name = request.json.get('role')
 | 
			
		||||
    if role_name is None:
 | 
			
		||||
        abort(400)
 | 
			
		||||
    corpus_follower_role = CorpusFollowerRole.query.filter_by(name=role_name).first_or_404()
 | 
			
		||||
    corpus_follower_association.role = corpus_follower_role
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return '', 204
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/public')
 | 
			
		||||
@login_required
 | 
			
		||||
def public_corpora():
 | 
			
		||||
    corpora = [
 | 
			
		||||
        c.to_json_serializeable()
 | 
			
		||||
        for c in Corpus.query.filter(Corpus.is_public == True).all()
 | 
			
		||||
    ]
 | 
			
		||||
    return render_template(
 | 
			
		||||
        'corpora/public_corpora.html.j2',
 | 
			
		||||
        corpora=corpora,
 | 
			
		||||
        title='Corpora'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/create', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@@ -154,6 +59,24 @@ def create_corpus():
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/public')
 | 
			
		||||
@login_required
 | 
			
		||||
def public_corpora():
 | 
			
		||||
    corpora = [
 | 
			
		||||
        c.to_json_serializeable()
 | 
			
		||||
        for c in Corpus.query.filter(Corpus.is_public == True).all()
 | 
			
		||||
    ]
 | 
			
		||||
    return render_template(
 | 
			
		||||
        'corpora/public_corpora.html.j2',
 | 
			
		||||
        corpora=corpora,
 | 
			
		||||
        title='Corpora'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Corpus                                                                     #
 | 
			
		||||
##############################################################################
 | 
			
		||||
#region corpus
 | 
			
		||||
@bp.route('/<hashid:corpus_id>')
 | 
			
		||||
@login_required
 | 
			
		||||
def corpus(corpus_id):
 | 
			
		||||
@@ -181,6 +104,18 @@ def corpus(corpus_id):
 | 
			
		||||
    abort(403)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/analyse')
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_follower_permission_required('VIEW')
 | 
			
		||||
def analyse_corpus(corpus_id):
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    return render_template(
 | 
			
		||||
        'corpora/analyse_corpus.html.j2',
 | 
			
		||||
        corpus=corpus,
 | 
			
		||||
        title=f'Analyse Corpus {corpus.title}'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/generate-corpus-share-link', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_follower_permission_required('GENERATE_SHARE_LINK')
 | 
			
		||||
@@ -195,9 +130,34 @@ def generate_corpus_share_link(corpus_id):
 | 
			
		||||
    return link
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/follow/<token>')
 | 
			
		||||
@login_required
 | 
			
		||||
def follow_corpus(corpus_id, token):
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    if current_user.follow_corpus_by_token(token):
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
        flash(f'You are following {corpus.title} now', category='corpus')
 | 
			
		||||
        return redirect(url_for('corpora.corpus', corpus_id=corpus_id))
 | 
			
		||||
    abort(403)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/import', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
def import_corpus():
 | 
			
		||||
    abort(503)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/export')
 | 
			
		||||
@login_required
 | 
			
		||||
def export_corpus(corpus_id):
 | 
			
		||||
    abort(503)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#region json-routes
 | 
			
		||||
@bp.route('/<hashid:corpus_id>', methods=['DELETE'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_owner_or_admin_required
 | 
			
		||||
@content_negotiation(produces='application/json')
 | 
			
		||||
def delete_corpus(corpus_id):
 | 
			
		||||
    def _delete_corpus(app, corpus_id):
 | 
			
		||||
        with app.app_context():
 | 
			
		||||
@@ -208,27 +168,22 @@ def delete_corpus(corpus_id):
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    thread = Thread(
 | 
			
		||||
        target=_delete_corpus,
 | 
			
		||||
        args=(current_app._get_current_object(), corpus_id)
 | 
			
		||||
        args=(current_app._get_current_object(), corpus.id)
 | 
			
		||||
    )
 | 
			
		||||
    thread.start()
 | 
			
		||||
    return {}, 202
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/analyse')
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_follower_permission_required('VIEW')
 | 
			
		||||
def analyse_corpus(corpus_id):
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    return render_template(
 | 
			
		||||
        'corpora/analyse_corpus.html.j2',
 | 
			
		||||
        corpus=corpus,
 | 
			
		||||
        title=f'Analyse Corpus {corpus.title}'
 | 
			
		||||
    )
 | 
			
		||||
    response_data = {
 | 
			
		||||
        'message': f'Corpus "{corpus.title}" marked for deletion',
 | 
			
		||||
        'category': 'corpus'
 | 
			
		||||
    }
 | 
			
		||||
    response = jsonify(response_data)
 | 
			
		||||
    response.status_code = 200
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/build', methods=['POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_owner_or_admin_required
 | 
			
		||||
@content_negotiation(produces='application/json')
 | 
			
		||||
def build_corpus(corpus_id):
 | 
			
		||||
    def _build_corpus(app, corpus_id):
 | 
			
		||||
        with app.app_context():
 | 
			
		||||
@@ -239,18 +194,51 @@ def build_corpus(corpus_id):
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    if not (corpus.user == current_user or current_user.is_administrator()):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    # Check if the corpus has corpus files
 | 
			
		||||
    if not corpus.files.all():
 | 
			
		||||
        response = {'errors': {'message': 'Corpus file(s) required'}}
 | 
			
		||||
        return response, 409
 | 
			
		||||
    if len(corpus.files.all()) == 0:
 | 
			
		||||
        abort(409)
 | 
			
		||||
    thread = Thread(
 | 
			
		||||
        target=_build_corpus,
 | 
			
		||||
        args=(current_app._get_current_object(), corpus_id)
 | 
			
		||||
    )
 | 
			
		||||
    thread.start()
 | 
			
		||||
    return {}, 202
 | 
			
		||||
    response_data = {
 | 
			
		||||
        'message': f'Corpus "{corpus.title}" marked for building',
 | 
			
		||||
        'category': 'corpus'
 | 
			
		||||
    }
 | 
			
		||||
    response = jsonify(response_data)
 | 
			
		||||
    response.status_code = 202
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/is_public', methods=['PUT'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_owner_or_admin_required
 | 
			
		||||
@content_negotiation(consumes='application/json', produces='application/json')
 | 
			
		||||
def update_corpus_is_public(corpus_id):
 | 
			
		||||
    is_public = request.json
 | 
			
		||||
    if not isinstance(is_public, bool):
 | 
			
		||||
        abort(400)
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    corpus.is_public = is_public
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    response_data = {
 | 
			
		||||
        'message': (
 | 
			
		||||
            f'Corpus "{corpus.title}" is now'
 | 
			
		||||
            f' {"public" if is_public else "private"}'
 | 
			
		||||
        ),
 | 
			
		||||
        'category': 'corpus'
 | 
			
		||||
    }
 | 
			
		||||
    response = jsonify(response_data)
 | 
			
		||||
    response.status_code = 200
 | 
			
		||||
    return response
 | 
			
		||||
#endregion json-routes
 | 
			
		||||
#endregion corpus
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Corpus/Files                                                               #
 | 
			
		||||
##############################################################################
 | 
			
		||||
#region files
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/files/create', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_follower_permission_required('ADD_CORPUS_FILE')
 | 
			
		||||
@@ -301,7 +289,7 @@ def create_corpus_file(corpus_id):
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_follower_permission_required('ADD_CORPUS_FILE', 'UPDATE_CORPUS_FILE', 'REMOVE_CORPUS_FILE')
 | 
			
		||||
@corpus_follower_permission_required('UPDATE_CORPUS_FILE')
 | 
			
		||||
def corpus_file(corpus_id, corpus_file_id):
 | 
			
		||||
    corpus_file = CorpusFile.query.filter_by(corpus_id = corpus_id, id=corpus_file_id).first_or_404()
 | 
			
		||||
    form = UpdateCorpusFileForm(data=corpus_file.to_json_serializeable())
 | 
			
		||||
@@ -322,27 +310,6 @@ def corpus_file(corpus_id, corpus_file_id):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['DELETE'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_follower_permission_required('REMOVE_CORPUS_FILE')
 | 
			
		||||
def delete_corpus_file(corpus_id, corpus_file_id):
 | 
			
		||||
    def _delete_corpus_file(app, corpus_file_id):
 | 
			
		||||
        with app.app_context():
 | 
			
		||||
            corpus_file = CorpusFile.query.get(corpus_file_id)
 | 
			
		||||
            corpus_file.delete()
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
 | 
			
		||||
    corpus_file = CorpusFile.query.filter_by(corpus_id = corpus_id, id=corpus_file_id).first_or_404()
 | 
			
		||||
    if not (corpus_file.corpus.user == current_user or current_user.is_administrator()):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    thread = Thread(
 | 
			
		||||
        target=_delete_corpus_file,
 | 
			
		||||
        args=(current_app._get_current_object(), corpus_file_id)
 | 
			
		||||
    )
 | 
			
		||||
    thread.start()
 | 
			
		||||
    return {}, 202
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>/download')
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_follower_permission_required('VIEW')
 | 
			
		||||
@@ -359,13 +326,95 @@ def download_corpus_file(corpus_id, corpus_file_id):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/import', methods=['GET', 'POST'])
 | 
			
		||||
#region json-routes
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['DELETE'])
 | 
			
		||||
@login_required
 | 
			
		||||
def import_corpus():
 | 
			
		||||
    abort(503)
 | 
			
		||||
@corpus_follower_permission_required('REMOVE_CORPUS_FILE')
 | 
			
		||||
@content_negotiation(produces='application/json')
 | 
			
		||||
def delete_corpus_file(corpus_id, corpus_file_id):
 | 
			
		||||
    def _delete_corpus_file(app, corpus_file_id):
 | 
			
		||||
        with app.app_context():
 | 
			
		||||
            corpus_file = CorpusFile.query.get(corpus_file_id)
 | 
			
		||||
            corpus_file.delete()
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
 | 
			
		||||
    corpus_file = CorpusFile.query.filter_by(corpus_id=corpus_id, id=corpus_file_id).first_or_404()
 | 
			
		||||
    thread = Thread(
 | 
			
		||||
        target=_delete_corpus_file,
 | 
			
		||||
        args=(current_app._get_current_object(), corpus_file.id)
 | 
			
		||||
    )
 | 
			
		||||
    thread.start()
 | 
			
		||||
    return {}, 202
 | 
			
		||||
#endregion json-routes
 | 
			
		||||
#endregion files
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Corpus/Followers                                                           #
 | 
			
		||||
##############################################################################
 | 
			
		||||
#region followers
 | 
			
		||||
#region json-routes
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/followers', methods=['POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_owner_or_admin_required
 | 
			
		||||
@content_negotiation(consumes='application/json', produces='application/json')
 | 
			
		||||
def add_corpus_followers(corpus_id):
 | 
			
		||||
    usernames = request.json
 | 
			
		||||
    if not (isinstance(usernames, list) or all(isinstance(u, str) for u in usernames)):
 | 
			
		||||
        abort(400)
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    for username in usernames:
 | 
			
		||||
        user = User.query.filter_by(username=username, is_public=True).first_or_404()
 | 
			
		||||
        user.follow_corpus(corpus)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    resonse_data = {
 | 
			
		||||
        'message': f'Users are now following "{corpus.title}"',
 | 
			
		||||
        'category': 'corpus'
 | 
			
		||||
    }
 | 
			
		||||
    response = jsonify(resonse_data)
 | 
			
		||||
    response.status_code = 200
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/export')
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>', methods=['DELETE'])
 | 
			
		||||
@login_required
 | 
			
		||||
def export_corpus(corpus_id):
 | 
			
		||||
    abort(503)
 | 
			
		||||
@content_negotiation(produces='application/json')
 | 
			
		||||
def unfollow_corpus(corpus_id, follower_id):
 | 
			
		||||
    corpus = Corpus.query.get_or_404(corpus_id)
 | 
			
		||||
    follower = User.query.get_or_404(follower_id)
 | 
			
		||||
    if not (corpus.user == current_user or follower == current_user or current_user.is_administrator()):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    if not follower.is_following_corpus(corpus):
 | 
			
		||||
        abort(409)  # 'User is not following the corpus'
 | 
			
		||||
    follower.unfollow_corpus(corpus)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    response_data = {
 | 
			
		||||
        'message': \
 | 
			
		||||
            f'"{follower.username}" is not following "{corpus.title}" anymore',
 | 
			
		||||
        'category': 'corpus'
 | 
			
		||||
    }
 | 
			
		||||
    response = jsonify(response_data)
 | 
			
		||||
    response.status_code = 200
 | 
			
		||||
    return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['PUT'])
 | 
			
		||||
@login_required
 | 
			
		||||
@corpus_owner_or_admin_required
 | 
			
		||||
@content_negotiation(consumes='application/json', produces='application/json')
 | 
			
		||||
def add_permission(corpus_id, follower_id):
 | 
			
		||||
    role_name = request.json
 | 
			
		||||
    if not isinstance(role_name, str):
 | 
			
		||||
        abort(400)
 | 
			
		||||
    cfr = CorpusFollowerRole.query.filter_by(name=role_name).first_or_404()
 | 
			
		||||
    cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404()
 | 
			
		||||
    cfa.role = cfr
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    resonse_data = {
 | 
			
		||||
        'message': f'User "{cfa.follower.username}" is now {cfa.role.name}',
 | 
			
		||||
        'category': 'corpus'
 | 
			
		||||
    }
 | 
			
		||||
    response = jsonify(resonse_data)
 | 
			
		||||
    response.status_code = 200
 | 
			
		||||
    return response
 | 
			
		||||
#endregion json-routes
 | 
			
		||||
#endregion followers
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
from flask import abort, current_app
 | 
			
		||||
from flask import abort, current_app, request
 | 
			
		||||
from flask_login import current_user
 | 
			
		||||
from functools import wraps
 | 
			
		||||
from threading import Thread
 | 
			
		||||
from typing import List, Union
 | 
			
		||||
from werkzeug.exceptions import NotAcceptable
 | 
			
		||||
from app.models import Permission
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -61,3 +63,37 @@ def background(f):
 | 
			
		||||
        thread.start()
 | 
			
		||||
        return thread
 | 
			
		||||
    return wrapped
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def content_negotiation(
 | 
			
		||||
    produces: Union[str, List[str], None] = None,
 | 
			
		||||
    consumes: Union[str, List[str], None] = None
 | 
			
		||||
):
 | 
			
		||||
    def decorator(f):
 | 
			
		||||
        @wraps(f)
 | 
			
		||||
        def decorated_function(*args, **kwargs):
 | 
			
		||||
            provided = request.mimetype
 | 
			
		||||
            if consumes is None:
 | 
			
		||||
                consumeables = None
 | 
			
		||||
            elif isinstance(consumes, str):
 | 
			
		||||
                consumeables = {consumes}
 | 
			
		||||
            elif isinstance(consumes, list) and all(isinstance(x, str) for x in consumes):
 | 
			
		||||
                consumeables = {*consumes}
 | 
			
		||||
            else:
 | 
			
		||||
                raise TypeError()
 | 
			
		||||
            accepted = {*request.accept_mimetypes.values()}
 | 
			
		||||
            if produces is None:
 | 
			
		||||
                produceables = None
 | 
			
		||||
            elif isinstance(produces, str):
 | 
			
		||||
                produceables = {produces}
 | 
			
		||||
            elif isinstance(produces, list) and all(isinstance(x, str) for x in produces):
 | 
			
		||||
                produceables = {*produces}
 | 
			
		||||
            else:
 | 
			
		||||
                raise TypeError()
 | 
			
		||||
            if produceables is not None and len(produceables & accepted) == 0:
 | 
			
		||||
                raise NotAcceptable()
 | 
			
		||||
            if consumeables is not None and provided not in consumeables:
 | 
			
		||||
                raise NotAcceptable()
 | 
			
		||||
            return f(*args, **kwargs)
 | 
			
		||||
        return decorated_function
 | 
			
		||||
    return decorator
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								app/static/js/Requests/Corpora.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/static/js/Requests/Corpora.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										37
									
								
								app/static/js/Requests/Requests.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/static/js/Requests/Requests.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
Requests = {};
 | 
			
		||||
 | 
			
		||||
Requests.JSONfetch = (input, init={}) => {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    let fixedInit = {};
 | 
			
		||||
    fixedInit.headers = {};
 | 
			
		||||
    fixedInit.headers['Accept'] = 'application/json';
 | 
			
		||||
    if (init.hasOwnProperty('body')) {
 | 
			
		||||
      fixedInit.headers['Content-Type'] = 'application/json';
 | 
			
		||||
    }
 | 
			
		||||
    fetch(input, Utils.mergeObjectsDeep(init, fixedInit))
 | 
			
		||||
      .then(
 | 
			
		||||
        (response) => {
 | 
			
		||||
          response.json()
 | 
			
		||||
            .then(
 | 
			
		||||
              (json) => {
 | 
			
		||||
                let message = json.message || json;
 | 
			
		||||
                let category = json.category || 'message';
 | 
			
		||||
                app.flash(message, category);
 | 
			
		||||
              },
 | 
			
		||||
              (error) => {
 | 
			
		||||
                app.flash(`[${response.status}]: ${response.statusText}`, 'error');
 | 
			
		||||
              }
 | 
			
		||||
            );
 | 
			
		||||
          if (response.ok) {
 | 
			
		||||
            resolve(response);
 | 
			
		||||
          } else {
 | 
			
		||||
            reject(response);
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        (response) => {
 | 
			
		||||
          app.flash('Something went wrong', 'error');
 | 
			
		||||
          reject(response);
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										5
									
								
								app/static/js/Requests/contributions/contributions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/static/js/Requests/contributions/contributions.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
* Contributions                                                              *
 | 
			
		||||
* Fetch requests for /contributions routes                                   *
 | 
			
		||||
*****************************************************************************/
 | 
			
		||||
Requests.contributions = {};
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
* SpaCy NLP Pipeline Models                                                  *
 | 
			
		||||
* Fetch requests for /contributions/spacy-nlp-pipeline-models routes         *
 | 
			
		||||
*****************************************************************************/
 | 
			
		||||
Requests.contributions.spacy_nlp_pipeline_models = {};
 | 
			
		||||
 | 
			
		||||
Requests.contributions.spacy_nlp_pipeline_models.entity = {};
 | 
			
		||||
 | 
			
		||||
Requests.contributions.spacy_nlp_pipeline_models.entity.delete = (spacyNlpPipelineModelId) => {
 | 
			
		||||
  let input = `/contributions/spacy-nlp-pipeline-models/${spacyNlpPipelineModelId}`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'DELETE'
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Requests.contributions.spacy_nlp_pipeline_models.entity.isPublic = {};
 | 
			
		||||
 | 
			
		||||
Requests.contributions.spacy_nlp_pipeline_models.entity.isPublic.update = (spacyNlpPipelineModelId, value) => {
 | 
			
		||||
  let input = `/contributions/spacy-nlp-pipeline-models/${spacyNlpPipelineModelId}/is_public`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'PUT',
 | 
			
		||||
    body: JSON.stringify(value)
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
* Tesseract OCR Pipeline Models                                              *
 | 
			
		||||
* Fetch requests for /contributions/tesseract-ocr-pipeline-models routes     *
 | 
			
		||||
*****************************************************************************/
 | 
			
		||||
Requests.contributions.tesseract_ocr_pipeline_models = {};
 | 
			
		||||
 | 
			
		||||
Requests.contributions.tesseract_ocr_pipeline_models.entity = {};
 | 
			
		||||
 | 
			
		||||
Requests.contributions.tesseract_ocr_pipeline_models.entity.delete = (tesseractOcrPipelineModelId) => {
 | 
			
		||||
  let input = `/contributions/tesseract-ocr-pipeline-models/${tesseractOcrPipelineModelId}`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'DELETE'
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic = {};
 | 
			
		||||
 | 
			
		||||
Requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic.update = (tesseractOcrPipelineModelId, value) => {
 | 
			
		||||
  let input = `/contributions/tesseract-ocr-pipeline-models/${tesseractOcrPipelineModelId}/is_public`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'PUT',
 | 
			
		||||
    body: JSON.stringify(value)
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										78
									
								
								app/static/js/Requests/corpora/corpora.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								app/static/js/Requests/corpora/corpora.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
* Corpora                                                                    *
 | 
			
		||||
* Fetch requests for /corpora routes                                         *
 | 
			
		||||
*****************************************************************************/
 | 
			
		||||
Requests.corpora = {};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent = {};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.delete = (corpusId) => {
 | 
			
		||||
  let input = `/corpora/${corpusId}`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'DELETE'
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.build = (corpusId) => {
 | 
			
		||||
  let input = `/corpora/${corpusId}/build`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'POST',
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.isPublic = {};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.isPublic.update = (corpusId, value) => {
 | 
			
		||||
  let input = `/corpora/${corpusId}/is_public`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'PUT',
 | 
			
		||||
    body: JSON.stringify(value)
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.files = {};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.files.ent = {};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.files.ent.delete = (corpusId, corpusFileId) => {
 | 
			
		||||
  let input = `/corpora/${corpusId}/files/${corpusFileId}`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'DELETE',
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.followers = {};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.followers.add = (corpusId, usernames) => {
 | 
			
		||||
  let input = `/corpora/${corpusId}/followers`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'POST',
 | 
			
		||||
    body: JSON.stringify(usernames)
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.followers.ent = {};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.followers.ent.delete = (corpusId, followerId) => {
 | 
			
		||||
  let input = `/corpora/${corpusId}/followers/${followerId}`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'DELETE',
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.followers.ent.role = {};
 | 
			
		||||
 | 
			
		||||
Requests.corpora.ent.followers.ent.role.update = (corpusId, followerId, value) => {
 | 
			
		||||
  let input = `/corpora/${corpusId}/followers/${followerId}/role`;
 | 
			
		||||
  let init = {
 | 
			
		||||
    method: 'PUT',
 | 
			
		||||
    body: JSON.stringify(value)
 | 
			
		||||
  };
 | 
			
		||||
  return Requests.JSONfetch(input, init);
 | 
			
		||||
};
 | 
			
		||||
@@ -1,16 +1,11 @@
 | 
			
		||||
class CorpusDisplay extends RessourceDisplay {
 | 
			
		||||
class CorpusDisplay extends ResourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    super(displayElement);
 | 
			
		||||
    this.corpusId = displayElement.dataset.corpusId;
 | 
			
		||||
    this.displayElement
 | 
			
		||||
      .querySelector('.action-button[data-action="build-request"]')
 | 
			
		||||
      .addEventListener('click', (event) => {
 | 
			
		||||
        Utils.buildCorpusRequest(this.userId, this.corpusId);
 | 
			
		||||
      });
 | 
			
		||||
    this.displayElement
 | 
			
		||||
      .querySelector('.action-button[data-action="delete-request"]')
 | 
			
		||||
      .addEventListener('click', (event) => {
 | 
			
		||||
        Utils.deleteCorpusRequest(this.userId, this.corpusId);
 | 
			
		||||
        Requests.corpora.corpus.build(this.corpusId);
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
class JobDisplay extends RessourceDisplay {
 | 
			
		||||
class JobDisplay extends ResourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    super(displayElement);
 | 
			
		||||
    this.jobId = this.displayElement.dataset.jobId;
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
class RessourceDisplay {
 | 
			
		||||
class ResourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    this.displayElement = displayElement;
 | 
			
		||||
    this.userId = this.displayElement.dataset.userId;
 | 
			
		||||
@@ -119,7 +119,11 @@ class SpaCyNLPPipelineModelList extends ResourceList {
 | 
			
		||||
    let listAction = listActionElement.dataset.listAction;
 | 
			
		||||
    switch (listAction) {
 | 
			
		||||
      case 'toggle-is-public': {
 | 
			
		||||
        Utils.spaCyNLPPipelineModelToggleIsPublicRequest(this.userId, itemId);
 | 
			
		||||
        let newIsPublicValue = listActionElement.checked;
 | 
			
		||||
        Requests.contributions.spacy_nlp_pipeline_models.entity.isPublic.update(itemId, newIsPublicValue)
 | 
			
		||||
          .catch((response) => {
 | 
			
		||||
            listActionElement.checked = !newIsPublicValue;
 | 
			
		||||
          });
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default: {
 | 
			
		||||
@@ -137,7 +141,37 @@ class SpaCyNLPPipelineModelList extends ResourceList {
 | 
			
		||||
    let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction;
 | 
			
		||||
    switch (listAction) {
 | 
			
		||||
      case 'delete-request': {
 | 
			
		||||
        Utils.deleteSpaCyNLPPipelineModelRequest(this.userId, itemId);
 | 
			
		||||
        let values = this.listjs.get('id', itemId)[0].values();
 | 
			
		||||
        let modalElement = Utils.HTMLToElement(
 | 
			
		||||
          `
 | 
			
		||||
            <div class="modal">
 | 
			
		||||
              <div class="modal-content">
 | 
			
		||||
                <h4>Confirm SpaCy NLP Pipeline Model deletion</h4>
 | 
			
		||||
                <p>Do you really want to delete the SpaCy NLP Pipeline Model <b>${values.title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="modal-footer">
 | 
			
		||||
                <a class="btn modal-close waves-effect waves-light">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) => {
 | 
			
		||||
          Requests.contributions.spacy_nlp_pipeline_models.entity.delete(itemId);
 | 
			
		||||
        });
 | 
			
		||||
        modal.open();
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 'view': {
 | 
			
		||||
 
 | 
			
		||||
@@ -128,7 +128,11 @@ class TesseractOCRPipelineModelList extends ResourceList {
 | 
			
		||||
    let listAction = listActionElement.dataset.listAction;
 | 
			
		||||
    switch (listAction) {
 | 
			
		||||
      case 'toggle-is-public': {
 | 
			
		||||
        Utils.tesseractOCRPipelineModelToggleIsPublicRequest(this.userId, itemId);
 | 
			
		||||
        let newIsPublicValue = listActionElement.checked;
 | 
			
		||||
        Requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic.update(itemId, newIsPublicValue)
 | 
			
		||||
          .catch((response) => {
 | 
			
		||||
            listActionElement.checked = !newIsPublicValue;
 | 
			
		||||
          });
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default: {
 | 
			
		||||
@@ -151,7 +155,37 @@ class TesseractOCRPipelineModelList extends ResourceList {
 | 
			
		||||
    let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction;
 | 
			
		||||
    switch (listAction) {
 | 
			
		||||
      case 'delete-request': {
 | 
			
		||||
        Utils.deleteTesseractOCRPipelineModelRequest(this.userId, itemId);
 | 
			
		||||
        let values = this.listjs.get('id', itemId)[0].values();
 | 
			
		||||
        let modalElement = Utils.HTMLToElement(
 | 
			
		||||
          `
 | 
			
		||||
            <div class="modal">
 | 
			
		||||
              <div class="modal-content">
 | 
			
		||||
                <h4>Confirm Tesseract OCR Pipeline Model deletion</h4>
 | 
			
		||||
                <p>Do you really want to delete the Tesseract OCR Pipeline Model <b>${values.title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="modal-footer">
 | 
			
		||||
                <a class="btn modal-close waves-effect waves-light">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) => {
 | 
			
		||||
          Requests.contributions.tesseract_ocr_pipeline_models.entity.delete(itemId);
 | 
			
		||||
        });
 | 
			
		||||
        modal.open();
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 'view': {
 | 
			
		||||
 
 | 
			
		||||
@@ -101,13 +101,21 @@ class Utils {
 | 
			
		||||
 | 
			
		||||
  static updateCorpusFollowerRole(corpusId, followerId, roleName) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      fetch(`/corpora/${corpusId}/followers/${followerId}/role`, {method: 'POST', headers: {Accept: 'application/json', 'Content-Type': 'application/json'}, body: JSON.stringify({role: roleName})})
 | 
			
		||||
      let fetchRessource = `/corpora/${corpusId}/followers/${followerId}/role`;
 | 
			
		||||
      let fetchOptions = {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Accept': 'application/json',
 | 
			
		||||
          'Content-Type': 'application/json'
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({role: roleName})
 | 
			
		||||
      };
 | 
			
		||||
      fetch(fetchRessource, fetchOptions)
 | 
			
		||||
        .then(
 | 
			
		||||
          (response) => {
 | 
			
		||||
            if (response.ok) {
 | 
			
		||||
              app.flash('Role updated', 'corpus');
 | 
			
		||||
              resolve(response);
 | 
			
		||||
              return;
 | 
			
		||||
            } else {
 | 
			
		||||
              app.flash(`${response.statusText}`, 'error');
 | 
			
		||||
              reject(response);
 | 
			
		||||
@@ -179,7 +187,15 @@ class Utils {
 | 
			
		||||
 | 
			
		||||
  static unfollowCorpusRequest(corpusId, followerId) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      fetch(`/corpora/${corpusId}/followers/${followerId}/unfollow`, {method: 'POST', headers: {Accept: 'application/json'}})
 | 
			
		||||
      let fetchRessource = `/corpora/${corpusId}/followers/${followerId}/unfollow`;
 | 
			
		||||
      let fetchOptions = {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Accept': 'application/json',
 | 
			
		||||
          'Content-Type': 'application/json'
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      fetch(fetchRessource, fetchOptions)
 | 
			
		||||
        .then(
 | 
			
		||||
          (response) => {
 | 
			
		||||
            if (response.ok) {
 | 
			
		||||
@@ -683,23 +699,27 @@ class Utils {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static tesseractOCRPipelineModelToggleIsPublicRequest(userId, tesseractOCRPipelineModelId, is_public) {
 | 
			
		||||
  static updateTesseractOCRPipelineModelIsPublicRequest(tesseractOCRPipelineModelId, newIsPublicValue) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      let tesseractOCRPipelineModel;
 | 
			
		||||
      try {
 | 
			
		||||
        tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId];
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        tesseractOCRPipelineModel = {};
 | 
			
		||||
      }
 | 
			
		||||
    
 | 
			
		||||
      fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}})
 | 
			
		||||
      let fetchRessource = `/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}/is_public`;
 | 
			
		||||
      let fetchOptions = {
 | 
			
		||||
        method: 'PUT',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Accept': 'application/json',
 | 
			
		||||
          'Content-Type': 'application/json'
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify(newIsPublicValue)
 | 
			
		||||
      };
 | 
			
		||||
      fetch(fetchRessource, fetchOptions)
 | 
			
		||||
        .then(
 | 
			
		||||
          (response) => {
 | 
			
		||||
            if (response.status === 403) {
 | 
			
		||||
              app.flash('Forbidden', 'error');
 | 
			
		||||
            if (response.ok) {
 | 
			
		||||
              response.json().then((data) => {app.flash(data);});
 | 
			
		||||
              resolve(response);
 | 
			
		||||
            } else {
 | 
			
		||||
              app.flash(`${response.statusText}`, 'error');
 | 
			
		||||
              reject(response);
 | 
			
		||||
            }
 | 
			
		||||
            resolve(response);
 | 
			
		||||
          },
 | 
			
		||||
          (response) => {
 | 
			
		||||
            app.flash('Something went wrong', 'error');
 | 
			
		||||
@@ -709,23 +729,27 @@ class Utils {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static spaCyNLPPipelineModelToggleIsPublicRequest(userId, spaCyNLPPipelineModelId) {
 | 
			
		||||
  static updateSpaCyNLPPipelineModelIsPublicRequest(SpaCyNLPPipelineModelId, newIsPublicValue) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      let spaCyNLPPipelineModel;
 | 
			
		||||
      try {
 | 
			
		||||
        spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId];
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        spaCyNLPPipelineModel = {};
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}})
 | 
			
		||||
      let fetchRessource = `/contributions/spacy-nlp-pipeline-models/${SpaCyNLPPipelineModelId}/is_public`;
 | 
			
		||||
      let fetchOptions = {
 | 
			
		||||
        method: 'PUT',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Accept': 'application/json',
 | 
			
		||||
          'Content-Type': 'application/json'
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify(newIsPublicValue)
 | 
			
		||||
      };
 | 
			
		||||
      fetch(fetchRessource, fetchOptions)
 | 
			
		||||
        .then(
 | 
			
		||||
          (response) => {
 | 
			
		||||
            if (response.status === 403) {
 | 
			
		||||
              app.flash('Forbidden', 'error');
 | 
			
		||||
            if (response.ok) {
 | 
			
		||||
              response.json().then((data) => {app.flash(data);});
 | 
			
		||||
              resolve(response);
 | 
			
		||||
            } else {
 | 
			
		||||
              app.flash(`${response.statusText}`, 'error');
 | 
			
		||||
              reject(response);
 | 
			
		||||
            }
 | 
			
		||||
            resolve(response);
 | 
			
		||||
          },
 | 
			
		||||
          (response) => {
 | 
			
		||||
            app.flash('Something went wrong', 'error');
 | 
			
		||||
 
 | 
			
		||||
@@ -6,18 +6,37 @@
 | 
			
		||||
  output='gen/app.%(version)s.js',
 | 
			
		||||
  'js/App.js',
 | 
			
		||||
  'js/Utils.js',
 | 
			
		||||
  'js/Forms/Form.js',
 | 
			
		||||
  'js/Forms/CreateCorpusFileForm.js',
 | 
			
		||||
  'js/Forms/CreateJobForm.js',
 | 
			
		||||
  'js/Forms/CreateContributionForm.js',
 | 
			
		||||
  'js/CorpusAnalysis/CQiClient.js',
 | 
			
		||||
  'js/CorpusAnalysis/CorpusAnalysisApp.js',
 | 
			
		||||
  'js/CorpusAnalysis/CorpusAnalysisConcordance.js',
 | 
			
		||||
  'js/CorpusAnalysis/CorpusAnalysisReader.js',
 | 
			
		||||
  'js/CorpusAnalysis/QueryBuilder.js',
 | 
			
		||||
  'js/RessourceDisplays/RessourceDisplay.js',
 | 
			
		||||
  'js/RessourceDisplays/CorpusDisplay.js',
 | 
			
		||||
  'js/RessourceDisplays/JobDisplay.js',
 | 
			
		||||
  'js/XMLtoObject.js'
 | 
			
		||||
%}
 | 
			
		||||
<script src="{{ ASSET_URL }}"></script>
 | 
			
		||||
{%- endassets %}
 | 
			
		||||
{%- assets
 | 
			
		||||
  filters='rjsmin',
 | 
			
		||||
  output='gen/Forms.%(version)s.js',
 | 
			
		||||
  'js/Forms/Form.js',
 | 
			
		||||
  'js/Forms/CreateCorpusFileForm.js',
 | 
			
		||||
  'js/Forms/CreateJobForm.js',
 | 
			
		||||
  'js/Forms/CreateContributionForm.js'
 | 
			
		||||
%}
 | 
			
		||||
<script src="{{ ASSET_URL }}"></script>
 | 
			
		||||
{%- endassets %}
 | 
			
		||||
{%- assets
 | 
			
		||||
  filters='rjsmin',
 | 
			
		||||
  output='gen/ResourceDisplays.%(version)s.js',
 | 
			
		||||
  'js/ResourceDisplays/ResourceDisplay.js',
 | 
			
		||||
  'js/ResourceDisplays/CorpusDisplay.js',
 | 
			
		||||
  'js/ResourceDisplays/JobDisplay.js'
 | 
			
		||||
%}
 | 
			
		||||
<script src="{{ ASSET_URL }}"></script>
 | 
			
		||||
{%- endassets %}
 | 
			
		||||
{%- assets
 | 
			
		||||
  filters='rjsmin',
 | 
			
		||||
  output='gen/ResourceLists.%(version)s.js',
 | 
			
		||||
  'js/ResourceLists/ResourceList.js',
 | 
			
		||||
  'js/ResourceLists/CorpusFileList.js',
 | 
			
		||||
  'js/ResourceLists/PublicCorpusFileList.js',
 | 
			
		||||
@@ -31,8 +50,18 @@
 | 
			
		||||
  'js/ResourceLists/TesseractOCRPipelineModelList.js',
 | 
			
		||||
  'js/ResourceLists/UserList.js',
 | 
			
		||||
  'js/ResourceLists/AdminUserList.js',
 | 
			
		||||
  'js/ResourceLists/CorpusFollowerList.js',
 | 
			
		||||
  'js/XMLtoObject.js'
 | 
			
		||||
  'js/ResourceLists/CorpusFollowerList.js'
 | 
			
		||||
%}
 | 
			
		||||
<script src="{{ ASSET_URL }}"></script>
 | 
			
		||||
{%- endassets %}
 | 
			
		||||
{%- assets
 | 
			
		||||
  filters='rjsmin',
 | 
			
		||||
  output='gen/Requests.%(version)s.js',
 | 
			
		||||
  'js/Requests/Requests.js',
 | 
			
		||||
  'js/Requests/contributions/contributions.js',
 | 
			
		||||
  'js/Requests/contributions/spacy_nlp_pipeline_models.js',
 | 
			
		||||
  'js/Requests/contributions/tesseract_ocr_pipeline_models.js',
 | 
			
		||||
  'js/Requests/Corpora.js'
 | 
			
		||||
%}
 | 
			
		||||
<script src="{{ ASSET_URL }}"></script>
 | 
			
		||||
{%- endassets %}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
 | 
			
		||||
@@ -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">
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
 | 
			
		||||
@@ -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">
 | 
			
		||||
@@ -68,7 +68,7 @@
 | 
			
		||||
              <a class="btn waves-effect waves-light modal-trigger" href="#publishing-modal" style="width: 100%;"><i class="material-icons left">publish</i>Publishing</a>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col s12 l6" style="padding: 5px 2.5px 0 2.5px;">
 | 
			
		||||
              <a class="action-button btn red waves-effect waves-light" data-action="delete-request" style="width: 100%;"><i class="material-icons left">delete</i>Delete</a>
 | 
			
		||||
              <a class="btn red waves-effect waves-light modal-trigger" href="#delete-modal" style="width: 100%;"><i class="material-icons left">delete</i>Delete</a>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <span class="card-title">Social</span>
 | 
			
		||||
@@ -131,6 +131,17 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="modal" id="delete-modal">
 | 
			
		||||
  <div class="modal-content">
 | 
			
		||||
    <h4>Confirm Corpus deletion</h4>
 | 
			
		||||
    <p>Do you really want to delete the Corpus <b>{{ corpus.title }}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="modal-footer">
 | 
			
		||||
    <a class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
    <a class="btn modal-close red waves-effect waves-light" id="delete-modal-delete-button">Delete</a>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="modal no-autoinit" id="invite-user-modal">
 | 
			
		||||
  <div class="modal-content">
 | 
			
		||||
    <h4>Invite a nopaque user by username</h4>
 | 
			
		||||
@@ -215,13 +226,21 @@
 | 
			
		||||
  let publishingModalIsPublicSwitchElement = document.querySelector('#publishing-modal-is-public-switch');
 | 
			
		||||
  publishingModalIsPublicSwitchElement.addEventListener('change', (event) => {
 | 
			
		||||
    let newIsPublic = publishingModalIsPublicSwitchElement.checked;
 | 
			
		||||
    Utils.updateCorpusIsPublicRequest(corpusId, newIsPublic)
 | 
			
		||||
    Requests.corpora.corpus.isPublic.update(corpusId, newIsPublic)
 | 
			
		||||
      .catch((response) => {
 | 
			
		||||
        publishingModalIsPublicSwitchElement.checked = !newIsPublic;
 | 
			
		||||
      });
 | 
			
		||||
  });
 | 
			
		||||
  // #endregion publishing_modal_js
 | 
			
		||||
 | 
			
		||||
  // #region delete_modal_js
 | 
			
		||||
  let deleteModalDeleteButtonElement = document.querySelector('#delete-modal-delete-button');
 | 
			
		||||
  deleteModalDeleteButtonElement.addEventListener('click', (event) => {
 | 
			
		||||
    Requests.corpora.corpus.delete(corpusId)
 | 
			
		||||
      .then((response) => {window.location.href = '/dashboard';});
 | 
			
		||||
  });
 | 
			
		||||
  // #endregion delete_modal_js
 | 
			
		||||
 | 
			
		||||
  // #region invite_user_modal_js
 | 
			
		||||
  let inviteUserModalElement = document.querySelector('#invite-user-modal');
 | 
			
		||||
  let inviteUserModalSearchElement = document.querySelector('#invite-user-modal-search');
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user