From 61a6ddd4be227df5a7cbc3de560b9138b205222e Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Tue, 13 Dec 2022 15:01:04 +0100 Subject: [PATCH] Privacy settings for profile pages --- app/__init__.py | 3 - app/api/schemas.py | 2 +- app/models.py | 40 +- app/profile/forms.py | 45 +- app/profile/routes.py | 83 +++- app/services/forms.py | 8 +- app/services/routes.py | 2 +- app/settings/forms.py | 12 - app/settings/routes.py | 14 +- .../SpacyNLPPipelineModelList.js | 14 +- .../TesseractOCRPipelineModelList.js | 14 +- app/static/js/Utils.js | 8 +- app/templates/_sidenav.html.j2 | 3 +- app/templates/profile/edit_profile.html.j2 | 188 +++++++- app/templates/profile/profile_page.html.j2 | 185 ++++--- .../services/tesseract_ocr_pipeline.html.j2 | 2 +- app/templates/settings/settings.html.j2 | 18 - .../test/analyse_corpus.concordance.html.j2 | 117 ----- app/templates/test/analyse_corpus.html.j2 | 454 ------------------ app/test/__init__.py | 5 - app/test/routes.py | 10 - migrations/versions/5b2a6e590166_.py | 35 ++ 22 files changed, 476 insertions(+), 786 deletions(-) delete mode 100644 app/templates/test/analyse_corpus.concordance.html.j2 delete mode 100644 app/templates/test/analyse_corpus.html.j2 delete mode 100644 app/test/__init__.py delete mode 100644 app/test/routes.py create mode 100644 migrations/versions/5b2a6e590166_.py diff --git a/app/__init__.py b/app/__init__.py index 960d4e90..77742800 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -90,7 +90,4 @@ def create_app(config: Config = Config) -> Flask: from .users import bp as users_blueprint app.register_blueprint(users_blueprint, url_prefix='/users') - from .test import bp as test_blueprint - app.register_blueprint(test_blueprint, url_prefix='/test') - return app diff --git a/app/api/schemas.py b/app/api/schemas.py index 7abb56de..f0792f7c 100644 --- a/app/api/schemas.py +++ b/app/api/schemas.py @@ -66,7 +66,7 @@ class TesseractOCRPipelineModelSchema(ma.SQLAlchemySchema): publishing_year = ma.Int( required=True ) - shared = ma.Boolean(required=True) + is_public = ma.Boolean(required=True) class JobSchema(ma.SQLAlchemySchema): diff --git a/app/models.py b/app/models.py index 7927c81f..d28331a0 100644 --- a/app/models.py +++ b/app/models.py @@ -61,6 +61,12 @@ class UserSettingJobStatusMailNotificationLevel(IntEnum): NONE = 1 END = 2 ALL = 3 + + +class ProfilePrivacySettings(IntEnum): + SHOW_EMAIL = 1 + SHOW_LAST_SEEN = 2 + SHOW_MEMBER_SINCE = 4 # endregion enums @@ -220,7 +226,6 @@ class Role(HashidMixin, db.Model): db.session.add(role) db.session.commit() - class Token(db.Model): __tablename__ = 'tokens' # Primary key @@ -255,12 +260,20 @@ class Avatar(HashidMixin, FileMixin, db.Model): def path(self): return os.path.join(self.user.path, 'avatar') + def delete(self): + try: + os.remove(self.path) + except OSError as e: + current_app.logger.error(e) + db.session.delete(self) + class User(HashidMixin, UserMixin, db.Model): __tablename__ = 'users' # Primary key id = db.Column(db.Integer, primary_key=True) # Foreign keys role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) + # privacy_id = db.Column(db.Integer, db.ForeignKey('privacies.id')) # Fields email = db.Column(db.String(254), index=True, unique=True) username = db.Column(db.String(64), index=True, unique=True) @@ -277,6 +290,8 @@ class User(HashidMixin, UserMixin, db.Model): location = db.Column(db.String(64)) website = db.Column(db.String(128)) organization = db.Column(db.String(128)) + is_public = db.Column(db.Boolean, default=False) + profile_privacy_settings = db.Column(db.Integer(), default=0) # Backrefs: role: Role # Relationships avatar = db.relationship( @@ -502,6 +517,22 @@ class User(HashidMixin, UserMixin, db.Model): return False return check_password_hash(self.password_hash, password) + #region Profile Privacy settings + def has_profile_privacy_setting(self, setting): + return self.profile_privacy_settings & setting == setting + + def add_profile_privacy_setting(self, setting): + if not self.has_profile_privacy_setting(setting): + self.profile_privacy_settings += setting + + def remove_profile_privacy_setting(self, setting): + if self.has_profile_privacy_setting(setting): + self.profile_privacy_settings -= setting + + def reset_profile_privacy_settings(self): + self.profile_privacy_settings = 0 + #endregion Profile Privacy settings + def to_json_serializeable(self, backrefs=False, relationships=False): json_serializeable = { 'id': self.hashid, @@ -519,7 +550,12 @@ class User(HashidMixin, UserMixin, db.Model): 'location': self.location, 'organization': self.organization, 'job_status_mail_notification_level': \ - self.setting_job_status_mail_notification_level.name + self.setting_job_status_mail_notification_level.name, + 'is_public': self.is_public, + 'show_email': self.has_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL), + 'show_last_seen': self.has_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN), + 'show_member_since': self.has_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE) + } if backrefs: json_serializeable['role'] = \ diff --git a/app/profile/forms.py b/app/profile/forms.py index 20bd4ad4..50594519 100644 --- a/app/profile/forms.py +++ b/app/profile/forms.py @@ -1,5 +1,6 @@ from flask_wtf import FlaskForm from wtforms import ( + BooleanField, FileField, StringField, SubmitField, @@ -16,9 +17,6 @@ from app.models import User from app.auth import USERNAME_REGEX class EditProfileSettingsForm(FlaskForm): - avatar = FileField( - 'Image File' - ) email = StringField( 'E-Mail', validators=[InputRequired(), Length(max=254), Email()] @@ -37,6 +35,26 @@ class EditProfileSettingsForm(FlaskForm): ) ] ) + submit = SubmitField() + + def __init__(self, user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + + def validate_email(self, field): + if (field.data != self.user.email + and User.query.filter_by(email=field.data).first()): + raise ValidationError('Email already registered') + + def validate_username(self, field): + if (field.data != self.user.username + and User.query.filter_by(username=field.data).first()): + raise ValidationError('Username already in use') + +class EditPublicProfileInformationForm(FlaskForm): + avatar = FileField( + 'Image File' + ) full_name = StringField( 'Full name', validators=[Length(max=128)] @@ -68,20 +86,13 @@ class EditProfileSettingsForm(FlaskForm): submit = SubmitField() - def __init__(self, user, *args, **kwargs): - super().__init__(*args, **kwargs) - self.user = user - - def validate_email(self, field): - if (field.data != self.user.email - and User.query.filter_by(email=field.data).first()): - raise ValidationError('Email already registered') - - def validate_username(self, field): - if (field.data != self.user.username - and User.query.filter_by(username=field.data).first()): - raise ValidationError('Username already in use') - def validate_image_file(self, field): if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'): raise ValidationError('only .jpg, .png and .jpeg!') + +class EditPrivacySettingsForm(FlaskForm): + is_public = BooleanField('Public profile') + show_email = BooleanField('Show email') + show_last_seen = BooleanField('Show last seen') + show_member_since = BooleanField('Show member since') + submit = SubmitField() diff --git a/app/profile/routes.py b/app/profile/routes.py index 4702dcb7..b4711aa5 100644 --- a/app/profile/routes.py +++ b/app/profile/routes.py @@ -1,5 +1,6 @@ from flask import ( abort, + current_app, flash, Markup, redirect, @@ -8,12 +9,15 @@ from flask import ( url_for ) from flask_login import current_user, login_required +from threading import Thread import os from app import db -from app.models import Avatar, User +from app.models import Avatar, ProfilePrivacySettings, User from . import bp from .forms import ( - EditProfileSettingsForm + EditPrivacySettingsForm, + EditProfileSettingsForm, + EditPublicProfileInformationForm ) @bp.before_request @@ -25,8 +29,12 @@ def before_request(): @bp.route('/') def profile(user_id): user = User.query.get_or_404(user_id) + user_data = user.to_json_serializeable() + if not user.is_public and user != current_user: + abort(403) return render_template('profile/profile_page.html.j2', - user=user) + user=user, + user_data=user_data) @bp.route('//avatars/') def avatar_download(user_id, avatar_id): @@ -41,6 +49,21 @@ def avatar_download(user_id, avatar_id): mimetype=avatar_file.mimetype ) +@bp.route('//avatars/', methods=['DELETE']) +def delete_avatar(avatar_id, user_id): + def _delete_avatar(app, avatar_id): + with app.app_context(): + avatar_file = Avatar.query.get(avatar_id) + print(avatar_file) + avatar_file.delete() + db.session.commit() + thread = Thread( + target=_delete_avatar, + args=(current_app._get_current_object(), avatar_id) + ) + thread.start() + return {}, 202 + @bp.route('//edit-profile', methods=['GET', 'POST']) def edit_profile(user_id): user = User.query.get_or_404(user_id) @@ -49,24 +72,58 @@ def edit_profile(user_id): data=current_user.to_json_serializeable(), prefix='edit-profile-settings-form' ) + edit_privacy_settings_form = EditPrivacySettingsForm( + data=current_user.to_json_serializeable(), + prefix='edit-privacy-settings-form' + ) + edit_public_profile_information_form = EditPublicProfileInformationForm( + data=current_user.to_json_serializeable(), + prefix='edit-public-profile-information-form' + ) + if edit_profile_settings_form.validate_on_submit(): - if edit_profile_settings_form.avatar.data: - try: - Avatar.create(edit_profile_settings_form.avatar.data, user=current_user) - except (AttributeError, OSError): - abort(500) current_user.email = edit_profile_settings_form.email.data current_user.username = edit_profile_settings_form.username.data - current_user.about_me = edit_profile_settings_form.about_me.data - current_user.location = edit_profile_settings_form.location.data - current_user.organization = edit_profile_settings_form.organization.data - current_user.website = edit_profile_settings_form.website.data - current_user.full_name = edit_profile_settings_form.full_name.data + db.session.commit() + message = Markup(f'Profile settings updated') + flash(message, 'success') + return redirect(url_for('.profile', user_id=user.id)) + if (edit_privacy_settings_form.submit.data + and edit_privacy_settings_form.validate()): + current_user.is_public = edit_privacy_settings_form.is_public.data + if edit_privacy_settings_form.show_email.data: + current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL) + else: + current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL) + if edit_privacy_settings_form.show_last_seen.data: + current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN) + else: + current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN) + if edit_privacy_settings_form.show_member_since.data: + current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE) + else: + current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE) + db.session.commit() + flash('Your changes have been saved') + return redirect(url_for('.profile', user_id=user.id)) + if edit_public_profile_information_form.validate_on_submit(): + if edit_public_profile_information_form.avatar.data: + try: + Avatar.create(edit_public_profile_information_form.avatar.data, user=current_user) + except (AttributeError, OSError): + abort(500) + current_user.about_me = edit_public_profile_information_form.about_me.data + current_user.location = edit_public_profile_information_form.location.data + current_user.organization = edit_public_profile_information_form.organization.data + current_user.website = edit_public_profile_information_form.website.data + current_user.full_name = edit_public_profile_information_form.full_name.data db.session.commit() message = Markup(f'Profile settings updated') flash(message, 'success') return redirect(url_for('.profile', user_id=user.id)) return render_template('profile/edit_profile.html.j2', edit_profile_settings_form=edit_profile_settings_form, + edit_privacy_settings_form=edit_privacy_settings_form, + edit_public_profile_information_form=edit_public_profile_information_form, user=user, title='Edit Profile') diff --git a/app/services/forms.py b/app/services/forms.py index 562696eb..1ad544dc 100644 --- a/app/services/forms.py +++ b/app/services/forms.py @@ -73,11 +73,11 @@ class CreateTesseractOCRPipelineJobForm(CreateJobBaseForm): if 'methods' in service_info: if 'binarization' in service_info['methods']: del self.binarization.render_kw['disabled'] - if 'ocropus_nlbin_threshold' in service_info['methods']: - del self.ocropus_nlbin_threshold.render_kw['disabled'] + if 'ocropus_nlbin_threshold' in service_info['methods']: + del self.ocropus_nlbin_threshold.render_kw['disabled'] models = [ x for x in TesseractOCRPipelineModel.query.order_by(TesseractOCRPipelineModel.title).all() - if version in x.compatible_service_versions and (x.shared == True or x.user == current_user) + if version in x.compatible_service_versions and (x.is_public == True or x.user == current_user) ] self.model.choices = [('', 'Choose your option')] self.model.choices += [(x.hashid, f'{x.title} [{x.version}]') for x in models] @@ -157,7 +157,7 @@ class CreateSpacyNLPPipelineJobForm(CreateJobBaseForm): del self.encoding_detection.render_kw['disabled'] models = [ x for x in SpaCyNLPPipelineModel.query.order_by(SpaCyNLPPipelineModel.title).all() - if version in x.compatible_service_versions and (x.shared == True or x.user == current_user) + if version in x.compatible_service_versions and (x.is_public == True or x.user == current_user) ] self.model.choices = [('', 'Choose your option')] self.model.choices += [(x.hashid, f'{x.title} [{x.version}]') for x in models] diff --git a/app/services/routes.py b/app/services/routes.py index 7748240c..24688c88 100644 --- a/app/services/routes.py +++ b/app/services/routes.py @@ -98,7 +98,7 @@ def tesseract_ocr_pipeline(): return {}, 201, {'Location': job.url} tesseract_ocr_pipeline_models = [ x for x in TesseractOCRPipelineModel.query.all() - if version in x.compatible_service_versions and (x.shared == True or x.user == current_user) + if version in x.compatible_service_versions and (x.is_public == True or x.user == current_user) ] return render_template( 'services/tesseract_ocr_pipeline.html.j2', diff --git a/app/settings/forms.py b/app/settings/forms.py index 335f73d3..55279927 100644 --- a/app/settings/forms.py +++ b/app/settings/forms.py @@ -62,15 +62,3 @@ class EditNotificationSettingsForm(FlaskForm): for x in UserSettingJobStatusMailNotificationLevel ] -class EditPrivacySettingsForm(FlaskForm): - private_profile = BooleanField( - 'Private profile' - ) - private_email = BooleanField( - 'Private email' - ) - only_username = BooleanField( - 'Show only username' - ) - - submit = SubmitField() diff --git a/app/settings/routes.py b/app/settings/routes.py index d7e36218..754a98cd 100644 --- a/app/settings/routes.py +++ b/app/settings/routes.py @@ -1,12 +1,11 @@ -from flask import flash, redirect, render_template, url_for +from flask import abort, flash, redirect, render_template, url_for from flask_login import current_user, login_required from app import db -from app.models import UserSettingJobStatusMailNotificationLevel +from app.models import ProfilePrivacySettings, UserSettingJobStatusMailNotificationLevel from . import bp from .forms import ( ChangePasswordForm, - EditNotificationSettingsForm, - EditPrivacySettingsForm + EditNotificationSettingsForm ) @@ -21,17 +20,13 @@ def settings(): data=current_user.to_json_serializeable(), prefix='edit-notification-settings-form' ) - edit_privacy_settings_form = EditPrivacySettingsForm( - data=current_user.to_json_serializeable(), - prefix='edit-privacy-settings-form' - ) if change_password_form.submit.data and change_password_form.validate(): current_user.password = change_password_form.new_password.data db.session.commit() flash('Your changes have been saved') return redirect(url_for('.index')) - if (edit_notification_settings_form.submit.data + if (edit_notification_settings_form.submit and edit_notification_settings_form.validate()): current_user.setting_job_status_mail_notification_level = ( UserSettingJobStatusMailNotificationLevel[ @@ -45,6 +40,5 @@ def settings(): 'settings/settings.html.j2', change_password_form=change_password_form, edit_notification_settings_form=edit_notification_settings_form, - edit_privacy_settings_form=edit_privacy_settings_form, title='Settings' ) diff --git a/app/static/js/RessourceLists/SpacyNLPPipelineModelList.js b/app/static/js/RessourceLists/SpacyNLPPipelineModelList.js index 78693c91..d03c79eb 100644 --- a/app/static/js/RessourceLists/SpacyNLPPipelineModelList.js +++ b/app/static/js/RessourceLists/SpacyNLPPipelineModelList.js @@ -34,9 +34,9 @@ class SpaCyNLPPipelineModelList extends RessourceList {
@@ -59,7 +59,7 @@ class SpaCyNLPPipelineModelList extends RessourceList { 'title': spaCyNLPPipelineModel.title, 'title-2': spaCyNLPPipelineModel.title, 'version': spaCyNLPPipelineModel.version, - 'shared': spaCyNLPPipelineModel.shared ? 'True' : 'False' + 'is_public': spaCyNLPPipelineModel.is_public ? 'True' : 'False' }; }, sortArgs: ['creation-date', {order: 'desc'}], @@ -75,7 +75,7 @@ class SpaCyNLPPipelineModelList extends RessourceList { 'title', 'title-2', 'version', - {name: 'shared', attr: 'data-checked'} + {name: 'is_public', attr: 'data-checked'} ] }; @@ -87,7 +87,7 @@ class SpaCyNLPPipelineModelList extends RessourceList { init(user) { this._init(user.spacy_nlp_pipeline_models); if (user.role.name !== ('Administrator' || 'Contributor')) { - for (let switchElement of this.listjs.list.querySelectorAll('.shared')) { + for (let switchElement of this.listjs.list.querySelectorAll('.is_public')) { switchElement.setAttribute('disabled', ''); } } @@ -134,8 +134,8 @@ class SpaCyNLPPipelineModelList extends RessourceList { let spaCyNLPPipelineModelId = spaCyNLPPipelineModelElement.dataset.id; switch (action) { case 'share-request': { - let shared = actionSwitchElement.querySelector('input').checked; - Utils.shareSpaCyNLPPipelineModelRequest(this.userId, spaCyNLPPipelineModelId, shared); + let is_public = actionSwitchElement.querySelector('input').checked; + Utils.shareSpaCyNLPPipelineModelRequest(this.userId, spaCyNLPPipelineModelId, is_public); break; } default: { diff --git a/app/static/js/RessourceLists/TesseractOCRPipelineModelList.js b/app/static/js/RessourceLists/TesseractOCRPipelineModelList.js index 22c4bc37..d299af1b 100644 --- a/app/static/js/RessourceLists/TesseractOCRPipelineModelList.js +++ b/app/static/js/RessourceLists/TesseractOCRPipelineModelList.js @@ -34,9 +34,9 @@ class TesseractOCRPipelineModelList extends RessourceList {
@@ -59,7 +59,7 @@ class TesseractOCRPipelineModelList extends RessourceList { 'title': tesseractOCRPipelineModel.title, 'title-2': tesseractOCRPipelineModel.title, 'version': tesseractOCRPipelineModel.version, - 'shared': tesseractOCRPipelineModel.shared ? 'True' : 'False' + 'is_public': tesseractOCRPipelineModel.is_public ? 'True' : 'False' }; }, sortArgs: ['creation-date', {order: 'desc'}], @@ -75,7 +75,7 @@ class TesseractOCRPipelineModelList extends RessourceList { 'title', 'title-2', 'version', - {name: 'shared', attr: 'data-checked'} + {name: 'is_public', attr: 'data-checked'} ] }; @@ -87,7 +87,7 @@ class TesseractOCRPipelineModelList extends RessourceList { init (user) { this._init(user.tesseract_ocr_pipeline_models); if (user.role.name !== ('Administrator' || 'Contributor')) { - for (let switchElement of this.listjs.list.querySelectorAll('.shared')) { + for (let switchElement of this.listjs.list.querySelectorAll('.is_public')) { switchElement.setAttribute('disabled', ''); } } @@ -134,8 +134,8 @@ class TesseractOCRPipelineModelList extends RessourceList { let tesseractOCRPipelineModelId = tesseractOCRPipelineModelElement.dataset.id; switch (action) { case 'share-request': { - let shared = actionSwitchElement.querySelector('input').checked; - Utils.shareTesseractOCRPipelineModelRequest(this.userId, tesseractOCRPipelineModelId, shared); + let is_public = actionSwitchElement.querySelector('input').checked; + Utils.shareTesseractOCRPipelineModelRequest(this.userId, tesseractOCRPipelineModelId, is_public); break; } default: { diff --git a/app/static/js/Utils.js b/app/static/js/Utils.js index ea8b1efc..899147b4 100644 --- a/app/static/js/Utils.js +++ b/app/static/js/Utils.js @@ -429,11 +429,11 @@ class Utils { }); } - static shareTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId, shared) { + static shareTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId, is_public) { return new Promise((resolve, reject) => { let tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId]; let msg = ''; - if (shared) { + if (is_public) { msg = `Model "${tesseractOCRPipelineModel.title}" is now public`; } else { msg = `Model "${tesseractOCRPipelineModel.title}" is now private`; @@ -453,11 +453,11 @@ class Utils { }); } - static shareSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId, shared) { + static shareSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId, is_public) { return new Promise((resolve, reject) => { let spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId]; let msg = ''; - if (shared) { + if (is_public) { msg = `Model "${spaCyNLPPipelineModel.title}" is now public`; } else { msg = `Model "${spaCyNLPPipelineModel.title}" is now private`; diff --git a/app/templates/_sidenav.html.j2 b/app/templates/_sidenav.html.j2 index 1aff7f89..ffdf86a6 100644 --- a/app/templates/_sidenav.html.j2 +++ b/app/templates/_sidenav.html.j2 @@ -33,7 +33,8 @@
  • Corpus analysis
  • Account
  • -
  • settingsSettings
  • +
  • settingsGeneral Settings
  • +
  • contact_pageProfile settings
  • Log out
  • {% if current_user.can(Permission.ADMINISTRATE) or current_user.can(Permission.USE_API) %}
  • diff --git a/app/templates/profile/edit_profile.html.j2 b/app/templates/profile/edit_profile.html.j2 index 1f4bd0e7..7b1dbef7 100644 --- a/app/templates/profile/edit_profile.html.j2 +++ b/app/templates/profile/edit_profile.html.j2 @@ -9,37 +9,15 @@
    +
    - {{ edit_profile_settings_form.hidden_tag() }}
    -
    -
    -
    -
    - {% if current_user.avatar %} - user-image - {% else %} - user-image - {% endif %} -
    -
    -
    -
    -
    - {{wtf.render_field(edit_profile_settings_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder="Choose an image file")}} -
    -
    -
    -
    +
    + {{ edit_profile_settings_form.hidden_tag() }} {{ wtf.render_field(edit_profile_settings_form.username, material_icon='person') }} {{ wtf.render_field(edit_profile_settings_form.email, material_icon='email') }} - {{ wtf.render_field(edit_profile_settings_form.full_name, material_icon='badge') }} - {{ wtf.render_field(edit_profile_settings_form.about_me, material_icon='description') }} - {{ wtf.render_field(edit_profile_settings_form.website, material_icon='laptop') }} - {{ wtf.render_field(edit_profile_settings_form.organization, material_icon='business') }} - {{ wtf.render_field(edit_profile_settings_form.location, material_icon='location_on') }}
    @@ -50,7 +28,167 @@
    + +
    + {{ edit_privacy_settings_form.hidden_tag() }} +
    +
    + Privacy settings +
    + {{ wtf.render_field(edit_privacy_settings_form.is_public, id='public-profile') }} +
    +
    +
    + {{ wtf.render_field(edit_privacy_settings_form.show_email, data_action='disable', disabled=true) }} +
    + {{ wtf.render_field(edit_privacy_settings_form.show_last_seen, data_action='disable', disabled=true) }} +
    + {{ wtf.render_field(edit_privacy_settings_form.show_member_since, data_action='disable', disabled=true) }} +
    +
    +
    +
    + {{ wtf.render_field(edit_privacy_settings_form.submit, material_icon='send') }} +
    +
    +
    +
    + +
    +
    +
    + {{ edit_public_profile_information_form.hidden_tag() }} +
    +
    + Public Profile +
    +
    +
    +
    +
    +
    +
    + {% if current_user.avatar %} + user-image + {% else %} + user-image + {% endif %} +
    +
    +
    +
    +
    +
    + delete +
    +
    +
    + {{wtf.render_field(edit_public_profile_information_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload', data_action='disable')}} +
    +
    +
    +
    +

    +
    + {{ wtf.render_field(edit_public_profile_information_form.full_name, material_icon='badge') }} + {{ wtf.render_field(edit_public_profile_information_form.about_me, material_icon='description') }} + {{ wtf.render_field(edit_public_profile_information_form.website, material_icon='laptop') }} + {{ wtf.render_field(edit_public_profile_information_form.organization, material_icon='business') }} + {{ wtf.render_field(edit_public_profile_information_form.location, material_icon='location_on') }} +
    +
    +
    +
    +
    + {{ wtf.render_field(edit_public_profile_information_form.submit, material_icon='send') }} +
    +
    +
    +
    +
    {% endblock page_content %} + +{% block modals %} + +{% endblock modals %} + +{% block scripts %} +{{ super() }} + +{% endblock scripts %} diff --git a/app/templates/profile/profile_page.html.j2 b/app/templates/profile/profile_page.html.j2 index dbcd78ca..be137392 100644 --- a/app/templates/profile/profile_page.html.j2 +++ b/app/templates/profile/profile_page.html.j2 @@ -2,96 +2,133 @@ {% import "materialize/wtf.html.j2" as wtf %} {% block page_content %} -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + {% if user.avatar %} + user-image + {% else %} + user-image + {% endif %} +
    +
    +
    -
    -
    - {% if user.avatar %} - user-image - {% else %} - user-image - {% endif %} +
    +

    {{ user.username }}

    -
    -

    {{ user.username }}

    +
    + {% if user_data['show_last_seen'] %}
    Last seen: {{ user.last_seen.strftime('%Y-%m-%d %H:%M') }}
    + {% endif %} {% if user.location %}

    location_on{{ user.location }}

    {% endif %}


    {% if user.about_me%} -
    -
    - About me -

    {{ user.about_me }}

    -
    +
    +
    About me
    +

    {{ user.about_me }}

    {% endif %}
    -
    -
    -
    - - {% if user.full_name %} - - - - - {% endif %} - {% if user.email %} - - - - - {% endif %} - {% if user.website %} - - - - - {% endif %} - {% if user.organization %} - - - - - {% endif %} -
    person{{ user.full_name }}
    email{{ user.email }}
    laptop{{ user.website }}
    business{{ user.organization }}
    -
    -

    Member since: {{ user.member_since.strftime('%Y-%m-%d') }}

    -

    -
    - {% if current_user.is_authenticated and current_user.id == user.id %} - Edit profile - {% endif %} -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Groups

    -
    -
    -
    -
    -
    -
    -

    Public corpora

    -
    + + +
    +
    +

    +
    +
    +
    +
    + + {% if user.full_name %} + + + + + {% endif %} + {% if user_data['show_email'] %} + + + + + {% endif %} + {% if user.website %} + + + + + {% endif %} + {% if user.organization %} + + + + + {% endif %} +
    person{{ user.full_name }}
    email{{ user.email }}
    laptop{{ user.website }}
    business{{ user.organization }}
    +
    + {% if user_data['show_member_since'] %} +

    Member since: {{ user.member_since.strftime('%Y-%m-%d') }}

    + {% endif %} +

    +
    + {% if current_user.is_authenticated and current_user.id == user.id %} + Edit profile + {% endif %}
    +
    +
    +
    +
    +
    +

    Groups

    +
    +
    +
    +
    +
    +
    +

    Public corpora

    + {#
    #} +
    +
    +
    +
    +
    {% endblock page_content %} + +{% block scripts %} +{{ super() }} + +{% endblock scripts %} + diff --git a/app/templates/services/tesseract_ocr_pipeline.html.j2 b/app/templates/services/tesseract_ocr_pipeline.html.j2 index 3bb14254..c9617d5a 100644 --- a/app/templates/services/tesseract_ocr_pipeline.html.j2 +++ b/app/templates/services/tesseract_ocr_pipeline.html.j2 @@ -85,6 +85,7 @@
    + {% endif %} {% if 'disabled' not in form.ocropus_nlbin_threshold.render_kw or not form.ocropus_nlbin_threshold.render_kw['disabled'] %}

    @@ -92,7 +93,6 @@

    {{ form.ocropus_nlbin_threshold() }}

    {% endif %} - {% endif %}