diff --git a/app/__init__.py b/app/__init__.py index 960d4e90ff926e2f36f9765a79eae95646806667..77742800ab76f8dff4e2ea89df4896a0e9e7c955 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 7abb56de79536da5334f13173edb11f843959048..f0792f7c4a32c5ca8c92b3b6da06bf55feee1c54 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 7927c81f7ae7b722978eef9e309482cd78905dd4..d28331a0fb693ee6c4a3d2c7e325240148c646f3 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 20bd4ad4b9ed93ca6de61211aa37c73310d6edd8..5059451981e0232cf466ad43285c28e4218fe132 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 4702dcb7ca5d573cfec733bc4c7a2b037d806e9b..b4711aa54c8113066150d738a9502f4995005232 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('/<hashid:user_id>') 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('/<hashid:user_id>/avatars/<hashid:avatar_id>') def avatar_download(user_id, avatar_id): @@ -41,6 +49,21 @@ def avatar_download(user_id, avatar_id): mimetype=avatar_file.mimetype ) +@bp.route('/<hashid:user_id>/avatars/<hashid:avatar_id>', 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('/<hashid:user_id>/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: + current_user.email = edit_profile_settings_form.email.data + current_user.username = edit_profile_settings_form.username.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_profile_settings_form.avatar.data, user=current_user) + Avatar.create(edit_public_profile_information_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 + 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 562696eb6320f35089cd6458101b144bc1fa48e0..1ad544dcd72be41e26a917d97ad2a64dfb2ac2e0 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 7748240c894761613ce4ea88a30b96c555087ab0..24688c88dd8ebb79e4764b6c547703fa8be25b3e 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 335f73d30887dc0423b3a6b648b9ed4aea2eb1ec..552799270c7c3c6e0c174e0052ba8925b8bc2f75 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 d7e36218f21ce80209e930d2bfa8eaac347bb729..754a98cde6d9ab2ce58777942336a1a305e48106 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 78693c912dfd1ffedc5f3970d29b0c258e305be4..d03c79ebd42f5368c1f93832daba3515bad334ba 100644 --- a/app/static/js/RessourceLists/SpacyNLPPipelineModelList.js +++ b/app/static/js/RessourceLists/SpacyNLPPipelineModelList.js @@ -34,9 +34,9 @@ class SpaCyNLPPipelineModelList extends RessourceList { <div class="switch action-switch center-align" data-action="share-request"> <span class="share"></span> <label> - <input type="checkbox" class="shared"> + <input type="checkbox" class="is_public"> <span class="lever"></span> - shared + public </label> </div> </td> @@ -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 22c4bc3782c4ba191c700ab4903157f92ab5b364..d299af1b41fe352e63e280c185f3b045d6722b67 100644 --- a/app/static/js/RessourceLists/TesseractOCRPipelineModelList.js +++ b/app/static/js/RessourceLists/TesseractOCRPipelineModelList.js @@ -34,9 +34,9 @@ class TesseractOCRPipelineModelList extends RessourceList { <div class="switch action-switch center-align" data-action="share-request"> <span class="share"></span> <label> - <input type="checkbox" class="shared"> + <input type="checkbox" class="is_public"> <span class="lever"></span> - shared + public </label> </div> </td> @@ -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 ea8b1efc0435099ac5ef33af5e35d69cfd4120e1..899147b46b3f3aa91ec9bc8f60c49f78e54d012c 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 1aff7f8950e34d65b6e4cd3d3d89fce00b637f16..ffdf86a69cf7d62c3d5eee51f24b95b56d13da2f 100644 --- a/app/templates/_sidenav.html.j2 +++ b/app/templates/_sidenav.html.j2 @@ -33,7 +33,8 @@ <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-icon" data-service="corpus-analysis"></i>Corpus analysis</a></li> <li><div class="divider"></div></li> <li><a class="subheader">Account</a></li> - <li><a href="{{ url_for('settings.settings') }}"><i class="material-icons">settings</i>Settings</a></li> + <li><a href="{{ url_for('settings.settings') }}"><i class="material-icons">settings</i>General Settings</a></li> + <li><a href="{{ url_for('profile.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">contact_page</i>Profile settings</a></li> <li><a href="{{ url_for('auth.logout') }}">Log out</a></li> {% if current_user.can(Permission.ADMINISTRATE) or current_user.can(Permission.USE_API) %} <li><div class="divider"></div></li> diff --git a/app/templates/profile/edit_profile.html.j2 b/app/templates/profile/edit_profile.html.j2 index 1f4bd0e7d032c3ae817d2470e1949f28d0c25c87..7b1dbef7232e39a63ae7291d83c49de750ff0739 100644 --- a/app/templates/profile/edit_profile.html.j2 +++ b/app/templates/profile/edit_profile.html.j2 @@ -9,48 +9,186 @@ </div> <div class="col s12"> + <div class="card"> <form method="POST" enctype="multipart/form-data"> <div class="card-content"> - {{ edit_profile_settings_form.hidden_tag() }} + <div class="row"> + <div class="col s6"> + {{ 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') }} + </div> + </div> + </div> + <div class="card-action"> + <div class="right-align"> + {{ wtf.render_field(edit_profile_settings_form.submit, material_icon='send') }} + </div> + </div> + </form> + </div> + + <form method="POST"> + {{ edit_privacy_settings_form.hidden_tag() }} + <div class="card"> + <div class="card-content"> + <span class="card-title">Privacy settings</span> + <br> + {{ wtf.render_field(edit_privacy_settings_form.is_public, id='public-profile') }} + <br> + <hr> + <br> + {{ wtf.render_field(edit_privacy_settings_form.show_email, data_action='disable', disabled=true) }} + <br> + {{ wtf.render_field(edit_privacy_settings_form.show_last_seen, data_action='disable', disabled=true) }} + <br> + {{ wtf.render_field(edit_privacy_settings_form.show_member_since, data_action='disable', disabled=true) }} + <br> + </div> + <div class="card-action"> + <div class="right-align"> + {{ wtf.render_field(edit_privacy_settings_form.submit, material_icon='send') }} + </div> + </div> + </div> + </form> + + <div class="card"> + <form method="POST" enctype="multipart/form-data"> + <div class="card-content"> + {{ edit_public_profile_information_form.hidden_tag() }} + <div class="row"> + <div class="col s12"> + <span class="card-title">Public Profile</span> + </div> + </div> <div class="row"> <div class="col s5"> <div class="row"> - <div class="col s1"></div> - <div class="col s10"> + <div class="col s2"></div> + <div class="col s8"> {% if current_user.avatar %} - <img src="{{ url_for('profile.avatar_download', user_id=user.id, avatar_id=current_user.avatar.id) }}" alt="user-image" class="circle responsive-img"> + <img src="{{ url_for('profile.avatar_download', user_id=user.id, avatar_id=current_user.avatar.id) }}" alt="user-image" class="circle responsive-img" id="avatar"> {% else %} - <img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img"> + <img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img" id="placeholder-avatar"> {% endif %} </div> - <div class="col s1"></div> + <div class="col s2"></div> </div> <div class="row"> - <div class="col s12"> - {{wtf.render_field(edit_profile_settings_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder="Choose an image file")}} + <div class="col s2"> + <div class="center-align"> + <a class="action-button btn-floating red waves-effect waves-light modal-trigger" href="#delete-request" style="margin-top:15px;"><i class="material-icons">delete</i></a> + </div> + </div> + <div class="col s10"> + {{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')}} </div> </div> </div> <div class="col s7"> - {{ 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') }} + <p></p> + <br> + {{ 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') }} </div> </div> </div> <div class="card-action"> <div class="right-align"> - {{ wtf.render_field(edit_profile_settings_form.submit, material_icon='send') }} + {{ wtf.render_field(edit_public_profile_information_form.submit, material_icon='send') }} </div> </div> </form> </div> + </div> </div> </div> {% endblock page_content %} + +{% block modals %} +<div class="modal" id="delete-request"> + <div class="modal-content"> + <h4>Confirm Avatar deletion</h4> + <p>Do you really want to delete your Avatar?</p> + </div> + <div class="modal-footer"> + <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a> + <a class="action-button btn modal-close red waves-effect waves-light" data-action="delete">Delete</a> + </div> +</div> +{% endblock modals %} + +{% block scripts %} +{{ super() }} +<script> +let publicProfile = document.querySelector('#public-profile'); +let disableButtons = document.querySelectorAll('[data-action="disable"]'); +let deleteButton = document.querySelector('[data-action="delete"]'); +let cancelButton = document.querySelector('[data-action="cancel"]'); +let deleteRequestModal = document.querySelector('#delete-request'); +let avatar = document.querySelector('#avatar'); +let placeholderAvatar = document.querySelector('#placeholder-avatar'); +let avatarUpload = document.querySelector('#avatar-upload'); + +for (let i = 0; i < disableButtons.length; i++) { + disableButtons[i].disabled = !publicProfile.checked; +} + +publicProfile.addEventListener('change', function() { + if (publicProfile.checked) { + for (let i = 0; i < disableButtons.length; i++) { + disableButtons[i].disabled = false; + } + + } else { + for (let i = 0; i < disableButtons.length; i++) { + disableButtons[i].checked = false; + disableButtons[i].disabled = true; + } + } +}); + +avatarUpload.addEventListener('change', function() { + let file = this.files[0]; + if (avatar){ + avatar.src = URL.createObjectURL(file); + } else { + placeholderAvatar.src = URL.createObjectURL(file); + } +}); + +deleteButton.addEventListener('click', function() { + return new Promise((resolve, reject) => { + let user_id = "{{ current_user.hashid }}"; + let avatar_id = "{{ current_user.avatar.hashid }}"; + + fetch(`/profile/${user_id}/avatars/${avatar_id}`, {method: 'DELETE', 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(`Avatar marked for deletion`, 'corpus'); + resolve(response); + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); + avatar.src = "{{ url_for('static', filename='images/user_avatar.png') }}"; + }); +}); + +cancelButton.addEventListener('click', function() { + let modal = M.Modal.getInstance(deleteRequestModal); + modal.close(); +}); + +</script> +{% endblock scripts %} diff --git a/app/templates/profile/profile_page.html.j2 b/app/templates/profile/profile_page.html.j2 index dbcd78cad576d67d08c65a04203131efbea64bdc..be137392dfb693662061edf308529590d5be1dfa 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 %} - <div class="container"> - <div class="row"> - <div class="col s12"> - <div class="card"> - <div class="card-content"> + <div class="container"> + <div class="row"> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <div class="row"> + <div class="col s1"></div> + <div class="col s3"> + <br> + <br> + {% if user.avatar %} + <img src="{{ url_for('profile.avatar_download', user_id=user.id, avatar_id=user.avatar.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 %} + </div> + <div class="col s1"></div> + <div class="col s7"> <div class="row"> - <div class="col s1"></div> - <div class="col s4"> - {% if user.avatar %} - <img src="{{ url_for('profile.avatar_download', user_id=user.id, avatar_id=user.avatar.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 %} + <div class="col s12"> + <h3 style="float:left">{{ user.username }}<span class="new badge green" id="public-information-badge" data-badge-caption="custom caption" style="margin-top:20px;"></span></h3> </div> - <div class="col s7"> - <h3>{{ user.username }}</h3> + <div class="col 12"> + {% if user_data['show_last_seen'] %} <div class="chip">Last seen: {{ user.last_seen.strftime('%Y-%m-%d %H:%M') }}</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> {% endif %} <p></p> <br> {% if user.about_me%} - <div class="card"> - <div class="card-content"> - <span class="card-title">About me</span> - <p>{{ user.about_me }}</p> - </div> + <div style="border-left: solid 3px #E91E63; padding-left: 15px;"> + <h5>About me</h5> + <p>{{ user.about_me }}</p> </div> {% endif %} </div> </div> - <div class="row"> - <div class="col s1"></div> - <div class="col s6"> - <table> - {% if user.full_name %} - <tr> - <td><span class="material-icons">person</span></td> - <td>{{ user.full_name }} </td> - </tr> - {% endif %} - {% if user.email %} - <tr> - <td><span class="material-icons">email</span></td> - <td>{{ user.email }}</td> - </tr> - {% endif %} - {% if user.website %} - <tr> - <td><span class="material-icons">laptop</span></td> - <td><a href="{{ user.website }}">{{ user.website }}</a></td> - </tr> - {% endif %} - {% if user.organization %} - <tr> - <td><span class="material-icons">business</span></td> - <td>{{ user.organization }}</td> - </tr> - {% endif %} - </table> - <br> - <p><i>Member since: {{ user.member_since.strftime('%Y-%m-%d') }}</i></p> - <p></p> - <br> - {% if current_user.is_authenticated and current_user.id == user.id %} - <a class="waves-effect waves-light btn-small" href="{{ url_for('profile.edit_profile', user_id=user.id) }}">Edit profile</a> - {% endif %} - </div> - </div> + + </div> </div> - </div> - </div> - <div class="row"> - <div class="col s6"> - <div class="card"> - <div class="card-content"> - <h4>Groups</h4> + <p></p> + <br> + <div class="row"> + <div class="col s1"></div> + <div class="col s8"> + <table> + {% if user.full_name %} + <tr> + <td><span class="material-icons">person</span></td> + <td>{{ user.full_name }} </td> + </tr> + {% endif %} + {% if user_data['show_email'] %} + <tr> + <td><span class="material-icons">email</span></td> + <td>{{ user.email }}</td> + </tr> + {% endif %} + {% if user.website %} + <tr> + <td><span class="material-icons">laptop</span></td> + <td><a href="{{ user.website }}">{{ user.website }}</a></td> + </tr> + {% endif %} + {% if user.organization %} + <tr> + <td><span class="material-icons">business</span></td> + <td>{{ user.organization }}</td> + </tr> + {% endif %} + </table> + <br> + {% if user_data['show_member_since'] %} + <p><i>Member since: {{ user.member_since.strftime('%Y-%m-%d') }}</i></p> + {% endif %} + <p></p> + <br> + {% if current_user.is_authenticated and current_user.id == user.id %} + <a class="waves-effect waves-light btn-small" href="{{ url_for('profile.edit_profile', user_id=user.id) }}">Edit profile</a> + {% endif %} </div> </div> </div> - <div class="col s6"> - <div class="card"> - <div class="card-content"> - <h4>Public corpora</h4> - <div class="public-corpora-list" data-user-id="{{ user.hashid }}"></div> - </div> - </div> + </div> + </div> + </div> + <div class="row"> + <div class="col s6"> + <div class="card"> + <div class="card-content"> + <h4>Groups</h4> </div> </div> + </div> + <div class="col s6"> + <div class="card"> + <div class="card-content"> + <h4>Public corpora</h4> + {# <div class="public-corpora-list" data-user-id="{{ user.hashid }}"></div> #} + </div> + </div> + </div> </div> + </div> {% endblock page_content %} + +{% block scripts %} +{{ super() }} +<script> +let publicInformationBadge = document.querySelector('#public-information-badge'); + +if ("{{ user }}" == "{{ current_user }}") { + if ("{{ user.is_public }}" == "True") { + publicInformationBadge.dataset.badgeCaption = 'Your profile is public'; + publicInformationBadge.classList.add('green'); + publicInformationBadge.classList.remove('red'); + } else { + publicInformationBadge.dataset.badgeCaption = 'Your profile is private'; + publicInformationBadge.classList.add('red'); + publicInformationBadge.classList.remove('green'); + } +} else { + publicInformationBadge.remove(); +} +</script> +{% endblock scripts %} + diff --git a/app/templates/services/tesseract_ocr_pipeline.html.j2 b/app/templates/services/tesseract_ocr_pipeline.html.j2 index 3bb14254cef558585aed5dba9ec1af462acaf221..c9617d5aa811a156addc37f4eec7a0e54617a1ae 100644 --- a/app/templates/services/tesseract_ocr_pipeline.html.j2 +++ b/app/templates/services/tesseract_ocr_pipeline.html.j2 @@ -85,6 +85,7 @@ </label> </div> </div> + {% endif %} {% if 'disabled' not in form.ocropus_nlbin_threshold.render_kw or not form.ocropus_nlbin_threshold.render_kw['disabled'] %} <div class="col s9 hide" id="create-job-form-ocropus_nlbin_threshold-container"> <br> @@ -92,7 +93,6 @@ <p class="range-field">{{ form.ocropus_nlbin_threshold() }}</p> </div> {% endif %} - {% endif %} <!-- Seperate each setting with the following <div class="col s12"><p> </p></div> diff --git a/app/templates/settings/settings.html.j2 b/app/templates/settings/settings.html.j2 index f272e63a8cb745e4f370cf4d647fcf39eaca4cf1..e9cfb97bda684a8a654a6d0bbde8bc1a19564ae1 100644 --- a/app/templates/settings/settings.html.j2 +++ b/app/templates/settings/settings.html.j2 @@ -23,24 +23,6 @@ </div> </div> </form> - - <div class="card"> - <div class="card-content"> - <span class="card-title">Privacy settings</span> - <br> - {{ wtf.render_field(edit_privacy_settings_form.private_profile) }} - <br> - {{ wtf.render_field(edit_privacy_settings_form.private_email) }} - <br> - {{ wtf.render_field(edit_privacy_settings_form.only_username) }} - <br> - </div> - <div class="card-action"> - <div class="right-align"> - {{ wtf.render_field(edit_notification_settings_form.submit, material_icon='send') }} - </div> - </div> - </div> <form method="POST"> {{ change_password_form.hidden_tag() }} diff --git a/app/templates/test/analyse_corpus.concordance.html.j2 b/app/templates/test/analyse_corpus.concordance.html.j2 deleted file mode 100644 index 31320f70d2346e4de66b12843920700e79b5d082..0000000000000000000000000000000000000000 --- a/app/templates/test/analyse_corpus.concordance.html.j2 +++ /dev/null @@ -1,117 +0,0 @@ -<div class="row" id="concordance-extension-container"> - <div class="col s12"> - <div class="card"> - <div class="card-content"> - <form id="concordance-extension-form"> - <div class="row"> - <div class="input-field col s12 m9"> - <i class="material-icons prefix">search</i> - <input class="validate corpus-analysis-action" id="concordance-extension-form-query" name="query" type="text" - required pattern=".*\S+.*" - placeholder="<ent_type="PERSON"> []* </ent_type> []* [simple_pos="VERB"] :: match.text_publishing_year="1991";"> - </input> - <label for="concordance-extension-form-query">Query</label> - <span class="error-color-text helper-text hide" id="concordance-extension-error"></span> - <a class="modal-trigger" href="#cql-tutorial-modal" style="margin-left: 40px;"><i class="material-icons" style="font-size: inherit;">help</i> - Corpus Query Language tutorial</a> - <span> | </span> - <a class="modal-trigger" href="#tagsets-modal"><i class="material-icons" style="font-size: inherit;">info</i> Tagsets</a> - </div> - <div class="input-field col s12 m3"> - <i class="material-icons prefix">arrow_forward</i> - <input class="validate corpus-analysis-action" id="concordance-extension-form-subcorpus-name" name="subcorpus-name" type="text" - required pattern="^[A-Z][a-z0-9\-]*" value="Last"> - </input> - <label for="concordance-extension-form-subcorpus-name">Subcorpus name</label> - </div> - <div class="col s12 m9 l9"> - <div class="row"> - <div class="input-field col s4 l3"> - <i class="material-icons prefix">short_text</i> - <select class="corpus-analysis-action" name="context"> - <option value="10" selected>10</option> - <option value="15">15</option> - <option value="20">20</option> - <option value="25">25</option> - <option value="30">30</option> - </select> - <label>Context</label> - </div> - <div class="input-field col s4 l3"> - <i class="material-icons prefix">format_list_numbered</i> - <select class="corpus-analysis-action" name="per-page"> - <option value="10" selected>10</option> - <option value="15">15</option> - <option value="20">20</option> - <option value="25">25</option> - </select> - <label>Matches per page</label> - </div> - <div class="input-field col s4 l3"> - <i class="material-icons prefix">format_shapes</i> - <select name="text-style"> - <option value="0">Plain text</option> - <option value="1" selected>Highlight entities</option> - <option value="2">Token text</option> - </select> - <label>Text style</label> - </div> - <div class="input-field col s4 l3"> - <i class="material-icons prefix">format_quote</i> - <select name="token-representation"> - <option value="lemma">lemma</option> - <option value="pos">pos</option> - <option value="simple_pos">simple_pos</option> - <option value="word" selected>word</option> - </select> - <label>Token representation</label> - </div> - </div> - </div> - <div class="col s12 m3 l3 right-align"> - <p class="hide-on-small-only"> </p> - <a class="btn waves-effect waves-light modal-trigger" href="#concordance-query-builder" id="concordance-query-builder-button"> - <i class="material-icons left">build</i> - Query builder - </a> - <button class="btn waves-effect waves-light corpus-analysis-action" id="concordance-extension-form-submit" type="submit" name="submit"> - Send - <i class="material-icons right">send</i> - </button> - </div> - </div> - </form> - </div> - </div> - </div> - - <div class="col s12"> - <div id="concordance-extension-subcorpus-list"></div> - - <div class="card"> - <div class="card-content"> - <div class="progress hide" id="concordance-extension-progress"> - <div class="indeterminate"></div> - </div> - <div class="row"> - <div class="col s9"><p class="hide" id="concordance-extension-subcorpus-info"></p></div> - <div class="col s3 right-align" id="concordance-extension-subcorpus-actions"></div> - </div> - <table class="highlight"> - <thead> - <tr> - <th style="width: 2%;"></th> - <th style="width: 8%;">Source</th> - <th class="right-align" style="width: 22.5%;">Left context</th> - <th class="center-align" style="width: 40%;">KWIC</th> - <th class="left-align" style="width: 22.5%;">Right Context</th> - <th class="left-align" style="width: 5%;"></th> - </tr> - </thead> - <tbody id="concordance-extension-subcorpus-items"></tbody> - </table> - <ul class="pagination hide" id="concordance-extension-subcorpus-pagination"></ul> - </div> - </div> - </div> -</div> diff --git a/app/templates/test/analyse_corpus.html.j2 b/app/templates/test/analyse_corpus.html.j2 deleted file mode 100644 index faef6abd6672c03c79510e64f4dff9116a478f2c..0000000000000000000000000000000000000000 --- a/app/templates/test/analyse_corpus.html.j2 +++ /dev/null @@ -1,454 +0,0 @@ -{% extends "base.html.j2" %} -{% import "materialize/wtf.html.j2" as wtf %} -<style> - a {color: #FFFFFF;} -</style> - -{% block main_attribs %} class="service-scheme" data-service="corpus-analysis" id="corpus-analysis-app-container"{% endblock main_attribs %} - -{% block page_content %} -<ul class="row tabs no-autoinit" id="corpus-analysis-app-extension-tabs"> - <li class="tab col s3"><a href="#corpus-analysis-app-overview"><i class="nopaque-icons service-icon left" data-service="corpus-analysis"></i>Corpus analysis</a></li> - <li class="tab col s3"><a class="active" href="#concordance-extension-container"><i class="material-icons left">list_alt</i>Concordance</a></li> - <li class="tab col s3"><a href="#reader-extension-container"><i class="material-icons left">chrome_reader_mode</i>Reader</a></li> -</ul> - -{# <div class="row" id="corpus-analysis-app-overview"> - <div class="col s12"> - <h1>{{ title }}</h1> - </div> - - <div class="col s3"> - <div class="card extension-selector hoverable" data-target="concordance-extension-container"> - <div class="card-content"> - <span class="card-title"><i class="material-icons left">list_alt</i>Concordance</span> - <p>Query your corpus with the CQP query language utilizing a KWIC view.</p> - </div> - </div> - </div> - - <div class="col s3"> - <div class="card extension-selector hoverable" data-target="reader-extension-container"> - <div class="card-content"> - <span class="card-title"><i class="material-icons left">chrome_reader_mode</i>Reader</span> - <p>Inspect your corpus in detail with a full text view, including annotations.</p> - </div> - </div> - </div> -</div> #} -{% include "test/analyse_corpus.concordance.html.j2" %} -{% endblock page_content %} - -{% block modals %} -{{ super() }} -<div class="modal no-autoinit" id="corpus-analysis-app-init-modal"> - <div class="modal-content"> - <h4>Initializing session...</h4> - <p>If the loading takes to long or an error occured, - <a onclick="window.location.reload()" href="#">click here</a> - to refresh your session or - <a href="">go back</a>! - </p> - <div class="progress" id="corpus-analysis-app-init-progress"> - <div class="indeterminate"></div> - </div> - <p class="error-color-text hide" id="corpus-analysis-app-init-error"></p> - </div> -</div> - - - -<div class="modal" id="cql-tutorial-modal"> - <div class="modal-content"> - {% with headline_num=4 %} - {% include "main/manual/_08_cqp_query_language.html.j2" %} - {% endwith %} - </div> -</div> - -<div class="modal" id="tagsets-modal"> - <div class="modal-content"> - <h4>Tagsets</h4> - <ul class="tabs"> - <li class="tab"><a class="active" href="#simple_pos-tagset">simple_pos</a></li> - <li class="tab"><a href="#english-ent_type-tagset">English ent_type</a></li> - <li class="tab"><a href="#english-pos-tagset">English pos</a></li> - <li class="tab"><a href="#german-ent_type-tagset">German ent_type</a></li> - <li class="tab"><a href="#german-pos-tagset">German pos</a></li> - </ul> - {% include "main/manual/_10_tagsets.html.j2" %} - </div> -</div> - - -<div class="modal" id="concordance-query-builder"> - <div class="modal-content"> - <div> - <nav> - <div class="nav-wrapper" id="query-builder-nav"> - <a href="#!" class="brand-logo"><i class="material-icons">build</i>Query Builder</a> - <i class="material-icons close right" id="close-query-builder">close</i> - <a class="modal-trigger" href="#query-builder-tutorial-modal" > - <i class="material-icons right tooltipped" id="query-builder-tutorial-info-icon" data-position="bottom" data-tooltip="Click here if you are unsure how to use the Query Builder <br>and want to find out what other options it offers.">help</i> - </a> - </div> - </nav> - </div> - - <p></p> - - <div id="query-container" class="hide"> - - <div class="row"> - <h6 class="col s2">Your Query: - <a class="modal-trigger" href="#query-builder-tutorial-modal"> - <i class="material-icons left" id="general-options-query-builder-tutorial-info-icon">help_outline</i></a> - </h6> - </div> - <div class="row"> - <div class="col s10" id="your-query" data-position="bottom" data-tooltip="You can edit your query by deleting individual elements or moving them via drag and drop."></div> - <a class="btn-small waves-effect waves-teal col s1" id="insert-query-button"> - <i class="material-icons">send</i> - </a> - </div> - <p><i> Preview:</i></p> - <p id="query-preview"></p> - <br> - </div> - - - <h6>Use the following options to build your query. If you need help, click on the question mark in the upper right corner!</h6> - <p></p> - <a class="btn-large waves-effect waves-light tooltipped" id="positional-attr-button" data-position="bottom" data-tooltip="Search for any token, for example a word, a lemma or a part-of-speech tag">Add new token to your query</a> - <a class="btn-large waves-effect waves-light tooltipped" id="structural-attr-button" data-position="bottom" data-tooltip="Structure your query with structural attributes, for example sentences, entities or annotate the text">Add structural attributes to your query</a> - - <div id="structural-attr" class="hide"> - <p></p> - <h6>Which structural attribute do you want to add to your query?<a class="modal-trigger" href="#query-builder-tutorial-modal"><i class="material-icons left" id="add-structural-attribute-tutorial-info-icon">help_outline</i></a></h6> - <p></p> - <div class="row"> - <div class="col s12"> - <a class="btn-small waves-effect waves-light" id="sentence">sentence</a> - <a class="btn-small waves-effect waves-light" id="entity">entity</a> - <a class="btn-small waves-effect waves-light" id="text-annotation">Meta Data</a> - </div> - </div> - - - <div id="entity-builder" class="hide"> - <p></p> - <br> - <div class="row"> - <a class="btn waves-effect waves-light col s4" id="empty-entity">Add Entity of any type</a> - <p class="col s1 l1"></p> - <div class= "input-field col s3"> - <select name="englishenttype" id="english-ent-type"> - <option value="" disabled selected>English ent_type</option> - <option value="CARDINAL">CARDINAL</option> - <option value="DATE">DATE</option> - <option value="EVENT">EVENT</option> - <option value="FAC">FAC</option> - <option value="GPE">GPE</option> - <option value="LANGUAGE">LANGUAGE</option> - <option value="LAW">LAW</option> - <option value="LOC">LOC</option> - <option value="MONEY">MONEY</option> - <option value="NORP">NORP</option> - <option value="ORDINAL">ORDINAL</option> - <option value="ORG">ORG</option> - <option value="PERCENT">PERCENT</option> - <option value="PERSON">PERSON</option> - <option value="PRODUCT">PRODUCT</option> - <option value="QUANTITY">QUANTITY</option> - <option value="TIME">TIME</option> - <option value="WORK_OF_ART">WORK_OF_ART</option> - </select> - <label>Entity Type</label> - </div> - <div class= "input-field col s3"> - <select name="germanenttype" id="german-ent-type"> - <option value="" disabled selected>German ent_type</option> - <option value="LOC">LOC</option> - <option value="MISC">MISC</option> - <option value="ORG">ORG</option> - <option value="PER">PER</option> - </select> - </div> - </div> - </div> - - - <div id="text-annotation-builder" class="hide"> - <p></p> - <br> - <div class="row"> - <div class= "input-field col s4 l3"> - <select name="text-annotation-options" id="text-annotation-options"> - <option class="btn-small waves-effect waves-light" value="address">address</option> - <option class="btn-small waves-effect waves-light" value="author">author</option> - <option class="btn-small waves-effect waves-light" value="booktitle">booktitle</option> - <option class="btn-small waves-effect waves-light" value="chapter">chapter</option> - <option class="btn-small waves-effect waves-light" value="editor">editor</option> - <option class="btn-small waves-effect waves-light" value="institution">institution</option> - <option class="btn-small waves-effect waves-light" value="journal">journal</option> - <option class="btn-small waves-effect waves-light" value="pages">pages</option> - <option class="btn-small waves-effect waves-light" value="publisher">publisher</option> - <option class="btn-small waves-effect waves-light" value="publishing_year">publishing year</option> - <option class="btn-small waves-effect waves-light" value="school">school</option> - <option class="btn-small waves-effect waves-light" value="title">title</option> - </select> - <label>Meta data</label> - </div> - <div class= "input-field col s7 l5"> - <i class="material-icons prefix">mode_edit</i> - <input placeholder="Type in your text annotation" type="text" id="text-annotation-input"> - </div> - <div class="col s1 l1 center-align"> - <p class="btn-floating waves-effect waves-light" id="text-annotation-submit"> - <i class="material-icons right">send</i> - </p> - </div> - <div class="hide" id="no-value-metadata-message"><i>No value entered!</i></div> - - </div> - </div> - </div> - - <div id="positional-attr" class="hide"> - <p></p> - <div class="row" id="token-kind-selector"> - <div class="col s5"> - <h6>Which kind of token are you looking for? <a class="modal-trigger" href="#query-builder-tutorial-modal"><i class="material-icons left" id="token-tutorial-info-icon">help_outline</i></a></h6> - </div> - <div class="input-field col s3"> - <select id="token-attr"> - <option value="word" selected>word</option> - <option value="lemma">lemma</option> - <option value="english-pos">english pos</option> - <option value="german-pos">german pos</option> - <option value="simple-pos-button">simple_pos</option> - <option value="empty-token">empty token</option> - </select> - </div> - </div> - <p></p> - <div id="token-builder-content"> - <div class="row" > - <div id="token-query"></div> - - <div id="word-builder"> - <div class= "input-field col s3 l4"> - <i class="material-icons prefix">mode_edit</i> - <input placeholder="Type in your word" type="text" id="word-input"> - </div> - </div> - - <div id="lemma-builder" class="hide" > - <div class= "input-field col s3 l4"> - <i class="material-icons prefix">mode_edit</i> - <input placeholder="Type in your lemma" type="text" id="lemma-input"> - </div> - </div> - - <div id="english-pos-builder" class="hide"> - <div class="col s6 m4 l4"> - <div class="row"> - <div class= "input-field col s12"> - <select name="englishpos" id="english-pos"> - <option value="default" disabled selected>English pos tagset</option> - <option value="ADD">email</option> - <option value="AFX">affix</option> - <option value="CC">conjunction, coordinating</option> - <option value="CD">cardinal number</option> - <option value="DT">determiner</option> - <option value="EX">existential there</option> - <option value="FW">foreign word</option> - <option value="HYPH">punctuation mark, hyphen</option> - <option value="IN">conjunction, subordinating or preposition</option> - <option value="JJ">adjective</option> - <option value="JJR">adjective, comparative</option> - <option value="JJS">adjective, superlative</option> - </select> - <label>Part-of-speech tags</label> - </div> - </div> - </div> - </div> - - <div id="german-pos-builder" class="hide"> - <div class="col s6 m4 l4"> - <div class="row"> - <div class= "input-field col s12"> - <select name="germanpos" id="german-pos"> - <option value="default" disabled selected>German pos tagset</option> - <option value="ADJA">adjective, attributive</option> - <option value="ADJD">adjective, adverbial or predicative</option> - <option value="ADV">adverb</option> - <option value="APPO">postposition</option> - <option value="APPR">preposition; circumposition left</option> - <option value="APPRART">preposition with article</option> - <option value="APZR">circumposition right</option> - <option value="ART">definite or indefinite article</option> - </select> - <label>Part-of-speech tags</label> - </div> - </div> - </div> - </div> - - <div id="simplepos-builder" class="hide"> - <div class="col s6 m4 l4"> - <div class="row"> - <div class= "input-field col s12"> - <select name="simplepos" id="simple-pos"> - <option value="default" disabled selected>simple_pos tagset</option> - <option value="ADJ">adjective</option> - <option value="ADP">adposition</option> - <option value="ADV">adverb</option> - <option value="AUX">auxiliary verb</option> - <option value="CONJ">coordinating conjunction</option> - <option value="DET">determiner</option> - <option value="INTJ">interjection</option> - <option value="NOUN">noun</option> - <option value="NUM">numeral</option> - <option value="PART">particle</option> - <option value="PRON">pronoun</option> - <option value="PROPN">proper noun</option> - <option value="PUNCT">punctuation</option> - <option value="SCONJ">subordinating conjunction</option> - <option value="SYM">symbol</option> - <option value="VERB">verb</option> - <option value="X">other</option> - </select> - <label>Simple part-of-speech tags</label> - </div> - </div> - </div> - </div> - <div class="col s1 l1 center-align"> - <p class="btn-floating waves-effect waves-light" id="token-submit"> - <i class="material-icons right">send</i> - </p> - </div> - <div class="hide" id="no-value-message"><i>No value entered!</i></div> - - </div> - - <div id="token-edit-options"> - <div class="row"> - <h6>Options to edit your token: <a class="modal-trigger" href="#query-builder-tutorial-modal"><i class="material-icons left" id="edit-options-tutorial-info-icon">help_outline</i></a></h6> - </div> - <p></p> - <div class="row"> - <div id="input-options" class="col s5 m5 l5 xl4"> - <a id="wildcard-char" class="btn-small waves-effect waves-light tooltipped" data-position="top" data-tooltip="Look for a variable character (also called wildcard character)">Wildcard character</a> - <a id="option-group" class="btn-small waves-effect waves-light tooltipped" data-position="top" data-tooltip="Find character sequences from a list of options">Option Group</a> - </div> - <div class="col s3 m3 l3 xl3" id="incidence-modifiers-button"> - <a class="dropdown-trigger btn-small waves-effect waves-light" href="#" data-target="incidence-modifiers" data-position="top" data-tooltip="Incidence Modifiers are special characters or patterns, <br>which determine how often a character represented previously should occur.">incidence modifiers</a> - </div> - - <ul id="incidence-modifiers" class="dropdown-content"> - <li><a id="one-or-more" data-token="+" class="tooltipped" data-position ="top" data-tooltip="...occurrences of the character/token before">one or more (+)</a></li> - <li><a id="zero-or-more" data-token="*" class="tooltipped" data-position ="top" data-tooltip="...occurrences of the character/token before">zero or more (*)</a></li> - <li><a id="zero-or-one" data-token="?" class="tooltipped" data-position ="top" data-tooltip="...occurrences of the character/token before">zero or one (?)</a></li> - <li><a id="exactly-n" class="modal-trigger tooltipped" href="#exactlyN" data-token="{n}" class="" data-position ="top" data-tooltip="...occurrences of the character/token before">exactly n ({n})</a></li> - <li><a id="between-n-m" class="modal-trigger tooltipped" href="#betweenNM" data-token="{n,m}" class="" data-position ="top" data-tooltip="...occurrences of the character/token before">between n and m ({n,m})</a></li> - </ul> - - <div id="ignore-case-checkbox" class="col s2 m2 l2 xl2"> - <p id="ignore-case"> - <label> - <input type="checkbox" class="filled-in" /> - <span>Ignore Case</span> - </label> - </p> - </div> - <div class="col s2 m2 l2 xl2" id="condition-container"> - <a class="btn-small tooltipped waves-effect waves-light" id="or" data-position="bottom" data-tooltip="You can add another condition to your token. <br>At least one must be fulfilled">or</a> - <a class="btn-small tooltipped waves-effect waves-light" id="and" data-position="bottom" data-tooltip="You can add another condition to your token. <br>Both must be fulfilled">and</a> - </div> - </div> - </div> - - </div> - - <div id="exactlyN" class="modal"> - <div class="row modal-content"> - <div class="input-field col s10"> - <i class="material-icons prefix">mode_edit</i> - <input placeholder="type in a number for 'n'" type="text" id="n-input"> - </div> - <div class="col s2"> - <p class="btn-floating waves-effect waves-light" id="n-submit"> - <i class="material-icons right">send</i> - </p> - </div> - </div> - </div> - - <div id="betweenNM" class="modal"> - <div class="row modal-content"> - <div class= "input-field col s5"> - <i class="material-icons prefix">mode_edit</i> - <input placeholder="number for 'n'" type="text" id="n-m-input"> - </div> - <div class= "input-field col s5"> - <i class="material-icons prefix">mode_edit</i> - <input placeholder="number for 'm'" type="text" id="m-input"> - </div> - <div class="col s2"> - <p class="btn-floating waves-effect waves-light" id="n-m-submit"> - <i class="material-icons right">send</i> - </p> - </div> - </div> - </div> - </div> - - </div> -</div> - -<div class="modal modal-fixed-footer" id="query-builder-tutorial-modal"> - <div class="modal-content" > - <div id="query-builder-tutorial-start"></div> - <ul class="tabs"> - <li class="tab"><a class="active" href="#query-builder-tutorial">Query Builder Tutorial</a></li> - {# <li class="tab"><a href="#qb-examples">Examples</a></li> #} - <li class="tab"><a href="#cql-cb-tutorial">Corpus Query Language Tutorial</a></li> - <li class="tab"><a href="#tagsets-cb-tutorial">Tagsets</a></li> - </ul> - - <div id="query-builder-tutorial"> - {% include "main/manual/_09_query_builder.html.j2" %} - </div> - {# <div id="qb-examples"></div> #} - <div id ="cql-cb-tutorial"> - {% with headline_num=4 %} - {% include "main/manual/_08_cqp_query_language.html.j2" %} - {% endwith %} - </div> - <div id="tagsets-cb-tutorial"> - <h4>Tagsets</h4> - {% include "main/manual/_10_tagsets.html.j2" %} - </div> - <div class="fixed-action-btn"> - <a class="btn-floating btn-large teal" id="scroll-up-button-query-builder-tutorial" href='#query-builder-tutorial-start'> - <i class="large material-icons">arrow_upward</i> - </a> - </div> - </div> -</div> - - - -{% endblock modals %} - -{% block scripts %} -{{ super() }} -<script> - - -const concordanceQueryBuilder = new ConcordanceQueryBuilder() -</script> -{% endblock scripts %} diff --git a/app/test/__init__.py b/app/test/__init__.py deleted file mode 100644 index 047d8dfccbfa55143c660eeb497250b36c27c6d5..0000000000000000000000000000000000000000 --- a/app/test/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from flask import Blueprint - - -bp = Blueprint('test', __name__) -from . import routes diff --git a/app/test/routes.py b/app/test/routes.py deleted file mode 100644 index 1fe424fa018dd3e6a7dee23a604da85f6bf85782..0000000000000000000000000000000000000000 --- a/app/test/routes.py +++ /dev/null @@ -1,10 +0,0 @@ -from flask import render_template -from flask_login import login_required -from app.models import Corpus, CorpusFile, CorpusStatus -from . import bp -import os - -@bp.route('') -@login_required -def test(): - return render_template('test/analyse_corpus.html.j2', title="Test") diff --git a/migrations/versions/5b2a6e590166_.py b/migrations/versions/5b2a6e590166_.py new file mode 100644 index 0000000000000000000000000000000000000000..1497f38478e0d35f9cae71d414e18248ed2938ed --- /dev/null +++ b/migrations/versions/5b2a6e590166_.py @@ -0,0 +1,35 @@ +"""Add is_public and profile_privacy_settings columns to users table + +Revision ID: 5b2a6e590166 +Revises: ef6a275f8079 +Create Date: 2022-12-12 12:39:09.339847 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5b2a6e590166' +down_revision = 'ef6a275f8079' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + 'users', + sa.Column('is_public', sa.Boolean(), nullable=True) + ) + op.add_column( + 'users', + sa.Column('profile_privacy_settings', sa.Integer(), nullable=True) + ) + op.execute('UPDATE users SET is_public = false;') + op.execute('UPDATE users SET profile_privacy_settings = 0;') + + +def downgrade(): + op.drop_column('users', 'is_public') + op.drop_column('users', 'profile_privacy_settings') +