Update admin user settings

This commit is contained in:
Patrick Jentsch 2023-03-29 14:32:35 +02:00
parent 9b2353105e
commit e4a8ad911f
16 changed files with 362 additions and 297 deletions

View File

@ -3,11 +3,12 @@ from app.forms import NopaqueForm
from app.models import Role
class AdminEditUserForm(NopaqueForm):
confirmed = BooleanField('Confirmed')
class UpdateUserForm(NopaqueForm):
role = SelectField('Role')
submit = SubmitField('Submit')
submit = SubmitField()
def __init__(self, *args, **kwargs):
def __init__(self, user, *args, **kwargs):
if 'data' not in kwargs:
kwargs['data'] = {'role': user.role.hashid}
super().__init__(*args, **kwargs)
self.role.choices = [(x.hashid, x.name) for x in Role.query.all()]

View File

@ -1,22 +1,23 @@
from flask import current_app
from threading import Thread
from flask import abort, request
from app import db
from app.decorators import content_negotiation
from app.models import User
from . import bp
@bp.route('/users/<hashid:user_id>/delete', methods=['DELETE'])
def delete_user(user_id):
def _delete_user(app, user_id):
with app.app_context():
user = User.query.get(user_id)
user.delete()
db.session.commit()
User.query.get_or_404(user_id)
thread = Thread(
target=_delete_user,
args=(current_app._get_current_object(), user_id)
)
thread.start()
return {}, 202
@bp.route('/users/<hashid:user_id>/confirmed', methods=['PUT'])
@content_negotiation(consumes='application/json', produces='application/json')
def update_user_role(user_id):
confirmed = request.json
if not isinstance(confirmed, bool):
abort(400)
user = User.query.get_or_404(user_id)
user.confirmed = confirmed
db.session.commit()
resonse_data = {
'message': (
f'User "{user.username}" is now '
f'{"confirmed" if confirmed else "unconfirmed"}'
)
}
return resonse_data, 200

View File

