Skip to content
Snippets Groups Projects
Commit 4c05c6cc authored by Inga Kirschnick's avatar Inga Kirschnick
Browse files

merge settings into users route

parent 018805a1
No related branches found
No related tags found
No related merge requests found
...@@ -81,9 +81,6 @@ def create_app(config: Config = Config) -> Flask: ...@@ -81,9 +81,6 @@ def create_app(config: Config = Config) -> Flask:
from .services import bp as services_blueprint from .services import bp as services_blueprint
app.register_blueprint(services_blueprint, url_prefix='/services') app.register_blueprint(services_blueprint, url_prefix='/services')
from .settings import bp as settings_blueprint
app.register_blueprint(settings_blueprint, url_prefix='/settings')
from .users import bp as users_blueprint from .users import bp as users_blueprint
app.register_blueprint(users_blueprint, url_prefix='/users') app.register_blueprint(users_blueprint, url_prefix='/users')
......
...@@ -4,7 +4,7 @@ from threading import Thread ...@@ -4,7 +4,7 @@ from threading import Thread
from app import db, hashids from app import db, hashids
from app.decorators import admin_required from app.decorators import admin_required
from app.models import Role, User, UserSettingJobStatusMailNotificationLevel from app.models import Role, User, UserSettingJobStatusMailNotificationLevel
from app.settings.forms import ( from app.users.forms import (
EditNotificationSettingsForm EditNotificationSettingsForm
) )
from app.users.forms import EditProfileSettingsForm from app.users.forms import EditProfileSettingsForm
......
from flask import Blueprint
bp = Blueprint('settings', __name__)
from . import routes # noqa
from flask_wtf import FlaskForm
from wtforms import PasswordField, SelectField, SubmitField, ValidationError
from wtforms.validators import DataRequired, EqualTo
from app.models import UserSettingJobStatusMailNotificationLevel
class ChangePasswordForm(FlaskForm):
password = PasswordField('Old password', validators=[DataRequired()])
new_password = PasswordField(
'New password',
validators=[
DataRequired(),
EqualTo('new_password_2', message='Passwords must match')
]
)
new_password_2 = PasswordField(
'New password confirmation',
validators=[
DataRequired(),
EqualTo('new_password', message='Passwords must match')
]
)
submit = SubmitField()
def __init__(self, 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 EditNotificationSettingsForm(FlaskForm):
job_status_mail_notification_level = SelectField(
'Job status mail notification level',
choices=[
(x.name, x.name.capitalize())
for x in UserSettingJobStatusMailNotificationLevel
],
validators=[DataRequired()]
)
submit = SubmitField()
from flask import flash, redirect, render_template, url_for
from flask_login import current_user, login_required
from app import db
from app.models import UserSettingJobStatusMailNotificationLevel
from . import bp
from .forms import ChangePasswordForm, EditNotificationSettingsForm
@bp.route('', methods=['GET', 'POST'])
@login_required
def settings():
change_password_form = ChangePasswordForm(
current_user,
prefix='change-password-form'
)
edit_notification_settings_form = EditNotificationSettingsForm(
data=current_user.to_json_serializeable(),
prefix='edit-notification-settings-form'
)
# region handle change_password_form POST
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('.settings'))
# endregion handle change_password_form POST
# region handle edit_notification_settings_form POST
if edit_notification_settings_form.submit and edit_notification_settings_form.validate():
current_user.setting_job_status_mail_notification_level = edit_notification_settings_form.job_status_mail_notification_level.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.settings'))
# endregion handle edit_notification_settings_form POST
return render_template(
'settings/settings.html.j2',
change_password_form=change_password_form,
edit_notification_settings_form=edit_notification_settings_form,
title='Settings'
)
...@@ -29,8 +29,7 @@ ...@@ -29,8 +29,7 @@
<ul class="dropdown-content" id="nav-more-dropdown"> <ul class="dropdown-content" id="nav-more-dropdown">
<li><a href="{{ url_for('main.user_manual') }}"><i class="material-icons left">help</i>Manual</a></li> <li><a href="{{ url_for('main.user_manual') }}"><i class="material-icons left">help</i>Manual</a></li>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<li><a href="{{ url_for('settings.settings') }}"><i class="material-icons left">settings</i>General settings</a></li> <li><a href="{{ url_for('users.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User settings</a></li>
<li><a href="{{ url_for('users.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">contact_page</i>Profile settings</a></li>
<li class="divider" tabindex="-1"></li> <li class="divider" tabindex="-1"></li>
<li><a href="{{ url_for('auth.logout') }}">Log out</a></li> <li><a href="{{ url_for('auth.logout') }}">Log out</a></li>
{% else %} {% else %}
......
...@@ -38,8 +38,7 @@ ...@@ -38,8 +38,7 @@
<li class="service-color service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.corpus_analysis') }}"><i class="nopaque-icons service-icons" data-service="corpus-analysis"></i>Corpus analysis</a></li> <li class="service-color service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.corpus_analysis') }}"><i class="nopaque-icons service-icons" data-service="corpus-analysis"></i>Corpus analysis</a></li>
<li><div class="divider"></div></li> <li><div class="divider"></div></li>
<li><a class="subheader">Account</a></li> <li><a class="subheader">Account</a></li>
<li><a href="{{ url_for('settings.settings') }}"><i class="material-icons">settings</i>General Settings</a></li> <li><a href="{{ url_for('users.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User Settings</a></li>
<li><a href="{{ url_for('users.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> <li><a href="{{ url_for('auth.logout') }}">Log out</a></li>
{% if current_user.can('ADMINISTRATE') or current_user.can('USE_API') %} {% if current_user.can('ADMINISTRATE') or current_user.can('USE_API') %}
<li><div class="divider"></div></li> <li><div class="divider"></div></li>
......
{% set breadcrumbs %}
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
{% if request.path == url_for('settings.settings') %}
<li class="tab"><a{%if request.path == url_for('settings.settings') %} class="active"{% endif %} href="{{ url_for('settings.settings') }}" target="_self">Settings</a></li>
{% endif %}
{% endset %}
{% extends "base.html.j2" %}
{% from "settings/_breadcrumbs.html.j2" import breadcrumbs with context %}
{% import "materialize/wtf.html.j2" as wtf %}
{% block page_content %}
<div class="container">
<div class="row">
<div class="col s12">
<h1 id="title">{{ title }}</h1>
</div>
<div class="col s12">
<form method="POST">
{{ edit_notification_settings_form.hidden_tag() }}
<div class="card">
<div class="card-content">
<span class="card-title">Notification settings</span>
{{ wtf.render_field(edit_notification_settings_form.job_status_mail_notification_level, material_icon='notifications') }}
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(edit_notification_settings_form.submit, material_icon='send') }}
</div>
</div>
</div>
</form>
<form method="POST">
{{ change_password_form.hidden_tag() }}
<div class="card">
<div class="card-content">
<span class="card-title">Change Password</span>
{{ wtf.render_field(change_password_form.password, material_icon='vpn_key') }}
{{ wtf.render_field(change_password_form.new_password, material_icon='vpn_key') }}
{{ wtf.render_field(change_password_form.new_password_2, material_icon='vpn_key') }}
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(change_password_form.submit, material_icon='send') }}
</div>
</div>
</div>
</form>
<div class="card">
<div class="card-content">
<span class="card-title">Delete account</span>
<p>Deleting an account has the following effects:</p>
<ul>
<li>All data associated with your corpora and jobs will be permanently deleted.</li>
<li>All settings will be permanently deleted.</li>
</ul>
</div>
<div class="card-action right-align">
<a class="btn red waves-effect waves-light modal-trigger" href="#delete-user"><i class="material-icons left">delete</i>Delete</a>
</div>
</div>
</div>
</div>
</div>
{% endblock page_content %}
{% block modals%}
<div class="modal" id="delete-user">
<div class="modal-content">
<h4>Confirm User deletion</h4>
<p>Do you really want to delete the User <b>{{ current_user.username }}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Cancel</a>
<a class="btn modal-close red waves-effect waves-light" id="delete-user-button">Delete</a>
</div>
</div>
{% endblock modals %}
{% block scripts %}
{{ super() }}
<script>
document.querySelector('#delete-user-button').addEventListener('click', (event) => {
Requests.users.entity.delete(currentUserId)
.then((response) => {window.location.href = '/';});
});
</script>
{% endblock scripts %}
...@@ -7,10 +7,11 @@ ...@@ -7,10 +7,11 @@
<div class="col s12"> <div class="col s12">
<h1 id="title">{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
</div>
<div class="col s12"> <div class="row">
<div class="col s12 m9 l10">
<div class="card"> <div id="user-settings" class="card section scrollspy">
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
<div class="card-content"> <div class="card-content">
<div class="row"> <div class="row">
...@@ -31,7 +32,7 @@ ...@@ -31,7 +32,7 @@
<form method="POST"> <form method="POST">
{{ edit_privacy_settings_form.hidden_tag() }} {{ edit_privacy_settings_form.hidden_tag() }}
<div class="card"> <div id="privacy-settings" class="card section scrollspy">
<div class="card-content"> <div class="card-content">
<span class="card-title">Privacy settings</span> <span class="card-title">Privacy settings</span>
<br> <br>
...@@ -54,7 +55,7 @@ ...@@ -54,7 +55,7 @@
</div> </div>
</form> </form>
<div class="card"> <div id="public-profile-settings" class="card section scrollspy">
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
<div class="card-content"> <div class="card-content">
{{ edit_public_profile_information_form.hidden_tag() }} {{ edit_public_profile_information_form.hidden_tag() }}
...@@ -106,6 +107,62 @@ ...@@ -106,6 +107,62 @@
</form> </form>
</div> </div>
<form method="POST">
{{ edit_notification_settings_form.hidden_tag() }}
<div id="notification-settings" class="card section scrollspy">
<div class="card-content">
<span class="card-title">Notification settings</span>
{{ wtf.render_field(edit_notification_settings_form.job_status_mail_notification_level, material_icon='notifications') }}
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(edit_notification_settings_form.submit, material_icon='send') }}
</div>
</div>
</div>
</form>
<form method="POST">
{{ change_password_form.hidden_tag() }}
<div id="change-password" class="card section scrollspy">
<div class="card-content">
<span class="card-title">Change Password</span>
{{ wtf.render_field(change_password_form.password, material_icon='vpn_key') }}
{{ wtf.render_field(change_password_form.new_password, material_icon='vpn_key') }}
{{ wtf.render_field(change_password_form.new_password_2, material_icon='vpn_key') }}
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(change_password_form.submit, material_icon='send') }}
</div>
</div>
</div>
</form>
<div id="delete-account" class="card section scrollspy">
<div class="card-content">
<span class="card-title">Delete account</span>
<p>Deleting an account has the following effects:</p>
<ul>
<li>All data associated with your corpora and jobs will be permanently deleted.</li>
<li>All settings will be permanently deleted.</li>
</ul>
</div>
<div class="card-action right-align">
<a class="btn red waves-effect waves-light modal-trigger" href="#delete-user"><i class="material-icons left">delete</i>Delete</a>
</div>
</div>
</div>
<div class="col hide-on-small-only m3 l2">
<ul class="section table-of-contents" style="position: fixed !important;">
<li><a href="#user-settings">User Settings</a></li>
<li><a href="#privacy-settings">Privacy Settings</a></li>
<li><a href="#public-profile-settings">Public Profile Settings</a></li>
<li><a href="#notification-settings">Notification Settings</a></li>
<li><a href="#change-password">Change Password</a></li>
<li><a href="#delete-account">Delete Account</a></li>
</ul>
</div> </div>
</div> </div>
</div> </div>
...@@ -122,4 +179,14 @@ ...@@ -122,4 +179,14 @@
<a class="btn modal-close red waves-effect waves-light" id="delete-avatar">Delete</a> <a class="btn modal-close red waves-effect waves-light" id="delete-avatar">Delete</a>
</div> </div>
</div> </div>
<div class="modal" id="delete-user">
<div class="modal-content">
<h4>Confirm User deletion</h4>
<p>Do you really want to delete the User <b>{{ current_user.username }}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Cancel</a>
<a class="btn modal-close red waves-effect waves-light" id="delete-user-button">Delete</a>
</div>
</div>
{% endblock modals %} {% endblock modals %}
...@@ -34,3 +34,8 @@ deleteButton.addEventListener('click', () => { ...@@ -34,3 +34,8 @@ deleteButton.addEventListener('click', () => {
} }
); );
}); });
document.querySelector('#delete-user-button').addEventListener('click', (event) => {
Requests.users.entity.delete(currentUserId)
.then((response) => {window.location.href = '/';});
});
...@@ -2,6 +2,8 @@ from flask_wtf import FlaskForm ...@@ -2,6 +2,8 @@ from flask_wtf import FlaskForm
from wtforms import ( from wtforms import (
BooleanField, BooleanField,
FileField, FileField,
PasswordField,
SelectField,
StringField, StringField,
SubmitField, SubmitField,
TextAreaField, TextAreaField,
...@@ -10,10 +12,11 @@ from wtforms import ( ...@@ -10,10 +12,11 @@ from wtforms import (
from wtforms.validators import ( from wtforms.validators import (
DataRequired, DataRequired,
Email, Email,
EqualTo,
Length, Length,
Regexp Regexp
) )
from app.models import User from app.models import User, UserSettingJobStatusMailNotificationLevel
from app.auth import USERNAME_REGEX from app.auth import USERNAME_REGEX
from app.wtf_validators import FileSizeLimit from app.wtf_validators import FileSizeLimit
...@@ -98,3 +101,41 @@ class EditPrivacySettingsForm(FlaskForm): ...@@ -98,3 +101,41 @@ class EditPrivacySettingsForm(FlaskForm):
show_last_seen = BooleanField('Show last seen') show_last_seen = BooleanField('Show last seen')
show_member_since = BooleanField('Show member since') show_member_since = BooleanField('Show member since')
submit = SubmitField() submit = SubmitField()
class ChangePasswordForm(FlaskForm):
password = PasswordField('Old password', validators=[DataRequired()])
new_password = PasswordField(
'New password',
validators=[
DataRequired(),
EqualTo('new_password_2', message='Passwords must match')
]
)
new_password_2 = PasswordField(
'New password confirmation',
validators=[
DataRequired(),
EqualTo('new_password', message='Passwords must match')
]
)
submit = SubmitField()
def __init__(self, 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 EditNotificationSettingsForm(FlaskForm):
job_status_mail_notification_level = SelectField(
'Job status mail notification level',
choices=[
(x.name, x.name.capitalize())
for x in UserSettingJobStatusMailNotificationLevel
],
validators=[DataRequired()]
)
submit = SubmitField()
...@@ -16,6 +16,8 @@ from app import db ...@@ -16,6 +16,8 @@ from app import db
from app.models import Avatar, Corpus, ProfilePrivacySettings, User from app.models import Avatar, Corpus, ProfilePrivacySettings, User
from . import bp from . import bp
from .forms import ( from .forms import (
ChangePasswordForm,
EditNotificationSettingsForm,
EditPrivacySettingsForm, EditPrivacySettingsForm,
EditProfileSettingsForm, EditProfileSettingsForm,
EditPublicProfileInformationForm EditPublicProfileInformationForm
...@@ -74,6 +76,7 @@ def edit_profile(user_id): ...@@ -74,6 +76,7 @@ def edit_profile(user_id):
user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
if not (user == current_user or current_user.is_administrator()): if not (user == current_user or current_user.is_administrator()):
abort(403) abort(403)
# region forms
edit_profile_settings_form = EditProfileSettingsForm( edit_profile_settings_form = EditProfileSettingsForm(
current_user, current_user,
data=current_user.to_json_serializeable(), data=current_user.to_json_serializeable(),
...@@ -87,12 +90,24 @@ def edit_profile(user_id): ...@@ -87,12 +90,24 @@ def edit_profile(user_id):
data=current_user.to_json_serializeable(), data=current_user.to_json_serializeable(),
prefix='edit-public-profile-information-form' prefix='edit-public-profile-information-form'
) )
change_password_form = ChangePasswordForm(
current_user,
prefix='change-password-form'
)
edit_notification_settings_form = EditNotificationSettingsForm(
data=current_user.to_json_serializeable(),
prefix='edit-notification-settings-form'
)
# endregion forms
# region handle edit profile settings form
if edit_profile_settings_form.validate_on_submit(): if edit_profile_settings_form.validate_on_submit():
current_user.email = edit_profile_settings_form.email.data current_user.email = edit_profile_settings_form.email.data
current_user.username = edit_profile_settings_form.username.data current_user.username = edit_profile_settings_form.username.data
db.session.commit() db.session.commit()
flash('Profile settings updated') flash('Profile settings updated')
return redirect(url_for('.user', user_id=user.id)) return redirect(url_for('.user', user_id=user.id))
# endregion handle edit profile settings form
# region handle edit privacy settings form
if edit_privacy_settings_form.submit.data and edit_privacy_settings_form.validate(): if edit_privacy_settings_form.submit.data and edit_privacy_settings_form.validate():
current_user.is_public = edit_privacy_settings_form.is_public.data current_user.is_public = edit_privacy_settings_form.is_public.data
if edit_privacy_settings_form.show_email.data: if edit_privacy_settings_form.show_email.data:
...@@ -110,7 +125,9 @@ def edit_profile(user_id): ...@@ -110,7 +125,9 @@ def edit_profile(user_id):
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.user', user_id=user.id)) return redirect(url_for('.user', user_id=user.id))
if edit_public_profile_information_form.validate_on_submit(): # endregion handle edit privacy settings form
# region handle edit public profile information form
if edit_public_profile_information_form.submit.data and edit_public_profile_information_form.validate():
if edit_public_profile_information_form.avatar.data: if edit_public_profile_information_form.avatar.data:
try: try:
Avatar.create(edit_public_profile_information_form.avatar.data, user=current_user) Avatar.create(edit_public_profile_information_form.avatar.data, user=current_user)
...@@ -124,11 +141,28 @@ def edit_profile(user_id): ...@@ -124,11 +141,28 @@ def edit_profile(user_id):
db.session.commit() db.session.commit()
flash('Profile settings updated') flash('Profile settings updated')
return redirect(url_for('.user', user_id=user.id)) return redirect(url_for('.user', user_id=user.id))
# endregion handle edit public profile information form
# region handle change_password_form POST
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('.edit_profile', user_id=user.id))
# endregion handle change_password_form POST
# region handle edit_notification_settings_form POST
if edit_notification_settings_form.submit and edit_notification_settings_form.validate():
current_user.setting_job_status_mail_notification_level = edit_notification_settings_form.job_status_mail_notification_level.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.edit_profile', user_id=user.id))
# endregion handle edit_notification_settings_form POST
return render_template( return render_template(
'users/edit_profile.html.j2', 'users/edit_profile.html.j2',
edit_profile_settings_form=edit_profile_settings_form, edit_profile_settings_form=edit_profile_settings_form,
edit_privacy_settings_form=edit_privacy_settings_form, edit_privacy_settings_form=edit_privacy_settings_form,
edit_public_profile_information_form=edit_public_profile_information_form, edit_public_profile_information_form=edit_public_profile_information_form,
change_password_form=change_password_form,
edit_notification_settings_form=edit_notification_settings_form,
user=user, user=user,
title='Edit Profile' title='Edit Profile'
) )
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment