From e2cdcd3f7cf3944873ec146cc492dd3a09dcb5b8 Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Tue, 31 Jan 2023 08:59:42 +0100 Subject: [PATCH 01/23] first implementation of follow mechanics --- app/corpora/forms.py | 4 ++ app/corpora/routes.py | 76 +++++++++++++++++++++++-- app/main/routes.py | 8 ++- app/models.py | 27 +++++++++ app/static/js/ResourceLists/UserList.js | 10 ++-- app/templates/_sidenav.html.j2 | 1 + app/templates/corpora/corpus.html.j2 | 42 ++++++++++++++ app/templates/main/dashboard.html.j2 | 28 +++++++++ app/templates/users/profile.html.j2 | 18 ++++++ 9 files changed, 202 insertions(+), 12 deletions(-) diff --git a/app/corpora/forms.py b/app/corpora/forms.py index 12ec1d9c..8950622d 100644 --- a/app/corpora/forms.py +++ b/app/corpora/forms.py @@ -1,6 +1,7 @@ from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileRequired from wtforms import ( + BooleanField, StringField, SubmitField, TextAreaField, @@ -77,6 +78,9 @@ class UpdateCorpusFileForm(CorpusFileBaseForm): kwargs['prefix'] = 'update-corpus-file-form' super().__init__(*args, **kwargs) +class ChangeCorpusSettingsForm(FlaskForm): + is_public = BooleanField('Public Corpus') + submit = SubmitField() class ImportCorpusForm(FlaskForm): pass diff --git a/app/corpora/routes.py b/app/corpora/routes.py index f3cdf8f1..a5035087 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -5,15 +5,17 @@ from flask import ( Markup, redirect, render_template, - send_from_directory + request, + send_from_directory, + url_for ) from flask_login import current_user, login_required from threading import Thread import os -from app import db -from app.models import Corpus, CorpusFile, CorpusStatus +from app import db, hashids +from app.models import Corpus, CorpusFile, CorpusStatus, CorpusFollowerAssociation, User from . import bp -from .forms import CreateCorpusFileForm, CreateCorpusForm, UpdateCorpusFileForm +from .forms import ChangeCorpusSettingsForm, CreateCorpusFileForm, CreateCorpusForm, UpdateCorpusFileForm def user_can_read_corpus(user, corpus): @@ -64,19 +66,30 @@ def create_corpus(): ) -@bp.route('/') +@bp.route('/', methods=['GET', 'POST']) @login_required def corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) if not user_can_read_corpus(current_user, corpus): abort(403) + corpus_settings_form = ChangeCorpusSettingsForm( + data=corpus.to_json_serializeable(), + prefix='corpus-settings-form' + ) + if corpus_settings_form.validate_on_submit(): + corpus.is_public = corpus_settings_form.is_public.data + db.session.commit() + flash('Your changes have been saved') + return redirect(url_for('.corpus', corpus_id=corpus.id)) return render_template( 'corpora/corpus.html.j2', + corpus_settings_form=corpus_settings_form, corpus=corpus, title='Corpus' ) + # @bp.route('//update') # @login_required # def update_corpus(corpus_id): @@ -263,3 +276,56 @@ def import_corpus(): @login_required def export_corpus(corpus_id): abort(503) + +@bp.route('//follow') +@login_required +# TODO: Wenn Query Paramter genutzt wird, prüfen, ob user_id ungleich current_user.id ist und dann gucken, ob es ein Admin ist. +# Sonst 403. +def follow_corpus(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + user_hashid = request.args.get('user_id') + if user_hashid is None: + user = current_user + else: + if not current_user.is_administrator(): + abort(403) + else: + user_id = hashids.decode(user_hashid) + user = User.query.get_or_404(user_id) + if not user.is_following_corpus(corpus): + user.follow_corpus(corpus) + db.session.commit() + return {}, 202 + +@bp.route('//unfollow') +@login_required +def unfollow_corpus(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + user_hashid = request.args.get('user_id') + if user_hashid is None: + user = current_user + else: + if not current_user.is_administrator(): + abort(403) + else: + user_id = hashids.decode(user_hashid) + user = User.query.get_or_404(user_id) + if user.is_following_corpus(corpus): + user.unfollow_corpus(corpus) + db.session.commit() + return {}, 202 + +@bp.route('/add_permission///') +def add_permission(corpus_id, user_id, permission): + a = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=user_id).first_or_404() + a.add_permission(permission) + db.session.commit() + return 'ok' + + +@bp.route('/remove_permission///') +def remove_permission(corpus_id, user_id, permission): + a = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=user_id).first_or_404() + a.remove_permission(permission) + db.session.commit() + return 'ok' diff --git a/app/main/routes.py b/app/main/routes.py index 9935c479..aed63853 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -1,7 +1,7 @@ from flask import flash, redirect, render_template, url_for from flask_login import current_user, login_required, login_user from app.auth.forms import LoginForm -from app.models import User +from app.models import Corpus, User from . import bp @@ -31,7 +31,11 @@ def dashboard(): u.to_json_serializeable(filter_by_privacy_settings=True) for u in User.query.filter(User.is_public == True, User.id != current_user.id).all() ] - return render_template('main/dashboard.html.j2', title='Dashboard', users=users) + corpora = [ + c.to_json_serializeable() for c + in Corpus.query.filter(Corpus.is_public == True).all() + ] + return render_template('main/dashboard.html.j2', title='Dashboard', users=users, corpora=corpora) @bp.route('/dashboard2') diff --git a/app/models.py b/app/models.py index af3d1cfc..24f59d60 100644 --- a/app/models.py +++ b/app/models.py @@ -68,6 +68,11 @@ class ProfilePrivacySettings(IntEnum): SHOW_EMAIL = 1 SHOW_LAST_SEEN = 2 SHOW_MEMBER_SINCE = 4 + +class CorpusFollowPermission(IntEnum): + VIEW = 1 + CONTRIBUTE = 2 + ADMINISTRATE = 4 # endregion enums @@ -298,6 +303,16 @@ class CorpusFollowerAssociation(db.Model): def __repr__(self): return f'' + def has_permission(self, permission): + return self.permissions & permission == permission + + def add_permission(self, permission): + if not self.has_permission(permission): + self.permissions += permission + + def remove_permission(self, permission): + if self.has_permission(permission): + self.permissions -= permission class User(HashidMixin, UserMixin, db.Model): __tablename__ = 'users' @@ -576,6 +591,18 @@ class User(HashidMixin, UserMixin, db.Model): self.profile_privacy_settings = 0 #endregion Profile Privacy settings + def follow_corpus(self, corpus): + if not self.is_following_corpus(corpus): + self.followed_corpora.append(corpus) + + def unfollow_corpus(self, corpus): + if self.is_following_corpus(corpus): + self.followed_corpora.remove(corpus) + + def is_following_corpus(self, corpus): + return corpus in self.followed_corpora + + def to_json_serializeable(self, backrefs=False, relationships=False, filter_by_privacy_settings=False): json_serializeable = { 'id': self.hashid, diff --git a/app/static/js/ResourceLists/UserList.js b/app/static/js/ResourceLists/UserList.js index d871ec31..03fabd73 100644 --- a/app/static/js/ResourceLists/UserList.js +++ b/app/static/js/ResourceLists/UserList.js @@ -1,7 +1,7 @@ class UserList extends ResourceList { static autoInit() { - for (let publicUserListElement of document.querySelectorAll('.user-list:not(.no-autoinit)')) { - new UserList(publicUserListElement); + for (let userListElement of document.querySelectorAll('.user-list:not(.no-autoinit)')) { + new UserList(userListElement); } } @@ -41,14 +41,14 @@ class UserList extends ResourceList { initListContainerElement() { if (!this.listContainerElement.hasAttribute('id')) { - this.listContainerElement.id = Utils.generateElementId('public-user-list-'); + this.listContainerElement.id = Utils.generateElementId('user-list-'); } let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`); this.listContainerElement.innerHTML = `
search - +
@@ -77,7 +77,7 @@ class UserList extends ResourceList { 'full-name': user.full_name ? user.full_name : '', 'location': user.location ? user.location : '', 'organization': user.organization ? user.organization : '', - 'corpora-online': '0' + 'corpora-online': '-' }; }; diff --git a/app/templates/_sidenav.html.j2 b/app/templates/_sidenav.html.j2 index eb547454..676b7069 100644 --- a/app/templates/_sidenav.html.j2 +++ b/app/templates/_sidenav.html.j2 @@ -20,6 +20,7 @@
  • dashboardDashboard
  • IMy Corpora
  • JMy Jobs
  • +
  • groupsSocial
  • new_labelContribute
  • Processes & Services
  • diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index 16af7d31..00e10bba 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -1,4 +1,5 @@ {% extends "base.html.j2" %} +{% import "materialize/wtf.html.j2" as wtf %} {% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %} {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %} @@ -10,6 +11,9 @@

    + {% if not corpus.user == current_user %} + addFollow Corpus + {% endif %}

     

    @@ -76,6 +80,24 @@
    + {% if current_user.can(Permission.ADMINISTRATE) or current_user.hashid == corpus.user.hashid %} +
    +
    + {{ corpus_settings_form.hidden_tag() }} +
    +
    + Corpus settings +
    +

    + {{ wtf.render_field(corpus_settings_form.is_public) }} +
    +
    + {{ wtf.render_field(corpus_settings_form.submit, material_icon='send') }} +
    +
    + +
    + {% endif %} {% endblock page_content %} @@ -84,5 +106,25 @@ {{ super() }} {% endblock scripts %} diff --git a/app/templates/main/dashboard.html.j2 b/app/templates/main/dashboard.html.j2 index 5474db97..5391a8af 100644 --- a/app/templates/main/dashboard.html.j2 +++ b/app/templates/main/dashboard.html.j2 @@ -42,6 +42,23 @@ +
    +

    Social

    +
    +
    + Other users +

    Find other users and follow them to see their corpora.

    +
    +
    +
    +
    +
    + Public corpora +

    Find public corpora

    +
    +
    +
    +
    {% endblock page_content %} @@ -96,3 +113,14 @@ {% endblock modals %} + +{% block scripts %} +{{ super() }} + +{% endblock scripts %} + diff --git a/app/templates/users/profile.html.j2 b/app/templates/users/profile.html.j2 index a98a372d..a6ad5b30 100644 --- a/app/templates/users/profile.html.j2 +++ b/app/templates/users/profile.html.j2 @@ -89,6 +89,24 @@ +
    +
    +
    +
    +

    Groups

    +
    +
    +
    +
    +
    +
    +

    Public corpora

    +
    +
    +
    +
    +
    + {% endblock page_content %} From b0f61b28fe270bc61b9b74fe80b72602ecf17cf3 Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Thu, 2 Feb 2023 09:14:50 +0100 Subject: [PATCH 02/23] Small changes on the Corpus page --- app/corpora/routes.py | 14 +++-- app/templates/corpora/corpus.html.j2 | 85 +++++++++++++++++++++------- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/app/corpora/routes.py b/app/corpora/routes.py index a5035087..f2128479 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -81,10 +81,18 @@ def corpus(corpus_id): db.session.commit() flash('Your changes have been saved') return redirect(url_for('.corpus', corpus_id=corpus.id)) + # if corpus.following_users == [None]: + # following_users = [] + # else: + # following_users = [ + # u.to_json_serializeable() for u + # in corpus.following_users + # ] return render_template( 'corpora/corpus.html.j2', corpus_settings_form=corpus_settings_form, corpus=corpus, + # following_users=following_users, title='Corpus' ) @@ -277,10 +285,8 @@ def import_corpus(): def export_corpus(corpus_id): abort(503) -@bp.route('//follow') +@bp.route('//follow', methods=['GET', 'POST']) @login_required -# TODO: Wenn Query Paramter genutzt wird, prüfen, ob user_id ungleich current_user.id ist und dann gucken, ob es ein Admin ist. -# Sonst 403. def follow_corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) user_hashid = request.args.get('user_id') @@ -297,7 +303,7 @@ def follow_corpus(corpus_id): db.session.commit() return {}, 202 -@bp.route('//unfollow') +@bp.route('//unfollow', methods=['GET', 'POST']) @login_required def unfollow_corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index 00e10bba..0f7b0bf4 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -10,10 +10,15 @@
    -

    + {#

    #} +

    {{ corpus.title }}

    {% if not corpus.user == current_user %} + {% if current_user.is_following_corpus(corpus) %} + addUnfollow Corpus + {% elif not current_user.is_following_corpus(corpus) %} addFollow Corpus {% endif %} + {% endif %}

     

    @@ -90,6 +95,7 @@

    {{ wtf.render_field(corpus_settings_form.is_public) }} +
    {{ wtf.render_field(corpus_settings_form.submit, material_icon='send') }} @@ -98,6 +104,14 @@
    {% endif %} +
    +
    +
    + Corpus followers +
    +
    +
    +
    {% endblock page_content %} @@ -106,25 +120,56 @@ {{ super() }} {% endblock scripts %} From bd6f94582b4e8b78b7c239f3898b2eea9679668f Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Mon, 6 Feb 2023 10:52:36 +0100 Subject: [PATCH 03/23] some testing --- app/corpora/routes.py | 11 ++++------- app/models.py | 4 ++++ app/static/js/ResourceLists/CorpusFileList.js | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/corpora/routes.py b/app/corpora/routes.py index f2128479..4c1ebd29 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -81,13 +81,10 @@ def corpus(corpus_id): db.session.commit() flash('Your changes have been saved') return redirect(url_for('.corpus', corpus_id=corpus.id)) - # if corpus.following_users == [None]: - # following_users = [] - # else: - # following_users = [ - # u.to_json_serializeable() for u - # in corpus.following_users - # ] + # following_users = [ + # u.to_json_serializeable() for u + # in corpus.following_users + # ] return render_template( 'corpora/corpus.html.j2', corpus_settings_form=corpus_settings_form, diff --git a/app/models.py b/app/models.py index 24f59d60..4c7a1362 100644 --- a/app/models.py +++ b/app/models.py @@ -650,6 +650,10 @@ class User(HashidMixin, UserMixin, db.Model): x.hashid: x.to_json_serializeable(relationships=True) for x in self.spacy_nlp_pipeline_models } + json_serializeable['followed_corpora'] = { + x.hashid: x.to_json_serializeable(relationships=True) + for x in self.followed_corpora + } if filter_by_privacy_settings: if not self.has_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL): diff --git a/app/static/js/ResourceLists/CorpusFileList.js b/app/static/js/ResourceLists/CorpusFileList.js index fd4ff480..ca27fe27 100644 --- a/app/static/js/ResourceLists/CorpusFileList.js +++ b/app/static/js/ResourceLists/CorpusFileList.js @@ -17,7 +17,7 @@ class CorpusFileList extends ResourceList { }); }); app.getUser(this.userId).then((user) => { - this.add(Object.values(user.corpora[this.corpusId].files)); + this.add(Object.values(user.corpora[this.corpusId].files || user.followed_corpora[this.corpusId].files)); this.isInitialized = true; }); } From 5b6e8894435b4acd17ffc116d690a595eab4f76e Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Tue, 7 Feb 2023 16:06:09 +0100 Subject: [PATCH 04/23] New public corpus route --- app/corpora/routes.py | 9 +++ app/templates/corpora/corpus.html.j2 | 51 ------------ app/templates/corpora/public_corpus.html.j2 | 86 +++++++++++++++++++++ 3 files changed, 95 insertions(+), 51 deletions(-) create mode 100644 app/templates/corpora/public_corpus.html.j2 diff --git a/app/corpora/routes.py b/app/corpora/routes.py index 4c1ebd29..63af1b07 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -332,3 +332,12 @@ def remove_permission(corpus_id, user_id, permission): a.remove_permission(permission) db.session.commit() return 'ok' + +@bp.route('/public/') +def public_corpus(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + return render_template( + 'corpora/public_corpus.html.j2', + corpus=corpus, + title=corpus.title + ) diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index 0f7b0bf4..c29da972 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -120,56 +120,5 @@ {{ super() }} {% endblock scripts %} diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/public_corpus.html.j2 new file mode 100644 index 00000000..f9cb4bc9 --- /dev/null +++ b/app/templates/corpora/public_corpus.html.j2 @@ -0,0 +1,86 @@ +{% extends "base.html.j2" %} +{% import "materialize/wtf.html.j2" as wtf %} + +{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %} + +{% block page_content %} +
    +
    +
    +

    {{ title }}

    +
    +
    +
    +
    +

    Description: {{ corpus.description }}

    +
    +

    +
    +
    +

    Creation date: {{ corpus.creation_date }}

    +
    +
    +

    Number of tokens used: {{ corpus.num_tokens }}

    +
    +
    +
    +
    +
    +
    +
    +{% endblock page_content %} + +{% block scripts %} +{{ super() }} + +{% endblock scripts %} From 965bc9d68a8d16f1e0e5456beb5e387e5302aaae Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Thu, 9 Feb 2023 11:05:27 +0100 Subject: [PATCH 05/23] Update public corpus logic --- app/corpora/routes.py | 63 ++++++++----------- ...c_corpus.html.j2 => corpus_public.html.j2} | 2 +- 2 files changed, 27 insertions(+), 38 deletions(-) rename app/templates/corpora/{public_corpus.html.j2 => corpus_public.html.j2} (98%) diff --git a/app/corpora/routes.py b/app/corpora/routes.py index 63af1b07..e1c67f40 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -18,18 +18,6 @@ from . import bp from .forms import ChangeCorpusSettingsForm, CreateCorpusFileForm, CreateCorpusForm, UpdateCorpusFileForm -def user_can_read_corpus(user, corpus): - return corpus.user == user or user.is_administrator() or corpus.is_public - - -def user_can_update_corpus(user, corpus): - return corpus.user == user or user.is_administrator() - - -def user_can_delete_corpus(user, corpus): - return user_can_update_corpus(user, corpus) - - @bp.route('') @login_required def corpora(): @@ -70,7 +58,10 @@ def create_corpus(): @login_required def corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) - if not user_can_read_corpus(current_user, corpus): + if not (corpus.user == current_user + or current_user.is_administrator() + or current_user.is_following_corpus(corpus) + or corpus.is_public): abort(403) corpus_settings_form = ChangeCorpusSettingsForm( data=corpus.to_json_serializeable(), @@ -81,17 +72,20 @@ def corpus(corpus_id): db.session.commit() flash('Your changes have been saved') return redirect(url_for('.corpus', corpus_id=corpus.id)) - # following_users = [ - # u.to_json_serializeable() for u - # in corpus.following_users - # ] - return render_template( - 'corpora/corpus.html.j2', - corpus_settings_form=corpus_settings_form, - corpus=corpus, - # following_users=following_users, - title='Corpus' - ) + if corpus.user == current_user or current_user.is_administrator(): + return render_template( + 'corpora/corpus.html.j2', + corpus_settings_form=corpus_settings_form, + corpus=corpus, + title='Corpus' + ) + else: + print('public') + return render_template( + 'corpora/corpus_public.html.j2', + corpus=corpus, + title='Corpus' + ) @@ -99,7 +93,7 @@ def corpus(corpus_id): # @login_required # def update_corpus(corpus_id): # corpus = Corpus.query.get_or_404(corpus_id) -# if not user_can_update_corpus(current_user, corpus): +# if not (corpus.user == current_user or current_user.is_administrator()): # abort(403) # return render_template( # 'corpora/update_corpus.html.j2', @@ -118,7 +112,7 @@ def delete_corpus(corpus_id): db.session.commit() corpus = Corpus.query.get_or_404(corpus_id) - if not user_can_delete_corpus(current_user, corpus): + if not (corpus.user == current_user or current_user.is_administrator()): abort(403) thread = Thread( target=_delete_corpus, @@ -132,7 +126,10 @@ def delete_corpus(corpus_id): @login_required def analyse_corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) - if not user_can_read_corpus(current_user, corpus): + if not (corpus.user == current_user + or current_user.is_administrator() + or current_user.is_following_corpus(corpus) + or corpus.is_public): abort(403) return render_template( 'corpora/analyse_corpus.html.j2', @@ -151,7 +148,7 @@ def build_corpus(corpus_id): db.session.commit() corpus = Corpus.query.get_or_404(corpus_id) - if not user_can_update_corpus(current_user, corpus): + 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(): @@ -169,7 +166,7 @@ def build_corpus(corpus_id): @login_required def create_corpus_file(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) - if not user_can_update_corpus(current_user, corpus): + if not (corpus.user == current_user or current_user.is_administrator()): abort(403) form = CreateCorpusFileForm() if form.is_submitted(): @@ -333,11 +330,3 @@ def remove_permission(corpus_id, user_id, permission): db.session.commit() return 'ok' -@bp.route('/public/') -def public_corpus(corpus_id): - corpus = Corpus.query.get_or_404(corpus_id) - return render_template( - 'corpora/public_corpus.html.j2', - corpus=corpus, - title=corpus.title - ) diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/corpus_public.html.j2 similarity index 98% rename from app/templates/corpora/public_corpus.html.j2 rename to app/templates/corpora/corpus_public.html.j2 index f9cb4bc9..7ca14378 100644 --- a/app/templates/corpora/public_corpus.html.j2 +++ b/app/templates/corpora/corpus_public.html.j2 @@ -38,7 +38,7 @@ {# let followingUserList = new UserList(document.querySelector('.user-list')); followingUserList.add({{ following_users|tojson }}); #} - corpusFollowingRequest.addEventListener('click', function() { + corpusFollowingRequest.addEventListener('click', () => { corpusFollowingRequest.innerHTML = 'addUnfollow Corpus'; if ("{{ current_user.is_following_corpus(corpus) }}" === "False") { corpusFollowingRequest.lastChild.textContent = 'Unfollow Corpus'; From a7136df5d23522a1f7092c0792b183708609ec9b Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Thu, 9 Feb 2023 11:06:03 +0100 Subject: [PATCH 06/23] let followers analyse corpus --- app/corpora/cqi_over_socketio/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/corpora/cqi_over_socketio/__init__.py b/app/corpora/cqi_over_socketio/__init__.py index c122e12e..cd02bedd 100644 --- a/app/corpora/cqi_over_socketio/__init__.py +++ b/app/corpora/cqi_over_socketio/__init__.py @@ -62,7 +62,9 @@ def connect(auth): if corpus is None: # return {'code': 404, 'msg': 'Not Found'} raise ConnectionRefusedError('Not Found') - if not (corpus.user == current_user or current_user.is_administrator()): + if not (corpus.user == current_user + or current_user.is_following_corpus(corpus) + or current_user.is_administrator()): # return {'code': 403, 'msg': 'Forbidden'} raise ConnectionRefusedError('Forbidden') if corpus.status not in [ From 71d13d482b342707ad3f2a15dcd65fc0a8a259cd Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Thu, 9 Feb 2023 11:07:13 +0100 Subject: [PATCH 07/23] Add back old news implementation --- app/templates/main/news_new.html.j2 | 269 ++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 app/templates/main/news_new.html.j2 diff --git a/app/templates/main/news_new.html.j2 b/app/templates/main/news_new.html.j2 new file mode 100644 index 00000000..d3e2f9b6 --- /dev/null +++ b/app/templates/main/news_new.html.j2 @@ -0,0 +1,269 @@ +{% extends "base.html.j2" %} +{% from "main/_breadcrumbs.html.j2" import breadcrumbs with context %} + +{% block page_content %} +
    +
    +
    +

    {{ title }}

    +
    + +
    +
    +
    +
    + April 2022 update +

    Dear users

    +
    +

    + with the April 2022 update we have improved nopaque in all places. + We have significantly reworked our backend code to utilize our servers more efficiently, + integrated a new service, updated all previously existing ones, rewrote a lot of code and made a few minor design improvements. +

    +
    + + Where is my Job data? +

    + At the beginning of the year, we realized that our storage limit had been reached. + This was the time when some users may have noticed system instabilities. + We were fortunately able to temporarily solve this problem without data loss + by deleting some non-nopaque related data on our system (yes we also do other things then nopaque). + In order to not face the same problem again, we had to dedicate ourselves to a long-term solution. + This consists of deleting all previous job data with this update and henceforth storing new job data + only for three months after job creation (important note: corpora are not affected). + All job data prior to this update has been backed up for you, + feel free to contact us at nopaque@uni-bielefeld.de if you would like to get this data back. +

    +
    + + What's new? +

    + By partnering up with Transkribus we reached one of our long term goals: integrate a HTR service into nopaque. + The Transkribus HTR Pipeline service is implemented as a kind of proxied service where the work is split between Transkribus and us. + That means we do the preprocessing, storage and postprocessing, while Transkribus handles the HTR itself. +

    +
    + +

    + One of the changes in the background was to fix our performance issues. While implementing the Transkribus HTR Pipeline service we + found some optimization potential within different steps of our processing routine. These optimizations are now also + available in our Tesseract OCR Pipeline service, resulting in a speed up of about 4x. + For now we are done with the most obvious optimizations but we may include more in the near future, so stay tuned! +

    +
    + +

    + The next step was to reorganize our Corpus Analysis code. Unfortunatly it was a bit messy, after a complete rewrite we are + now able to query a corpus without long loading times and with better error handling, resulting in way more stable user experience. + The Corpus Analysis service is now modularized and comes with 2 modules that recreate and extend the functionality of the old service.
    + For now we had to disable the Query Result viewer, the code was based on the old Corpus Analysis service and will be reintegrated as a module to the Corpus Analysis. +

    +
    + +

    + The spaCy NLP Pipeline service got some love in the form of smaller updates too. + This is important preliminary work to support more models/languages that does not provide the full set of linguistic features (lemma, ner, pos, simple_pos). It still needs some testing and tweaking but will be ready soon! +

    +
    + +

    + Last but not least we made some design changes. Now you can find colors in places where we had just black and white before. + Nothing big but the new colors will help you identify ressources more efficient! +

    +
    + + Database cleanup +

    + We may be a bit late with our spring cleaning but with this update we tidied up within our database system. + This means we deleted old corpora with no corpus files, unconfirmed user accounts and in general unnecessary data fields. +

    +
    + +

    + That's it, thank you for using nopaque! We hope you like the update and appreciate all your past and future feedback. +

    +
    +
    +
    + +
    +
    +
    + Maintenance +

    Dear users

    +
    +

    Currently we are rewriting big parts of our project infrastructure. Due to this the following features are not available:

    +
      +
    • Corpus export and import
    • +
    • Query result export, import and view
    • +
    +

    We hope to add these features back in the near future, until then check out our updated corpus analysis.

    +
    +
    +
    + +
    +
    +
    + Natural Language Processing removed language support +

    Dear users

    +
    +

    Not all language models support all features we utizlize in our NLP service. Thats why we had to drop them, as soon as they meet our requirements we will add them back!

    +
    +
    +
    + +
    +
    +
    + nopaque's beta launch +

    Dear users

    +
    +

    A few days ago we went live with nopaque. Right now nopaque is still in its Beta phase. So some bugs are to be expected. If you encounter any bugs or some feature is not working as expected please send as an email using the feedback button at the botton of the page in the footer!

    +

    We are happy to help you with any issues and will use the feedback to fix all mentioned bugs!

    +
    +
    +
    +
    +
    +{% endblock page_content %} + +{% block scripts %} +{{ super() }} + +{% endblock scripts %} From f06508b412f90d50101862f5961375a9221d79c1 Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Thu, 9 Feb 2023 11:21:03 +0100 Subject: [PATCH 08/23] New Public Corpus Page --- app/corpora/routes.py | 3 + .../js/ResourceLists/PublicCorpusFileList.js | 15 ++++ app/templates/_scripts.html.j2 | 1 + app/templates/corpora/corpus.html.j2 | 3 +- app/templates/corpora/public_corpus.html.j2 | 77 ++++++++++++------- 5 files changed, 69 insertions(+), 30 deletions(-) create mode 100644 app/static/js/ResourceLists/PublicCorpusFileList.js diff --git a/app/corpora/routes.py b/app/corpora/routes.py index 63af1b07..d89b7567 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -298,6 +298,7 @@ def follow_corpus(corpus_id): if not user.is_following_corpus(corpus): user.follow_corpus(corpus) db.session.commit() + # flash('Hallo Inga Kirschnick') return {}, 202 @bp.route('//unfollow', methods=['GET', 'POST']) @@ -336,8 +337,10 @@ def remove_permission(corpus_id, user_id, permission): @bp.route('/public/') def public_corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) + corpus_files = [cf.to_json_serializeable() for cf in CorpusFile.query.filter_by(corpus_id = corpus_id).all()] return render_template( 'corpora/public_corpus.html.j2', corpus=corpus, + corpus_files=corpus_files, title=corpus.title ) diff --git a/app/static/js/ResourceLists/PublicCorpusFileList.js b/app/static/js/ResourceLists/PublicCorpusFileList.js new file mode 100644 index 00000000..51362138 --- /dev/null +++ b/app/static/js/ResourceLists/PublicCorpusFileList.js @@ -0,0 +1,15 @@ +class PublicCorpusFileList extends CorpusFileList { + get item() { + return ` +
    + + + + + + + `.trim(); + } +} diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 95406079..64629b6c 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -20,6 +20,7 @@ 'js/RessourceDisplays/JobDisplay.js', 'js/ResourceLists/ResourceList.js', 'js/ResourceLists/CorpusFileList.js', + 'js/ResourceLists/PublicCorpusFileList.js', 'js/ResourceLists/CorpusList.js', 'js/ResourceLists/JobList.js', 'js/ResourceLists/JobInputList.js', diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index c29da972..5311e92b 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -10,8 +10,7 @@
    - {#

    #} -

    {{ corpus.title }}

    +

    {% if not corpus.user == current_user %} {% if current_user.is_following_corpus(corpus) %} addUnfollow Corpus diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/public_corpus.html.j2 index f9cb4bc9..90a45f62 100644 --- a/app/templates/corpora/public_corpus.html.j2 +++ b/app/templates/corpora/public_corpus.html.j2 @@ -8,9 +8,27 @@

    {{ title }}

    +
    +
    +

    Status:

    +

    +
    +

    Description: {{ corpus.description }}


    @@ -25,6 +43,13 @@
    +
    +
    + Corpus files +
    +
    +
    +
    @@ -33,45 +58,42 @@ {% block scripts %} {{ super() }} From b83f8e8fb9b0b1d4ec32d6c196531fb8c1feda2a Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Thu, 9 Feb 2023 11:33:16 +0100 Subject: [PATCH 09/23] Fix admin lists --- app/static/js/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/static/js/App.js b/app/static/js/App.js index 532bff5c..87d44d71 100644 --- a/app/static/js/App.js +++ b/app/static/js/App.js @@ -8,7 +8,7 @@ class App { this.socket.on('PATCH', (patch) => {this.onPatch(patch);}); } - getUser(userId, backrefs=false, relationships=false) { + getUser(userId, backrefs=true, relationships=true) { if (userId in this.data.promises.getUser) { return this.data.promises.getUser[userId]; } From 66fbf4d57b6a497f0be487715024bb7578989312 Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Thu, 9 Feb 2023 11:56:02 +0100 Subject: [PATCH 10/23] update corpus public page --- app/templates/corpora/corpus_public.html.j2 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/templates/corpora/corpus_public.html.j2 b/app/templates/corpora/corpus_public.html.j2 index caaf011a..90a45f62 100644 --- a/app/templates/corpora/corpus_public.html.j2 +++ b/app/templates/corpora/corpus_public.html.j2 @@ -67,9 +67,7 @@ followingUserList.add({{ following_users|tojson }}); #} corpusFollowingRequest.addEventListener('click', () => { - corpusFollowingRequest.innerHTML = 'addUnfollow Corpus'; - if ("{{ current_user.is_following_corpus(corpus) }}" === "False") { - corpusFollowingRequest.lastChild.textContent = 'Unfollow Corpus'; + if ({{ current_user.is_following_corpus(corpus)|tojson }}) { return new Promise((resolve, reject) => { fetch(`/corpora/{{ corpus.hashid }}/unfollow`, {method: 'POST', headers: {Accept: 'application/json'}}) .then( From 52fa23ff650d16761470ea5899b5403bbcf54a86 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Thu, 9 Feb 2023 12:02:00 +0100 Subject: [PATCH 11/23] Fix some problems after merge --- app/corpora/routes.py | 3 ++- app/templates/corpora/corpus_public.html.j2 | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/corpora/routes.py b/app/corpora/routes.py index 3b591140..0312bf4c 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -80,10 +80,11 @@ def corpus(corpus_id): title='Corpus' ) else: - print('public') + corpus_files = [x.to_json_serializeable() for x in corpus.files] return render_template( 'corpora/corpus_public.html.j2', corpus=corpus, + corpus_files=corpus_files, title='Corpus' ) diff --git a/app/templates/corpora/corpus_public.html.j2 b/app/templates/corpora/corpus_public.html.j2 index 90a45f62..86356b87 100644 --- a/app/templates/corpora/corpus_public.html.j2 +++ b/app/templates/corpora/corpus_public.html.j2 @@ -76,7 +76,7 @@ if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} {# app.flash(`You are not following "{{ corpus.title }}" anymore`, 'corpus'); #} resolve(response); - window.location.href = '{{ url_for("corpora.public_corpus", corpus_id=corpus.id) }}'; + window.location.href = '{{ url_for("corpora.corpus", corpus_id=corpus.id) }}'; }, (response) => { app.flash('Something went wrong', 'error'); @@ -92,7 +92,7 @@ if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} {# app.flash(`You follow "{{ corpus.title }}" now`, 'corpus'); #} - window.location.href = '{{ url_for("corpora.public_corpus", corpus_id=corpus.id) }}'; + window.location.href = '{{ url_for("corpora.corpus", corpus_id=corpus.id) }}'; resolve(response); }, (response) => { From 91a800f7fd8970fd3b46eff485fe3f377d78c5c3 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Thu, 9 Feb 2023 12:10:37 +0100 Subject: [PATCH 12/23] Add analyse button in public corpus pages --- app/templates/corpora/corpus_public.html.j2 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/templates/corpora/corpus_public.html.j2 b/app/templates/corpora/corpus_public.html.j2 index 86356b87..f35c66e6 100644 --- a/app/templates/corpora/corpus_public.html.j2 +++ b/app/templates/corpora/corpus_public.html.j2 @@ -10,7 +10,6 @@

    {{ title }}

    - {% if not corpus.user == current_user %} {% if current_user.is_following_corpus(corpus) %} addUnfollow Corpus @@ -18,6 +17,8 @@ addFollow Corpus {% endif %} + {% if corpus.status.name in ['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'] and current_user.is_following_corpus(corpus) %} + Analyse {% endif %}
    From e03b54cfcd3e03bae1d940fcc674294393b6b4d1 Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Thu, 9 Feb 2023 14:03:03 +0100 Subject: [PATCH 13/23] update public corpora list on dashboard --- app/corpora/routes.py | 2 +- app/static/js/ResourceLists/PublicCorpusList.js | 14 ++++++++++++++ app/templates/_scripts.html.j2 | 1 + app/templates/main/dashboard.html.j2 | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 app/static/js/ResourceLists/PublicCorpusList.js diff --git a/app/corpora/routes.py b/app/corpora/routes.py index 0312bf4c..f2cbac63 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -72,7 +72,7 @@ def corpus(corpus_id): db.session.commit() flash('Your changes have been saved') return redirect(url_for('.corpus', corpus_id=corpus.id)) - if corpus.user == current_user or current_user.is_administrator(): + if corpus.user == current_user: return render_template( 'corpora/corpus.html.j2', corpus_settings_form=corpus_settings_form, diff --git a/app/static/js/ResourceLists/PublicCorpusList.js b/app/static/js/ResourceLists/PublicCorpusList.js new file mode 100644 index 00000000..fdb2e9ed --- /dev/null +++ b/app/static/js/ResourceLists/PublicCorpusList.js @@ -0,0 +1,14 @@ +class PublicCorpusList extends CorpusList { + get item() { + return ` +
    + + + + + + `.trim(); + } +} diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 64629b6c..53a0469c 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -22,6 +22,7 @@ 'js/ResourceLists/CorpusFileList.js', 'js/ResourceLists/PublicCorpusFileList.js', 'js/ResourceLists/CorpusList.js', + 'js/ResourceLists/PublicCorpusList.js', 'js/ResourceLists/JobList.js', 'js/ResourceLists/JobInputList.js', 'js/ResourceLists/JobResultList.js', diff --git a/app/templates/main/dashboard.html.j2 b/app/templates/main/dashboard.html.j2 index 5391a8af..0feba7b5 100644 --- a/app/templates/main/dashboard.html.j2 +++ b/app/templates/main/dashboard.html.j2 @@ -119,7 +119,7 @@ {% endblock scripts %} From 5a4464ebb3f31111f6b670d654b80071b94fcce9 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Thu, 9 Feb 2023 15:24:33 +0100 Subject: [PATCH 14/23] Change sqlalchemy version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1c6a94f5..96b26269 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,6 +24,6 @@ pyScss python-dotenv pyyaml redis -SQLAlchemy==1.4.46 +SQLAlchemy==1.4.45 tqdm wtforms[email] From 428400df3f76dace23cfcd80cbe3e117150aba69 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Thu, 9 Feb 2023 16:22:37 +0100 Subject: [PATCH 15/23] Add public switch without form --- app/corpora/routes.py | 40 +++++---- .../js/RessourceDisplays/CorpusDisplay.js | 11 +++ app/static/js/Utils.js | 82 +++++++++++++++++++ app/templates/corpora/corpus.html.j2 | 49 ++++------- 4 files changed, 130 insertions(+), 52 deletions(-) diff --git a/app/corpora/routes.py b/app/corpora/routes.py index f2cbac63..79bdf4ab 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -63,19 +63,9 @@ def corpus(corpus_id): or current_user.is_following_corpus(corpus) or corpus.is_public): abort(403) - corpus_settings_form = ChangeCorpusSettingsForm( - data=corpus.to_json_serializeable(), - prefix='corpus-settings-form' - ) - if corpus_settings_form.validate_on_submit(): - corpus.is_public = corpus_settings_form.is_public.data - db.session.commit() - flash('Your changes have been saved') - return redirect(url_for('.corpus', corpus_id=corpus.id)) if corpus.user == current_user: return render_template( 'corpora/corpus.html.j2', - corpus_settings_form=corpus_settings_form, corpus=corpus, title='Corpus' ) @@ -89,18 +79,26 @@ def corpus(corpus_id): ) +@bp.route('//enable_is_public', methods=['POST']) +@login_required +def enable_corpus_is_public(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + if not (corpus.user == current_user or current_user.is_administrator()): + abort(403) + corpus.is_public = True + db.session.commit() + return '', 204 -# @bp.route('//update') -# @login_required -# def update_corpus(corpus_id): -# corpus = Corpus.query.get_or_404(corpus_id) -# if not (corpus.user == current_user or current_user.is_administrator()): -# abort(403) -# return render_template( -# 'corpora/update_corpus.html.j2', -# corpus=corpus, -# title='Corpus' -# ) + +@bp.route('//disable_is_public', methods=['POST']) +@login_required +def disable_corpus_is_public(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + if not (corpus.user == current_user or current_user.is_administrator()): + abort(403) + corpus.is_public = False + db.session.commit() + return '', 204 @bp.route('/', methods=['DELETE']) diff --git a/app/static/js/RessourceDisplays/CorpusDisplay.js b/app/static/js/RessourceDisplays/CorpusDisplay.js index af4da56a..6dd3dc72 100644 --- a/app/static/js/RessourceDisplays/CorpusDisplay.js +++ b/app/static/js/RessourceDisplays/CorpusDisplay.js @@ -12,12 +12,23 @@ class CorpusDisplay extends RessourceDisplay { .addEventListener('click', (event) => { Utils.deleteCorpusRequest(this.userId, this.corpusId); }); + this.displayElement + .querySelector('.action-switch[data-action="toggle-is-public"]') + .addEventListener('click', (event) => { + if (event.target.tagName !== 'INPUT') {return;} + if (event.target.checked) { + Utils.enableCorpusIsPublicRequest(this.userId, this.corpusId); + } else { + Utils.disableCorpusIsPublicRequest(this.userId, this.corpusId); + } + }); } init(user) { let corpus = user.corpora[this.corpusId]; this.setCreationDate(corpus.creation_date); this.setDescription(corpus.description); + this.setIsPublic(corpus.is_public); this.setStatus(corpus.status); this.setTitle(corpus.title); this.setNumTokens(corpus.num_tokens); diff --git a/app/static/js/Utils.js b/app/static/js/Utils.js index d63be025..aee382a0 100644 --- a/app/static/js/Utils.js +++ b/app/static/js/Utils.js @@ -69,6 +69,88 @@ class Utils { return Utils.mergeObjectsDeep(mergedObject, ...objects.slice(2)); } + static enableCorpusIsPublicRequest(userId, corpusId) { + return new Promise((resolve, reject) => { + let corpus; + try { + corpus = app.data.users[userId].corpora[corpusId]; + } catch (error) { + corpus = {}; + } + + let modalElement = Utils.HTMLToElement( + ` + + ` + ); + document.querySelector('#modals').appendChild(modalElement); + let modal = M.Modal.init( + modalElement, + { + dismissible: false, + onCloseEnd: () => { + modal.destroy(); + modalElement.remove(); + } + } + ); + + let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); + confirmElement.addEventListener('click', (event) => { + let corpusTitle = corpus?.title; + fetch(`/corpora/${corpusId}/enable_is_public`, {method: 'POST', headers: {Accept: 'application/json'}}) + .then( + (response) => { + if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} + if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} + app.flash(`Corpus "${corpusTitle}" is public now`, 'corpus'); + resolve(response); + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); + }); + modal.open(); + }); + } + + static disableCorpusIsPublicRequest(userId, corpusId) { + return new Promise((resolve, reject) => { + let corpus; + try { + corpus = app.data.users[userId].corpora[corpusId]; + } catch (error) { + corpus = {}; + } + + let corpusTitle = corpus?.title; + fetch(`/corpora/${corpusId}/disable_is_public`, {method: 'POST', headers: {Accept: 'application/json'}}) + .then( + (response) => { + if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} + if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} + app.flash(`Corpus "${corpusTitle}" is private now`, 'corpus'); + resolve(response); + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); + }); + } + static buildCorpusRequest(userId, corpusId) { return new Promise((resolve, reject) => { let corpus; diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index 5311e92b..1c2167ec 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -11,13 +11,6 @@

    - {% if not corpus.user == current_user %} - {% if current_user.is_following_corpus(corpus) %} - addUnfollow Corpus - {% elif not current_user.is_following_corpus(corpus) %} - addFollow Corpus - {% endif %} - {% endif %}

     

    @@ -64,11 +57,23 @@
    - @@ -84,25 +89,7 @@ - {% if current_user.can(Permission.ADMINISTRATE) or current_user.hashid == corpus.user.hashid %} -
    -
    - {{ corpus_settings_form.hidden_tag() }} -
    -
    - Corpus settings -
    -

    - {{ wtf.render_field(corpus_settings_form.is_public) }} -
    -
    -
    - {{ wtf.render_field(corpus_settings_form.submit, material_icon='send') }} -
    -
    - -
    - {% endif %} +
    From 33a54b6206c7a23750facc47bce5e57de79ddb1d Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Fri, 10 Feb 2023 09:37:31 +0100 Subject: [PATCH 16/23] Corpus first share link --- app/corpora/routes.py | 35 ++++++++++++++++-- app/templates/corpora/corpus.html.j2 | 39 +++++++++++++-------- app/templates/corpora/corpus_public.html.j2 | 2 +- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/app/corpora/routes.py b/app/corpora/routes.py index f2cbac63..383c9aa0 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -1,3 +1,4 @@ +from datetime import datetime, timedelta from flask import ( abort, current_app, @@ -11,6 +12,7 @@ from flask import ( ) from flask_login import current_user, login_required from threading import Thread +import jwt import os from app import db, hashids from app.models import Corpus, CorpusFile, CorpusStatus, CorpusFollowerAssociation, User @@ -57,6 +59,7 @@ def create_corpus(): @bp.route('/', methods=['GET', 'POST']) @login_required def corpus(corpus_id): + print(corpus_id) corpus = Corpus.query.get_or_404(corpus_id) if not (corpus.user == current_user or current_user.is_administrator() @@ -72,11 +75,23 @@ def corpus(corpus_id): db.session.commit() flash('Your changes have been saved') return redirect(url_for('.corpus', corpus_id=corpus.id)) + now = datetime.utcnow() + payload = { + 'iat': now, + 'iss': current_app.config['SERVER_NAME'], + 'sub': corpus.hashid + } + token = jwt.encode( + payload, + current_app.config['SECRET_KEY'], + algorithm='HS256' + ) if corpus.user == current_user: return render_template( 'corpora/corpus.html.j2', corpus_settings_form=corpus_settings_form, corpus=corpus, + token=token, title='Corpus' ) else: @@ -88,8 +103,23 @@ def corpus(corpus_id): title='Corpus' ) +@bp.route('/share/', methods=['GET', 'POST']) +def share_corpus(token): + try: + payload = jwt.decode( + token, + current_app.config['SECRET_KEY'], + algorithms=['HS256'], + issuer=current_app.config['SERVER_NAME'], + options={'require': ['iat', 'iss', 'sub']} + ) + except jwt.PyJWTError: + return False + corpus_hashid = payload.get('sub') + corpus_id = hashids.decode(corpus_hashid) + return redirect(url_for('.corpus', corpus_id=corpus_id)) - + # @bp.route('//update') # @login_required # def update_corpus(corpus_id): @@ -296,7 +326,7 @@ def follow_corpus(corpus_id): if not user.is_following_corpus(corpus): user.follow_corpus(corpus) db.session.commit() - # flash('Hallo Inga Kirschnick') + flash(f'You are following {corpus.title} now', category='corpus') return {}, 202 @bp.route('//unfollow', methods=['GET', 'POST']) @@ -315,6 +345,7 @@ def unfollow_corpus(corpus_id): if user.is_following_corpus(corpus): user.unfollow_corpus(corpus) db.session.commit() + flash(f'You are not following {corpus.title} anymore', category='corpus') return {}, 202 @bp.route('/add_permission///') diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index 5311e92b..265dfdd2 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -86,21 +86,13 @@
    {% if current_user.can(Permission.ADMINISTRATE) or current_user.hashid == corpus.user.hashid %}
    -
    - {{ corpus_settings_form.hidden_tag() }} -
    -
    - Corpus settings -
    -

    - {{ wtf.render_field(corpus_settings_form.is_public) }} -
    -
    -
    - {{ wtf.render_field(corpus_settings_form.submit, material_icon='send') }} -
    +
    {% endif %}
    @@ -119,5 +111,24 @@ {{ super() }} {% endblock scripts %} diff --git a/app/templates/corpora/corpus_public.html.j2 b/app/templates/corpora/corpus_public.html.j2 index f35c66e6..1668e55c 100644 --- a/app/templates/corpora/corpus_public.html.j2 +++ b/app/templates/corpora/corpus_public.html.j2 @@ -7,7 +7,7 @@
    -

    {{ title }}

    +

    {{ corpus.title }}

    From 1767ceee010a1fae971b4a857fc84bc81fb10018 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Fri, 10 Feb 2023 09:53:59 +0100 Subject: [PATCH 17/23] Remove function call of missing function --- app/static/js/RessourceDisplays/CorpusDisplay.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/static/js/RessourceDisplays/CorpusDisplay.js b/app/static/js/RessourceDisplays/CorpusDisplay.js index 6dd3dc72..fd342dd4 100644 --- a/app/static/js/RessourceDisplays/CorpusDisplay.js +++ b/app/static/js/RessourceDisplays/CorpusDisplay.js @@ -28,7 +28,6 @@ class CorpusDisplay extends RessourceDisplay { let corpus = user.corpora[this.corpusId]; this.setCreationDate(corpus.creation_date); this.setDescription(corpus.description); - this.setIsPublic(corpus.is_public); this.setStatus(corpus.status); this.setTitle(corpus.title); this.setNumTokens(corpus.num_tokens); From 874c963cc42752ae82585ee2c0e4d5cf0080e00e Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Fri, 10 Feb 2023 14:51:47 +0100 Subject: [PATCH 18/23] Fix some list problems --- app/static/js/ResourceLists/CorpusFileList.js | 1 + app/static/js/ResourceLists/CorpusList.js | 3 ++- app/static/js/ResourceLists/JobInputList.js | 1 + app/static/js/ResourceLists/JobList.js | 1 + app/static/js/ResourceLists/JobResultList.js | 1 + app/static/js/ResourceLists/SpacyNLPPipelineModelList.js | 1 + app/static/js/ResourceLists/TesseractOCRPipelineModelList.js | 1 + 7 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/static/js/ResourceLists/CorpusFileList.js b/app/static/js/ResourceLists/CorpusFileList.js index ca27fe27..c052b2ea 100644 --- a/app/static/js/ResourceLists/CorpusFileList.js +++ b/app/static/js/ResourceLists/CorpusFileList.js @@ -11,6 +11,7 @@ class CorpusFileList extends ResourceList { this.isInitialized = false; this.userId = listContainerElement.dataset.userId; this.corpusId = listContainerElement.dataset.corpusId; + if (this.userId === undefined || this.corpusId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/CorpusList.js b/app/static/js/ResourceLists/CorpusList.js index e4af0b12..bf62ebed 100644 --- a/app/static/js/ResourceLists/CorpusList.js +++ b/app/static/js/ResourceLists/CorpusList.js @@ -8,8 +8,9 @@ class CorpusList extends ResourceList { constructor(listContainerElement, options = {}) { super(listContainerElement, options); this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); - this.isInitialized = false; + this.isInitialized = false this.userId = listContainerElement.dataset.userId; + if (this.userId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/JobInputList.js b/app/static/js/ResourceLists/JobInputList.js index 36cb47f7..7f1a5105 100644 --- a/app/static/js/ResourceLists/JobInputList.js +++ b/app/static/js/ResourceLists/JobInputList.js @@ -11,6 +11,7 @@ class JobInputList extends ResourceList { this.isInitialized = false; this.userId = listContainerElement.dataset.userId; this.jobId = listContainerElement.dataset.jobId; + if (this.userId === undefined || this.jobId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/JobList.js b/app/static/js/ResourceLists/JobList.js index 1fa14ea2..ff7f82b2 100644 --- a/app/static/js/ResourceLists/JobList.js +++ b/app/static/js/ResourceLists/JobList.js @@ -10,6 +10,7 @@ class JobList extends ResourceList { this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.isInitialized = false; this.userId = listContainerElement.dataset.userId; + if (this.userId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/JobResultList.js b/app/static/js/ResourceLists/JobResultList.js index 71c430af..b0cbc088 100644 --- a/app/static/js/ResourceLists/JobResultList.js +++ b/app/static/js/ResourceLists/JobResultList.js @@ -11,6 +11,7 @@ class JobResultList extends ResourceList { this.isInitialized = false; this.userId = listContainerElement.dataset.userId; this.jobId = listContainerElement.dataset.jobId; + if (this.userId === undefined || this.jobId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js index 078ec90c..b90fb06b 100644 --- a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js +++ b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js @@ -11,6 +11,7 @@ class SpaCyNLPPipelineModelList extends ResourceList { this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.isInitialized = false; this.userId = listContainerElement.dataset.userId; + if (this.userId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} diff --git a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js index ad319041..4ad3f5b1 100644 --- a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js +++ b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js @@ -11,6 +11,7 @@ class TesseractOCRPipelineModelList extends ResourceList { this.listjs.list.addEventListener('click', (event) => {this.onClick(event)}); this.isInitialized = false; this.userId = listContainerElement.dataset.userId; + if (this.userId === undefined) {return;} app.subscribeUser(this.userId).then((response) => { app.socket.on('PATCH', (patch) => { if (this.isInitialized) {this.onPatch(patch);} From 8595e2a2035c9b97b8959d2ee2700b62e6c91de3 Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Mon, 13 Feb 2023 10:16:44 +0100 Subject: [PATCH 19/23] Share link expiration update and small fixes --- app/corpora/routes.py | 1 + app/main/routes.py | 2 +- app/templates/corpora/corpus_public.html.j2 | 10 +++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/corpora/routes.py b/app/corpora/routes.py index 5c857e61..bd65bfca 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -76,6 +76,7 @@ def corpus(corpus_id): return redirect(url_for('.corpus', corpus_id=corpus.id)) now = datetime.utcnow() payload = { + 'exp': now + timedelta(weeks=1), 'iat': now, 'iss': current_app.config['SERVER_NAME'], 'sub': corpus.hashid diff --git a/app/main/routes.py b/app/main/routes.py index aed63853..dd4a5007 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -33,7 +33,7 @@ def dashboard(): ] corpora = [ c.to_json_serializeable() for c - in Corpus.query.filter(Corpus.is_public == True).all() + in Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all() ] return render_template('main/dashboard.html.j2', title='Dashboard', users=users, corpora=corpora) diff --git a/app/templates/corpora/corpus_public.html.j2 b/app/templates/corpora/corpus_public.html.j2 index 1668e55c..a489e27a 100644 --- a/app/templates/corpora/corpus_public.html.j2 +++ b/app/templates/corpora/corpus_public.html.j2 @@ -18,7 +18,7 @@ {% endif %} {% if corpus.status.name in ['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'] and current_user.is_following_corpus(corpus) %} - Analyse + Analyze {% endif %}
    @@ -44,12 +44,14 @@
    + {% if current_user.is_following_corpus(corpus) %}
    Corpus files
    + {% endif %}
    @@ -59,8 +61,10 @@ {% block scripts %} {{ super() }} +{# + #} {% endblock scripts %} diff --git a/app/templates/corpora/corpus_public.html.j2 b/app/templates/corpora/corpus_public.html.j2 deleted file mode 100644 index 1668e55c..00000000 --- a/app/templates/corpora/corpus_public.html.j2 +++ /dev/null @@ -1,108 +0,0 @@ -{% extends "base.html.j2" %} -{% import "materialize/wtf.html.j2" as wtf %} - -{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %} - -{% block page_content %} -
    -
    -
    -

    {{ corpus.title }}

    -
    -
    - - {% if current_user.is_following_corpus(corpus) %} - addUnfollow Corpus - {% else %} - addFollow Corpus - {% endif %} - - {% if corpus.status.name in ['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'] and current_user.is_following_corpus(corpus) %} - Analyse - {% endif %} -
    -
    -
    -
    -
    -
    -

    Status:

    -

    -
    -
    -
    -

    Description: {{ corpus.description }}

    -
    -

    -
    -
    -

    Creation date: {{ corpus.creation_date }}

    -
    -
    -

    Number of tokens used: {{ corpus.num_tokens }}

    -
    -
    -
    -
    -
    -
    - Corpus files -
    -
    -
    -
    -
    -
    -
    -{% endblock page_content %} - -{% block scripts %} -{{ super() }} - -{% endblock scripts %} diff --git a/app/templates/corpora/corpora.html.j2 b/app/templates/corpora/public_corpora.html.j2 similarity index 100% rename from app/templates/corpora/corpora.html.j2 rename to app/templates/corpora/public_corpora.html.j2 diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/public_corpus.html.j2 new file mode 100644 index 00000000..ace8665f --- /dev/null +++ b/app/templates/corpora/public_corpus.html.j2 @@ -0,0 +1,80 @@ +{% extends "base.html.j2" %} +{% import "materialize/wtf.html.j2" as wtf %} + +{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %} + +{% block page_content %} +
    +
    +
    +

    {{ corpus.title }}

    +
    +
    + {% if current_user.is_following_corpus(corpus) %} + addUnfollow Corpus + {% endif %} + {% if corpus.status.name in ['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'] and current_user.is_following_corpus(corpus) %} + Analyse + {% endif %} +
    +
    +
    +
    +
    +
    +

    Status:

    +

    +
    +
    +
    +

    Description: {{ corpus.description }}

    +
    +

    +
    +
    +

    Creation date: {{ corpus.creation_date }}

    +
    +
    +

    Number of tokens used: {{ corpus.num_tokens }}

    +
    +
    +
    +
    +
    +
    + Corpus files +
    +
    +
    +
    +
    +
    + +{% endblock page_content %} + +{% block scripts %} +{{ super() }} + +{% endblock scripts %} From dcdb71ac74f72145e0b8fad207c8bfe0444e173f Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Wed, 15 Feb 2023 11:30:27 +0100 Subject: [PATCH 22/23] Remove more UI leftovers from community update --- app/main/routes.py | 23 +++++++++++++-------- app/templates/corpora/public_corpus.html.j2 | 2 +- app/templates/main/dashboard.html.j2 | 8 +++---- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app/main/routes.py b/app/main/routes.py index dd4a5007..eff0dadd 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -27,15 +27,20 @@ def faq(): @bp.route('/dashboard') @login_required def dashboard(): - users = [ - u.to_json_serializeable(filter_by_privacy_settings=True) for u - in User.query.filter(User.is_public == True, User.id != current_user.id).all() - ] - corpora = [ - c.to_json_serializeable() for c - in Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all() - ] - return render_template('main/dashboard.html.j2', title='Dashboard', users=users, corpora=corpora) + # users = [ + # u.to_json_serializeable(filter_by_privacy_settings=True) for u + # in User.query.filter(User.is_public == True, User.id != current_user.id).all() + # ] + # corpora = [ + # c.to_json_serializeable() for c + # in Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all() + # ] + return render_template( + 'main/dashboard.html.j2', + title='Dashboard', + # users=users, + # corpora=corpora + ) @bp.route('/dashboard2') diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/public_corpus.html.j2 index ace8665f..94b3524c 100644 --- a/app/templates/corpora/public_corpus.html.j2 +++ b/app/templates/corpora/public_corpus.html.j2 @@ -14,7 +14,7 @@ addUnfollow Corpus {% endif %} {% if corpus.status.name in ['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'] and current_user.is_following_corpus(corpus) %} - Analyse + Analyze {% endif %} diff --git a/app/templates/main/dashboard.html.j2 b/app/templates/main/dashboard.html.j2 index 0feba7b5..ca5422df 100644 --- a/app/templates/main/dashboard.html.j2 +++ b/app/templates/main/dashboard.html.j2 @@ -42,7 +42,7 @@ -
    + {#

    Social

    @@ -58,7 +58,7 @@
    -
    +
    #} {% endblock page_content %} @@ -116,11 +116,11 @@ {% block scripts %} {{ super() }} - + #} {% endblock scripts %} From fc8517649f0122c2faaabdcce69ed8d60917f074 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Wed, 15 Feb 2023 11:31:47 +0100 Subject: [PATCH 23/23] Remove debug messages and more leftovers --- app/static/js/App.js | 1 + app/static/js/ResourceLists/PublicCorpusFileList.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/static/js/App.js b/app/static/js/App.js index 532bff5c..833f5f0e 100644 --- a/app/static/js/App.js +++ b/app/static/js/App.js @@ -9,6 +9,7 @@ class App { } getUser(userId, backrefs=false, relationships=false) { + console.log(userId); if (userId in this.data.promises.getUser) { return this.data.promises.getUser[userId]; } diff --git a/app/static/js/ResourceLists/PublicCorpusFileList.js b/app/static/js/ResourceLists/PublicCorpusFileList.js index 51362138..37a284cf 100644 --- a/app/static/js/ResourceLists/PublicCorpusFileList.js +++ b/app/static/js/ResourceLists/PublicCorpusFileList.js @@ -7,7 +7,7 @@ class PublicCorpusFileList extends CorpusFileList {
    `.trim();
    + file_download +
    book
    + send +
    - file_download + send