@ -1,15 +1,16 @@
from flask import abort, flash, redirect, render_template, url_for
from flask_breadcrumbs import register_breadcrumb
from app import db
from app.models import Avatar, Corpus, User
from app import db, hashids
from app.models import Avatar, Corpus, Role, User
from app.settings.forms import (
ChangePasswordForm,
EditNotificationsForm,
EditAccountForm,
EditProfileForm
UpdateAvatarForm,
UpdatePasswordForm,
UpdateNotificationsForm,
UpdateAccountInformationForm,
UpdateProfileInformationForm
)
from . import bp
from .forms import AdminEditUserForm
from .forms import UpdateUserForm
from app.users.utils import (
user_endpoint_arguments_constructor as user_eac,
user_dynamic_list_constructor as user_dlc
@ -62,112 +63,82 @@ def user(user_id):
@register_breadcrumb(bp, '.users.entity.settings', '<i class="material-icons left">settings</i>Settings')
def user_settings(user_id):
user = User.query.get_or_404(user_id)
# region forms
edit_account_form = EditAccountForm(user=user)
edit_profile_form = EditProfileForm(user=user)
change_password_form = ChangePasswordForm(user=user)
edit_notifications_form = EditNotificationsForm(user=user)
# endregion forms
# region handle edit profile settings form
if edit_account_form.validate_on_submit():
user.email = edit_account_form.email.data
user.username = edit_account_form.username.data
update_account_information_form = UpdateAccountInformationForm(user=user)
update_profile_information_form = UpdateProfileInformationForm(user=user)
update_avatar_form = UpdateAvatarForm(user=user)
update_password_form = UpdatePasswordForm(user=user)
update_notifications_form = UpdateNotificationsForm(user=user)
update_user_form = UpdateUserForm(user)
# region handle update profile information form
if update_profile_information_form.submit.data and update_profile_information_form.validate():
user.about_me = update_profile_information_form.about_me.data
user.location = update_profile_information_form.location.data
user.organization = update_profile_information_form.organization.data
user.website = update_profile_information_form.website.data
user.full_name = update_profile_information_form.full_name.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.user_settings', user_id=user.id))
# endregion handle update profile information form
# region handle update avatar form
if update_avatar_form.submit.data and update_avatar_form.validate():
try:
Avatar.create(
update_avatar_form.avatar.data,
user=user
)
except (AttributeError, OSError):
abort(500)
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.user_settings', user_id=user.id))
# endregion handle update avatar form
# region handle update account information form
if update_account_information_form.submit.data and update_account_information_form.validate():
user.email = update_account_information_form.email.data
user.username = update_account_information_form.username.data
db.session.commit()
flash('Profile settings updated')
return redirect(url_for('.user_settings'))
# endregion handle edit profile settings forms
# region handle edit public profile information form
if edit_profile_form.validate_on_submit():
if edit_profile_form.avatar.data:
try:
Avatar.create(
edit_profile_form.avatar.data,
user=user
)
except (AttributeError, OSError):
abort(500)
user.about_me = edit_profile_form.about_me.data
user.location = edit_profile_form.location.data
user.organization = edit_profile_form.organization.data
user.website = edit_profile_form.website.data
user.full_name = edit_profile_form.full_name.data
return redirect(url_for('.user_settings', user_id=user.id))
# endregion handle update account information form
# region handle update password form
if update_password_form.submit.data and update_password_form.validate():
user.password = update_password_form.new_password.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.user_settings'))
# endregion handle edit public profile information form
# region handle change_password_form POST
if change_password_form.validate_on_submit():
user.password = change_password_form.new_password.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.user_settings'))
# endregion handle change_password_form POST
# region handle edit_notification_settings_form POST
if edit_notifications_form.validate_on_submit():
return redirect(url_for('.user_settings', user_id=user.id))
# endregion handle update password form
# region handle update notifications form
if update_notifications_form.submit.data and update_notifications_form.validate():
user.setting_job_status_mail_notification_level = \
edit_notifications_form.job_status_mail_notification_level.data
update_notifications_form.job_status_mail_notification_level.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.user_settings'))
# endregion handle edit_notification_settings_form POST
return redirect(url_for('.user_settings', user_id=user.id))
# endregion handle update notifications form
# region handle update user form
if update_user_form.submit.data and update_user_form.validate():
role_id = hashids.decode(update_user_form.role.data)
user.role = Role.query.get(role_id)
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.user_settings', user_id=user.id))
# endregion handle update user form
return render_template(
'admin/user_settings.html.j2',
title='Settings',
change_password_form=change_password_form,
edit_account_form=edit_account_form,
edit_notifications_form=edit_notifications_form,
edit_profile_form=edit_profile_form,
update_account_information_form=update_account_information_form,
update_avatar_form=update_avatar_form,
update_notifications_form=update_notifications_form,
update_password_form=update_password_form,
update_profile_information_form=update_profile_information_form,
update_user_form=update_user_form,
user=user
)
# @bp.route('/users/<hashid:user_id>/edit', methods=['GET', 'POST'])
# @register_breadcrumb(bp, '.users.entity.edit', 'Edit', endpoint_arguments_constructor=user_eac)
# def edit_user(user_id):
# user = User.query.get_or_404(user_id)
# admin_edit_user_form = AdminEditUserForm(
# data={'confirmed': user.confirmed, 'role': user.role.hashid},
# prefix='admin-edit-user-form'
# )
# edit_profile_settings_form = EditAccountForm(
# user,
# data=user.to_json_serializeable(),
# prefix='edit-profile-settings-form'
# )
# edit_notification_settings_form = EditNotificationsForm(
# data=user.to_json_serializeable(),
# prefix='edit-notification-settings-form'
# )
# if (admin_edit_user_form.submit.data
# and admin_edit_user_form.validate()):
# user.confirmed = admin_edit_user_form.confirmed.data
# role_id = hashids.decode(admin_edit_user_form.role.data)
# user.role = Role.query.get(role_id)
# db.session.commit()
# flash('Your changes have been saved')
# return redirect(url_for('.edit_user', user_id=user.id))
# if (edit_profile_settings_form.submit.data
# and edit_profile_settings_form.validate()):
# user.email = edit_profile_settings_form.email.data
# user.username = edit_profile_settings_form.username.data
# db.session.commit()
# flash('Your changes have been saved')
# return redirect(url_for('.edit_user', user_id=user.id))
# if (edit_notification_settings_form.submit.data
# and edit_notification_settings_form.validate()):
# user.setting_job_status_mail_notification_level = \
# UserSettingJobStatusMailNotificationLevel[
# edit_notification_settings_form.job_status_mail_notification_level.data # noqa
# ]
# db.session.commit()
# flash('Your changes have been saved')
# return redirect(url_for('.edit_user', user_id=user.id))
# return render_template(
# 'admin/edit_user.html.j2',
# admin_edit_user_form=admin_edit_user_form,
# edit_profile_settings_form=edit_profile_settings_form,
# edit_notification_settings_form=edit_notification_settings_form,
# title='Edit user',
# user=user
# )

View File

@ -2,7 +2,6 @@ from apifairy.fields import FileField
from marshmallow import validate, validates, ValidationError
from marshmallow.decorators import post_dump
from app import ma
from app.auth import USERNAME_REGEX
from app.models import (
Job,
JobStatus,
@ -142,7 +141,10 @@ class UserSchema(ma.SQLAlchemySchema):
username = ma.auto_field(
validate=[
validate.Length(min=1, max=64),
validate.Regexp(USERNAME_REGEX, error='Usernames must have only letters, numbers, dots or underscores')
validate.Regexp(
User.username_pattern,
error='Usernames must have only letters, numbers, dots or underscores'
)
]
)
email = ma.auto_field(validate=validate.Email())

View File

@ -1,8 +1,5 @@
from flask import Blueprint
USERNAME_REGEX = '^[A-Za-zÄÖÜäöüß0-9_.]*$'
bp = Blueprint('auth', __name__)
from . import routes

View File

@ -8,7 +8,6 @@ from wtforms import (
from wtforms.validators import InputRequired, Email, EqualTo, Length, Regexp
from app.forms import NopaqueForm
from app.models import User
from . import USERNAME_REGEX
class RegistrationForm(NopaqueForm):
@ -22,7 +21,7 @@ class RegistrationForm(NopaqueForm):
InputRequired(),
Length(max=64),
Regexp(
USERNAME_REGEX,
User.username_pattern,
message=(
'Usernames must have only letters, numbers, dots or '
'underscores'

View File

@ -1,9 +1,26 @@
from flask_wtf import FlaskForm
from wtforms.validators import ValidationError
import re
form_prefix_pattern = re.compile(r'(?<!^)(?=[A-Z])')
def LimitFileSize(max_size_mb):
max_size_b = max_size_mb * 1024 * 1024
def file_length_check(form, field):
if len(field.data.read()) >= max_size_b:
raise ValidationError(
f'File size must be less or equal than {max_size_mb} MB'
)
field.data.seek(0)
return file_length_check
class NopaqueForm(FlaskForm):
def __init__(self, *args, **kwargs):
if 'prefix' not in kwargs:
kwargs['prefix'] = re.sub(r'(?<!^)(?=[A-Z])', '-', self.__class__.__name__).lower()
kwargs['prefix'] = \
form_prefix_pattern.sub('-', self.__class__.__name__).lower()
super().__init__(*args, **kwargs)

View File

@ -1,6 +1,6 @@
from flask_login import current_user
from flask_wtf.file import FileField, FileRequired
from wtforms import (
FileField,
PasswordField,
SelectField,
StringField,
@ -15,13 +15,11 @@ from wtforms.validators import (
Length,
Regexp
)
from app.forms import NopaqueForm
from app.forms import NopaqueForm, LimitFileSize
from app.models import User, UserSettingJobStatusMailNotificationLevel
from app.auth import USERNAME_REGEX
from app.wtf_validators import FileSizeLimit
class EditAccountForm(NopaqueForm):
class UpdateAccountInformationForm(NopaqueForm):
email = StringField(
'E-Mail',
validators=[DataRequired(), Length(max=254), Email()]
@ -32,7 +30,7 @@ class EditAccountForm(NopaqueForm):
DataRequired(),
Length(max=64),
Regexp(
USERNAME_REGEX,
User.username_pattern,
message=(
'Usernames must have only letters, numbers, dots or '
'underscores'
@ -42,8 +40,7 @@ class EditAccountForm(NopaqueForm):
)
submit = SubmitField()
def __init__(self, *args, **kwargs):
user = kwargs.get('user', current_user._get_current_object())
def __init__(self, *args, user=current_user, **kwargs):
if 'data' not in kwargs:
kwargs['data'] = user.to_json_serializeable()
super().__init__(*args, **kwargs)
@ -59,15 +56,8 @@ class EditAccountForm(NopaqueForm):
and User.query.filter_by(username=field.data).first()):
raise ValidationError('Username already in use')
def validate_on_submit(self):
return self.submit.data and self.validate()
class EditProfileForm(NopaqueForm):
avatar = FileField(
'Image File',
[FileSizeLimit(max_size_in_mb=2)]
)
class UpdateProfileInformationForm(NopaqueForm):
full_name = StringField(
'Full name',
validators=[Length(max=128)]
@ -98,21 +88,22 @@ class EditProfileForm(NopaqueForm):
)
submit = SubmitField()
def __init__(self, *args, **kwargs):
def __init__(self, *args, user=current_user, **kwargs):
if 'data' not in kwargs:
user = current_user._get_current_object()
kwargs['data'] = user.to_json_serializeable()
super().__init__(*args, **kwargs)
def validate_image_file(self, field):
if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'):
raise ValidationError('only .jpg, .png and .jpeg!')
def validate_on_submit(self):
return self.submit.data and self.validate()
class UpdateAvatarForm(NopaqueForm):
avatar = FileField('File', validators=[FileRequired(), LimitFileSize(2)])
submit = SubmitField()
def validate_avatar(self, field):
valid_mimetypes = ['image/jpeg', 'image/png']
if field.data.mimetype not in valid_mimetypes:
raise ValidationError('JPEG and PNG files only!')
class ChangePasswordForm(NopaqueForm):
class UpdatePasswordForm(NopaqueForm):
password = PasswordField('Old password', validators=[DataRequired()])
new_password = PasswordField(
'New password',
@ -130,8 +121,7 @@ class ChangePasswordForm(NopaqueForm):
)
submit = SubmitField()
def __init__(self, *args, **kwargs):
user = kwargs.get('user', current_user._get_current_object())
def __init__(self, *args, user=current_user, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
@ -139,11 +129,8 @@ class ChangePasswordForm(NopaqueForm):
if not self.user.verify_password(field.data):
raise ValidationError('Invalid password')
def validate_on_submit(self):
return self.submit.data and self.validate()
class EditNotificationsForm(NopaqueForm):
class UpdateNotificationsForm(NopaqueForm):
job_status_mail_notification_level = SelectField(
'Job status mail notification level',
choices=[
@ -154,11 +141,7 @@ class EditNotificationsForm(NopaqueForm):
)
submit = SubmitField()
def __init__(self, *args, **kwargs):
def __init__(self, *args, user=current_user, **kwargs):
if 'data' not in kwargs:
user = current_user._get_current_object()
kwargs['data'] = user.to_json_serializeable()
super().__init__(*args, **kwargs)
def validate_on_submit(self):
return self.submit.data and self.validate()

View File

@ -5,10 +5,11 @@ from app import db
from app.models import Avatar
from . import bp
from .forms import (
ChangePasswordForm,
EditNotificationsForm,
EditAccountForm,
EditProfileForm
UpdateAvatarForm,
UpdatePasswordForm,
UpdateNotificationsForm,
UpdateAccountInformationForm,
UpdateProfileInformationForm
)
@ -16,61 +17,72 @@ from .forms import (
@register_breadcrumb(bp, '.', '<i class="material-icons left">settings</i>Settings')
@login_required
def settings():
user = current_user._get_current_object()
# region forms
edit_account_form = EditAccountForm()
edit_profile_form = EditProfileForm()
change_password_form = ChangePasswordForm()
edit_notifications_form = EditNotificationsForm()
# endregion forms
# region handle edit profile settings form
if edit_account_form.validate_on_submit():
user.email = edit_account_form.email.data
user.username = edit_account_form.username.data
user = current_user
update_account_information_form = UpdateAccountInformationForm()
update_profile_information_form = UpdateProfileInformationForm()
update_avatar_form = UpdateAvatarForm()
update_password_form = UpdatePasswordForm()
update_notifications_form = UpdateNotificationsForm()
# region handle update profile information form
if update_profile_information_form.submit.data and update_profile_information_form.validate():
user.about_me = update_profile_information_form.about_me.data
user.location = update_profile_information_form.location.data
user.organization = update_profile_information_form.organization.data
user.website = update_profile_information_form.website.data
user.full_name = update_profile_information_form.full_name.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.settings'))
# endregion handle update profile information form
# region handle update avatar form
if update_avatar_form.submit.data and update_avatar_form.validate():
try:
Avatar.create(
update_avatar_form.avatar.data,
user=user
)
except (AttributeError, OSError):
abort(500)
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.settings'))
# endregion handle update avatar form
# region handle update account information form
if update_account_information_form.submit.data and update_account_information_form.validate():
user.email = update_account_information_form.email.data
user.username = update_account_information_form.username.data
db.session.commit()
flash('Profile settings updated')
return redirect(url_for('.settings'))
# endregion handle edit profile settings forms
# region handle edit public profile information form
if edit_profile_form.validate_on_submit():
if edit_profile_form.avatar.data:
try:
Avatar.create(
edit_profile_form.avatar.data,
user=user
)
except (AttributeError, OSError):
abort(500)
user.about_me = edit_profile_form.about_me.data
user.location = edit_profile_form.location.data
user.organization = edit_profile_form.organization.data
user.website = edit_profile_form.website.data
user.full_name = edit_profile_form.full_name.data
# endregion handle update account information form
# region handle update password form
if update_password_form.submit.data and update_password_form.validate():
user.password = update_password_form.new_password.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.settings'))
# endregion handle edit public profile information form
# region handle change_password_form POST
if change_password_form.validate_on_submit():
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_notifications_form.validate_on_submit():
# endregion handle update password form
# region handle update notifications form
if update_notifications_form.submit.data and update_notifications_form.validate():
user.setting_job_status_mail_notification_level = \
edit_notifications_form.job_status_mail_notification_level.data
update_notifications_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
# endregion handle update notifications form
return render_template(
'settings/settings.html.j2',
title='Settings',
change_password_form=change_password_form,
edit_account_form=edit_account_form,
edit_notifications_form=edit_notifications_form,
edit_profile_form=edit_profile_form,
update_account_information_form=update_account_information_form,
update_avatar_form=update_avatar_form,
update_notifications_form=update_notifications_form,
update_password_form=update_password_form,
update_profile_information_form=update_profile_information_form,
user=user
)

View File

@ -0,0 +1,20 @@
/*****************************************************************************
* Admin *
* Fetch requests for /admin routes *
*****************************************************************************/
Requests.admin = {};
Requests.admin.users = {};
Requests.admin.users.entity = {};
Requests.admin.users.entity.confirmed = {};
Requests.admin.users.entity.confirmed.update = (userId, value) => {
let input = `/admin/users/${userId}/confirmed`;
let init = {
method: 'PUT',
body: JSON.stringify(value)
};
return Requests.JSONfetch(input, init);
};

View File

@ -58,6 +58,7 @@
filters='rjsmin',
output='gen/Requests.%(version)s.js',
'js/Requests/Requests.js',
'js/Requests/admin/admin.js',
'js/Requests/contributions/contributions.js',
'js/Requests/contributions/spacy_nlp_pipeline_models.js',
'js/Requests/contributions/tesseract_ocr_pipeline_models.js',

View File

@ -96,7 +96,7 @@
</div>
<div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Cancel</a>
<a href="{{ url_for('.delete_user', user_id=user.id) }}" class="btn red modal-close waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
<a class="btn red modal-close waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
</div>
</div>
{% endblock modals %}

View File

@ -1,6 +1,74 @@
{% extends "settings/settings.html.j2" %}
{% block page_content %}
{% block admin_settings %}
<div class="col s12 l4">
<h4>Administrator Settings</h4>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
Stet clita kasd gubergren, no sea tak</p>
</div>
<div class="col s12 l8">
<br>
<ul class="collapsible no-autoinit settings-collapsible">
<li>
<div class="collapsible-header" style="justify-content: space-between;">
<span>Confirmation status</span>
<i class="caret material-icons">keyboard_arrow_right</i>
</div>
<div class="collapsible-body">
<div class="row">
<div class="col s12 l1">
<p><i class="material-icons">check</i></p>
</div>
<div class="col s12 l7">
<p>
Confirmed<br>
<span class="light">Change confirmation status manually.</span>
</p>
</div>
<div class="col s3 l4">
<div class="switch">
<label>
unconfirmed
<input {% if user.confirmed %}checked{% endif %} id="user-confirmed-switch" type="checkbox">
<span class="lever"></span>
confirmed
</label>
</div>
</div>
</div>
</div>
</li>
<li>
<div class="collapsible-header" style="justify-content: space-between;">
<span>Role</span>
<i class="caret material-icons">keyboard_arrow_right</i>
</div>
<div class="collapsible-body">
<form method="POST">
{{ update_user_form.hidden_tag() }}
{{ wtf.render_field(update_user_form.role, material_icon='manage_accounts') }}
<div class="right-align">
{{ wtf.render_field(update_user_form.submit, material_icon='send') }}
</div>
</form>
</div>
</li>
</ul>
</div>
{% endblock admin_settings %}
{% block scripts %}
{{ super() }}
ADMIN ADDITIONS
{% endblock page_content %}
<script>
let userConfirmedSwitchElement = document.querySelector('#user-confirmed-switch');
userConfirmedSwitchElement.addEventListener('change', (event) => {
let newConfirmed = userConfirmedSwitchElement.checked;
Requests.admin.users.entity.confirmed.update({{ user.hashid|tojson }}, newConfirmed)
.catch((response) => {
userConfirmedSwitchElement.checked = !userConfirmedSwitchElement;
});
});
</script>
{% endblock scripts %}

View File

@ -50,8 +50,4 @@
{% block scripts %}
{{ super() }}
{% include "_scripts.html.j2" %}
{% set page_script = self._TemplateReference__context.name|replace('.html.j2', '.js.j2') %}
<script>
{% include page_script ignore missing %}
</script>
{% endblock scripts %}

View File

@ -7,8 +7,7 @@
<div class="col s12">
<h1 id="title">{{ title }}</h1>
</div>
</div>
<div class="row">
<div class="col s12 l4">
<h4>Profile Settings</h4>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
@ -21,7 +20,7 @@
<ul class="collapsible no-autoinit settings-collapsible">
<li>
<div class="collapsible-header" style="justify-content: space-between;">
<span>Profile Privacy Settings</span>
<span>Profile Privacy</span>
<i class="material-icons caret">keyboard_arrow_right</i>
</div>
<div class="collapsible-body">
@ -62,38 +61,47 @@
<span>Profile information</span>
<i class="material-icons caret">keyboard_arrow_right</i>
</div>
<div class="collapsible-body">
<form method="POST">
{{ update_profile_information_form.hidden_tag() }}
{{ wtf.render_field(update_profile_information_form.full_name, material_icon='badge') }}
{{ wtf.render_field(update_profile_information_form.about_me, material_icon='description', id='about-me-textfield') }}
{{ wtf.render_field(update_profile_information_form.website, material_icon='laptop') }}
{{ wtf.render_field(update_profile_information_form.organization, material_icon='business') }}
{{ wtf.render_field(update_profile_information_form.location, material_icon='location_on') }}
<div class="right-align">
{{ wtf.render_field(update_profile_information_form.submit, material_icon='send') }}
</div>
</form>
</div>
</li>
<li>
<div class="collapsible-header" style="justify-content: space-between;">
<span>Avatar</span>
<i class="material-icons caret">keyboard_arrow_right</i>
</div>
<div class="collapsible-body">
<form method="POST" enctype="multipart/form-data">
{{ edit_profile_form.hidden_tag() }}
{{ wtf.render_field(edit_profile_form.full_name, material_icon='badge') }}
{{ wtf.render_field(edit_profile_form.about_me, material_icon='description', id='about-me-textfield') }}
{{ wtf.render_field(edit_profile_form.website, material_icon='laptop') }}
{{ wtf.render_field(edit_profile_form.organization, material_icon='business') }}
{{ wtf.render_field(edit_profile_form.location, material_icon='location_on') }}
<p></p>
{{ update_avatar_form.hidden_tag() }}
<div class="row">
<div class="col s12 m2">
<img src="{{ url_for('users.user_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img" id="avatar">
<div class="col s12 l2">
<img src="{{ url_for('users.user_avatar', user_id=user.id) }}" alt="Avatar" class="circle responsive-img" id="update-avatar-form-avatar-preview">
</div>
<div class="col s12 m6">
{{ wtf.render_field(edit_profile_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 class="col s12 l10">
<br class="hide-on-med-and-down">
{{ wtf.render_field(update_avatar_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose a JPEG or PNG file') }}
</div>
</div>
<br>
<p></p>
<div class="right-align">
{{ wtf.render_field(edit_profile_form.submit, material_icon='send') }}
<a class="btn red waves-effect waves-light modal-trigger" href="#delete-avatar-modal"><i class="material-icons left">delete</i>Delete</a>
{{ wtf.render_field(update_avatar_form.submit, material_icon='send') }}
</div>
</form>
</div>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col s12 l4">
<h4>General Settings</h4>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
@ -109,38 +117,14 @@
<span>Account</span>
<i class="caret material-icons">keyboard_arrow_right</i>
</div>
<div class="collapsible-body">
<form method="POST" enctype="multipart/form-data">
{{ edit_account_form.hidden_tag() }}
{{ wtf.render_field(edit_account_form.username, material_icon='person') }}
{{ wtf.render_field(edit_account_form.email, material_icon='email') }}
<div class="right-align">
{{ wtf.render_field(edit_account_form.submit, material_icon='send') }}
</div>
</form>
<br>
<div class="divider"></div>
<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 class="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>
</li>
<li>
<div class="collapsible-header" style="justify-content: space-between;">
<span>Notifications</span>
<i class="caret material-icons">keyboard_arrow_right</i>
</div>
<div class="collapsible-body">
<form method="POST">
{{ edit_notifications_form.hidden_tag() }}
{{ wtf.render_field(edit_notifications_form.job_status_mail_notification_level, material_icon='notifications') }}
{{ update_account_information_form.hidden_tag() }}
{{ wtf.render_field(update_account_information_form.username, material_icon='person') }}
{{ wtf.render_field(update_account_information_form.email, material_icon='email') }}
<div class="right-align">
{{ wtf.render_field(edit_notifications_form.submit, material_icon='send') }}
<a class="btn red waves-effect waves-light modal-trigger" href="#delete-user"><i class="material-icons left">delete</i>Delete</a>
{{ wtf.render_field(update_account_information_form.submit, material_icon='send') }}
</div>
</form>
</div>
@ -152,18 +136,35 @@
</div>
<div class="collapsible-body">
<form method="POST">
{{ change_password_form.hidden_tag() }}
{{ 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') }}
{{ update_password_form.hidden_tag() }}
{{ wtf.render_field(update_password_form.password, material_icon='vpn_key') }}
{{ wtf.render_field(update_password_form.new_password, material_icon='vpn_key') }}
{{ wtf.render_field(update_password_form.new_password_2, material_icon='vpn_key') }}
<div class="right-align">
{{ wtf.render_field(change_password_form.submit, material_icon='send') }}
{{ wtf.render_field(update_password_form.submit, material_icon='send') }}
</div>
</form>
</div>
</li>
<li>
<div class="collapsible-header" style="justify-content: space-between;">
<span>Notifications</span>
<i class="caret material-icons">keyboard_arrow_right</i>
</div>
<div class="collapsible-body">
<form method="POST">
{{ update_notifications_form.hidden_tag() }}
{{ wtf.render_field(update_notifications_form.job_status_mail_notification_level, material_icon='notifications') }}
<div class="right-align">
{{ wtf.render_field(update_notifications_form.submit, material_icon='send') }}
</div>
</form>
</div>
</li>
</ul>
</div>
{% block admin_settings %}{% endblock admin_settings %}
</div>
</div>
{% endblock page_content %}
@ -184,11 +185,16 @@
<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>{{ user.username }}</b>? All files will be permanently deleted!</p>
<p>Do you really want to delete the User <b>{{ user.username }}</b>?</p>
<p>Deleting an account has the following effects:</p>
<ul>
<li>All data (Jobs, Corpora, ...) associated with the account will be permanently deleted.</li>
<li>All settings will be permanently deleted.</li>
</ul>
</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>
<a class="btn modal-close red waves-effect waves-light" id="delete-user">Delete</a>
</div>
</div>
{% endblock modals %}
@ -196,25 +202,25 @@
{% block scripts %}
{{ super() }}
<script>
let deleteButtonElement = document.querySelector('#delete-avatar');
let avatarElement = document.querySelector('#avatar');
let avatarUploadElement = document.querySelector('#avatar-upload');
let deleteAvatarButtonElement = document.querySelector('#delete-avatar');
let avatarPreviewElement = document.querySelector('#update-avatar-form-avatar-preview');
let avatarUploadElement = document.querySelector('#update-avatar-form-avatar');
avatarUploadElement.addEventListener('change', () => {
let file = avatarUploadElement.files[0];
avatarElement.src = URL.createObjectURL(file);
avatarPreviewElement.src = URL.createObjectURL(file);
});
deleteButtonElement.addEventListener('click', () => {
deleteAvatarButtonElement.addEventListener('click', () => {
Requests.settings.entity.deleteAvatar({{ user.hashid|tojson }})
.then(
(response) => {
avatarElement.src = {{ url_for('static', filename='images/user_avatar.png')|tojson }};
avatarPreviewElement.src = {{ url_for('static', filename='images/user_avatar.png')|tojson }};
}
);
});
document.querySelector('#delete-user-button').addEventListener('click', (event) => {
document.querySelector('#delete-user').addEventListener('click', (event) => {
Requests.settings.entity.delete({{ user.hashid|tojson }})
.then((response) => {window.location.href = '/';});
});

View File

@ -1,9 +0,0 @@
from wtforms.validators import ValidationError
def FileSizeLimit(max_size_in_mb):
max_bytes = max_size_in_mb*1024*1024
def file_length_check(form, field):
if len(field.data.read()) > max_bytes:
raise ValidationError(f"File size must be less than {max_size_in_mb}MB")
field.data.seek(0)
return file_length_check