From 700e11ec23db9fee8f359a13a8423a51848cab43 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Fri, 23 Oct 2020 13:52:01 +0200 Subject: [PATCH] Rename and rework profile packege (new name settings) --- web/app/__init__.py | 21 +-- web/app/auth/forms.py | 7 +- web/app/profile/forms.py | 52 ------- web/app/profile/tasks.py | 13 -- web/app/profile/views.py | 69 --------- web/app/{profile => settings}/__init__.py | 2 +- web/app/settings/forms.py | 86 +++++++++++ web/app/settings/views.py | 73 ++++++++++ web/app/templates/nopaque.html.j2 | 7 +- web/app/templates/profile/settings.html.j2 | 136 ------------------ web/app/templates/settings/_menu.html.j2 | 5 + .../settings/change_password.html.j2 | 35 +++++ .../settings/edit_general_settings.html.j2 | 63 ++++++++ .../edit_notification_settings.html.j2 | 34 +++++ web/config.py | 1 + 15 files changed, 316 insertions(+), 288 deletions(-) delete mode 100644 web/app/profile/forms.py delete mode 100644 web/app/profile/tasks.py delete mode 100644 web/app/profile/views.py rename web/app/{profile => settings}/__init__.py (57%) create mode 100644 web/app/settings/forms.py create mode 100644 web/app/settings/views.py delete mode 100644 web/app/templates/profile/settings.html.j2 create mode 100644 web/app/templates/settings/_menu.html.j2 create mode 100644 web/app/templates/settings/change_password.html.j2 create mode 100644 web/app/templates/settings/edit_general_settings.html.j2 create mode 100644 web/app/templates/settings/edit_notification_settings.html.j2 diff --git a/web/app/__init__.py b/web/app/__init__.py index 7a5d8b68..a39a51a5 100644 --- a/web/app/__init__.py +++ b/web/app/__init__.py @@ -28,22 +28,23 @@ def create_app(config_name): socketio.init_app( app, message_queue=config[config_name].SOCKETIO_MESSAGE_QUEUE_URI) - from . import events - from .admin import admin as admin_blueprint + with app.app_context(): + from . import events + from .admin import admin as admin_blueprint + from .auth import auth as auth_blueprint + from .corpora import corpora as corpora_blueprint + from .errors import errors as errors_blueprint + from .jobs import jobs as jobs_blueprint + from .main import main as main_blueprint + from .services import services as services_blueprint + from .settings import settings as settings_blueprint app.register_blueprint(admin_blueprint, url_prefix='/admin') - from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth') - from .corpora import corpora as corpora_blueprint app.register_blueprint(corpora_blueprint, url_prefix='/corpora') - from .errors import errors as errors_blueprint app.register_blueprint(errors_blueprint) - from .jobs import jobs as jobs_blueprint app.register_blueprint(jobs_blueprint, url_prefix='/jobs') - from .main import main as main_blueprint app.register_blueprint(main_blueprint) - from .profile import profile as profile_blueprint - app.register_blueprint(profile_blueprint, url_prefix='/profile') - from .services import services as services_blueprint app.register_blueprint(services_blueprint, url_prefix='/services') + app.register_blueprint(settings_blueprint, url_prefix='/settings') return app diff --git a/web/app/auth/forms.py b/web/app/auth/forms.py index e3f5c276..3344096b 100644 --- a/web/app/auth/forms.py +++ b/web/app/auth/forms.py @@ -1,3 +1,4 @@ +from flask import current_app from ..models import User from flask_wtf import FlaskForm from wtforms import (BooleanField, PasswordField, StringField, SubmitField, @@ -17,9 +18,9 @@ class RegistrationForm(FlaskForm): username = StringField( 'Username', validators=[DataRequired(), Length(1, 64), - Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, - 'Usernames must have only letters, numbers, dots ' - 'or underscores')] + Regexp(current_app.config['ALLOWED_USERNAME_REGEX'], + message='Usernames must have only letters, numbers,' + ' dots or underscores')] ) password = PasswordField( 'Password', diff --git a/web/app/profile/forms.py b/web/app/profile/forms.py deleted file mode 100644 index 14517e29..00000000 --- a/web/app/profile/forms.py +++ /dev/null @@ -1,52 +0,0 @@ -from flask_wtf import FlaskForm -from wtforms import (BooleanField, PasswordField, SelectField, StringField, - SubmitField, ValidationError) -from wtforms.validators import DataRequired, Email, EqualTo - - -class EditEmailForm(FlaskForm): - email = StringField('New email', validators=[Email(), DataRequired()]) - save_email = SubmitField('Save email') - - -class EditGeneralSettingsForm(FlaskForm): - dark_mode = BooleanField('Dark mode') - job_status_mail_notifications = SelectField( - 'Job status mail notifications', - choices=[('', 'Choose your option'), - ('all', 'Notify on all status changes'), - ('end', 'Notify only when a job ended'), - ('none', 'No status update notifications')], - validators=[DataRequired()]) - job_status_site_notifications = SelectField( - 'Job status site notifications', - choices=[('', 'Choose your option'), - ('all', 'Notify on all status changes'), - ('end', 'Notify only when a job ended'), - ('none', 'No status update notifications')], - validators=[DataRequired()]) - save_settings = SubmitField('Save settings') - - -class EditPasswordForm(FlaskForm): - current_password = PasswordField('Current password', - validators=[DataRequired()]) - password = PasswordField( - 'New password', - validators=[DataRequired(), EqualTo('password_confirmation', - message='Passwords must match.')] - ) - password_confirmation = PasswordField( - 'Password confirmation', - validators=[DataRequired(), - EqualTo('password', message='Passwords must match.')] - ) - save_password = SubmitField('Save password') - - def __init__(self, user, *args, **kwargs): - super(EditPasswordForm, self).__init__(*args, **kwargs) - self.user = user - - def validate_current_password(self, field): - if not self.user.verify_password(field.data): - raise ValidationError('Invalid password.') diff --git a/web/app/profile/tasks.py b/web/app/profile/tasks.py deleted file mode 100644 index 61f737c5..00000000 --- a/web/app/profile/tasks.py +++ /dev/null @@ -1,13 +0,0 @@ -from .. import db -from ..decorators import background -from ..models import User - - -@background -def delete_user(user_id, *args, **kwargs): - with kwargs['app'].app_context(): - user = User.query.get(user_id) - if user is None: - raise Exception('User {} not found'.format(user_id)) - user.delete() - db.session.commit() diff --git a/web/app/profile/views.py b/web/app/profile/views.py deleted file mode 100644 index f156715f..00000000 --- a/web/app/profile/views.py +++ /dev/null @@ -1,69 +0,0 @@ -from flask import flash, redirect, render_template, url_for -from flask_login import current_user, login_required, logout_user -from . import profile -from . import tasks -from .forms import EditEmailForm, EditGeneralSettingsForm, EditPasswordForm -from .. import db - - -@profile.route('/settings', methods=['GET', 'POST']) -@login_required -def settings(): - edit_email_form = EditEmailForm(prefix='edit-email-form') - edit_general_settings_form = EditGeneralSettingsForm( - prefix='edit-general-settings-form') - edit_password_form = EditPasswordForm(prefix='edit-password-form', - user=current_user) - # Check if edit_email_form is submitted and valid - if (edit_email_form.save_email.data - and edit_email_form.validate_on_submit()): - db.session.add(current_user) - db.session.commit() - flash('Your email address has been updated.') - return redirect(url_for('profile.settings')) - # Check if edit_settings_form is submitted and valid - if (edit_general_settings_form.save_settings.data - and edit_general_settings_form.validate_on_submit()): - current_user.setting_dark_mode = \ - edit_general_settings_form.dark_mode.data - current_user.setting_job_status_mail_notifications = \ - edit_general_settings_form.job_status_mail_notifications.data - current_user.setting_job_status_site_notifications = \ - edit_general_settings_form.job_status_site_notifications.data - db.session.add(current_user) - db.session.commit() - flash('Your settings have been updated.') - return redirect(url_for('profile.settings')) - # Check if edit_password_form is submitted and valid - if (edit_password_form.save_password.data - and edit_password_form.validate_on_submit()): - current_user.password = edit_password_form.password.data - db.session.add(current_user) - db.session.commit() - flash('Your password has been updated.') - return redirect(url_for('profile.settings')) - # If no form is submitted or valid, fill out fields with current values - edit_email_form.email.data = current_user.email - edit_general_settings_form.dark_mode.data = current_user.setting_dark_mode - edit_general_settings_form.job_status_site_notifications.data = \ - current_user.setting_job_status_site_notifications - edit_general_settings_form.job_status_mail_notifications.data = \ - current_user.setting_job_status_mail_notifications - return render_template( - 'profile/settings.html.j2', - edit_email_form=edit_email_form, - edit_password_form=edit_password_form, - edit_general_settings_form=edit_general_settings_form, - title='Settings') - - -@profile.route('/delete', methods=['GET', 'POST']) -@login_required -def delete(): - """ - View to delete yourslef and all associated data. - """ - tasks.delete_user(current_user.id) - logout_user() - flash('Your account has been deleted!') - return redirect(url_for('main.index')) diff --git a/web/app/profile/__init__.py b/web/app/settings/__init__.py similarity index 57% rename from web/app/profile/__init__.py rename to web/app/settings/__init__.py index 85b9f61c..2329d880 100644 --- a/web/app/profile/__init__.py +++ b/web/app/settings/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -profile = Blueprint('profile', __name__) +settings = Blueprint('settings', __name__) from . import views # noqa diff --git a/web/app/settings/forms.py b/web/app/settings/forms.py new file mode 100644 index 00000000..8cf96415 --- /dev/null +++ b/web/app/settings/forms.py @@ -0,0 +1,86 @@ +from flask import current_app +from flask_login import current_user +from flask_wtf import FlaskForm +from wtforms import (BooleanField, PasswordField, SelectField, StringField, + SubmitField, ValidationError) +from wtforms.validators import DataRequired, Email, EqualTo, Length, Regexp + + +class ChangePasswordForm(FlaskForm): + password = PasswordField('Old password', validators=[DataRequired()]) + new_password = PasswordField( + 'New password', + validators=[DataRequired(), EqualTo('password_confirmation', + message='Passwords must match.')] + ) + new_password2 = PasswordField( + 'Confirm new password', validators=[DataRequired()]) + submit = SubmitField('Change password') + + def __init__(self, user=current_user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + + def validate_current_password(self, field): + if not self.user.verify_password(field.data): + raise ValidationError('Invalid password.') + + +class EditGeneralSettingsForm(FlaskForm): + dark_mode = BooleanField('Dark mode') + email = StringField('E-Mail', + validators=[DataRequired(), Length(1, 254), Email()]) + username = StringField( + 'Benutzername', + validators=[DataRequired(), + Length(1, 64), + Regexp(current_app.config['ALLOWED_USERNAME_REGEX'], + message='Usernames must have only letters, numbers,' + ' dots or underscores')] + ) + submit = SubmitField('Submit') + + def __init__(self, user=current_user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + self.email.data = self.email.data or user.email + self.dark_mode.data = self.dark_mode.data or user.setting_dark_mode + self.username.data = self.username.data or user.username + + 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 EditNotificationSettingsForm(FlaskForm): + job_status_mail_notifications = SelectField( + 'Job status mail notifications', + choices=[('', 'Choose your option'), + ('all', 'Notify on all status changes'), + ('end', 'Notify only when a job ended'), + ('none', 'No status update notifications')], + validators=[DataRequired()]) + job_status_site_notifications = SelectField( + 'Job status site notifications', + choices=[('', 'Choose your option'), + ('all', 'Notify on all status changes'), + ('end', 'Notify only when a job ended'), + ('none', 'No status update notifications')], + validators=[DataRequired()]) + submit = SubmitField('Save settings') + + def __init__(self, user=current_user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + self.job_status_mail_notifications.data = \ + self.job_status_mail_notifications.data \ + or user.setting_job_status_mail_notifications + self.job_status_site_notifications.data = \ + self.job_status_site_notifications.data \ + or user.setting_job_status_site_notifications diff --git a/web/app/settings/views.py b/web/app/settings/views.py new file mode 100644 index 00000000..a90d8ab2 --- /dev/null +++ b/web/app/settings/views.py @@ -0,0 +1,73 @@ +from flask import current_app, flash, redirect, render_template, url_for +from flask_login import current_user, login_required +from . import settings +from .forms import (ChangePasswordForm, EditGeneralSettingsForm, + EditNotificationSettingsForm) +from .. import db +from ..decorators import admin_required +from ..models import Role, User +import os +import uuid + + +@settings.route('/') +@login_required +def index(): + return redirect(url_for('.edit_general_settings')) + + +@settings.route('/change_password', methods=['GET', 'POST']) +@login_required +def change_password(): + form = ChangePasswordForm() + if form.validate_on_submit(): + current_user.password = form.new_password.data + db.session.commit() + flash('Your password has been updated.') + return redirect(url_for('.change_password')) + return render_template('settings/change_password.html.j2', + form=form, + title='Change password') + + +@settings.route('/edit_general_settings') +@login_required +def edit_general_settings(): + form = EditGeneralSettingsForm() + if form.validate_on_submit(): + current_user.email = form.email.data + current_user.setting_dark_mode = form.dark_mode.data + current_user.username = form.username.data + db.session.commit() + flash('Your changes have been saved.') + return render_template('settings/edit_general_settings.html.j2', + form=form, + title='General settings') + + +@settings.route('/edit_notification_settings') +@login_required +def edit_notification_settings(): + form = EditNotificationSettingsForm() + if form.validate_on_submit(): + current_user.setting_job_status_mail_notifications = \ + form.job_status_mail_notifications.data + current_user.setting_job_status_site_notifications = \ + form.job_status_site_notifications.data + db.session.commit() + flash('Your changes have been saved.') + return render_template('settings/edit_notification_settings.html.j2', + form=form, + title='Notification settings') + + +@settings.route('/delete') +@login_required +def delete(): + """ + View to delete current_user and all associated data. + """ + tasks.delete_user(current_user.id) + logout_user() + flash('Your account has been deleted!') + return redirect(url_for('main.index')) diff --git a/web/app/templates/nopaque.html.j2 b/web/app/templates/nopaque.html.j2 index c9097dd1..c5576a06 100644 --- a/web/app/templates/nopaque.html.j2 +++ b/web/app/templates/nopaque.html.j2 @@ -84,8 +84,7 @@ assignmentRegister loginLog in {% else %} - bookMy Corpora - workMy Jobs + dashboardDashboard
  • more_vert
  • {% endif %} @@ -98,7 +97,7 @@ {% if current_user.is_authenticated %} @@ -127,7 +126,7 @@
  • format_textdirection_l_to_rNLP
  • searchCorpus analysis
  • -
  • settingsSettings
  • +
  • settingsSettings
  • Log out
  • {% else %}
  • assignmentRegister
  • diff --git a/web/app/templates/profile/settings.html.j2 b/web/app/templates/profile/settings.html.j2 deleted file mode 100644 index 64e5152b..00000000 --- a/web/app/templates/profile/settings.html.j2 +++ /dev/null @@ -1,136 +0,0 @@ -{% extends "nopaque.html.j2" %} - -{% block page_content %} -
    -

    General settings

    -
    -
    -
    -
    -
    -
    - {{ edit_general_settings_form.hidden_tag() }} -
    -
    -

    brightness_3{{ edit_general_settings_form.dark_mode.label.text }}

    -

    Activate dark mode to ease your eyes.

    -
    -
    - {{ M.render_field(edit_general_settings_form.dark_mode, label=False) }} -
    -

     

    -
    -

     

    -
    -

    notificationsJob status site notifications

    -

    Receive site notifications about job status changes.

    -
    -
    - {{ M.render_field(edit_general_settings_form.job_status_site_notifications, label=False) }} -
    -

     

    -
    -

     

    -
    -

    notificationsJob status mail notifications

    -

    Receive mail notifications about job status changes.

    -
    -
    - {{ M.render_field(edit_general_settings_form.job_status_mail_notifications, label=False) }} -
    - -
    -
    -
    - {{ M.render_field(edit_general_settings_form.save_settings, material_icon='send') }} -
    -
    -
    -
    - - -
    - - -
    -

    Change password

    -
    -
    -
    -
    -
    -
    - {{ edit_password_form.hidden_tag() }} - {{ M.render_field(edit_password_form.current_password, data_length='128', material_icon='vpn_key') }} - {{ M.render_field(edit_password_form.password, data_length='128', material_icon='vpn_key') }} - {{ M.render_field(edit_password_form.password_confirmation, data_length='128', material_icon='vpn_key') }} -
    -
    - {{ M.render_field(edit_password_form.save_password, material_icon='send') }} -
    -
    -
    -
    - - -
    - - -
    -

    Change email

    -
    -
    -
    -
    -
    -
    - {{ edit_email_form.hidden_tag() }} - {{ M.render_field(edit_email_form.email, class_='validate', material_icon='email', type='email') }} -
    -
    - {{ M.render_field(edit_email_form.save_email, material_icon='send') }} -
    -
    -
    -
    - - -
    - - -
    -

    Delete account

    -
    -
    -
    -
    -
    -

    Deleting an account has the following effects:

    -
      -
    • All data associated with your corpora and jobs will be permanently deleted.
    • -
    • All settings will be permanently deleted.
    • -
    -
    - -
    -
    - - - - -{% endblock %} diff --git a/web/app/templates/settings/_menu.html.j2 b/web/app/templates/settings/_menu.html.j2 new file mode 100644 index 00000000..554a3f67 --- /dev/null +++ b/web/app/templates/settings/_menu.html.j2 @@ -0,0 +1,5 @@ + diff --git a/web/app/templates/settings/change_password.html.j2 b/web/app/templates/settings/change_password.html.j2 new file mode 100644 index 00000000..771886bf --- /dev/null +++ b/web/app/templates/settings/change_password.html.j2 @@ -0,0 +1,35 @@ +{% extends 'nopaque.html.j2' %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% block page_content %} +
    +
    +
    +

    Settings

    +
    + +
    + {% include 'settings/_menu.html.j2' %} +
    + +
    +
    +
    +
    + {{ title }} + {{ form.hidden_tag() }} + {{ wtf.render_field(form.password, material_icon='vpn_key') }} + {{ wtf.render_field(form.new_password, material_icon='vpn_key') }} + {{ wtf.render_field(form.new_password2, material_icon='vpn_key') }} +
    +
    +
    + {{ wtf.render_field(form.submit, material_icon='send') }} +
    +
    +
    +
    +
    +
    +
    +{% endblock page_content %} diff --git a/web/app/templates/settings/edit_general_settings.html.j2 b/web/app/templates/settings/edit_general_settings.html.j2 new file mode 100644 index 00000000..4126ee8e --- /dev/null +++ b/web/app/templates/settings/edit_general_settings.html.j2 @@ -0,0 +1,63 @@ +{% extends 'nopaque.html.j2' %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% block page_content %} +
    +
    +
    +

    Settings

    +
    + +
    + {% include 'settings/_menu.html.j2' %} +
    + +
    +
    +
    +
    + {{ title }} + {{ form.hidden_tag() }} + {{ wtf.render_field(form.username, data_length='64', material_icon='person') }} + {{ wtf.render_field(form.email, data_length='254', material_icon='email') }} + {{ wtf.render_field(form.dark_mode, material_icon='brightness_3') }} +
    +
    +
    + + {{ wtf.render_field(form.submit, material_icon='send') }} +
    +
    +
    +
    + +
    +
    + Delete account +

    Deleting an account has the following effects:

    +
      +
    • All data associated with your corpora and jobs will be permanently deleted.
    • +
    • All settings will be permanently deleted.
    • +
    +
    + +
    +
    +
    +
    + + + + +{% endblock page_content %} diff --git a/web/app/templates/settings/edit_notification_settings.html.j2 b/web/app/templates/settings/edit_notification_settings.html.j2 new file mode 100644 index 00000000..2a8e7b24 --- /dev/null +++ b/web/app/templates/settings/edit_notification_settings.html.j2 @@ -0,0 +1,34 @@ +{% extends 'nopaque.html.j2' %} +{% import 'materialize/wtf.html.j2' as wtf %} + +{% block page_content %} +
    +
    +
    +

    Settings

    +
    + +
    + {% include 'settings/_menu.html.j2' %} +
    + +
    +
    +
    +
    + {{ title }} + {{ form.hidden_tag() }} + {{ wtf.render_field(form.job_status_mail_notifications, material_icon='notifications') }} + {{ wtf.render_field(form.job_status_site_notifications, material_icon='feedback') }} +
    +
    +
    + {{ wtf.render_field(form.submit, material_icon='send') }} +
    +
    +
    +
    +
    +
    +
    +{% endblock page_content %} diff --git a/web/config.py b/web/config.py index 0f78e6ad..4ca3704f 100644 --- a/web/config.py +++ b/web/config.py @@ -31,6 +31,7 @@ class Config: ''' # General # ''' ADMIN_EMAIL_ADRESS = os.environ.get('NOPAQUE_ADMIN_EMAIL_ADRESS') + ALLOWED_USERNAME_REGEX = '^[A-Za-zÄÖÜäöüß0-9_.]*$' CONTACT_EMAIL_ADRESS = os.environ.get('NOPAQUE_CONTACT_EMAIL_ADRESS') DATA_DIR = os.environ.get('NOPAQUE_DATA_DIR', '/mnt/nopaque') SECRET_KEY = os.environ.get('NOPAQUE_SECRET_KEY', 'hard to guess string')