diff --git a/.dockerignore b/.dockerignore index edcfc859..b53209a6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,4 +9,5 @@ !config.py !docker-nopaque-entrypoint.sh !requirements.txt +!requirements.freezed.txt !wsgi.py diff --git a/Dockerfile b/Dockerfile index 7f91301c..d6c52229 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,19 +35,17 @@ ENV PATH="${NOPAQUE_PYTHON3_VENV_PATH}/bin:${PATH}" # Install Python dependencies -COPY --chown=nopaque:nopaque requirements.txt requirements.txt -RUN python3 -m pip install --requirement requirements.txt \ - && rm requirements.txt +COPY --chown=nopaque:nopaque requirements.freezed.txt requirements.freezed.txt +RUN python3 -m pip install --requirement requirements.freezed.txt \ + && rm requirements.freezed.txt # Install the application COPY docker-nopaque-entrypoint.sh /usr/local/bin/ - COPY --chown=nopaque:nopaque app app COPY --chown=nopaque:nopaque migrations migrations COPY --chown=nopaque:nopaque tests tests -COPY --chown=nopaque:nopaque boot.sh config.py wsgi.py requirements.txt ./ - +COPY --chown=nopaque:nopaque boot.sh config.py wsgi.py ./ RUN mkdir logs diff --git a/app/__init__.py b/app/__init__.py index 3b8d8a15..925e3b95 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,10 +4,9 @@ from docker import DockerClient from flask import Flask from flask_apscheduler import APScheduler from flask_assets import Environment -from flask_breadcrumbs import Breadcrumbs, default_breadcrumb_root from flask_login import LoginManager from flask_mail import Mail -from flask_marshmallow import Marshmallow +# from flask_marshmallow import Marshmallow from flask_migrate import Migrate from flask_paranoid import Paranoid from flask_socketio import SocketIO @@ -17,14 +16,13 @@ from flask_hashids import Hashids apifairy = APIFairy() assets = Environment() -breadcrumbs = Breadcrumbs() db = SQLAlchemy() docker_client = DockerClient() hashids = Hashids() login = LoginManager() login.login_view = 'auth.login' login.login_message = 'Please log in to access this page.' -ma = Marshmallow() +# ma = Marshmallow() mail = Mail() migrate = Migrate(compare_type=True) paranoid = Paranoid() @@ -49,11 +47,10 @@ def create_app(config: Config = Config) -> Flask: apifairy.init_app(app) assets.init_app(app) - breadcrumbs.init_app(app) db.init_app(app) hashids.init_app(app) login.init_app(app) - ma.init_app(app) + # ma.init_app(app) mail.init_app(app) migrate.init_app(app, db) paranoid.init_app(app) @@ -64,23 +61,19 @@ def create_app(config: Config = Config) -> Flask: register_event_listeners() from .admin import bp as admin_blueprint - default_breadcrumb_root(admin_blueprint, '.admin') app.register_blueprint(admin_blueprint, url_prefix='/admin') - from .api import bp as api_blueprint - app.register_blueprint(api_blueprint, url_prefix='/api') + # from .api import bp as api_blueprint + # app.register_blueprint(api_blueprint, url_prefix='/api') from .auth import bp as auth_blueprint - default_breadcrumb_root(auth_blueprint, '.') app.register_blueprint(auth_blueprint) from .contributions import bp as contributions_blueprint - default_breadcrumb_root(contributions_blueprint, '.contributions') app.register_blueprint(contributions_blueprint, url_prefix='/contributions') from .corpora import bp as corpora_blueprint from .corpora.cqi_over_sio import CQiNamespace - default_breadcrumb_root(corpora_blueprint, '.corpora') app.register_blueprint(corpora_blueprint, cli_group='corpus', url_prefix='/corpora') socketio.on_namespace(CQiNamespace('/cqi_over_sio')) @@ -88,23 +81,18 @@ def create_app(config: Config = Config) -> Flask: app.register_blueprint(errors_bp) from .jobs import bp as jobs_blueprint - default_breadcrumb_root(jobs_blueprint, '.jobs') app.register_blueprint(jobs_blueprint, url_prefix='/jobs') from .main import bp as main_blueprint - default_breadcrumb_root(main_blueprint, '.') app.register_blueprint(main_blueprint, cli_group=None) from .services import bp as services_blueprint - default_breadcrumb_root(services_blueprint, '.services') app.register_blueprint(services_blueprint, url_prefix='/services') from .settings import bp as settings_blueprint - default_breadcrumb_root(settings_blueprint, '.settings') app.register_blueprint(settings_blueprint, url_prefix='/settings') from .users import bp as users_blueprint - default_breadcrumb_root(users_blueprint, '.users') app.register_blueprint(users_blueprint, cli_group='user', url_prefix='/users') from .workshops import bp as workshops_blueprint diff --git a/app/admin/routes.py b/app/admin/routes.py index a1f5e455..49938b48 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -1,5 +1,4 @@ from flask import abort, flash, redirect, render_template, url_for -from flask_breadcrumbs import register_breadcrumb from app import db, hashids from app.models import Avatar, Corpus, Role, User from app.users.settings.forms import ( @@ -9,16 +8,11 @@ from app.users.settings.forms import ( UpdateAccountInformationForm, UpdateProfileInformationForm ) -from app.users.utils import ( - user_endpoint_arguments_constructor as user_eac, - user_dynamic_list_constructor as user_dlc -) from . import bp from .forms import UpdateUserForm @bp.route('') -@register_breadcrumb(bp, '.', 'admin_panel_settingsAdministration') def admin(): return render_template( 'admin/admin.html.j2', @@ -27,7 +21,6 @@ def admin(): @bp.route('/corpora') -@register_breadcrumb(bp, '.corpora', 'Corpora') def corpora(): corpora = Corpus.query.all() return render_template( @@ -38,7 +31,6 @@ def corpora(): @bp.route('/users') -@register_breadcrumb(bp, '.users', 'groupUsers') def users(): users = User.query.all() return render_template( @@ -49,7 +41,6 @@ def users(): @bp.route('/users/') -@register_breadcrumb(bp, '.users.entity', '', dynamic_list_constructor=user_dlc) def user(user_id): user = User.query.get_or_404(user_id) corpora = Corpus.query.filter(Corpus.user == user).all() @@ -62,7 +53,6 @@ def user(user_id): @bp.route('/users//settings', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.users.entity.settings', 'settingsSettings') def user_settings(user_id): user = User.query.get_or_404(user_id) update_account_information_form = UpdateAccountInformationForm(user) diff --git a/app/auth/routes.py b/app/auth/routes.py index 54337d69..e53ae2ca 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -1,5 +1,4 @@ from flask import abort, flash, redirect, render_template, request, url_for -from flask_breadcrumbs import register_breadcrumb from flask_login import current_user, login_user, login_required, logout_user from app import db from app.email import create_message, send @@ -30,7 +29,6 @@ def before_request(): @bp.route('/register', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.register', 'Register') def register(): if current_user.is_authenticated: return redirect(url_for('main.dashboard')) @@ -67,7 +65,6 @@ def register(): @bp.route('/login', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.login', 'Login') def login(): if current_user.is_authenticated: return redirect(url_for('main.dashboard')) @@ -98,7 +95,6 @@ def logout(): @bp.route('/unconfirmed') -@register_breadcrumb(bp, '.unconfirmed', 'Unconfirmed') @login_required def unconfirmed(): if current_user.confirmed: @@ -141,7 +137,6 @@ def confirm(token): @bp.route('/reset-password-request', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.reset_password_request', 'Password Reset') def reset_password_request(): if current_user.is_authenticated: return redirect(url_for('main.dashboard')) @@ -171,7 +166,6 @@ def reset_password_request(): @bp.route('/reset-password/', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.reset_password', 'Password Reset') def reset_password(token): if current_user.is_authenticated: return redirect(url_for('main.dashboard')) diff --git a/app/contributions/routes.py b/app/contributions/routes.py index 82fc63ba..03b30b04 100644 --- a/app/contributions/routes.py +++ b/app/contributions/routes.py @@ -1,9 +1,7 @@ from flask import redirect, url_for -from flask_breadcrumbs import register_breadcrumb from . import bp @bp.route('') -@register_breadcrumb(bp, '.', 'new_labelMy Contributions') def contributions(): return redirect(url_for('main.dashboard', _anchor='contributions')) diff --git a/app/contributions/spacy_nlp_pipeline_models/routes.py b/app/contributions/spacy_nlp_pipeline_models/routes.py index 8723be47..1369b052 100644 --- a/app/contributions/spacy_nlp_pipeline_models/routes.py +++ b/app/contributions/spacy_nlp_pipeline_models/routes.py @@ -1,5 +1,4 @@ from flask import abort, flash, redirect, render_template, url_for -from flask_breadcrumbs import register_breadcrumb from flask_login import current_user from app import db from app.models import SpaCyNLPPipelineModel @@ -8,11 +7,9 @@ from .forms import ( CreateSpaCyNLPPipelineModelForm, UpdateSpaCyNLPPipelineModelForm ) -from .utils import spacy_nlp_pipeline_model_dlc @bp.route('/spacy-nlp-pipeline-models') -@register_breadcrumb(bp, '.spacy_nlp_pipeline_models', 'SpaCy NLP Pipeline Models') def spacy_nlp_pipeline_models(): return render_template( 'contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_models.html.j2', @@ -21,7 +18,6 @@ def spacy_nlp_pipeline_models(): @bp.route('/spacy-nlp-pipeline-models/create', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.spacy_nlp_pipeline_models.create', 'Create') def create_spacy_nlp_pipeline_model(): form = CreateSpaCyNLPPipelineModelForm() if form.is_submitted(): @@ -55,7 +51,6 @@ def create_spacy_nlp_pipeline_model(): @bp.route('/spacy-nlp-pipeline-models/', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.spacy_nlp_pipeline_models.entity', '', dynamic_list_constructor=spacy_nlp_pipeline_model_dlc) def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id): snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id) if not (snpm.user == current_user or current_user.is_administrator): diff --git a/app/contributions/spacy_nlp_pipeline_models/utils.py b/app/contributions/spacy_nlp_pipeline_models/utils.py deleted file mode 100644 index e73bbfd6..00000000 --- a/app/contributions/spacy_nlp_pipeline_models/utils.py +++ /dev/null @@ -1,13 +0,0 @@ -from flask import request, url_for -from app.models import SpaCyNLPPipelineModel - - -def spacy_nlp_pipeline_model_dlc(): - snpm_id = request.view_args['spacy_nlp_pipeline_model_id'] - snpm = SpaCyNLPPipelineModel.query.get_or_404(snpm_id) - return [ - { - 'text': f'{snpm.title} {snpm.version}', - 'url': url_for('.spacy_nlp_pipeline_model', spacy_nlp_pipeline_model_id=snpm_id) - } - ] diff --git a/app/contributions/tesseract_ocr_pipeline_models/routes.py b/app/contributions/tesseract_ocr_pipeline_models/routes.py index e5a5ce45..64ea6e2c 100644 --- a/app/contributions/tesseract_ocr_pipeline_models/routes.py +++ b/app/contributions/tesseract_ocr_pipeline_models/routes.py @@ -1,5 +1,4 @@ from flask import abort, flash, redirect, render_template, url_for -from flask_breadcrumbs import register_breadcrumb from flask_login import current_user from app import db from app.models import TesseractOCRPipelineModel @@ -8,11 +7,9 @@ from .forms import ( CreateTesseractOCRPipelineModelForm, UpdateTesseractOCRPipelineModelForm ) -from .utils import tesseract_ocr_pipeline_model_dlc @bp.route('/tesseract-ocr-pipeline-models') -@register_breadcrumb(bp, '.tesseract_ocr_pipeline_models', 'Tesseract OCR Pipeline Models') def tesseract_ocr_pipeline_models(): return render_template( 'contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_models.html.j2', @@ -21,7 +18,6 @@ def tesseract_ocr_pipeline_models(): @bp.route('/tesseract-ocr-pipeline-models/create', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.tesseract_ocr_pipeline_models.create', 'Create') def create_tesseract_ocr_pipeline_model(): form = CreateTesseractOCRPipelineModelForm() if form.is_submitted(): @@ -54,7 +50,6 @@ def create_tesseract_ocr_pipeline_model(): @bp.route('/tesseract-ocr-pipeline-models/', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.tesseract_ocr_pipeline_models.entity', '', dynamic_list_constructor=tesseract_ocr_pipeline_model_dlc) def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id): topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id) if not (topm.user == current_user or current_user.is_administrator): diff --git a/app/contributions/tesseract_ocr_pipeline_models/utils.py b/app/contributions/tesseract_ocr_pipeline_models/utils.py deleted file mode 100644 index d0fcd758..00000000 --- a/app/contributions/tesseract_ocr_pipeline_models/utils.py +++ /dev/null @@ -1,13 +0,0 @@ -from flask import request, url_for -from app.models import TesseractOCRPipelineModel - - -def tesseract_ocr_pipeline_model_dlc(): - topm_id = request.view_args['tesseract_ocr_pipeline_model_id'] - topm = TesseractOCRPipelineModel.query.get_or_404(topm_id) - return [ - { - 'text': f'{topm.title} {topm.version}', - 'url': url_for('.tesseract_ocr_pipeline_model', tesseract_ocr_pipeline_model_id=topm_id) - } - ] diff --git a/app/corpora/files/routes.py b/app/corpora/files/routes.py index a5a696f6..9c145be3 100644 --- a/app/corpora/files/routes.py +++ b/app/corpora/files/routes.py @@ -6,24 +6,19 @@ from flask import ( send_from_directory, url_for ) -from flask_breadcrumbs import register_breadcrumb from app import db from app.models import Corpus, CorpusFile, CorpusStatus from ..decorators import corpus_follower_permission_required -from ..utils import corpus_endpoint_arguments_constructor as corpus_eac from . import bp from .forms import CreateCorpusFileForm, UpdateCorpusFileForm -from .utils import corpus_file_dynamic_list_constructor as corpus_file_dlc @bp.route('//files') -@register_breadcrumb(bp, '.entity.files', 'Files', endpoint_arguments_constructor=corpus_eac) def corpus_files(corpus_id): return redirect(url_for('.corpus', _anchor='files', corpus_id=corpus_id)) @bp.route('//files/create', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.entity.files.create', 'Create', endpoint_arguments_constructor=corpus_eac) @corpus_follower_permission_required('MANAGE_FILES') def create_corpus_file(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) @@ -65,7 +60,6 @@ def create_corpus_file(corpus_id): @bp.route('//files/', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.entity.files.entity', '', dynamic_list_constructor=corpus_file_dlc) @corpus_follower_permission_required('MANAGE_FILES') 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() @@ -94,6 +88,6 @@ def download_corpus_file(corpus_id, corpus_file_id): corpus_file.path.parent, corpus_file.path.name, as_attachment=True, - attachment_filename=corpus_file.filename, + download_name=corpus_file.filename, mimetype=corpus_file.mimetype ) diff --git a/app/corpora/files/utils.py b/app/corpora/files/utils.py deleted file mode 100644 index 69a66f79..00000000 --- a/app/corpora/files/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -from flask import request, url_for -from app.models import CorpusFile - - -def corpus_file_dynamic_list_constructor(): - corpus_id = request.view_args['corpus_id'] - corpus_file_id = request.view_args['corpus_file_id'] - corpus_file = CorpusFile.query.filter_by(corpus_id=corpus_id, id=corpus_file_id).first_or_404() - return [ - { - 'text': f'{corpus_file.author}: {corpus_file.title} ({corpus_file.publishing_year})', - 'url': url_for('.corpus_file', corpus_id=corpus_id, corpus_file_id=corpus_file_id) - } - ] diff --git a/app/corpora/routes.py b/app/corpora/routes.py index 8ab6178a..66975ea1 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -1,5 +1,4 @@ from flask import abort, flash, redirect, render_template, url_for -from flask_breadcrumbs import register_breadcrumb from flask_login import current_user from app import db from app.models import ( @@ -11,20 +10,14 @@ from app.models import ( from . import bp from .decorators import corpus_follower_permission_required from .forms import CreateCorpusForm -from .utils import ( - corpus_endpoint_arguments_constructor as corpus_eac, - corpus_dynamic_list_constructor as corpus_dlc -) @bp.route('') -@register_breadcrumb(bp, '.', 'IMy Corpora') def corpora(): return redirect(url_for('main.dashboard', _anchor='corpora')) @bp.route('/create', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.create', 'Create') def create_corpus(): form = CreateCorpusForm() if form.validate_on_submit(): @@ -47,7 +40,6 @@ def create_corpus(): @bp.route('/') -@register_breadcrumb(bp, '.entity', '', dynamic_list_constructor=corpus_dlc) def corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) cfrs = CorpusFollowerRole.query.all() @@ -87,7 +79,6 @@ def corpus(corpus_id): @bp.route('//analysis') @corpus_follower_permission_required('VIEW') -@register_breadcrumb(bp, '.entity.analysis', 'Analysis', endpoint_arguments_constructor=corpus_eac) def analysis(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) return render_template( @@ -108,13 +99,11 @@ def follow_corpus(corpus_id, token): @bp.route('/import', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.import', 'Import') def import_corpus(): abort(503) @bp.route('//export') @corpus_follower_permission_required('VIEW') -@register_breadcrumb(bp, '.entity.export', 'Export', endpoint_arguments_constructor=corpus_eac) def export_corpus(corpus_id): abort(503) diff --git a/app/corpora/utils.py b/app/corpora/utils.py deleted file mode 100644 index f5319dce..00000000 --- a/app/corpora/utils.py +++ /dev/null @@ -1,17 +0,0 @@ -from flask import request, url_for -from app.models import Corpus - - -def corpus_endpoint_arguments_constructor(): - return {'corpus_id': request.view_args['corpus_id']} - - -def corpus_dynamic_list_constructor(): - corpus_id = request.view_args['corpus_id'] - corpus = Corpus.query.get_or_404(corpus_id) - return [ - { - 'text': f'book{corpus.title}', - 'url': url_for('.corpus', corpus_id=corpus_id) - } - ] diff --git a/app/jobs/routes.py b/app/jobs/routes.py index 9f3b2e2b..7e50526b 100644 --- a/app/jobs/routes.py +++ b/app/jobs/routes.py @@ -5,21 +5,17 @@ from flask import ( send_from_directory, url_for ) -from flask_breadcrumbs import register_breadcrumb from flask_login import current_user from app.models import Job, JobInput, JobResult from . import bp -from .utils import job_dynamic_list_constructor as job_dlc @bp.route('') -@register_breadcrumb(bp, '.', 'JMy Jobs') def corpora(): return redirect(url_for('main.dashboard', _anchor='jobs')) @bp.route('/') -@register_breadcrumb(bp, '.entity', '', dynamic_list_constructor=job_dlc) def job(job_id): job = Job.query.get_or_404(job_id) if not (job.user == current_user or current_user.is_administrator): @@ -40,7 +36,7 @@ def download_job_input(job_id, job_input_id): job_input.path.parent, job_input.path.name, as_attachment=True, - attachment_filename=job_input.filename, + download_name=job_input.filename, mimetype=job_input.mimetype ) @@ -54,6 +50,6 @@ def download_job_result(job_id, job_result_id): job_result.path.parent, job_result.path.name, as_attachment=True, - attachment_filename=job_result.filename, + download_name=job_result.filename, mimetype=job_result.mimetype ) diff --git a/app/jobs/utils.py b/app/jobs/utils.py deleted file mode 100644 index 554db354..00000000 --- a/app/jobs/utils.py +++ /dev/null @@ -1,13 +0,0 @@ -from flask import request, url_for -from app.models import Job - - -def job_dynamic_list_constructor(): - job_id = request.view_args['job_id'] - job = Job.query.get_or_404(job_id) - return [ - { - 'text': f'{job.title}', - 'url': url_for('.job', job_id=job_id) - } - ] diff --git a/app/main/routes.py b/app/main/routes.py index 255edb2d..932e7e50 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -1,14 +1,11 @@ from flask import flash, redirect, render_template, url_for -from flask_breadcrumbs import register_breadcrumb from flask_login import current_user, login_required, login_user from app.auth.forms import LoginForm from app.models import Corpus, User -from sqlalchemy import or_ from . import bp @bp.route('/', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.', 'home') def index(): form = LoginForm() if form.validate_on_submit(): @@ -27,7 +24,6 @@ def index(): @bp.route('/faq') -@register_breadcrumb(bp, '.faq', 'Frequently Asked Questions') def faq(): return render_template( 'main/faq.html.j2', @@ -36,7 +32,6 @@ def faq(): @bp.route('/dashboard') -@register_breadcrumb(bp, '.dashboard', 'dashboardDashboard') @login_required def dashboard(): return render_template( @@ -46,7 +41,6 @@ def dashboard(): @bp.route('/news') -@register_breadcrumb(bp, '.news', 'emailNews') def news(): return render_template( 'main/news.html.j2', @@ -55,7 +49,6 @@ def news(): @bp.route('/privacy_policy') -@register_breadcrumb(bp, '.privacy_policy', 'Private statement (GDPR)') def privacy_policy(): return render_template( 'main/privacy_policy.html.j2', @@ -64,7 +57,6 @@ def privacy_policy(): @bp.route('/terms_of_use') -@register_breadcrumb(bp, '.terms_of_use', 'Terms of Use') def terms_of_use(): return render_template( 'main/terms_of_use.html.j2', @@ -73,7 +65,6 @@ def terms_of_use(): @bp.route('/social-area') -@register_breadcrumb(bp, '.social_area', 'groupSocial Area') @login_required def social_area(): print('test') diff --git a/app/services/routes.py b/app/services/routes.py index 9f4cfdc0..b872bb70 100644 --- a/app/services/routes.py +++ b/app/services/routes.py @@ -1,5 +1,4 @@ -from flask import abort, current_app, flash, Markup, redirect, render_template, request, url_for -from flask_breadcrumbs import register_breadcrumb +from flask import abort, current_app, flash, redirect, render_template, request, url_for from flask_login import current_user import requests from app import db, hashids @@ -20,13 +19,11 @@ from .forms import ( @bp.route('/services') -@register_breadcrumb(bp, '.', 'Services') def services(): return redirect(url_for('main.dashboard')) @bp.route('/file-setup-pipeline', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.file_setup_pipeline', 'File Setup') def file_setup_pipeline(): service = 'file-setup-pipeline' service_manifest = SERVICES[service] @@ -56,7 +53,7 @@ def file_setup_pipeline(): abort(500) job.status = JobStatus.SUBMITTED db.session.commit() - message = Markup(f'Job "{job.title}" created') + message = f'Job "{job.title}" created' flash(message, 'job') return {}, 201, {'Location': job.url} return render_template( @@ -67,7 +64,6 @@ def file_setup_pipeline(): @bp.route('/tesseract-ocr-pipeline', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.tesseract_ocr_pipeline', 'Tesseract OCR Pipeline') def tesseract_ocr_pipeline(): service_name = 'tesseract-ocr-pipeline' service_manifest = SERVICES[service_name] @@ -100,7 +96,7 @@ def tesseract_ocr_pipeline(): abort(500) job.status = JobStatus.SUBMITTED db.session.commit() - message = Markup(f'Job "{job.title}" created') + message = f'Job "{job.title}" created' flash(message, 'job') return {}, 201, {'Location': job.url} tesseract_ocr_pipeline_models = [ @@ -118,7 +114,6 @@ def tesseract_ocr_pipeline(): @bp.route('/transkribus-htr-pipeline', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.transkribus_htr_pipeline', 'Transkribus HTR Pipeline') def transkribus_htr_pipeline(): if not current_app.config.get('NOPAQUE_TRANSKRIBUS_ENABLED'): abort(404) @@ -164,7 +159,7 @@ def transkribus_htr_pipeline(): abort(500) job.status = JobStatus.SUBMITTED db.session.commit() - message = Markup(f'Job "{job.title}" created') + message = f'Job "{job.title}" created' flash(message, 'job') return {}, 201, {'Location': job.url} return render_template( @@ -176,7 +171,6 @@ def transkribus_htr_pipeline(): @bp.route('/spacy-nlp-pipeline', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.spacy_nlp_pipeline', 'SpaCy NLP Pipeline') def spacy_nlp_pipeline(): service = 'spacy-nlp-pipeline' service_manifest = SERVICES[service] @@ -210,7 +204,7 @@ def spacy_nlp_pipeline(): abort(500) job.status = JobStatus.SUBMITTED db.session.commit() - message = Markup(f'Job "{job.title}" created') + message = f'Job "{job.title}" created' flash(message, 'job') return {}, 201, {'Location': job.url} return render_template( @@ -223,7 +217,6 @@ def spacy_nlp_pipeline(): @bp.route('/corpus-analysis') -@register_breadcrumb(bp, '.corpus_analysis', 'Corpus Analysis') def corpus_analysis(): return render_template( 'services/corpus_analysis.html.j2', diff --git a/app/settings/routes.py b/app/settings/routes.py index 837c0f6f..35d9fbe0 100644 --- a/app/settings/routes.py +++ b/app/settings/routes.py @@ -1,12 +1,10 @@ from flask import g, url_for -from flask_breadcrumbs import register_breadcrumb from flask_login import current_user from app.users.settings.routes import settings as settings_route from . import bp @bp.route('/settings', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.', 'settingsSettings') def settings(): g._nopaque_redirect_location_on_post = url_for('.settings') return settings_route(current_user.id) diff --git a/app/static/css/spacing.css b/app/static/css/spacing.css new file mode 100644 index 00000000..b88aafe2 --- /dev/null +++ b/app/static/css/spacing.css @@ -0,0 +1,482 @@ +/************************** + Utility Spacing Classes +**************************/ +.m-0 { + margin: 0 !important; +} + +.mt-0 { + margin-top: 0 !important; +} + +.mr-0 { + margin-right: 0 !important; +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.ml-0 { + margin-left: 0 !important; +} + +.mx-0 { + margin-left: 0 !important; + margin-right: 0 !important; +} + +.my-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.mt-1 { + margin-top: 0.25rem !important; +} + +.mr-1 { + margin-right: 0.25rem !important; +} + +.mb-1 { + margin-bottom: 0.25rem !important; +} + +.ml-1 { + margin-left: 0.25rem !important; +} + +.mx-1 { + margin-left: 0.25rem !important; + margin-right: 0.25rem !important; +} + +.my-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.mt-2 { + margin-top: 0.5rem !important; +} + +.mr-2 { + margin-right: 0.5rem !important; +} + +.mb-2 { + margin-bottom: 0.5rem !important; +} + +.ml-2 { + margin-left: 0.5rem !important; +} + +.mx-2 { + margin-left: 0.5rem !important; + margin-right: 0.5rem !important; +} + +.my-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; +} + +.m-3 { + margin: 0.75rem !important; +} + +.mt-3 { + margin-top: 0.75rem !important; +} + +.mr-3 { + margin-right: 0.75rem !important; +} + +.mb-3 { + margin-bottom: 0.75rem !important; +} + +.ml-3 { + margin-left: 0.75rem !important; +} + +.mx-3 { + margin-left: 0.75rem !important; + margin-right: 0.75rem !important; +} + +.my-3 { + margin-top: 0.75rem !important; + margin-bottom: 0.75rem !important; +} + +.m-4 { + margin: 1rem !important; +} + +.mt-4 { + margin-top: 1rem !important; +} + +.mr-4 { + margin-right: 1rem !important; +} + +.mb-4 { + margin-bottom: 1rem !important; +} + +.ml-4 { + margin-left: 1rem !important; +} + +.mx-4 { + margin-left: 1rem !important; + margin-right: 1rem !important; +} + +.my-4 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; +} + +.m-5 { + margin: 1.5rem !important; +} + +.mt-5 { + margin-top: 1.5rem !important; +} + +.mr-5 { + margin-right: 1.5rem !important; +} + +.mb-5 { + margin-bottom: 1.5rem !important; +} + +.ml-5 { + margin-left: 1.5rem !important; +} + +.mx-5 { + margin-left: 1.5rem !important; + margin-right: 1.5rem !important; +} + +.my-5 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} + +.m-6 { + margin: 3rem !important; +} + +.mt-6 { + margin-top: 3rem !important; +} + +.mr-6 { + margin-right: 3rem !important; +} + +.mb-6 { + margin-bottom: 3rem !important; +} + +.ml-6 { + margin-left: 3rem !important; +} + +.mx-6 { + margin-left: 3rem !important; + margin-right: 3rem !important; +} + +.my-6 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mt-auto { + margin-top: auto !important; +} + +.mr-auto { + margin-right: auto !important; +} + +.mb-auto { + margin-bottom: auto !important; +} + +.ml-auto { + margin-left: auto !important; +} + +.mx-auto { + margin-left: auto !important; + margin-right: auto !important; +} + +.my-auto { + margin-top: auto !important; + margin-bottom: auto !important; +} + +.p-0 { + padding: 0 !important; +} + +.pt-0 { + padding-top: 0 !important; +} + +.pr-0 { + padding-right: 0 !important; +} + +.pb-0 { + padding-bottom: 0 !important; +} + +.pl-0 { + padding-left: 0 !important; +} + +.px-0 { + padding-left: 0 !important; + padding-right: 0 !important; +} + +.py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.pt-1 { + padding-top: 0.25rem !important; +} + +.pr-1 { + padding-right: 0.25rem !important; +} + +.pb-1 { + padding-bottom: 0.25rem !important; +} + +.pl-1 { + padding-left: 0.25rem !important; +} + +.px-1 { + padding-left: 0.25rem !important; + padding-right: 0.25rem !important; +} + +.py-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.pt-2 { + padding-top: 0.5rem !important; +} + +.pr-2 { + padding-right: 0.5rem !important; +} + +.pb-2 { + padding-bottom: 0.5rem !important; +} + +.pl-2 { + padding-left: 0.5rem !important; +} + +.px-2 { + padding-left: 0.5rem !important; + padding-right: 0.5rem !important; +} + +.py-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; +} + +.p-3 { + padding: 0.75rem !important; +} + +.pt-3 { + padding-top: 0.75rem !important; +} + +.pr-3 { + padding-right: 0.75rem !important; +} + +.pb-3 { + padding-bottom: 0.75rem !important; +} + +.pl-3 { + padding-left: 0.75rem !important; +} + +.px-3 { + padding-left: 0.75rem !important; + padding-right: 0.75rem !important; +} + +.py-3 { + padding-top: 0.75rem !important; + padding-bottom: 0.75rem !important; +} + +.p-4 { + padding: 1rem !important; +} + +.pt-4 { + padding-top: 1rem !important; +} + +.pr-4 { + padding-right: 1rem !important; +} + +.pb-4 { + padding-bottom: 1rem !important; +} + +.pl-4 { + padding-left: 1rem !important; +} + +.px-4 { + padding-left: 1rem !important; + padding-right: 1rem !important; +} + +.py-4 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; +} + +.p-5 { + padding: 1.5rem !important; +} + +.pt-5 { + padding-top: 1.5rem !important; +} + +.pr-5 { + padding-right: 1.5rem !important; +} + +.pb-5 { + padding-bottom: 1.5rem !important; +} + +.pl-5 { + padding-left: 1.5rem !important; +} + +.px-5 { + padding-left: 1.5rem !important; + padding-right: 1.5rem !important; +} + +.py-5 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; +} + +.p-6 { + padding: 3rem !important; +} + +.pt-6 { + padding-top: 3rem !important; +} + +.pr-6 { + padding-right: 3rem !important; +} + +.pb-6 { + padding-bottom: 3rem !important; +} + +.pl-6 { + padding-left: 3rem !important; +} + +.px-6 { + padding-left: 3rem !important; + padding-right: 3rem !important; +} + +.py-6 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; +} + +.p-auto { + padding: auto !important; +} + +.pt-auto { + padding-top: auto !important; +} + +.pr-auto { + padding-right: auto !important; +} + +.pb-auto { + padding-bottom: auto !important; +} + +.pl-auto { + padding-left: auto !important; +} + +.px-auto { + padding-left: auto !important; + padding-right: auto !important; +} + +.py-auto { + padding-top: auto !important; + padding-bottom: auto !important; +} diff --git a/app/static/css/style.css b/app/static/css/style.css index e85d697e..163b8449 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -1,7 +1,4 @@ -/* Change navbar height bacause an extended and fixed navbar is used */ -.navbar-fixed { - height: 112px; -} +.scroll {overflow: auto;} /* Change placholdertext color of file uplaod fields */ ::placeholder { diff --git a/app/templates/_base/navbar.html.j2 b/app/templates/_base/navbar.html.j2 index 0cd7319f..7dc49dd8 100644 --- a/app/templates/_base/navbar.html.j2 +++ b/app/templates/_base/navbar.html.j2 @@ -1,5 +1,5 @@ - + {% if current_user.is_authenticated %} menu @@ -7,12 +7,11 @@ - - emailNews + - + {% if current_user.is_authenticated %} - + {{ current_user.username }} ({{ current_user.email }}) {% else %} more_vert @@ -21,17 +20,6 @@ - - - {%- for breadcrumb in breadcrumbs -%} - {{ breadcrumb.text }} - {% if not loop.last %} - navigate_next - {% endif %} - {%- endfor -%} - - school - @@ -42,7 +30,7 @@ Log out {% else %} - assignmentRegister - loginLog in + assignmentRegister + loginLog in {% endif %} diff --git a/app/templates/_base/sidenav.html.j2 b/app/templates/_base/sidenav.html.j2 index 1c834b25..59d79047 100644 --- a/app/templates/_base/sidenav.html.j2 +++ b/app/templates/_base/sidenav.html.j2 @@ -6,9 +6,6 @@ - - - homenopaque emailNews @@ -73,9 +70,9 @@ admin_panel_settingsAdministration {% endif %} - {% if current_user.can('USE_API') %} + {# {% if current_user.can('USE_API') %} apiAPI - {% endif %} + {% endif %} #} diff --git a/app/templates/_base/styles.html.j2 b/app/templates/_base/styles.html.j2 index cb047f8f..1328ea8b 100644 --- a/app/templates/_base/styles.html.j2 +++ b/app/templates/_base/styles.html.j2 @@ -3,6 +3,7 @@ {% endif %} + {%- assets diff --git a/app/users/routes.py b/app/users/routes.py index 829b9d70..b4b81ffb 100644 --- a/app/users/routes.py +++ b/app/users/routes.py @@ -5,21 +5,17 @@ from flask import ( send_from_directory, url_for ) -from flask_breadcrumbs import register_breadcrumb from flask_login import current_user from app.models import User from . import bp -from .utils import user_dynamic_list_constructor as user_dlc @bp.route('') -@register_breadcrumb(bp, '.', 'groupUsers') def users(): return redirect(url_for('main.social_area', _anchor='users')) @bp.route('/') -@register_breadcrumb(bp, '.entity', '', dynamic_list_constructor=user_dlc) def user(user_id): user = User.query.get_or_404(user_id) if not (user.is_public or user == current_user or current_user.is_administrator): @@ -42,6 +38,6 @@ def user_avatar(user_id): user.avatar.path.parent, user.avatar.path.name, as_attachment=True, - attachment_filename=user.avatar.filename, + download_name=user.avatar.filename, mimetype=user.avatar.mimetype ) diff --git a/app/users/settings/routes.py b/app/users/settings/routes.py index 1a6d8b86..0916ef70 100644 --- a/app/users/settings/routes.py +++ b/app/users/settings/routes.py @@ -1,9 +1,7 @@ from flask import abort, flash, g, redirect, render_template, url_for -from flask_breadcrumbs import register_breadcrumb from flask_login import current_user from app import db from app.models import Avatar, User -from ..utils import user_endpoint_arguments_constructor as user_eac from . import bp from .forms import ( UpdateAvatarForm, @@ -15,7 +13,6 @@ from .forms import ( @bp.route('//settings', methods=['GET', 'POST']) -@register_breadcrumb(bp, '.entity.settings', 'settingsSettings', endpoint_arguments_constructor=user_eac) def settings(user_id): user = User.query.get_or_404(user_id) if not (user == current_user or current_user.is_administrator): diff --git a/app/users/utils.py b/app/users/utils.py deleted file mode 100644 index 5c1ca83e..00000000 --- a/app/users/utils.py +++ /dev/null @@ -1,17 +0,0 @@ -from flask import request, url_for -from app.models import User - - -def user_endpoint_arguments_constructor(): - return {'user_id': request.view_args['user_id']} - - -def user_dynamic_list_constructor(): - user_id = request.view_args['user_id'] - user = User.query.get_or_404(user_id) - return [ - { - 'text': f'person{user.username}', - 'url': url_for('.user', user_id=user_id) - } - ] diff --git a/app/workshops/routes.py b/app/workshops/routes.py index 2597ddc7..ad7e6ffd 100644 --- a/app/workshops/routes.py +++ b/app/workshops/routes.py @@ -1,16 +1,13 @@ from flask import redirect, render_template, url_for -from flask_breadcrumbs import register_breadcrumb from . import bp @bp.route('') -@register_breadcrumb(bp, '.', 'business_centerWorkshops') def workshops(): return redirect(url_for('main.dashboard')) @bp.route('/fgho_sommerschule_2023') -@register_breadcrumb(bp, '.fgho_sommerschule_2023', 'FGHO Sommerschule 2023') def fgho_sommerschule_2023(): return render_template( 'workshops/fgho_sommerschule_2023.html.j2', diff --git a/docker-compose/docker-compose.development.yml b/docker-compose/docker-compose.development.yml index 162aa4fa..04104a5e 100644 --- a/docker-compose/docker-compose.development.yml +++ b/docker-compose/docker-compose.development.yml @@ -1,7 +1,7 @@ services: nopaque: environment: - - FLASK_ENV=development + - FLASK_DEBUG=True ports: - "5000:5000" volumes: diff --git a/requirements.freezed.txt b/requirements.freezed.txt new file mode 100644 index 00000000..e1427633 --- /dev/null +++ b/requirements.freezed.txt @@ -0,0 +1,64 @@ +alembic==1.13.1 +apifairy==1.4.0 +apispec==6.6.1 +APScheduler==3.10.4 +async-timeout==4.0.3 +bidict==0.23.1 +blinker==1.8.1 +certifi==2024.2.2 +charset-normalizer==3.3.2 +click==8.1.7 +cqi==0.1.7 +dnspython==2.5.0 +docker==7.0.0 +email_validator==2.1.1 +eventlet==0.34.2 +Flask==2.3.3 +Flask-APScheduler==1.13.1 +Flask-Assets==2.1.0 +Flask-Hashids==1.0.3 +Flask-HTTPAuth==4.8.0 +Flask-Login==0.6.3 +Flask-Mail==0.9.1 +flask-marshmallow==1.2.1 +Flask-Migrate==4.0.7 +Flask-Paranoid==0.3.0 +Flask-SocketIO==5.3.6 +Flask-SQLAlchemy==2.5.1 +Flask-WTF==1.2.1 +greenlet==3.0.3 +h11==0.14.0 +hashids==1.3.1 +idna==3.7 +itsdangerous==2.2.0 +Jinja2==3.1.3 +joblib==1.4.0 +Mako==1.3.3 +MarkupSafe==2.1.5 +marshmallow==3.21.1 +nltk==3.8.1 +packaging==24.0 +psycopg2==2.9.9 +PyJWT==2.8.0 +pyScss==1.4.0 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +python-engineio==4.9.0 +python-socketio==5.11.2 +pytz==2024.1 +PyYAML==6.0.1 +redis==5.0.4 +regex==2024.4.28 +requests==2.31.0 +simple-websocket==1.0.0 +six==1.16.0 +SQLAlchemy==1.4.52 +tqdm==4.66.2 +typing_extensions==4.11.0 +tzlocal==5.2 +urllib3==2.2.1 +webargs==8.4.0 +webassets==2.0 +Werkzeug==3.0.2 +wsproto==1.2.0 +WTForms==3.1.2 diff --git a/requirements.txt b/requirements.txt index 422d1a2d..886edd31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,22 @@ apifairy -cqi>=0.1.7 -dnspython==2.2.1 +cqi +dnspython==2.5.0 docker eventlet==0.34.2 -Flask==2.1.3 +Flask==2.3.3 Flask-APScheduler -Flask-Assets==2.0 -Flask-Breadcrumbs -Flask-Hashids>=1.0.1 -Flask-HTTPAuth +Flask-Assets +Flask-Hashids +# Flask-HTTPAuth Flask-Login Flask-Mail -Flask-Marshmallow==0.14.0 -Flask-Menu==0.7.2 +# flask-marshmallow Flask-Migrate Flask-Paranoid -Flask-SocketIO==5.3.6 +Flask-SocketIO Flask-SQLAlchemy==2.5.1 Flask-WTF -hiredis -MarkupSafe==2.0.1 -marshmallow-sqlalchemy==0.29.0 +# marshmallow-sqlalchemy nltk psycopg2 PyJWT @@ -28,6 +24,6 @@ pyScss python-dotenv pyyaml redis -SQLAlchemy==1.4.45 +SQLAlchemy==1.4.52 tqdm wtforms[email]