From 57813b4bc2dac150f4a5aa8a70cf4ae48bf9e932 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch <p.jentsch@uni-bielefeld.de> Date: Wed, 22 Mar 2023 12:06:33 +0100 Subject: [PATCH] Fix link issues --- app/admin/routes.py | 2 +- app/models.py | 40 ++++++--- app/templates/_sidenav.html.j2 | 87 ++++++++++++------- app/templates/admin/user.html.j2 | 2 +- app/templates/corpora/public_corpus.html.j2 | 6 +- app/templates/settings/settings.html.j2 | 2 +- .../users/{profile.html.j2 => user.html.j2} | 57 +++++++----- app/users/routes.py | 20 +---- 8 files changed, 127 insertions(+), 89 deletions(-) rename app/templates/users/{profile.html.j2 => user.html.j2} (71%) diff --git a/app/admin/routes.py b/app/admin/routes.py index b2d0643b..c1014250 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -14,7 +14,7 @@ from app.users.utils import ( @bp.route('') @register_breadcrumb(bp, '.', '<i class="material-icons left">admin_panel_settings</i>Administration') -def index(): +def admin(): return render_template( 'admin/admin.html.j2', title='Administration' diff --git a/app/models.py b/app/models.py index 1ed18ada..a527c1b2 100644 --- a/app/models.py +++ b/app/models.py @@ -100,6 +100,15 @@ class ProfilePrivacySettings(IntEnum): SHOW_LAST_SEEN = 2 SHOW_MEMBER_SINCE = 4 + @staticmethod + def get(profile_privacy_setting: Union['ProfilePrivacySettings', int, str]) -> 'ProfilePrivacySettings': + if isinstance(profile_privacy_setting, ProfilePrivacySettings): + return profile_privacy_setting + if isinstance(profile_privacy_setting, int): + return ProfilePrivacySettings(profile_privacy_setting) + if isinstance(profile_privacy_setting, str): + return ProfilePrivacySettings[profile_privacy_setting] + raise TypeError('profile_privacy_setting must be ProfilePrivacySettings, int, or str') class CorpusFollowerPermission(IntEnum): VIEW = 1 @@ -227,18 +236,18 @@ class Role(HashidMixin, db.Model): return f'<Role {self.name}>' def has_permission(self, permission: Union[Permission, int, str]): - perm = Permission.get(permission) - return self.permissions & perm.value == perm.value + p = Permission.get(permission) + return self.permissions & p.value == p.value def add_permission(self, permission: Union[Permission, int, str]): - perm = Permission.get(permission) - if not self.has_permission(perm): - self.permissions += perm.value + p = Permission.get(permission) + if not self.has_permission(p): + self.permissions += p.value def remove_permission(self, permission: Union[Permission, int, str]): - perm = Permission.get(permission) - if self.has_permission(perm): - self.permissions -= perm.value + p = Permission.get(permission) + if self.has_permission(p): + self.permissions -= p.value def reset_permissions(self): self.permissions = 0 @@ -763,15 +772,18 @@ class User(HashidMixin, UserMixin, db.Model): #region Profile Privacy settings def has_profile_privacy_setting(self, setting): - return self.profile_privacy_settings & setting == setting + s = ProfilePrivacySettings.get(setting) + return self.profile_privacy_settings & s.value == s.value def add_profile_privacy_setting(self, setting): - if not self.has_profile_privacy_setting(setting): - self.profile_privacy_settings += setting + s = ProfilePrivacySettings.get(setting) + if not self.has_profile_privacy_setting(s): + self.profile_privacy_settings += s.value def remove_profile_privacy_setting(self, setting): - if self.has_profile_privacy_setting(setting): - self.profile_privacy_settings -= setting + s = ProfilePrivacySettings.get(setting) + if self.has_profile_privacy_setting(s): + self.profile_privacy_settings -= s.value def reset_profile_privacy_settings(self): self.profile_privacy_settings = 0 @@ -838,7 +850,7 @@ class User(HashidMixin, UserMixin, db.Model): json_serializeable = { 'id': self.hashid, 'confirmed': self.confirmed, - 'avatar': url_for('users.profile_avatar', user_id=self.id), + 'avatar': url_for('users.user_avatar', user_id=self.id), 'email': self.email, 'last_seen': ( None if self.last_seen is None diff --git a/app/templates/_sidenav.html.j2 b/app/templates/_sidenav.html.j2 index 5f6f6482..3a01ec4a 100644 --- a/app/templates/_sidenav.html.j2 +++ b/app/templates/_sidenav.html.j2 @@ -4,13 +4,9 @@ <div class="background primary-color"></div> <div class="row"> <div class="col s4"> - <a href="{{ url_for('users.user', user_id=current_user.id) }}"> - {% if current_user.avatar %} - <img src="{{ url_for('users.profile_avatar', user_id=current_user.id) }}" alt="user-image" class="circle responsive-img" style="height:80%; margin-top: 13px; margin-left:-15px;"> - {% else %} - <img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img" style="height:80%; margin-top: 13px; margin-left:-15px;"> - {% endif %} - </a> + <a href="{{ url_for('users.user', user_id=current_user.id) }}"> + <img src="{{ url_for('users.user_avatar', user_id=current_user.id) }}" alt="user-image" class="circle responsive-img" style="height:80%; margin-top: 13px; margin-left:-15px;"> + </a> </div> <div class="col s8"> <span class="white-text name">{{ current_user.username }}</span> @@ -25,39 +21,72 @@ </div> </li> #} {# <li><a href="{{ url_for('main.index') }}">nopaque</a></li> #} - <li class="hide-on-large-only"><a href="{{ url_for('main.news') }}"><i class="material-icons left">email</i>News</a></li> + <li class="hide-on-large-only"> + <a class="waves-effect" href="{{ url_for('main.news') }}"><i class="material-icons left">email</i>News</a> + </li> {# <li><a href="{{ url_for('main.user_manual') }}"><i class="material-icons">help</i>Manual</a></li> #} - <li><a href="{{ url_for('main.dashboard') }}" class="subheader">Dashboard</a></li> - <li><a href="{{ url_for('main.dashboard', _anchor='corpora') }}"><i class="nopaque-icons">I</i>My Corpora</a></li> - <li><a href="{{ url_for('main.dashboard', _anchor='jobs') }}"><i class="nopaque-icons">J</i>My Jobs</a></li> - <li><a href="{{ url_for('main.dashboard', _anchor='contributions') }}"><i class="material-icons">new_label</i>My Contributions</a></li> + <li> + <a class="waves-effect" class="waves-effect" href="{{ url_for('main.dashboard') }}"><i class="material-icons">dashboard</i>Dashboard</a> + <ul> + <li> + <a class="waves-effect" href="{{ url_for('main.dashboard', _anchor='corpora') }}" style="padding-left: 47px;"><i class="nopaque-icons">I</i>My Corpora</a> + </li> + <li> + <a class="waves-effect" href="{{ url_for('main.dashboard', _anchor='jobs') }}" style="padding-left: 47px;"><i class="nopaque-icons">J</i>My Jobs</a> + </li> + <li> + <a class="waves-effect" href="{{ url_for('main.dashboard', _anchor='contributions') }}" style="padding-left: 47px;"><i class="material-icons">new_label</i>My Contributions</a> + </li> + </ul> + </li> <li><div class="divider"></div></li> <li><a class="subheader">Processes & Services</a></li> - <li class="service-color service-color-border border-darken" data-service="file-setup-pipeline" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.file_setup_pipeline') }}"><i class="nopaque-icons service-icons" data-service="file-setup-pipeline"></i>File setup</a></li> - <li class="service-color service-color-border border-darken" data-service="tesseract-ocr-pipeline" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.tesseract_ocr_pipeline') }}"><i class="nopaque-icons service-icons" data-service="tesseract-ocr-pipeline"></i>OCR</a></li> + <li class="service-color service-color-border border-darken" data-service="file-setup-pipeline" style="border-left: 10px solid; margin-top: 5px;"> + <a class="waves-effect" href="{{ url_for('services.file_setup_pipeline') }}"><i class="nopaque-icons service-icons" data-service="file-setup-pipeline"></i>File setup</a> + </li> + <li class="service-color service-color-border border-darken" data-service="tesseract-ocr-pipeline" style="border-left: 10px solid; margin-top: 5px;"> + <a class="waves-effect" href="{{ url_for('services.tesseract_ocr_pipeline') }}"><i class="nopaque-icons service-icons" data-service="tesseract-ocr-pipeline"></i>OCR</a> + </li> {% if config.NOPAQUE_TRANSKRIBUS_ENABLED %} - <li class="service-color service-color-border border-darken" data-service="transkribus-htr-pipeline" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.transkribus_htr_pipeline') }}"><i class="nopaque-icons service-icons" data-service="transkribus-htr-pipeline"></i>HTR</a></li> + <li class="service-color service-color-border border-darken" data-service="transkribus-htr-pipeline" style="border-left: 10px solid; margin-top: 5px;"> + <a class="waves-effect" href="{{ url_for('services.transkribus_htr_pipeline') }}"><i class="nopaque-icons service-icons" data-service="transkribus-htr-pipeline"></i>HTR</a> + </li> {% endif %} - <li class="service-color service-color-border border-darken" data-service="spacy-nlp-pipeline" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.spacy_nlp_pipeline') }}"><i class="nopaque-icons service-icons" data-service="spacy-nlp-pipeline"></i>NLP</a></li> - <li class="service-color service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.corpus_analysis') }}"><i class="nopaque-icons service-icons" data-service="corpus-analysis"></i>Corpus Analysis</a></li> + <li class="service-color service-color-border border-darken" data-service="spacy-nlp-pipeline" style="border-left: 10px solid; margin-top: 5px;"> + <a class="waves-effect" href="{{ url_for('services.spacy_nlp_pipeline') }}"><i class="nopaque-icons service-icons" data-service="spacy-nlp-pipeline"></i>NLP</a> + </li> + <li class="service-color service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;"> + <a class="waves-effect" href="{{ url_for('services.corpus_analysis') }}"><i class="nopaque-icons service-icons" data-service="corpus-analysis"></i>Corpus Analysis</a> + </li> <li><div class="divider"></div></li> - <li><a class="subheader">Social Area</a></li> - <li><a href="{{ url_for('main.social_area', _anchor='public-users') }}"><i class="material-icons">person</i>Public Users</a></li> - <li><a href="{{ url_for('main.social_area', _anchor='public-corproa') }}"><i class="nopaque-icons">I</i>Public Corpora</a></li> + <li> + <a class="waves-effect" class="waves-effect" href="{{ url_for('main.social_area') }}"><i class="material-icons">rocket_launch</i>Social Area</a> + <ul> + <li> + <a class="waves-effect" href="{{ url_for('main.social_area', _anchor='public-users') }}" style="padding-left: 47px;"><i class="material-icons">person</i>Public Users</a> + </li> + <li> + <a class="waves-effect" href="{{ url_for('main.social_area', _anchor='public-corproa') }}" style="padding-left: 47px;"><i class="nopaque-icons">I</i>Public Corpora</a> + </li> + </ul> + </li> <li class="hide-on-large-only"><div class="divider"></div></li> <li class="hide-on-large-only"><a class="subheader">Account</a></li> - <li class="hide-on-large-only"><a href="{{ url_for('settings.settings') }}"><i class="material-icons left">settings</i>Settings</a></li> - <li class="hide-on-large-only"><a href="{{ url_for('auth.logout') }}">Log out</a></li> + <li class="hide-on-large-only"> + <a class="waves-effect" href="{{ url_for('settings.settings') }}"><i class="material-icons left">settings</i>Settings</a> + </li> + <li class="hide-on-large-only"> + <a class="waves-effect" href="{{ url_for('auth.logout') }}">Log out</a> + </li> {% if current_user.can('ADMINISTRATE') %} <li><div class="divider"></div></li> - <li><a class="subheader">Administration</a></li> - <li><a href="{{ url_for('admin.corpora') }}"><i class="nopaque-icons">I</i>Corpora</a></li> - <li><a href="{{ url_for('admin.corpora') }}"><i class="nopaque-icons">J</i>Jobs</a></li> - <li><a href="{{ url_for('admin.users') }}"><i class="material-icons">group</i>Users</a></li> + <li> + <a class="waves-effect" href="{{ url_for('admin.admin') }}"><i class="material-icons">admin_panel_settings</i>Administration</a> + </li> {% endif %} {% if current_user.can('USE_API') %} - <li><div class="divider"></div></li> - <li><a class="subheader">API</a></li> - <li><a href="{{ url_for('apifairy.docs') }}"><i class="material-icons">api</i>API</a></li> + <li> + <a class="waves-effect" href="{{ url_for('apifairy.docs') }}"><i class="material-icons">api</i>API</a> + </li> {% endif %} </ul> diff --git a/app/templates/admin/user.html.j2 b/app/templates/admin/user.html.j2 index 30f7c084..f8943430 100644 --- a/app/templates/admin/user.html.j2 +++ b/app/templates/admin/user.html.j2 @@ -5,7 +5,7 @@ <div class="row"> <div class="col s12 l2"> <p> </p> - <img src="{{ url_for('users.profile_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img"> + <img src="{{ url_for('users.user_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img"> </div> <div class="col s12 l10"> <h1 id="title">{{ title }}</h1> diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/public_corpus.html.j2 index ff445e38..77e3ea4b 100644 --- a/app/templates/corpora/public_corpus.html.j2 +++ b/app/templates/corpora/public_corpus.html.j2 @@ -48,11 +48,7 @@ <table> <tr> <td style="width:10%; margin-top:25px;"> - {% if corpus.user.avatar %} - <img src="{{ url_for('users.profile_avatar', user_id=corpus.user.id) }}" alt="user-image" class="circle responsive-img"> - {% else %} - <img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img"> - {% endif %} + <img src="{{ url_for('users.user_avatar', user_id=corpus.user.id) }}" alt="user-image" class="circle responsive-img"> </td> <td></td> <td> diff --git a/app/templates/settings/settings.html.j2 b/app/templates/settings/settings.html.j2 index fe60f0d2..282d4185 100644 --- a/app/templates/settings/settings.html.j2 +++ b/app/templates/settings/settings.html.j2 @@ -114,7 +114,7 @@ <p></p> <div class="row"> <div class="col s12 m2"> - <img src="{{ url_for('users.profile_avatar', user_id=current_user.id) }}" alt="user-image" class="circle responsive-img" id="avatar"> + <img src="{{ url_for('users.user_avatar', user_id=current_user.id) }}" alt="user-image" class="circle responsive-img" id="avatar"> </div> <div class="col s12 m6"> {{ wtf.render_field(edit_public_profile_information_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload') }} diff --git a/app/templates/users/profile.html.j2 b/app/templates/users/user.html.j2 similarity index 71% rename from app/templates/users/profile.html.j2 rename to app/templates/users/user.html.j2 index 6e02a42c..cc138794 100644 --- a/app/templates/users/profile.html.j2 +++ b/app/templates/users/user.html.j2 @@ -1,6 +1,7 @@ {% extends "base.html.j2" %} {% import "materialize/wtf.html.j2" as wtf %} + {% block page_content %} <div class="container"> <div class="row"> @@ -10,9 +11,8 @@ <div class="row"> <div class="col s1"></div> <div class="col s3"> - <br> - <br> - <img src="{{ url_for('.profile_avatar', user_id=user_id) }}" alt="user-image" class="circle responsive-img"> + <p> </p> + <img src="{{ url_for('.user_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img"> </div> <div class="col s1"></div> <div class="col s7"> @@ -27,16 +27,18 @@ {% else %} <span class="chip white-text" style="background-color: #f44336;">Private Profile</span> {% endif %} + {% if user.has_profile_privacy_setting('SHOW_MEMBER_SINCE') %} + <span class="chip">Member since: {{ user.member_since.strftime('%Y-%m-%d') }}</span> + {% endif %} + {% if user.has_profile_privacy_setting('SHOW_LAST_SEEN') %} + <span class="chip">Last seen: {{ user.last_seen.strftime('%Y-%m-%d') }}</span> + {% endif %} </div> <div class="col 12"> - {% if user.show_last_seen %} - <div class="chip">Last seen: {{ last_seen }}</div> - {% endif %} {% if user.location %} - <p><span class="material-icons" style="margin-right:20px; margin-top:20px;">location_on</span><i>{{ user.location }}</i></p> + <p> </p> + <p><i class="material-icons left">location_on</i>{{ user.location }}</p> {% endif %} - <p></p> - <br> {% if user.about_me %} <blockquote> <h5>About me</h5> @@ -59,7 +61,7 @@ <td>{{ user.full_name }} </td> </tr> {% endif %} - {% if user.show_email %} + {% if user.has_profile_privacy_setting('SHOW_EMAIL') %} <tr> <td><span class="material-icons">email</span></td> <td>{{ user.email }}</td> @@ -78,18 +80,17 @@ </tr> {% endif %} </table> - <br> - {% if user.show_member_since %} - <p><i>Member since: {{ member_since }}</i></p> - {% endif %} - <p></p> - <br> - {% if current_user.hashid == user.id %} - <a class="waves-effect waves-light btn-small" href="{{ url_for('settings.settings') }}">Edit profile</a> - {% endif %} + <p> </p> </div> </div> </div> + {% if current_user == user %} + <div class="card-action"> + <p class="right-align"> + <a class="btn waves-effect waves-light" href="{{ url_for('settings.settings') }}">Edit profile</a> + </p> + </div> + {% endif %} </div> </div> </div> @@ -119,8 +120,22 @@ {{ super() }} <script> let followedCorpusList = new FollowedCorpusList(document.querySelector('.followed-corpus-list')); -followedCorpusList.add({{ followed_corpora|tojson }}); +followedCorpusList.add( + [ + {% for corpus in user.followed_corpora %} + {{ corpus.to_json_serializeable()|tojson }} + {% if not loop.last %},{% endif %} + {% endfor %} + ] +); let publicCorpusList = new PublicCorpusList(document.querySelector('.public-corpus-list')); -publicCorpusList.add({{ own_public_corpora|tojson }}); +publicCorpusList.add( + [ + {% for corpus in user.corpora if corpus.is_public %} + {{ corpus.to_json_serializeable()|tojson }} + {% if not loop.last %},{% endif %} + {% endfor %} + ] +); </script> {% endblock scripts %} diff --git a/app/users/routes.py b/app/users/routes.py index 5d51e81f..2c2334f3 100644 --- a/app/users/routes.py +++ b/app/users/routes.py @@ -25,32 +25,18 @@ def users(): @login_required def user(user_id): user = User.query.get_or_404(user_id) - last_seen = user.last_seen.strftime('%Y-%m-%d %H:%M') - member_since = user.member_since.strftime('%Y-%m-%d') - followed_corpora = [ - c.to_json_serializeable() for c in user.followed_corpora - ] - own_public_corpora = [ - c.to_json_serializeable() for c - in Corpus.query.filter_by(is_public = True, user = user).all() - ] - if not user.is_public and user != current_user: + if not (user.is_public or user == current_user or current_user.is_administrator()): abort(403) return render_template( - 'users/profile.html.j2', - followed_corpora=followed_corpora, - last_seen=last_seen, - member_since=member_since, - own_public_corpora=own_public_corpora, + 'users/user.html.j2', user=user, - user_id=user_id, title=user.username ) @bp.route('/<hashid:user_id>/avatar') @login_required -def profile_avatar(user_id): +def user_avatar(user_id): user = User.query.get_or_404(user_id) if not (user.is_public or user == current_user or current_user.is_administrator()): abort(403) -- GitLab