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 @@
Deleting an account has the following effects:
-Deleting an account has the following effects:
+