small update settings page+new package 'settings'

This commit is contained in:
Inga Kirschnick 2023-03-17 15:56:37 +01:00
parent 823e42faf0
commit 41096445a6
15 changed files with 256 additions and 416 deletions

View File

@ -91,6 +91,10 @@ def create_app(config: Config = Config) -> Flask:
default_breadcrumb_root(services_blueprint, '.services') default_breadcrumb_root(services_blueprint, '.services')
app.register_blueprint(services_blueprint, url_prefix='/services') app.register_blueprint(services_blueprint, url_prefix='/services')
from .settings import bp as settings_blueprint
default_breadcrumb_root(settings_blueprint, '.settings')
app.register_blueprint(settings_blueprint, url_prefix='/settings')
from .users import bp as users_blueprint from .users import bp as users_blueprint
default_breadcrumb_root(users_blueprint, '.social_area.users') default_breadcrumb_root(users_blueprint, '.social_area.users')
app.register_blueprint(users_blueprint, url_prefix='/users') app.register_blueprint(users_blueprint, url_prefix='/users')

6
app/settings/__init__.py Normal file
View File

@ -0,0 +1,6 @@
from flask import Blueprint
bp = Blueprint('settings', __name__)
from . import routes, json_routes

View File

@ -56,6 +56,9 @@ class EditProfileSettingsForm(FlaskForm):
raise ValidationError('Username already in use') raise ValidationError('Username already in use')
class EditPublicProfileInformationForm(FlaskForm): class EditPublicProfileInformationForm(FlaskForm):
show_email = BooleanField('Email')
show_last_seen = BooleanField('Last seen')
show_member_since = BooleanField('Member since')
avatar = FileField( avatar = FileField(
'Image File', 'Image File',
[FileSizeLimit(max_size_in_mb=2)] [FileSizeLimit(max_size_in_mb=2)]
@ -97,13 +100,6 @@ class EditPublicProfileInformationForm(FlaskForm):
if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'): if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'):
raise ValidationError('only .jpg, .png and .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()
class ChangePasswordForm(FlaskForm): class ChangePasswordForm(FlaskForm):
password = PasswordField('Old password', validators=[DataRequired()]) password = PasswordField('Old password', validators=[DataRequired()])
new_password = PasswordField( new_password = PasswordField(

View File

@ -1,4 +1,4 @@
from flask import abort, current_app from flask import abort, current_app, request
from flask_login import current_user, login_required, logout_user from flask_login import current_user, login_required, logout_user
from threading import Thread from threading import Thread
import os import os
@ -52,3 +52,22 @@ def delete_profile_avatar(user_id):
'message': f'Avatar marked for deletion' 'message': f'Avatar marked for deletion'
} }
return response_data, 202 return response_data, 202
@bp.route('/<hashid:user_id>/is_public', methods=['PUT'])
@login_required
@content_negotiation(consumes='application/json', produces='application/json')
def update_user_is_public(user_id):
is_public = request.json
if not isinstance(is_public, bool):
abort(400)
user = User.query.get_or_404(user_id)
user.is_public = is_public
db.session.commit()
response_data = {
'message': (
f'User "{user.username}" is now'
f' {"public" if is_public else "private"}'
),
'category': 'corpus'
}
return response_data, 200

108
app/settings/routes.py Normal file
View File

@ -0,0 +1,108 @@
from flask import (
abort,
flash,
redirect,
render_template,
url_for
)
from flask_breadcrumbs import register_breadcrumb
from flask_login import current_user, login_required
import os
from app import db
from app.models import Avatar, Corpus, ProfilePrivacySettings, User
from . import bp
from .forms import (
ChangePasswordForm,
EditNotificationSettingsForm,
EditProfileSettingsForm,
EditPublicProfileInformationForm
)
from .utils import user_endpoint_arguments_constructor as user_eac
@bp.route('/<hashid:user_id>/edit', methods=['GET', 'POST'])
@register_breadcrumb(bp, 'breadcrumbs.settings', '<i class="material-icons left">settings</i>Settings', endpoint_arguments_constructor=user_eac)
@login_required
def edit_profile(user_id):
user = User.query.get_or_404(user_id)
if not (user == current_user or current_user.is_administrator()):
abort(403)
# region forms
edit_profile_settings_form = EditProfileSettingsForm(
current_user,
data=current_user.to_json_serializeable(),
prefix='edit-profile-settings-form'
)
edit_public_profile_information_form = EditPublicProfileInformationForm(
data=current_user.to_json_serializeable(),
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():
current_user.email = edit_profile_settings_form.email.data
current_user.username = edit_profile_settings_form.username.data
db.session.commit()
flash('Profile settings updated')
return redirect(url_for('users.user', user_id=user.id))
# endregion handle edit profile settings forms
# region handle edit public profile information form
if edit_public_profile_information_form.submit.data and edit_public_profile_information_form.validate():
print(edit_public_profile_information_form.show_email.data)
if edit_public_profile_information_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_public_profile_information_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_public_profile_information_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)
if edit_public_profile_information_form.avatar.data:
try:
Avatar.create(edit_public_profile_information_form.avatar.data, user=current_user)
except (AttributeError, OSError):
abort(500)
current_user.about_me = edit_public_profile_information_form.about_me.data
current_user.location = edit_public_profile_information_form.location.data
current_user.organization = edit_public_profile_information_form.organization.data
current_user.website = edit_public_profile_information_form.website.data
current_user.full_name = edit_public_profile_information_form.full_name.data
db.session.commit()
flash('Profile settings updated')
return redirect(url_for('users.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(
'settings/edit_profile.html.j2',
edit_profile_settings_form=edit_profile_settings_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,
title='Settings'
)

6
app/settings/utils.py Normal file
View File

@ -0,0 +1,6 @@
from flask import request, url_for
from app.models import User
def user_endpoint_arguments_constructor():
return {'user_id': request.view_args['user_id']}

View File

@ -0,0 +1,35 @@
/*****************************************************************************
* Users *
* Fetch requests for /users routes *
*****************************************************************************/
Requests.settings = {};
Requests.settings.entity = {};
Requests.settings.entity.delete = (userId) => {
let input = `/settings/${userId}`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
}
Requests.settings.entity.deleteAvatar = (userId) => {
let input = `/settings/${userId}/avatar`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
}
Requests.settings.entity.isPublic = {};
Requests.settings.entity.isPublic.update = (userId, isPublic) => {
let input = `/settings/${userId}/is_public`;
let init = {
method: 'PUT',
body: JSON.stringify(isPublic)
};
return Requests.JSONfetch(input, init);
};

View File

@ -1,23 +0,0 @@
/*****************************************************************************
* Users *
* Fetch requests for /users routes *
*****************************************************************************/
Requests.users = {};
Requests.users.entity = {};
Requests.users.entity.delete = (userId) => {
let input = `/users/${userId}`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
}
Requests.users.entity.deleteAvatar = (userId) => {
let input = `/users/${userId}/avatar`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
}

View File

@ -32,7 +32,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('users.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User settings</a></li> <li><a href="{{ url_for('settings.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User 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 %}

View File

@ -65,7 +65,7 @@
'js/Requests/corpora/files.js', 'js/Requests/corpora/files.js',
'js/Requests/corpora/followers.js', 'js/Requests/corpora/followers.js',
'js/Requests/jobs/jobs.js', 'js/Requests/jobs/jobs.js',
'js/Requests/users/users.js' 'js/Requests/settings/settings.js'
%} %}
<script src="{{ ASSET_URL }}"></script> <script src="{{ ASSET_URL }}"></script>
{%- endassets %} {%- endassets %}

View File

@ -46,7 +46,7 @@
<li><a href="{{ url_for('main.social_area', _anchor='public-corproa') }}"><i class="nopaque-icons">I</i>Public Corpora</a></li> <li><a href="{{ url_for('main.social_area', _anchor='public-corproa') }}"><i class="nopaque-icons">I</i>Public Corpora</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('users.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User Settings</a></li> <li><a href="{{ url_for('settings.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User 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>

View File

@ -16,7 +16,8 @@
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
Stet clita kasd gubergren, no sea tak</p> Stet clita kasd gubergren, no sea tak</p>
</div> </div>
<div class="col s8" style="margin-top: 22.8px;"> <div class="col s8">
<br>
<ul class="collapsible"> <ul class="collapsible">
<li> <li>
<div class="collapsible-header" style="justify-content: space-between;"> <div class="collapsible-header" style="justify-content: space-between;">
@ -38,7 +39,7 @@
</form> </form>
</div> </div>
</li> </li>
<li> {# <li>
<div class="collapsible-header" style="justify-content: space-between;"><span>Privacy Settings</span><i class="material-icons caret right">keyboard_arrow_right</i></div> <div class="collapsible-header" style="justify-content: space-between;"><span>Privacy Settings</span><i class="material-icons caret right">keyboard_arrow_right</i></div>
<div class="collapsible-body"> <div class="collapsible-body">
<form method="POST"> <form method="POST">
@ -59,21 +60,48 @@
</div> </div>
</form> </form>
</div> </div>
</li> </li> #}
<li> <li>
<div class="collapsible-header" style="justify-content: space-between;"><span>Public Profile</span><i class="material-icons caret right">keyboard_arrow_right</i></div> <div class="collapsible-header" style="justify-content: space-between;"><span>Public Profile</span><i class="material-icons caret right">keyboard_arrow_right</i></div>
<div class="collapsible-body"> <div class="collapsible-body">
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
{{ edit_public_profile_information_form.hidden_tag() }} {{ edit_public_profile_information_form.hidden_tag() }}
<div class="switch">
<label>
private
<input {% if user.is_public %}checked{% endif %} id="profile-is-public-switch" type="checkbox">
<span class="lever"></span>
public
</label>
</div>
<p></p>
<br>
<div class="divider"></div>
<p>Show:</p>
<div class="row"> <div class="row">
<div class="col s12 m2"> <div class="col s3">
<img src="{{ url_for('.profile_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img" id="avatar"> <p>
<label>
{{ edit_public_profile_information_form.show_email() }}
<span>{{ edit_public_profile_information_form.show_email.label.text }}</span>
</label>
</p>
</div> </div>
<div class="col s12 m6"> <div class="col s3">
{{ wtf.render_field(edit_public_profile_information_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload') }} <p>
<label>
{{ edit_public_profile_information_form.show_last_seen() }}
<span>{{ edit_public_profile_information_form.show_last_seen.label.text }}</span>
</label>
</p>
</div> </div>
<div class="col s12 m1"> <div class="col s4">
<a class="btn-floating red waves-effect waves-light modal-trigger" style="margin-top:15px;" href="#delete-avatar-modal"><i class="material-icons">delete</i></a> <p>
<label>
{{ edit_public_profile_information_form.show_member_since() }}
<span>{{ edit_public_profile_information_form.show_member_since.label.text }}</span>
</label>
</p>
</div> </div>
</div> </div>
<p></p> <p></p>
@ -83,6 +111,20 @@
{{ wtf.render_field(edit_public_profile_information_form.website, material_icon='laptop') }} {{ 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.organization, material_icon='business') }}
{{ wtf.render_field(edit_public_profile_information_form.location, material_icon='location_on') }} {{ wtf.render_field(edit_public_profile_information_form.location, material_icon='location_on') }}
<p></p>
<div class="row">
<div class="col s12 m2">
<img src="{{ url_for('users.profile_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img" id="avatar">
</div>
<div class="col s12 m6">
{{ wtf.render_field(edit_public_profile_information_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload') }}
</div>
<div class="col s12 m1">
<a class="btn-floating red waves-effect waves-light modal-trigger" style="margin-top:15px;" href="#delete-avatar-modal"><i class="material-icons">delete</i></a>
</div>
</div>
<br>
<p></p>
<div class="right-align"> <div class="right-align">
{{ wtf.render_field(edit_public_profile_information_form.submit, material_icon='send') }} {{ wtf.render_field(edit_public_profile_information_form.submit, material_icon='send') }}
</div> </div>
@ -100,7 +142,8 @@
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
Stet clita kasd gubergren, no sea tak</p> Stet clita kasd gubergren, no sea tak</p>
</div> </div>
<div class="col s8" style="margin-top: 22.8px;"> <div class="col s8">
<br>
<ul class="collapsible"> <ul class="collapsible">
<li> <li>
<div class="collapsible-header" style="justify-content: space-between;"> <div class="collapsible-header" style="justify-content: space-between;">
@ -182,36 +225,17 @@
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script> <script>
let publicProfile = document.querySelector('#public-profile');
let disableButtons = document.querySelectorAll('[data-action="disable"]');
let deleteButton = document.querySelector('#delete-avatar'); let deleteButton = document.querySelector('#delete-avatar');
let avatar = document.querySelector('#avatar'); let avatar = document.querySelector('#avatar');
let avatarUpload = document.querySelector('#avatar-upload'); let avatarUpload = document.querySelector('#avatar-upload');
for (let disableButton of disableButtons) {
disableButton.disabled = !publicProfile.checked;
}
publicProfile.addEventListener('change', () => {
if (publicProfile.checked) {
for (let disableButton of disableButtons) {
disableButton.disabled = false;
}
} else {
for (let disableButton of disableButtons) {
disableButton.checked = false;
disableButton.disabled = true;
}
}
});
avatarUpload.addEventListener('change', function() { avatarUpload.addEventListener('change', function() {
let file = this.files[0]; let file = this.files[0];
avatar.src = URL.createObjectURL(file); avatar.src = URL.createObjectURL(file);
}); });
deleteButton.addEventListener('click', () => { deleteButton.addEventListener('click', () => {
Requests.users.entity.deleteAvatar(currentUserId) Requests.settings.entity.deleteAvatar(currentUserId)
.then( .then(
(response) => { (response) => {
avatar.src = "{{ url_for('static', filename='images/user_avatar.png') }}"; avatar.src = "{{ url_for('static', filename='images/user_avatar.png') }}";
@ -220,7 +244,7 @@ deleteButton.addEventListener('click', () => {
}); });
document.querySelector('#delete-user-button').addEventListener('click', (event) => { document.querySelector('#delete-user-button').addEventListener('click', (event) => {
Requests.users.entity.delete(currentUserId) Requests.settings.entity.delete(currentUserId)
.then((response) => {window.location.href = '/';}); .then((response) => {window.location.href = '/';});
}); });
@ -239,5 +263,16 @@ document.querySelectorAll('.collapsible').forEach((collapsible) => {
}); });
}); });
// #region Public Switch
let profileIsPublicSwitchElement = document.querySelector('#profile-is-public-switch');
profileIsPublicSwitchElement.addEventListener('change', (event) => {
let newIsPublic = profileIsPublicSwitchElement.checked;
Requests.settings.entity.isPublic.update(currentUserId, newIsPublic)
.catch((response) => {
profileIsPublicSwitchElement.checked = !newIsPublic;
});
});
// #endregion Public Switch
</script> </script>
{% endblock scripts %} {% endblock scripts %}

View File

@ -1,236 +0,0 @@
{% extends "base.html.j2" %}
{% 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>
<div class="row">
<div class="col s12 m9 l10">
<div id="user-settings" class="card section scrollspy">
<form method="POST" enctype="multipart/form-data">
<div class="card-content">
<span class="card-title">User Settings</span>
<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 id="privacy-settings" class="card section scrollspy">
<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 id="public-profile-settings" class="card section scrollspy">
<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 s2"></div>
<div class="col s8">
<img src="{{ url_for('.profile_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img" id="avatar">
</div>
<div class="col s2"></div>
</div>
<div class="row">
<div class="col s2">
<div class="center-align">
<a class="btn-floating red waves-effect waves-light modal-trigger" style="margin-top:15px;" href="#delete-avatar-modal"><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') }}
</div>
</div>
</div>
<div class="col s7">
<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_public_profile_information_form.submit, material_icon='send') }}
</div>
</div>
</form>
</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>
{% endblock page_content %}
{% block modals %}
<div class="modal" id="delete-avatar-modal">
<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="btn modal-close waves-effect waves-light">Cancel</a>
<a class="btn modal-close red waves-effect waves-light" id="delete-avatar">Delete</a>
</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 %}
{% block scripts %}
{{ super() }}
<script>
let publicProfile = document.querySelector('#public-profile');
let disableButtons = document.querySelectorAll('[data-action="disable"]');
let deleteButton = document.querySelector('#delete-avatar');
let avatar = document.querySelector('#avatar');
let avatarUpload = document.querySelector('#avatar-upload');
for (let disableButton of disableButtons) {
disableButton.disabled = !publicProfile.checked;
}
publicProfile.addEventListener('change', () => {
if (publicProfile.checked) {
for (let disableButton of disableButtons) {
disableButton.disabled = false;
}
} else {
for (let disableButton of disableButtons) {
disableButton.checked = false;
disableButton.disabled = true;
}
}
});
avatarUpload.addEventListener('change', function() {
let file = this.files[0];
avatar.src = URL.createObjectURL(file);
});
deleteButton.addEventListener('click', () => {
Requests.users.entity.deleteAvatar(currentUserId)
.then(
(response) => {
avatar.src = "{{ url_for('static', filename='images/user_avatar.png') }}";
}
);
});
document.querySelector('#delete-user-button').addEventListener('click', (event) => {
Requests.users.entity.delete(currentUserId)
.then((response) => {window.location.href = '/';});
});
</script>
{% endblock scripts %}

View File

@ -81,7 +81,7 @@
<p></p> <p></p>
<br> <br>
{% if current_user.is_authenticated and current_user.hashid == user.id %} {% if current_user.is_authenticated and current_user.hashid == user.id %}
<a class="waves-effect waves-light btn-small" href="{{ url_for('.edit_profile', user_id=current_user.id) }}">Edit profile</a> <a class="waves-effect waves-light btn-small" href="{{ url_for('settings.edit_profile', user_id=current_user.id) }}">Edit profile</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -1,6 +1,5 @@
from flask import ( from flask import (
abort, abort,
flash,
redirect, redirect,
render_template, render_template,
send_from_directory, send_from_directory,
@ -10,19 +9,9 @@ from flask_breadcrumbs import register_breadcrumb
from flask_login import current_user, login_required from flask_login import current_user, login_required
import os import os
from app import db from app import db
from app.models import Avatar, Corpus, ProfilePrivacySettings, User from app.models import Corpus, User
from . import bp from . import bp
from .forms import ( from .utils import user_dynamic_list_constructor as user_dlc
ChangePasswordForm,
EditNotificationSettingsForm,
EditPrivacySettingsForm,
EditProfileSettingsForm,
EditPublicProfileInformationForm
)
from .utils import (
user_endpoint_arguments_constructor as user_eac,
user_dynamic_list_constructor as user_dlc
)
@bp.route('') @bp.route('')
@ -75,102 +64,3 @@ def profile_avatar(user_id):
attachment_filename=user.avatar.filename, attachment_filename=user.avatar.filename,
mimetype=user.avatar.mimetype mimetype=user.avatar.mimetype
) )
@bp.route('/<hashid:user_id>/edit', methods=['GET', 'POST'])
@register_breadcrumb(bp, 'breadcrumbs.settings', '<i class="material-icons left">settings</i>Settings', endpoint_arguments_constructor=user_eac)
@login_required
def edit_profile(user_id):
user = User.query.get_or_404(user_id)
if not (user == current_user or current_user.is_administrator()):
abort(403)
# region forms
edit_profile_settings_form = EditProfileSettingsForm(
current_user,
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'
)
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():
current_user.email = edit_profile_settings_form.email.data
current_user.username = edit_profile_settings_form.username.data
db.session.commit()
flash('Profile settings updated')
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():
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('.user', user_id=user.id))
# 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:
try:
Avatar.create(edit_public_profile_information_form.avatar.data, user=current_user)
except (AttributeError, OSError):
abort(500)
current_user.about_me = edit_public_profile_information_form.about_me.data
current_user.location = edit_public_profile_information_form.location.data
current_user.organization = edit_public_profile_information_form.organization.data
current_user.website = edit_public_profile_information_form.website.data
current_user.full_name = edit_public_profile_information_form.full_name.data
db.session.commit()
flash('Profile settings updated')
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(
'users/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,
change_password_form=change_password_form,
edit_notification_settings_form=edit_notification_settings_form,
user=user,
title='Settings'
)