diff --git a/app/__init__.py b/app/__init__.py index bf465cbbf5f0bd26ea55f1d10523eb3115b00ba9..50e70fd29fc32ef33160daf603e84c3214d4fb75 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -91,6 +91,10 @@ def create_app(config: Config = Config) -> Flask: default_breadcrumb_root(services_blueprint, '.services') app.register_blueprint(services_blueprint, url_prefix='/services') + from .settings import bp as settings_blueprint + default_breadcrumb_root(settings_blueprint, '.settings') + app.register_blueprint(settings_blueprint, url_prefix='/settings') + from .users import bp as users_blueprint default_breadcrumb_root(users_blueprint, '.social_area.users') app.register_blueprint(users_blueprint, url_prefix='/users') diff --git a/app/settings/__init__.py b/app/settings/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..aaa1cb1ba97f0c2f8b94da9a8340e32ba846fcbe --- /dev/null +++ b/app/settings/__init__.py @@ -0,0 +1,6 @@ +from flask import Blueprint + + +bp = Blueprint('settings', __name__) +from . import routes, json_routes + diff --git a/app/users/forms.py b/app/settings/forms.py similarity index 93% rename from app/users/forms.py rename to app/settings/forms.py index 73d72c8dcee458a5172c7cd416cc01ceaf93e1a9..a814feee301bfc65f614335eab9176267d002e2b 100644 --- a/app/users/forms.py +++ b/app/settings/forms.py @@ -56,6 +56,9 @@ class EditProfileSettingsForm(FlaskForm): raise ValidationError('Username already in use') class EditPublicProfileInformationForm(FlaskForm): + show_email = BooleanField('Email') + show_last_seen = BooleanField('Last seen') + show_member_since = BooleanField('Member since') avatar = FileField( 'Image File', [FileSizeLimit(max_size_in_mb=2)] @@ -97,13 +100,6 @@ class EditPublicProfileInformationForm(FlaskForm): if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'): raise ValidationError('only .jpg, .png and .jpeg!') -class EditPrivacySettingsForm(FlaskForm): - is_public = BooleanField('Public profile') - show_email = BooleanField('Show email') - show_last_seen = BooleanField('Show last seen') - show_member_since = BooleanField('Show member since') - submit = SubmitField() - class ChangePasswordForm(FlaskForm): password = PasswordField('Old password', validators=[DataRequired()]) new_password = PasswordField( diff --git a/app/users/json_routes.py b/app/settings/json_routes.py similarity index 71% rename from app/users/json_routes.py rename to app/settings/json_routes.py index 0e421078fdfeaf3f21eb38187abbb5ea4bbd1115..77b421d9e0c9a5d4411e08e90ee986c5a66389e3 100644 --- a/app/users/json_routes.py +++ b/app/settings/json_routes.py @@ -1,4 +1,4 @@ -from flask import abort, current_app +from flask import abort, current_app, request from flask_login import current_user, login_required, logout_user from threading import Thread import os @@ -52,3 +52,22 @@ def delete_profile_avatar(user_id): 'message': f'Avatar marked for deletion' } return response_data, 202 + +@bp.route('/<hashid:user_id>/is_public', methods=['PUT']) +@login_required +@content_negotiation(consumes='application/json', produces='application/json') +def update_user_is_public(user_id): + is_public = request.json + if not isinstance(is_public, bool): + abort(400) + user = User.query.get_or_404(user_id) + user.is_public = is_public + db.session.commit() + response_data = { + 'message': ( + f'User "{user.username}" is now' + f' {"public" if is_public else "private"}' + ), + 'category': 'corpus' + } + return response_data, 200 diff --git a/app/settings/routes.py b/app/settings/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..bc36a9c8fcf91c974bbcc66a7c2767555392c428 --- /dev/null +++ b/app/settings/routes.py @@ -0,0 +1,108 @@ +from flask import ( + abort, + flash, + redirect, + render_template, + url_for +) +from flask_breadcrumbs import register_breadcrumb +from flask_login import current_user, login_required +import os +from app import db +from app.models import Avatar, Corpus, ProfilePrivacySettings, User +from . import bp +from .forms import ( + ChangePasswordForm, + EditNotificationSettingsForm, + EditProfileSettingsForm, + EditPublicProfileInformationForm +) +from .utils import user_endpoint_arguments_constructor as user_eac + + +@bp.route('/<hashid:user_id>/edit', methods=['GET', 'POST']) +@register_breadcrumb(bp, 'breadcrumbs.settings', '<i class="material-icons left">settings</i>Settings', endpoint_arguments_constructor=user_eac) +@login_required +def edit_profile(user_id): + user = User.query.get_or_404(user_id) + if not (user == current_user or current_user.is_administrator()): + abort(403) + # region forms + edit_profile_settings_form = EditProfileSettingsForm( + current_user, + data=current_user.to_json_serializeable(), + prefix='edit-profile-settings-form' + ) + edit_public_profile_information_form = EditPublicProfileInformationForm( + data=current_user.to_json_serializeable(), + prefix='edit-public-profile-information-form' + ) + change_password_form = ChangePasswordForm( + current_user, + prefix='change-password-form' + ) + edit_notification_settings_form = EditNotificationSettingsForm( + data=current_user.to_json_serializeable(), + prefix='edit-notification-settings-form' + ) + # endregion forms + # region handle edit profile settings form + if edit_profile_settings_form.validate_on_submit(): + current_user.email = edit_profile_settings_form.email.data + current_user.username = edit_profile_settings_form.username.data + db.session.commit() + flash('Profile settings updated') + return redirect(url_for('users.user', user_id=user.id)) + # endregion handle edit profile settings forms + # region handle edit public profile information form + if edit_public_profile_information_form.submit.data and edit_public_profile_information_form.validate(): + print(edit_public_profile_information_form.show_email.data) + if edit_public_profile_information_form.show_email.data: + current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL) + else: + current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL) + if edit_public_profile_information_form.show_last_seen.data: + current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN) + else: + current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN) + if edit_public_profile_information_form.show_member_since.data: + current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE) + else: + current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE) + if edit_public_profile_information_form.avatar.data: + try: + Avatar.create(edit_public_profile_information_form.avatar.data, user=current_user) + except (AttributeError, OSError): + abort(500) + current_user.about_me = edit_public_profile_information_form.about_me.data + current_user.location = edit_public_profile_information_form.location.data + current_user.organization = edit_public_profile_information_form.organization.data + current_user.website = edit_public_profile_information_form.website.data + current_user.full_name = edit_public_profile_information_form.full_name.data + db.session.commit() + flash('Profile settings updated') + return redirect(url_for('users.user', user_id=user.id)) + # endregion handle edit public profile information form + # region handle change_password_form POST + if change_password_form.submit.data and change_password_form.validate(): + current_user.password = change_password_form.new_password.data + db.session.commit() + flash('Your changes have been saved') + return redirect(url_for('.edit_profile', user_id=user.id)) + # endregion handle change_password_form POST + # region handle edit_notification_settings_form POST + if edit_notification_settings_form.submit and edit_notification_settings_form.validate(): + current_user.setting_job_status_mail_notification_level = edit_notification_settings_form.job_status_mail_notification_level.data + db.session.commit() + flash('Your changes have been saved') + return redirect(url_for('.edit_profile', user_id=user.id)) + # endregion handle edit_notification_settings_form POST + return render_template( + 'settings/edit_profile.html.j2', + edit_profile_settings_form=edit_profile_settings_form, + edit_public_profile_information_form=edit_public_profile_information_form, + change_password_form=change_password_form, + edit_notification_settings_form=edit_notification_settings_form, + user=user, + title='Settings' + ) diff --git a/app/settings/utils.py b/app/settings/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..53572740ddd417ad228b9d8def0604c77e94a769 --- /dev/null +++ b/app/settings/utils.py @@ -0,0 +1,6 @@ +from flask import request, url_for +from app.models import User + + +def user_endpoint_arguments_constructor(): + return {'user_id': request.view_args['user_id']} diff --git a/app/static/js/Requests/settings/settings.js b/app/static/js/Requests/settings/settings.js new file mode 100644 index 0000000000000000000000000000000000000000..55ae94b27ed9f6bbb9466a77deb588dc976878fb --- /dev/null +++ b/app/static/js/Requests/settings/settings.js @@ -0,0 +1,35 @@ +/***************************************************************************** +* Users * +* Fetch requests for /users routes * +*****************************************************************************/ +Requests.settings = {}; + +Requests.settings.entity = {}; + +Requests.settings.entity.delete = (userId) => { + let input = `/settings/${userId}`; + let init = { + method: 'DELETE' + }; + return Requests.JSONfetch(input, init); +} + +Requests.settings.entity.deleteAvatar = (userId) => { + let input = `/settings/${userId}/avatar`; + let init = { + method: 'DELETE' + }; + return Requests.JSONfetch(input, init); +} + +Requests.settings.entity.isPublic = {}; + +Requests.settings.entity.isPublic.update = (userId, isPublic) => { + let input = `/settings/${userId}/is_public`; + let init = { + method: 'PUT', + body: JSON.stringify(isPublic) + }; + return Requests.JSONfetch(input, init); +}; + diff --git a/app/static/js/Requests/users/users.js b/app/static/js/Requests/users/users.js deleted file mode 100644 index 0ae1434e68893546eea6b4c5ae3957b70258f19f..0000000000000000000000000000000000000000 --- a/app/static/js/Requests/users/users.js +++ /dev/null @@ -1,23 +0,0 @@ -/***************************************************************************** -* Users * -* Fetch requests for /users routes * -*****************************************************************************/ -Requests.users = {}; - -Requests.users.entity = {}; - -Requests.users.entity.delete = (userId) => { - let input = `/users/${userId}`; - let init = { - method: 'DELETE' - }; - return Requests.JSONfetch(input, init); -} - -Requests.users.entity.deleteAvatar = (userId) => { - let input = `/users/${userId}/avatar`; - let init = { - method: 'DELETE' - }; - return Requests.JSONfetch(input, init); -} diff --git a/app/templates/_navbar.html.j2 b/app/templates/_navbar.html.j2 index 467db7dd878d9f3922159ce2d869b6f46579100d..8819d076eb493a1e09425dc2317c71eed0cfe746 100644 --- a/app/templates/_navbar.html.j2 +++ b/app/templates/_navbar.html.j2 @@ -32,7 +32,7 @@ <ul class="dropdown-content" id="nav-more-dropdown"> {# <li><a href="{{ url_for('main.user_manual') }}"><i class="material-icons left">help</i>Manual</a></li> #} {% if current_user.is_authenticated %} - <li><a href="{{ url_for('users.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User settings</a></li> + <li><a href="{{ url_for('settings.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User settings</a></li> <li class="divider" tabindex="-1"></li> <li><a href="{{ url_for('auth.logout') }}">Log out</a></li> {% else %} diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 11398cb9b0358b81d974272451e784dbfc6689fa..bed2aef2ce9a4c91e1ec8ca55d83453bbc5fe142 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -65,7 +65,7 @@ 'js/Requests/corpora/files.js', 'js/Requests/corpora/followers.js', 'js/Requests/jobs/jobs.js', - 'js/Requests/users/users.js' + 'js/Requests/settings/settings.js' %} <script src="{{ ASSET_URL }}"></script> {%- endassets %} diff --git a/app/templates/_sidenav.html.j2 b/app/templates/_sidenav.html.j2 index 2d66dc2390f4faa6cf17edaa212642057d8ed6d8..f48ae4be4bc0e8dd80ccb51b512fe2c8a530da10 100644 --- a/app/templates/_sidenav.html.j2 +++ b/app/templates/_sidenav.html.j2 @@ -46,7 +46,7 @@ <li><a href="{{ url_for('main.social_area', _anchor='public-corproa') }}"><i class="nopaque-icons">I</i>Public Corpora</a></li> <li><div class="divider"></div></li> <li><a class="subheader">Account</a></li> - <li><a href="{{ url_for('users.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User Settings</a></li> + <li><a href="{{ url_for('settings.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">settings</i>User Settings</a></li> <li><a href="{{ url_for('auth.logout') }}">Log out</a></li> {% if current_user.can('ADMINISTRATE') or current_user.can('USE_API') %} <li><div class="divider"></div></li> diff --git a/app/templates/users/edit_profile.html.j2 b/app/templates/settings/edit_profile.html.j2 similarity index 79% rename from app/templates/users/edit_profile.html.j2 rename to app/templates/settings/edit_profile.html.j2 index 1da2f37777cf3cba5dbc788b20ddd8aa6473abe8..afdc652de848ebc8d3e1856999f1f3c4e7ce725a 100644 --- a/app/templates/users/edit_profile.html.j2 +++ b/app/templates/settings/edit_profile.html.j2 @@ -16,7 +16,8 @@ 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 s8" style="margin-top: 22.8px;"> + <div class="col s8"> + <br> <ul class="collapsible"> <li> <div class="collapsible-header" style="justify-content: space-between;"> @@ -38,7 +39,7 @@ </form> </div> </li> - <li> + {# <li> <div class="collapsible-header" style="justify-content: space-between;"><span>Privacy Settings</span><i class="material-icons caret right">keyboard_arrow_right</i></div> <div class="collapsible-body"> <form method="POST"> @@ -59,21 +60,48 @@ </div> </form> </div> - </li> + </li> #} <li> <div class="collapsible-header" style="justify-content: space-between;"><span>Public Profile</span><i class="material-icons caret right">keyboard_arrow_right</i></div> <div class="collapsible-body"> <form method="POST" enctype="multipart/form-data"> {{ edit_public_profile_information_form.hidden_tag() }} + <div class="switch"> + <label> + private + <input {% if user.is_public %}checked{% endif %} id="profile-is-public-switch" type="checkbox"> + <span class="lever"></span> + public + </label> + </div> + <p></p> + <br> + <div class="divider"></div> + <p>Show:</p> <div class="row"> - <div class="col s12 m2"> - <img src="{{ url_for('.profile_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img" id="avatar"> + <div class="col s3"> + <p> + <label> + {{ edit_public_profile_information_form.show_email() }} + <span>{{ edit_public_profile_information_form.show_email.label.text }}</span> + </label> + </p> </div> - <div class="col s12 m6"> - {{ wtf.render_field(edit_public_profile_information_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload') }} - </div> - <div class="col s12 m1"> - <a class="btn-floating red waves-effect waves-light modal-trigger" style="margin-top:15px;" href="#delete-avatar-modal"><i class="material-icons">delete</i></a> + <div class="col s3"> + <p> + <label> + {{ edit_public_profile_information_form.show_last_seen() }} + <span>{{ edit_public_profile_information_form.show_last_seen.label.text }}</span> + </label> + </p> + </div> + <div class="col s4"> + <p> + <label> + {{ edit_public_profile_information_form.show_member_since() }} + <span>{{ edit_public_profile_information_form.show_member_since.label.text }}</span> + </label> + </p> </div> </div> <p></p> @@ -83,9 +111,23 @@ {{ wtf.render_field(edit_public_profile_information_form.website, material_icon='laptop') }} {{ wtf.render_field(edit_public_profile_information_form.organization, material_icon='business') }} {{ wtf.render_field(edit_public_profile_information_form.location, material_icon='location_on') }} + <p></p> + <div class="row"> + <div class="col s12 m2"> + <img src="{{ url_for('users.profile_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img" id="avatar"> + </div> + <div class="col s12 m6"> + {{ wtf.render_field(edit_public_profile_information_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload') }} + </div> + <div class="col s12 m1"> + <a class="btn-floating red waves-effect waves-light modal-trigger" style="margin-top:15px;" href="#delete-avatar-modal"><i class="material-icons">delete</i></a> + </div> + </div> + <br> + <p></p> <div class="right-align"> {{ wtf.render_field(edit_public_profile_information_form.submit, material_icon='send') }} - </div> + </div> </form> </div> </li> @@ -100,7 +142,8 @@ 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 s8" style="margin-top: 22.8px;"> + <div class="col s8"> + <br> <ul class="collapsible"> <li> <div class="collapsible-header" style="justify-content: space-between;"> @@ -182,36 +225,17 @@ {% block scripts %} {{ super() }} <script> -let publicProfile = document.querySelector('#public-profile'); -let disableButtons = document.querySelectorAll('[data-action="disable"]'); let deleteButton = document.querySelector('#delete-avatar'); let avatar = document.querySelector('#avatar'); let avatarUpload = document.querySelector('#avatar-upload'); -for (let disableButton of disableButtons) { - disableButton.disabled = !publicProfile.checked; -} - -publicProfile.addEventListener('change', () => { - if (publicProfile.checked) { - for (let disableButton of disableButtons) { - disableButton.disabled = false; - } - } else { - for (let disableButton of disableButtons) { - disableButton.checked = false; - disableButton.disabled = true; - } - } -}); - avatarUpload.addEventListener('change', function() { let file = this.files[0]; avatar.src = URL.createObjectURL(file); }); deleteButton.addEventListener('click', () => { - Requests.users.entity.deleteAvatar(currentUserId) + Requests.settings.entity.deleteAvatar(currentUserId) .then( (response) => { avatar.src = "{{ url_for('static', filename='images/user_avatar.png') }}"; @@ -220,7 +244,7 @@ deleteButton.addEventListener('click', () => { }); document.querySelector('#delete-user-button').addEventListener('click', (event) => { - Requests.users.entity.delete(currentUserId) + Requests.settings.entity.delete(currentUserId) .then((response) => {window.location.href = '/';}); }); @@ -239,5 +263,16 @@ document.querySelectorAll('.collapsible').forEach((collapsible) => { }); }); +// #region Public Switch +let profileIsPublicSwitchElement = document.querySelector('#profile-is-public-switch'); +profileIsPublicSwitchElement.addEventListener('change', (event) => { + let newIsPublic = profileIsPublicSwitchElement.checked; + Requests.settings.entity.isPublic.update(currentUserId, newIsPublic) + .catch((response) => { + profileIsPublicSwitchElement.checked = !newIsPublic; + }); +}); +// #endregion Public Switch + </script> {% endblock scripts %} diff --git a/app/templates/users/edit_profile_testing.html.j2 b/app/templates/users/edit_profile_testing.html.j2 deleted file mode 100644 index 88eb8fb5a1e3cec735f92183627ee11e16ad31ca..0000000000000000000000000000000000000000 --- a/app/templates/users/edit_profile_testing.html.j2 +++ /dev/null @@ -1,236 +0,0 @@ -{% extends "base.html.j2" %} -{% import "materialize/wtf.html.j2" as wtf %} - -{% block page_content %} -<div class="container"> - <div class="row"> - <div class="col s12"> - <h1 id="title">{{ title }}</h1> - </div> - </div> - <div class="row"> - <div class="col s12 m9 l10"> - - <div id="user-settings" class="card section scrollspy"> - <form method="POST" enctype="multipart/form-data"> - <div class="card-content"> - <span class="card-title">User Settings</span> - <div class="row"> - <div class="col s6"> - {{ edit_profile_settings_form.hidden_tag() }} - {{ wtf.render_field(edit_profile_settings_form.username, material_icon='person') }} - {{ wtf.render_field(edit_profile_settings_form.email, material_icon='email') }} - </div> - </div> - </div> - <div class="card-action"> - <div class="right-align"> - {{ wtf.render_field(edit_profile_settings_form.submit, material_icon='send') }} - </div> - </div> - </form> - </div> - - <form method="POST"> - {{ edit_privacy_settings_form.hidden_tag() }} - <div id="privacy-settings" class="card section scrollspy"> - <div class="card-content"> - <span class="card-title">Privacy settings</span> - <br> - {{ wtf.render_field(edit_privacy_settings_form.is_public, id='public-profile') }} - <br> - <hr> - <br> - {{ wtf.render_field(edit_privacy_settings_form.show_email, data_action='disable', disabled=true) }} - <br> - {{ wtf.render_field(edit_privacy_settings_form.show_last_seen, data_action='disable', disabled=true) }} - <br> - {{ wtf.render_field(edit_privacy_settings_form.show_member_since, data_action='disable', disabled=true) }} - <br> - </div> - <div class="card-action"> - <div class="right-align"> - {{ wtf.render_field(edit_privacy_settings_form.submit, material_icon='send') }} - </div> - </div> - </div> - </form> - - <div id="public-profile-settings" class="card section scrollspy"> - <form method="POST" enctype="multipart/form-data"> - <div class="card-content"> - {{ edit_public_profile_information_form.hidden_tag() }} - <div class="row"> - <div class="col s12"> - <span class="card-title">Public Profile</span> - </div> - </div> - <div class="row"> - <div class="col s5"> - <div class="row"> - <div class="col s2"></div> - <div class="col s8"> - <img src="{{ url_for('.profile_avatar', user_id=user.id) }}" alt="user-image" class="circle responsive-img" id="avatar"> - </div> - <div class="col s2"></div> - </div> - <div class="row"> - <div class="col s2"> - <div class="center-align"> - <a class="btn-floating red waves-effect waves-light modal-trigger" style="margin-top:15px;" href="#delete-avatar-modal"><i class="material-icons">delete</i></a> - </div> - </div> - <div class="col s10"> - {{ wtf.render_field(edit_public_profile_information_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload') }} - </div> - </div> - </div> - <div class="col s7"> - <p></p> - <br> - {{ wtf.render_field(edit_public_profile_information_form.full_name, material_icon='badge') }} - {{ wtf.render_field(edit_public_profile_information_form.about_me, material_icon='description') }} - {{ wtf.render_field(edit_public_profile_information_form.website, material_icon='laptop') }} - {{ wtf.render_field(edit_public_profile_information_form.organization, material_icon='business') }} - {{ wtf.render_field(edit_public_profile_information_form.location, material_icon='location_on') }} - </div> - </div> - </div> - <div class="card-action"> - <div class="right-align"> - {{ wtf.render_field(edit_public_profile_information_form.submit, material_icon='send') }} - </div> - </div> - </form> - </div> - - <form method="POST"> - {{ edit_notification_settings_form.hidden_tag() }} - <div id="notification-settings" class="card section scrollspy"> - <div class="card-content"> - <span class="card-title">Notification settings</span> - {{ wtf.render_field(edit_notification_settings_form.job_status_mail_notification_level, material_icon='notifications') }} - </div> - <div class="card-action"> - <div class="right-align"> - {{ wtf.render_field(edit_notification_settings_form.submit, material_icon='send') }} - </div> - </div> - </div> - </form> - - <form method="POST"> - {{ change_password_form.hidden_tag() }} - <div id="change-password" class="card section scrollspy"> - <div class="card-content"> - <span class="card-title">Change Password</span> - {{ wtf.render_field(change_password_form.password, material_icon='vpn_key') }} - {{ wtf.render_field(change_password_form.new_password, material_icon='vpn_key') }} - {{ wtf.render_field(change_password_form.new_password_2, material_icon='vpn_key') }} - </div> - <div class="card-action"> - <div class="right-align"> - {{ wtf.render_field(change_password_form.submit, material_icon='send') }} - </div> - </div> - </div> - </form> - - <div id="delete-account" class="card section scrollspy"> - <div class="card-content"> - <span class="card-title">Delete account</span> - <p>Deleting an account has the following effects:</p> - <ul> - <li>All data associated with your corpora and jobs will be permanently deleted.</li> - <li>All settings will be permanently deleted.</li> - </ul> - </div> - <div class="card-action right-align"> - <a class="btn red waves-effect waves-light modal-trigger" href="#delete-user"><i class="material-icons left">delete</i>Delete</a> - </div> - </div> - - </div> - <div class="col hide-on-small-only m3 l2"> - <ul class="section table-of-contents" style="position: fixed !important;"> - <li><a href="#user-settings">User Settings</a></li> - <li><a href="#privacy-settings">Privacy Settings</a></li> - <li><a href="#public-profile-settings">Public Profile Settings</a></li> - <li><a href="#notification-settings">Notification Settings</a></li> - <li><a href="#change-password">Change Password</a></li> - <li><a href="#delete-account">Delete Account</a></li> - </ul> - </div> - </div> -</div> -{% endblock page_content %} - -{% block modals %} -<div class="modal" id="delete-avatar-modal"> - <div class="modal-content"> - <h4>Confirm Avatar deletion</h4> - <p>Do you really want to delete your Avatar?</p> - </div> - <div class="modal-footer"> - <a class="btn modal-close waves-effect waves-light">Cancel</a> - <a class="btn modal-close red waves-effect waves-light" id="delete-avatar">Delete</a> - </div> -</div> -<div class="modal" id="delete-user"> - <div class="modal-content"> - <h4>Confirm User deletion</h4> - <p>Do you really want to delete the User <b>{{ current_user.username }}</b>? All files will be permanently deleted!</p> - </div> - <div class="modal-footer"> - <a class="btn modal-close waves-effect waves-light">Cancel</a> - <a class="btn modal-close red waves-effect waves-light" id="delete-user-button">Delete</a> - </div> -</div> -{% endblock modals %} - -{% block scripts %} -{{ super() }} -<script> -let publicProfile = document.querySelector('#public-profile'); -let disableButtons = document.querySelectorAll('[data-action="disable"]'); -let deleteButton = document.querySelector('#delete-avatar'); -let avatar = document.querySelector('#avatar'); -let avatarUpload = document.querySelector('#avatar-upload'); - -for (let disableButton of disableButtons) { - disableButton.disabled = !publicProfile.checked; -} - -publicProfile.addEventListener('change', () => { - if (publicProfile.checked) { - for (let disableButton of disableButtons) { - disableButton.disabled = false; - } - } else { - for (let disableButton of disableButtons) { - disableButton.checked = false; - disableButton.disabled = true; - } - } -}); - -avatarUpload.addEventListener('change', function() { - let file = this.files[0]; - avatar.src = URL.createObjectURL(file); -}); - -deleteButton.addEventListener('click', () => { - Requests.users.entity.deleteAvatar(currentUserId) - .then( - (response) => { - avatar.src = "{{ url_for('static', filename='images/user_avatar.png') }}"; - } - ); -}); - -document.querySelector('#delete-user-button').addEventListener('click', (event) => { - Requests.users.entity.delete(currentUserId) - .then((response) => {window.location.href = '/';}); -}); -</script> -{% endblock scripts %} diff --git a/app/templates/users/profile.html.j2 b/app/templates/users/profile.html.j2 index 31c12359cc476569ab3cdef04baa4550841c0534..895fba82e975e1004ae59ae430d6730d79856f56 100644 --- a/app/templates/users/profile.html.j2 +++ b/app/templates/users/profile.html.j2 @@ -81,7 +81,7 @@ <p></p> <br> {% if current_user.is_authenticated and current_user.hashid == user.id %} - <a class="waves-effect waves-light btn-small" href="{{ url_for('.edit_profile', user_id=current_user.id) }}">Edit profile</a> + <a class="waves-effect waves-light btn-small" href="{{ url_for('settings.edit_profile', user_id=current_user.id) }}">Edit profile</a> {% endif %} </div> </div> diff --git a/app/users/routes.py b/app/users/routes.py index 52e25f1842c47672db35f6d2810aa1608110d129..876df2915a2f8cb0b6562c1bd5e16a5ba0f0b1dd 100644 --- a/app/users/routes.py +++ b/app/users/routes.py @@ -1,6 +1,5 @@ from flask import ( abort, - flash, redirect, render_template, send_from_directory, @@ -10,19 +9,9 @@ from flask_breadcrumbs import register_breadcrumb from flask_login import current_user, login_required import os from app import db -from app.models import Avatar, Corpus, ProfilePrivacySettings, User +from app.models import Corpus, User from . import bp -from .forms import ( - ChangePasswordForm, - EditNotificationSettingsForm, - EditPrivacySettingsForm, - EditProfileSettingsForm, - EditPublicProfileInformationForm -) -from .utils import ( - user_endpoint_arguments_constructor as user_eac, - user_dynamic_list_constructor as user_dlc -) +from .utils import user_dynamic_list_constructor as user_dlc @bp.route('') @@ -75,102 +64,3 @@ def profile_avatar(user_id): attachment_filename=user.avatar.filename, mimetype=user.avatar.mimetype ) - - -@bp.route('/<hashid:user_id>/edit', methods=['GET', 'POST']) -@register_breadcrumb(bp, 'breadcrumbs.settings', '<i class="material-icons left">settings</i>Settings', endpoint_arguments_constructor=user_eac) -@login_required -def edit_profile(user_id): - user = User.query.get_or_404(user_id) - if not (user == current_user or current_user.is_administrator()): - abort(403) - # region forms - edit_profile_settings_form = EditProfileSettingsForm( - current_user, - data=current_user.to_json_serializeable(), - prefix='edit-profile-settings-form' - ) - edit_privacy_settings_form = EditPrivacySettingsForm( - data=current_user.to_json_serializeable(), - prefix='edit-privacy-settings-form' - ) - edit_public_profile_information_form = EditPublicProfileInformationForm( - data=current_user.to_json_serializeable(), - prefix='edit-public-profile-information-form' - ) - change_password_form = ChangePasswordForm( - current_user, - prefix='change-password-form' - ) - edit_notification_settings_form = EditNotificationSettingsForm( - data=current_user.to_json_serializeable(), - prefix='edit-notification-settings-form' - ) - # endregion forms - # region handle edit profile settings form - if edit_profile_settings_form.validate_on_submit(): - current_user.email = edit_profile_settings_form.email.data - current_user.username = edit_profile_settings_form.username.data - db.session.commit() - flash('Profile settings updated') - return redirect(url_for('.user', user_id=user.id)) - # endregion handle edit profile settings form - # region handle edit privacy settings form - if edit_privacy_settings_form.submit.data and edit_privacy_settings_form.validate(): - current_user.is_public = edit_privacy_settings_form.is_public.data - if edit_privacy_settings_form.show_email.data: - current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL) - else: - current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL) - if edit_privacy_settings_form.show_last_seen.data: - current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN) - else: - current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN) - if edit_privacy_settings_form.show_member_since.data: - current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE) - else: - current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE) - db.session.commit() - flash('Your changes have been saved') - return redirect(url_for('.user', user_id=user.id)) - # endregion handle edit privacy settings form - # region handle edit public profile information form - if edit_public_profile_information_form.submit.data and edit_public_profile_information_form.validate(): - if edit_public_profile_information_form.avatar.data: - try: - Avatar.create(edit_public_profile_information_form.avatar.data, user=current_user) - except (AttributeError, OSError): - abort(500) - current_user.about_me = edit_public_profile_information_form.about_me.data - current_user.location = edit_public_profile_information_form.location.data - current_user.organization = edit_public_profile_information_form.organization.data - current_user.website = edit_public_profile_information_form.website.data - current_user.full_name = edit_public_profile_information_form.full_name.data - db.session.commit() - flash('Profile settings updated') - return redirect(url_for('.user', user_id=user.id)) - # endregion handle edit public profile information form - # region handle change_password_form POST - if change_password_form.submit.data and change_password_form.validate(): - current_user.password = change_password_form.new_password.data - db.session.commit() - flash('Your changes have been saved') - return redirect(url_for('.edit_profile', user_id=user.id)) - # endregion handle change_password_form POST - # region handle edit_notification_settings_form POST - if edit_notification_settings_form.submit and edit_notification_settings_form.validate(): - current_user.setting_job_status_mail_notification_level = edit_notification_settings_form.job_status_mail_notification_level.data - db.session.commit() - flash('Your changes have been saved') - return redirect(url_for('.edit_profile', user_id=user.id)) - # endregion handle edit_notification_settings_form POST - return render_template( - 'users/edit_profile.html.j2', - edit_profile_settings_form=edit_profile_settings_form, - edit_privacy_settings_form=edit_privacy_settings_form, - edit_public_profile_information_form=edit_public_profile_information_form, - change_password_form=change_password_form, - edit_notification_settings_form=edit_notification_settings_form, - user=user, - title='Settings' - )