diff --git a/app/admin/forms.py b/app/admin/forms.py index b063b938..f24ce8f3 100644 --- a/app/admin/forms.py +++ b/app/admin/forms.py @@ -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()] diff --git a/app/admin/json_routes.py b/app/admin/json_routes.py index d8b45425..d4c21307 100644 --- a/app/admin/json_routes.py +++ b/app/admin/json_routes.py @@ -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//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//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 diff --git a/app/admin/routes.py b/app/admin/routes.py index 899b4dfd..9a5401b8 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -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', 'settingsSettings') 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//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 -# ) diff --git a/app/api/schemas.py b/app/api/schemas.py index f0792f7c..74f4cb2a 100644 --- a/app/api/schemas.py +++ b/app/api/schemas.py @@ -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()) diff --git a/app/auth/__init__.py b/app/auth/__init__.py index 505e7e42..6f6ba82d 100644 --- a/app/auth/__init__.py +++ b/app/auth/__init__.py @@ -1,8 +1,5 @@ from flask import Blueprint -USERNAME_REGEX = '^[A-Za-zÄÖÜäöüß0-9_.]*$' - - bp = Blueprint('auth', __name__) from . import routes diff --git a/app/auth/forms.py b/app/auth/forms.py index 5655b3af..43db510a 100644 --- a/app/auth/forms.py +++ b/app/auth/forms.py @@ -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' diff --git a/app/forms.py b/app/forms.py index 55e80d01..6ac58347 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,9 +1,26 @@ from flask_wtf import FlaskForm +from wtforms.validators import ValidationError import re +form_prefix_pattern = re.compile(r'(?= 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'(?settingsSettings') @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 ) diff --git a/app/static/js/Requests/admin/admin.js b/app/static/js/Requests/admin/admin.js new file mode 100644 index 00000000..77fdb6b1 --- /dev/null +++ b/app/static/js/Requests/admin/admin.js @@ -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); +}; diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index bed2aef2..45e5457b 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -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', diff --git a/app/templates/admin/user.html.j2 b/app/templates/admin/user.html.j2 index dc5af986..0512db75 100644 --- a/app/templates/admin/user.html.j2 +++ b/app/templates/admin/user.html.j2 @@ -96,7 +96,7 @@ {% endblock modals %} diff --git a/app/templates/admin/user_settings.html.j2 b/app/templates/admin/user_settings.html.j2 index 49d8ba4d..66d6250e 100644 --- a/app/templates/admin/user_settings.html.j2 +++ b/app/templates/admin/user_settings.html.j2 @@ -1,6 +1,74 @@ {% extends "settings/settings.html.j2" %} -{% block page_content %} +{% block admin_settings %} +
+

Administrator Settings

+

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

+
+
+
+
    +
  • +
    + Confirmation status + keyboard_arrow_right +
    +
    +
    +
    +

    check

    +
    +
    +

    + Confirmed
    + Change confirmation status manually. +

    +
    +
    +
    + +
    +
    +
    +
    +
  • +
  • +
    + Role + keyboard_arrow_right +
    +
    +
    + {{ update_user_form.hidden_tag() }} + {{ wtf.render_field(update_user_form.role, material_icon='manage_accounts') }} +
    + {{ wtf.render_field(update_user_form.submit, material_icon='send') }} +
    +
    +
    +
  • +
+
+{% endblock admin_settings %} + +{% block scripts %} {{ super() }} -ADMIN ADDITIONS -{% endblock page_content %} + +{% endblock scripts %} diff --git a/app/templates/base.html.j2 b/app/templates/base.html.j2 index a924e9b7..82132828 100644 --- a/app/templates/base.html.j2 +++ b/app/templates/base.html.j2 @@ -50,8 +50,4 @@ {% block scripts %} {{ super() }} {% include "_scripts.html.j2" %} -{% set page_script = self._TemplateReference__context.name|replace('.html.j2', '.js.j2') %} - {% endblock scripts %} diff --git a/app/templates/settings/settings.html.j2 b/app/templates/settings/settings.html.j2 index b6ec4fa5..004a553a 100644 --- a/app/templates/settings/settings.html.j2 +++ b/app/templates/settings/settings.html.j2 @@ -7,8 +7,7 @@

{{ title }}

- -
+

Profile Settings

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam @@ -21,7 +20,7 @@

  • - Profile Privacy Settings + Profile Privacy keyboard_arrow_right
    @@ -62,38 +61,47 @@ Profile information keyboard_arrow_right
    +
    +
    + {{ 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') }} +
    + {{ wtf.render_field(update_profile_information_form.submit, material_icon='send') }} +
    +
    +
    +
  • +
  • +
    + Avatar + keyboard_arrow_right +
    - {{ 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') }} -

    + {{ update_avatar_form.hidden_tag() }}
    -
    - user-image +
    + Avatar
    -
    - {{ wtf.render_field(edit_profile_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload') }} -
    -
    - delete +
    +
    + {{ wtf.render_field(update_avatar_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose a JPEG or PNG file') }}
    -
    -

    - {{ wtf.render_field(edit_profile_form.submit, material_icon='send') }} + deleteDelete + {{ wtf.render_field(update_avatar_form.submit, material_icon='send') }}
-
-
+

General Settings

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam @@ -109,38 +117,14 @@ Account keyboard_arrow_right

-
-
- {{ 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') }} -
- {{ wtf.render_field(edit_account_form.submit, material_icon='send') }} -
-
-
-
-

Deleting an account has the following effects:

-
    -
  • All data associated with your corpora and jobs will be permanently deleted.
  • -
  • All settings will be permanently deleted.
  • -
- -
- -
  • -
    - Notifications - keyboard_arrow_right -
    - {{ 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') }}
    - {{ wtf.render_field(edit_notifications_form.submit, material_icon='send') }} + deleteDelete + {{ wtf.render_field(update_account_information_form.submit, material_icon='send') }}
    @@ -152,18 +136,35 @@
  • - {{ 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') }}
    - {{ wtf.render_field(change_password_form.submit, material_icon='send') }} + {{ wtf.render_field(update_password_form.submit, material_icon='send') }} +
    +
    +
    + +
  • +
    + Notifications + keyboard_arrow_right +
    +
    +
    + {{ update_notifications_form.hidden_tag() }} + {{ wtf.render_field(update_notifications_form.job_status_mail_notification_level, material_icon='notifications') }} +
    + {{ wtf.render_field(update_notifications_form.submit, material_icon='send') }}
  • + + {% block admin_settings %}{% endblock admin_settings %} {% endblock page_content %} @@ -184,11 +185,16 @@ {% endblock modals %} @@ -196,25 +202,25 @@ {% block scripts %} {{ super() }}