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 from app.models import Role
class AdminEditUserForm(NopaqueForm): class UpdateUserForm(NopaqueForm):
confirmed = BooleanField('Confirmed')
role = SelectField('Role') 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) super().__init__(*args, **kwargs)
self.role.choices = [(x.hashid, x.name) for x in Role.query.all()] 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 flask import abort, request
from threading import Thread
from app import db from app import db
from app.decorators import content_negotiation
from app.models import User from app.models import User
from . import bp from . import bp
@bp.route('/users/<hashid:user_id>/delete', methods=['DELETE']) @bp.route('/users/<hashid:user_id>/confirmed', methods=['PUT'])
def delete_user(user_id): @content_negotiation(consumes='application/json', produces='application/json')
def _delete_user(app, user_id): def update_user_role(user_id):
with app.app_context(): confirmed = request.json
user = User.query.get(user_id) if not isinstance(confirmed, bool):
user.delete() abort(400)
db.session.commit() user = User.query.get_or_404(user_id)
user.confirmed = confirmed
User.query.get_or_404(user_id) db.session.commit()
thread = Thread( resonse_data = {
target=_delete_user, 'message': (
args=(current_app._get_current_object(), user_id) f'User "{user.username}" is now '
) f'{"confirmed" if confirmed else "unconfirmed"}'
thread.start() )
return {}, 202 }
return resonse_data, 200

View File

@ -1,15 +1,16 @@
from flask import abort, flash, redirect, render_template, url_for from flask import abort, flash, redirect, render_template, url_for
from flask_breadcrumbs import register_breadcrumb from flask_breadcrumbs import register_breadcrumb
from app import db from app import db, hashids
from app.models import Avatar, Corpus, User from app.models import Avatar, Corpus, Role, User
from app.settings.forms import ( from app.settings.forms import (
ChangePasswordForm, UpdateAvatarForm,
EditNotificationsForm, UpdatePasswordForm,
EditAccountForm, UpdateNotificationsForm,
EditProfileForm UpdateAccountInformationForm,
UpdateProfileInformationForm
) )
from . import bp from . import bp
from .forms import AdminEditUserForm from .forms import UpdateUserForm
from app.users.utils import ( from app.users.utils import (
user_endpoint_arguments_constructor as user_eac, user_endpoint_arguments_constructor as user_eac,
user_dynamic_list_constructor as user_dlc 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') @register_breadcrumb(bp, '.users.entity.settings', '<i class="material-icons left">settings</i>Settings')
def user_settings(user_id): def user_settings(user_id):
user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
# region forms update_account_information_form = UpdateAccountInformationForm(user=user)
edit_account_form = EditAccountForm(user=user) update_profile_information_form = UpdateProfileInformationForm(user=user)
edit_profile_form = EditProfileForm(user=user) update_avatar_form = UpdateAvatarForm(user=user)
change_password_form = ChangePasswordForm(user=user) update_password_form = UpdatePasswordForm(user=user)
edit_notifications_form = EditNotificationsForm(user=user) update_notifications_form = UpdateNotificationsForm(user=user)
# endregion forms update_user_form = UpdateUserForm(user)
# region handle edit profile settings form
if edit_account_form.validate_on_submit(): # region handle update profile information form
user.email = edit_account_form.email.data if update_profile_information_form.submit.data and update_profile_information_form.validate():
user.username = edit_account_form.username.data 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() db.session.commit()
flash('Profile settings updated') flash('Profile settings updated')
return redirect(url_for('.user_settings')) return redirect(url_for('.user_settings', user_id=user.id))
# endregion handle edit profile settings forms # endregion handle update account information form
# region handle edit public profile information form
if edit_profile_form.validate_on_submit(): # region handle update password form
if edit_profile_form.avatar.data: if update_password_form.submit.data and update_password_form.validate():
try: user.password = update_password_form.new_password.data
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
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.user_settings')) return redirect(url_for('.user_settings', user_id=user.id))
# endregion handle edit public profile information form # endregion handle update password form
# region handle change_password_form POST
if change_password_form.validate_on_submit(): # region handle update notifications form
user.password = change_password_form.new_password.data if update_notifications_form.submit.data and update_notifications_form.validate():
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():
user.setting_job_status_mail_notification_level = \ 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() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.user_settings')) return redirect(url_for('.user_settings', user_id=user.id))
# endregion handle edit_notification_settings_form POST # 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( return render_template(
'admin/user_settings.html.j2', 'admin/user_settings.html.j2',
title='Settings', title='Settings',
change_password_form=change_password_form, update_account_information_form=update_account_information_form,
edit_account_form=edit_account_form, update_avatar_form=update_avatar_form,
edit_notifications_form=edit_notifications_form, update_notifications_form=update_notifications_form,
edit_profile_form=edit_profile_form, update_password_form=update_password_form,
update_profile_information_form=update_profile_information_form,
update_user_form=update_user_form,
user=user 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 import validate, validates, ValidationError
from marshmallow.decorators import post_dump from marshmallow.decorators import post_dump
from app import ma from app import ma
from app.auth import USERNAME_REGEX
from app.models import ( from app.models import (
Job, Job,
JobStatus, JobStatus,
@ -142,7 +141,10 @@ class UserSchema(ma.SQLAlchemySchema):
username = ma.auto_field( username = ma.auto_field(
validate=[ validate=[
validate.Length(min=1, max=64), 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()) email = ma.auto_field(validate=validate.Email())

View File

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

View File

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

View File

@ -1,9 +1,26 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms.validators import ValidationError
import re 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): class NopaqueForm(FlaskForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if 'prefix' not in 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) super().__init__(*args, **kwargs)

View File

@ -1,6 +1,6 @@
from flask_login import current_user from flask_login import current_user
from flask_wtf.file import FileField, FileRequired
from wtforms import ( from wtforms import (
FileField,
PasswordField, PasswordField,
SelectField, SelectField,
StringField, StringField,
@ -15,13 +15,11 @@ from wtforms.validators import (
Length, Length,
Regexp Regexp
) )
from app.forms import NopaqueForm from app.forms import NopaqueForm, LimitFileSize
from app.models import User, UserSettingJobStatusMailNotificationLevel 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( email = StringField(
'E-Mail', 'E-Mail',
validators=[DataRequired(), Length(max=254), Email()] validators=[DataRequired(), Length(max=254), Email()]
@ -32,7 +30,7 @@ class EditAccountForm(NopaqueForm):
DataRequired(), DataRequired(),
Length(max=64), Length(max=64),
Regexp( Regexp(
USERNAME_REGEX, User.username_pattern,
message=( message=(
'Usernames must have only letters, numbers, dots or ' 'Usernames must have only letters, numbers, dots or '
'underscores' 'underscores'
@ -42,8 +40,7 @@ class EditAccountForm(NopaqueForm):
) )
submit = SubmitField() submit = SubmitField()
def __init__(self, *args, **kwargs): def __init__(self, *args, user=current_user, **kwargs):
user = kwargs.get('user', current_user._get_current_object())
if 'data' not in kwargs: if 'data' not in kwargs:
kwargs['data'] = user.to_json_serializeable() kwargs['data'] = user.to_json_serializeable()
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -59,15 +56,8 @@ class EditAccountForm(NopaqueForm):
and User.query.filter_by(username=field.data).first()): and User.query.filter_by(username=field.data).first()):
raise ValidationError('Username already in use') raise ValidationError('Username already in use')
def validate_on_submit(self):
return self.submit.data and self.validate()
class UpdateProfileInformationForm(NopaqueForm):
class EditProfileForm(NopaqueForm):
avatar = FileField(
'Image File',
[FileSizeLimit(max_size_in_mb=2)]
)
full_name = StringField( full_name = StringField(
'Full name', 'Full name',
validators=[Length(max=128)] validators=[Length(max=128)]
@ -98,21 +88,22 @@ class EditProfileForm(NopaqueForm):
) )
submit = SubmitField() submit = SubmitField()
def __init__(self, *args, **kwargs): def __init__(self, *args, user=current_user, **kwargs):
if 'data' not in kwargs: if 'data' not in kwargs:
user = current_user._get_current_object()
kwargs['data'] = user.to_json_serializeable() kwargs['data'] = user.to_json_serializeable()
super().__init__(*args, **kwargs) 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): class UpdateAvatarForm(NopaqueForm):
return self.submit.data and self.validate() 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()]) password = PasswordField('Old password', validators=[DataRequired()])
new_password = PasswordField( new_password = PasswordField(
'New password', 'New password',
@ -130,8 +121,7 @@ class ChangePasswordForm(NopaqueForm):
) )
submit = SubmitField() submit = SubmitField()
def __init__(self, *args, **kwargs): def __init__(self, *args, user=current_user, **kwargs):
user = kwargs.get('user', current_user._get_current_object())
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.user = user self.user = user
@ -139,11 +129,8 @@ class ChangePasswordForm(NopaqueForm):
if not self.user.verify_password(field.data): if not self.user.verify_password(field.data):
raise ValidationError('Invalid password') raise ValidationError('Invalid password')
def validate_on_submit(self):
return self.submit.data and self.validate()
class UpdateNotificationsForm(NopaqueForm):
class EditNotificationsForm(NopaqueForm):
job_status_mail_notification_level = SelectField( job_status_mail_notification_level = SelectField(
'Job status mail notification level', 'Job status mail notification level',
choices=[ choices=[
@ -154,11 +141,7 @@ class EditNotificationsForm(NopaqueForm):
) )
submit = SubmitField() submit = SubmitField()
def __init__(self, *args, **kwargs): def __init__(self, *args, user=current_user, **kwargs):
if 'data' not in kwargs: if 'data' not in kwargs:
user = current_user._get_current_object()
kwargs['data'] = user.to_json_serializeable() kwargs['data'] = user.to_json_serializeable()
super().__init__(*args, **kwargs) 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 app.models import Avatar
from . import bp from . import bp
from .forms import ( from .forms import (
ChangePasswordForm, UpdateAvatarForm,
EditNotificationsForm, UpdatePasswordForm,
EditAccountForm, UpdateNotificationsForm,
EditProfileForm UpdateAccountInformationForm,
UpdateProfileInformationForm
) )
@ -16,61 +17,72 @@ from .forms import (
@register_breadcrumb(bp, '.', '<i class="material-icons left">settings</i>Settings') @register_breadcrumb(bp, '.', '<i class="material-icons left">settings</i>Settings')
@login_required @login_required
def settings(): def settings():
user = current_user._get_current_object() user = current_user
# region forms update_account_information_form = UpdateAccountInformationForm()
edit_account_form = EditAccountForm() update_profile_information_form = UpdateProfileInformationForm()
edit_profile_form = EditProfileForm() update_avatar_form = UpdateAvatarForm()
change_password_form = ChangePasswordForm() update_password_form = UpdatePasswordForm()
edit_notifications_form = EditNotificationsForm() update_notifications_form = UpdateNotificationsForm()
# endregion forms
# region handle edit profile settings form # region handle update profile information form
if edit_account_form.validate_on_submit(): if update_profile_information_form.submit.data and update_profile_information_form.validate():
user.email = edit_account_form.email.data user.about_me = update_profile_information_form.about_me.data
user.username = edit_account_form.username.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() db.session.commit()
flash('Profile settings updated') flash('Profile settings updated')
return redirect(url_for('.settings')) return redirect(url_for('.settings'))
# endregion handle edit profile settings forms # endregion handle update account information form
# region handle edit public profile information form
if edit_profile_form.validate_on_submit(): # region handle update password form
if edit_profile_form.avatar.data: if update_password_form.submit.data and update_password_form.validate():
try: user.password = update_password_form.new_password.data
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
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.settings')) return redirect(url_for('.settings'))
# endregion handle edit public profile information form # endregion handle update password form
# region handle change_password_form POST
if change_password_form.validate_on_submit(): # region handle update notifications form
user.password = change_password_form.new_password.data if update_notifications_form.submit.data and update_notifications_form.validate():
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():
user.setting_job_status_mail_notification_level = \ 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() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.settings')) return redirect(url_for('.settings'))
# endregion handle edit_notification_settings_form POST # endregion handle update notifications form
return render_template( return render_template(
'settings/settings.html.j2', 'settings/settings.html.j2',
title='Settings', title='Settings',
change_password_form=change_password_form, update_account_information_form=update_account_information_form,
edit_account_form=edit_account_form, update_avatar_form=update_avatar_form,
edit_notifications_form=edit_notifications_form, update_notifications_form=update_notifications_form,
edit_profile_form=edit_profile_form, update_password_form=update_password_form,
update_profile_information_form=update_profile_information_form,
user=user 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', filters='rjsmin',
output='gen/Requests.%(version)s.js', output='gen/Requests.%(version)s.js',
'js/Requests/Requests.js', 'js/Requests/Requests.js',
'js/Requests/admin/admin.js',
'js/Requests/contributions/contributions.js', 'js/Requests/contributions/contributions.js',
'js/Requests/contributions/spacy_nlp_pipeline_models.js', 'js/Requests/contributions/spacy_nlp_pipeline_models.js',
'js/Requests/contributions/tesseract_ocr_pipeline_models.js', 'js/Requests/contributions/tesseract_ocr_pipeline_models.js',

View File

@ -96,7 +96,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Cancel</a> <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>
</div> </div>
{% endblock modals %} {% endblock modals %}

View File

@ -1,6 +1,74 @@
{% extends "settings/settings.html.j2" %} {% 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() }} {{ super() }}
ADMIN ADDITIONS <script>
{% endblock page_content %} 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 %} {% block scripts %}
{{ super() }} {{ super() }}
{% include "_scripts.html.j2" %} {% 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 %} {% endblock scripts %}

View File

@ -7,8 +7,7 @@
<div class="col s12"> <div class="col s12">
<h1 id="title">{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
</div>
<div class="row">
<div class="col s12 l4"> <div class="col s12 l4">
<h4>Profile Settings</h4> <h4>Profile Settings</h4>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
@ -21,7 +20,7 @@
<ul class="collapsible no-autoinit settings-collapsible"> <ul class="collapsible no-autoinit settings-collapsible">
<li> <li>
<div class="collapsible-header" style="justify-content: space-between;"> <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> <i class="material-icons caret">keyboard_arrow_right</i>
</div> </div>
<div class="collapsible-body"> <div class="collapsible-body">
@ -62,38 +61,47 @@
<span>Profile information</span> <span>Profile information</span>
<i class="material-icons caret">keyboard_arrow_right</i> <i class="material-icons caret">keyboard_arrow_right</i>
</div> </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"> <div class="collapsible-body">
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
{{ edit_profile_form.hidden_tag() }} {{ update_avatar_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>
<div class="row"> <div class="row">
<div class="col s12 m2"> <div class="col s12 l2">
<img src="{{ url_for('users.user_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img" id="avatar"> <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>
<div class="col s12 m6"> <div class="col s12 l10">
{{ wtf.render_field(edit_profile_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload') }} <br class="hide-on-med-and-down">
</div> {{ wtf.render_field(update_avatar_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose a JPEG or PNG file') }}
<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>
</div> </div>
<br>
<p></p>
<div class="right-align"> <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> </div>
</form> </form>
</div> </div>
</li> </li>
</ul> </ul>
</div> </div>
</div>
<div class="row">
<div class="col s12 l4"> <div class="col s12 l4">
<h4>General Settings</h4> <h4>General Settings</h4>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
@ -109,38 +117,14 @@
<span>Account</span> <span>Account</span>
<i class="caret material-icons">keyboard_arrow_right</i> <i class="caret material-icons">keyboard_arrow_right</i>
</div> </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"> <div class="collapsible-body">
<form method="POST"> <form method="POST">
{{ edit_notifications_form.hidden_tag() }} {{ update_account_information_form.hidden_tag() }}
{{ wtf.render_field(edit_notifications_form.job_status_mail_notification_level, material_icon='notifications') }} {{ 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"> <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> </div>
</form> </form>
</div> </div>
@ -152,18 +136,35 @@
</div> </div>
<div class="collapsible-body"> <div class="collapsible-body">
<form method="POST"> <form method="POST">
{{ change_password_form.hidden_tag() }} {{ update_password_form.hidden_tag() }}
{{ wtf.render_field(change_password_form.password, material_icon='vpn_key') }} {{ wtf.render_field(update_password_form.password, material_icon='vpn_key') }}
{{ wtf.render_field(change_password_form.new_password, material_icon='vpn_key') }} {{ wtf.render_field(update_password_form.new_password, material_icon='vpn_key') }}
{{ wtf.render_field(change_password_form.new_password_2, material_icon='vpn_key') }} {{ wtf.render_field(update_password_form.new_password_2, material_icon='vpn_key') }}
<div class="right-align"> <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> </div>
</form> </form>
</div> </div>
</li> </li>
</ul> </ul>
</div> </div>
{% block admin_settings %}{% endblock admin_settings %}
</div> </div>
</div> </div>
{% endblock page_content %} {% endblock page_content %}
@ -184,11 +185,16 @@
<div class="modal" id="delete-user"> <div class="modal" id="delete-user">
<div class="modal-content"> <div class="modal-content">
<h4>Confirm User deletion</h4> <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>
<div class="modal-footer"> <div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Cancel</a> <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>
</div> </div>
{% endblock modals %} {% endblock modals %}
@ -196,25 +202,25 @@
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script> <script>
let deleteButtonElement = document.querySelector('#delete-avatar'); let deleteAvatarButtonElement = document.querySelector('#delete-avatar');
let avatarElement = document.querySelector('#avatar'); let avatarPreviewElement = document.querySelector('#update-avatar-form-avatar-preview');
let avatarUploadElement = document.querySelector('#avatar-upload'); let avatarUploadElement = document.querySelector('#update-avatar-form-avatar');
avatarUploadElement.addEventListener('change', () => { avatarUploadElement.addEventListener('change', () => {
let file = avatarUploadElement.files[0]; 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 }}) Requests.settings.entity.deleteAvatar({{ user.hashid|tojson }})
.then( .then(
(response) => { (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 }}) Requests.settings.entity.delete({{ user.hashid|tojson }})
.then((response) => {window.location.href = '/';}); .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