From 8c3789b8b35d5307b8e6031352bd7886667e8d21 Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Tue, 5 Sep 2023 14:02:02 -0400 Subject: [PATCH 1/6] Rearraged the trainings frontend into course & trainings --- .../user/templates/user/edit_course.html | 51 +++++++++++++++++++ .../user/templates/user/edit_training.html | 32 +----------- .../user/templatetags/user_templatetags.py | 2 +- physionet-django/user/urls.py | 1 + physionet-django/user/views.py | 50 +++++++++++++++--- 5 files changed, 96 insertions(+), 40 deletions(-) create mode 100644 physionet-django/user/templates/user/edit_course.html diff --git a/physionet-django/user/templates/user/edit_course.html b/physionet-django/user/templates/user/edit_course.html new file mode 100644 index 0000000000..93e35632be --- /dev/null +++ b/physionet-django/user/templates/user/edit_course.html @@ -0,0 +1,51 @@ +{% extends "user/settings.html" %} + +{% block title %}{{ SITE_NAME }} Courses{% endblock %} + +{% block main_content %} +

Courses

+
+

To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

+
+

Submit evidence of a completed course

+ +

Please use the form below to submit a new completion report.

+

For CITI training, please refer to our step-by-step instructions and + be sure to upload the training report, not the certificate. Please ensure that you complete the "Data or + Specimens Only Research" course (with HIPAA module). + {% if ticket_system_url %}To raise a support request, please click here. + {% endif %}

+ +
+ {% include "descriptive_inline_form_snippet.html" with form=training_form %} + + +
+ +{% endblock %} + +{% block local_js_bottom %} + +{% endblock %} diff --git a/physionet-django/user/templates/user/edit_training.html b/physionet-django/user/templates/user/edit_training.html index 8791e0199f..d8a5ac6f48 100644 --- a/physionet-django/user/templates/user/edit_training.html +++ b/physionet-django/user/templates/user/edit_training.html @@ -28,7 +28,6 @@

{{ status|capfirst }}

{% endfor %}
-

Submit evidence of a completed course

{% for status, group in training_by_status.items %} {% if status == 'under review' and group %} @@ -50,36 +49,7 @@

Submit evidence of a completed course

{% endif %} {% endfor %} -

Please use the form below to submit a new completion report.

-

For CITI training, please refer to our step-by-step instructions and - be sure to upload the training report, not the certificate. Please ensure that you complete the "Data or - Specimens Only Research" course (with HIPAA module). - {% if ticket_system_url %}To raise a support request, please click here. - {% endif %}

- -
- {% include "descriptive_inline_form_snippet.html" with form=training_form %} - - -
+
To submit a new completion report, please go to the Courses page.
{% endblock %} diff --git a/physionet-django/user/templatetags/user_templatetags.py b/physionet-django/user/templatetags/user_templatetags.py index c680e53a27..f2bd99344d 100644 --- a/physionet-django/user/templatetags/user_templatetags.py +++ b/physionet-django/user/templatetags/user_templatetags.py @@ -6,7 +6,7 @@ @register.inclusion_tag('user/settings_tabs.html') def settings_tabs(hide_password_settings: bool): - default_tabs = ['Profile', 'Emails', 'Username', 'Cloud', 'ORCID', 'Credentialing', 'Training', 'Agreements'] + default_tabs = ['Profile', 'Emails', 'Username', 'Cloud', 'ORCID', 'Credentialing', 'Course', 'Training', 'Agreements'] if not hide_password_settings: default_tabs.insert(1, 'Password') return {'settings_tabs': default_tabs} diff --git a/physionet-django/user/urls.py b/physionet-django/user/urls.py index 7e9dbf832c..15e8cc6918 100644 --- a/physionet-django/user/urls.py +++ b/physionet-django/user/urls.py @@ -23,6 +23,7 @@ path('settings/credentialing/applications/', views.user_credential_applications, name='user_credential_applications'), path('settings/training/', views.edit_training, name='edit_training'), + path('settings/course/', views.edit_course, name='edit_course'), path('settings/training//', views.edit_training_detail, name='edit_training_detail'), path('settings/agreements/', views.view_agreements, name='edit_agreements'), path('settings/agreements//', views.view_signed_agreement, name='view_signed_agreement'), diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 9303e9e066..12fb75e4c6 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -716,7 +716,42 @@ def credential_application(request): 'code_of_conduct': code_of_conduct, }, ) +@login_required +def edit_course(request): + """ + Courses settings page. + """ + if settings.TICKET_SYSTEM_URL: + ticket_system_url = settings.TICKET_SYSTEM_URL + else: + ticket_system_url = None + if request.method == 'POST': + training_form = forms.TrainingForm( + user=request.user, data=request.POST, files=request.FILES, training_type=request.POST.get('training_type') + ) + if training_form.is_valid(): + training_form.save() + messages.success(request, 'The training has been submitted successfully.') + training_application_request(request, training_form) + training_form = forms.TrainingForm(user=request.user) + else: + messages.error(request, 'Invalid submission. Check the errors below.') + + else: + training_type = request.GET.get('trainingType') + if training_type: + training_form = forms.TrainingForm(user=request.user, training_type=training_type) + else: + training_form = forms.TrainingForm(user=request.user) + + return render( + request, + 'user/edit_course.html', + { + 'training_form': training_form, + 'ticket_system_url': ticket_system_url}, + ) @login_required def edit_training(request): @@ -749,18 +784,17 @@ def edit_training(request): training = Training.objects.select_related('training_type').filter(user=request.user).order_by('-status') training_by_status = { - 'under review': training.get_review(), - 'active': training.get_valid(), - 'expired': training.get_expired(), - 'rejected': training.get_rejected(), - } + 'under review': training.get_review(), + 'active': training.get_valid(), + 'expired': training.get_expired(), + 'rejected': training.get_rejected(), + } return render( request, 'user/edit_training.html', - {'training_form': training_form, - 'training_by_status': training_by_status, - 'ticket_system_url': ticket_system_url}, + { + 'training_by_status': training_by_status}, ) From 14ef23879d62055d44638c9da6d50784d976ae5c Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Tue, 5 Sep 2023 15:56:54 -0400 Subject: [PATCH 2/6] Styling changes --- .../user/templatetags/user_templatetags.py | 20 +- physionet-django/user/urls.py | 176 +++-- physionet-django/user/views.py | 616 +++++++++++------- 3 files changed, 497 insertions(+), 315 deletions(-) diff --git a/physionet-django/user/templatetags/user_templatetags.py b/physionet-django/user/templatetags/user_templatetags.py index f2bd99344d..f86c6e4108 100644 --- a/physionet-django/user/templatetags/user_templatetags.py +++ b/physionet-django/user/templatetags/user_templatetags.py @@ -4,14 +4,24 @@ register = template.Library() -@register.inclusion_tag('user/settings_tabs.html') +@register.inclusion_tag("user/settings_tabs.html") def settings_tabs(hide_password_settings: bool): - default_tabs = ['Profile', 'Emails', 'Username', 'Cloud', 'ORCID', 'Credentialing', 'Course', 'Training', 'Agreements'] + default_tabs = [ + "Profile", + "Emails", + "Username", + "Cloud", + "ORCID", + "Credentialing", + "Course", + "Training", + "Agreements", + ] if not hide_password_settings: - default_tabs.insert(1, 'Password') - return {'settings_tabs': default_tabs} + default_tabs.insert(1, "Password") + return {"settings_tabs": default_tabs} -@register.filter(name='has_group') +@register.filter(name="has_group") def has_group(user, group_name): return user.groups.filter(name=group_name).exists() diff --git a/physionet-django/user/urls.py b/physionet-django/user/urls.py index 15e8cc6918..8547e7bd11 100644 --- a/physionet-django/user/urls.py +++ b/physionet-django/user/urls.py @@ -7,90 +7,130 @@ urlpatterns = [ - path('login/', login_view, name='login'), - - path('logout/', views.logout, name='logout'), - + path("login/", login_view, name="login"), + path("logout/", views.logout, name="logout"), # Settings - path('settings/', views.user_settings, name='user_settings'), - path('settings/profile/', views.edit_profile, name='edit_profile'), - path('settings/emails/', views.edit_emails, name='edit_emails'), - path('settings/username/', views.edit_username, name='edit_username'), - path('settings/cloud/', views.edit_cloud, name='edit_cloud'), - path('settings/orcid/', views.edit_orcid, name='edit_orcid'), - path('authorcid/', views.auth_orcid, name='auth_orcid'), - path('settings/credentialing/', views.edit_credentialing, name='edit_credentialing'), - path('settings/credentialing/applications/', - views.user_credential_applications, name='user_credential_applications'), - path('settings/training/', views.edit_training, name='edit_training'), - path('settings/course/', views.edit_course, name='edit_course'), - path('settings/training//', views.edit_training_detail, name='edit_training_detail'), - path('settings/agreements/', views.view_agreements, name='edit_agreements'), - path('settings/agreements//', views.view_signed_agreement, name='view_signed_agreement'), - + path("settings/", views.user_settings, name="user_settings"), + path("settings/profile/", views.edit_profile, name="edit_profile"), + path("settings/emails/", views.edit_emails, name="edit_emails"), + path("settings/username/", views.edit_username, name="edit_username"), + path("settings/cloud/", views.edit_cloud, name="edit_cloud"), + path("settings/orcid/", views.edit_orcid, name="edit_orcid"), + path("authorcid/", views.auth_orcid, name="auth_orcid"), + path( + "settings/credentialing/", views.edit_credentialing, name="edit_credentialing" + ), + path( + "settings/credentialing/applications/", + views.user_credential_applications, + name="user_credential_applications", + ), + path("settings/training/", views.edit_training, name="edit_training"), + path("settings/course/", views.edit_course, name="edit_course"), + path( + "settings/training//", + views.edit_training_detail, + name="edit_training_detail", + ), + path("settings/agreements/", views.view_agreements, name="edit_agreements"), + path( + "settings/agreements//", + views.view_signed_agreement, + name="view_signed_agreement", + ), # Current tokens are 20 characters long and consist of 0-9A-Za-z # Obsolete tokens are 34 characters long and also include a hyphen - re_path(r'^verify/(?P[0-9A-Za-z_\-]+)/(?P[-0-9A-Za-z]{1,34})/$', - views.verify_email, name='verify_email'), - - + re_path( + r"^verify/(?P[0-9A-Za-z_\-]+)/(?P[-0-9A-Za-z]{1,34})/$", + views.verify_email, + name="verify_email", + ), # Public user profile - path('users//', views.public_profile, - name='public_profile'), - path('users//profile-photo/', views.profile_photo, - name='profile_photo'), - path('credential-application/', views.credential_application, - name='credential_application'), + path("users//", views.public_profile, name="public_profile"), + path("users//profile-photo/", views.profile_photo, name="profile_photo"), + path( + "credential-application/", + views.credential_application, + name="credential_application", + ), # TODO: remove this after 30 days of commit merge, we want let the old links that was sent to the referees work - path('credential-reference//', - views.credential_reference, name='credential_reference'), - path('credential-reference///', - views.credential_reference_verification, name='credential_reference_verification'), - path('trainings//report/', views.training_report, name='training_report'), + path( + "credential-reference//", + views.credential_reference, + name="credential_reference", + ), + path( + "credential-reference///", + views.credential_reference_verification, + name="credential_reference_verification", + ), + path( + "trainings//report/", + views.training_report, + name="training_report", + ), ] if not settings.ENABLE_SSO: - urlpatterns.extend([ - path('register/', views.register, name='register'), - path('settings/password/', views.edit_password, name='edit_password'), - path('settings/password/changed/', views.edit_password_complete, name='edit_password_complete'), - re_path(r'^activate/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$', + urlpatterns.extend( + [ + path("register/", views.register, name="register"), + path("settings/password/", views.edit_password, name="edit_password"), + path( + "settings/password/changed/", + views.edit_password_complete, + name="edit_password_complete", + ), + re_path( + r"^activate/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$", views.activate_user, - name='activate_user'), - # Request password reset - path('reset-password/', views.reset_password_request, - name='reset_password_request'), - # Page shown after reset email has been sent - path('reset-password/sent/', views.reset_password_sent, - name='reset_password_sent'), - # Prompt user to enter new password and carry out password reset (if url is valid) - re_path(r'^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$', - views.reset_password_confirm, name='reset_password_confirm'), - # Password reset successfully carried out - path('reset/complete/', views.reset_password_complete, - name='reset_password_complete'), - ]) + name="activate_user", + ), + # Request password reset + path( + "reset-password/", + views.reset_password_request, + name="reset_password_request", + ), + # Page shown after reset email has been sent + path( + "reset-password/sent/", + views.reset_password_sent, + name="reset_password_sent", + ), + # Prompt user to enter new password and carry out password reset (if url is valid) + re_path( + r"^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$", + views.reset_password_confirm, + name="reset_password_confirm", + ), + # Password reset successfully carried out + path( + "reset/complete/", + views.reset_password_complete, + name="reset_password_complete", + ), + ] + ) # Parameters for testing URLs (see physionet/test_urls.py) TEST_DEFAULTS = { - '_user_': 'aewj', - 'training_id': 106, - 'dua_signature_id': 1, - 'application_slug': 'Osm0FMaavviixpsL26v2', - 'verification_token': 'rJ2i7vlzh6AgZ1Wwtcz8zCoI5BqxH0kU', - 'username': 'rgmark', + "_user_": "aewj", + "training_id": 106, + "dua_signature_id": 1, + "application_slug": "Osm0FMaavviixpsL26v2", + "verification_token": "rJ2i7vlzh6AgZ1Wwtcz8zCoI5BqxH0kU", + "username": "rgmark", } TEST_CASES = { - 'verify_email': { - 'uidb64': 'MjEx', - 'token': 'oax3ZcG47GYUhAobbJyp', + "verify_email": { + "uidb64": "MjEx", + "token": "oax3ZcG47GYUhAobbJyp", }, - # Testing activate_user and reset_password_confirm requires a # dynamically-generated token. Skip these URLs for now. - 'activate_user': {'uidb64': 'x', 'token': 'x', '_skip_': True}, - 'reset_password_confirm': {'uidb64': 'x', 'token': 'x', '_skip_': True}, - + "activate_user": {"uidb64": "x", "token": "x", "_skip_": True}, + "reset_password_confirm": {"uidb64": "x", "token": "x", "_skip_": True}, # Testing auth_orcid requires a mock oauth server. Skip this URL. - 'auth_orcid': {'_skip_': True}, + "auth_orcid": {"_skip_": True}, } diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 12fb75e4c6..4875a02fd8 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -63,30 +63,30 @@ logger = logging.getLogger(__name__) -@method_decorator(allow_post_during_maintenance, 'dispatch') +@method_decorator(allow_post_during_maintenance, "dispatch") class LoginView(auth_views.LoginView): - template_name = 'user/login.html' + template_name = "user/login.html" authentication_form = forms.LoginForm redirect_authenticated_user = True -@method_decorator(allow_post_during_maintenance, 'dispatch') +@method_decorator(allow_post_during_maintenance, "dispatch") class SSOLoginView(auth_views.LoginView): - template_name = 'sso/login.html' + template_name = "sso/login.html" authentication_form = forms.LoginForm redirect_authenticated_user = True def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) try: - login_static_page = StaticPage.objects.get(url='/about/login/') + login_static_page = StaticPage.objects.get(url="/about/login/") instruction_sections = Section.objects.filter(static_page=login_static_page) except StaticPage.DoesNotExist: instruction_sections = [] sso_extra_context = { - 'sso_login_button_text': settings.SSO_LOGIN_BUTTON_TEXT, - 'login_instruction_sections': instruction_sections, + "sso_login_button_text": settings.SSO_LOGIN_BUTTON_TEXT, + "login_instruction_sections": instruction_sections, } return {**context, **sso_extra_context} @@ -97,33 +97,33 @@ class LogoutView(auth_views.LogoutView): # Request password reset class PasswordResetView(auth_views.PasswordResetView): - template_name = 'user/reset_password_request.html' - success_url = reverse_lazy('reset_password_sent') - email_template_name = 'user/email/reset_password_email.html' - extra_email_context = {'SITE_NAME': settings.SITE_NAME} + template_name = "user/reset_password_request.html" + success_url = reverse_lazy("reset_password_sent") + email_template_name = "user/email/reset_password_email.html" + extra_email_context = {"SITE_NAME": settings.SITE_NAME} # Page shown after reset email has been sent class PasswordResetDoneView(auth_views.PasswordResetDoneView): - template_name = 'user/reset_password_sent.html' + template_name = "user/reset_password_sent.html" # Prompt user to enter new password and carry out password reset (if # url is valid) -@method_decorator(disallow_during_maintenance, 'dispatch') +@method_decorator(disallow_during_maintenance, "dispatch") class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): - template_name = 'user/reset_password_confirm.html' - success_url = reverse_lazy('reset_password_complete') + template_name = "user/reset_password_confirm.html" + success_url = reverse_lazy("reset_password_complete") # Password reset successfully carried out class PasswordResetCompleteView(auth_views.PasswordResetCompleteView): - template_name = 'user/reset_password_complete.html' + template_name = "user/reset_password_complete.html" class PasswordChangeView(auth_views.PasswordChangeView): - success_url = reverse_lazy('edit_password_complete') - template_name = 'user/edit_password.html' + success_url = reverse_lazy("edit_password_complete") + template_name = "user/edit_password.html" login = LoginView.as_view() @@ -136,7 +136,7 @@ class PasswordChangeView(auth_views.PasswordChangeView): edit_password = PasswordChangeView.as_view() -@sensitive_post_parameters('password1', 'password2') +@sensitive_post_parameters("password1", "password2") @disallow_during_maintenance def activate_user(request, uidb64, token): """ @@ -144,10 +144,10 @@ def activate_user(request, uidb64, token): The user will create the password at this stage and then logged in. """ - activation_session_token = '_activation_reset_token' - activation_url_token = 'user-activation' + activation_session_token = "_activation_reset_token" + activation_url_token = "user-activation" title = "Account activation" - context = {'title': 'Invalid Activation Link', 'isvalid': False} + context = {"title": "Invalid Activation Link", "isvalid": False} try: uid = force_str(urlsafe_base64_decode(uidb64)) @@ -156,17 +156,18 @@ def activate_user(request, uidb64, token): user = None if user and user.is_active: - messages.success(request, 'The account is active.') - return redirect('login') + messages.success(request, "The account is active.") + return redirect("login") - if request.method == 'GET': + if request.method == "GET": if token == activation_url_token: session_token = request.session.get(activation_session_token) if default_token_generator.check_token(user, session_token): # If the token is valid, display the password reset form. form = forms.ActivationForm(user=user) - return render(request, 'user/activate_user.html', { - 'form': form, 'title': title}) + return render( + request, "user/activate_user.html", {"form": form, "title": title} + ) else: if default_token_generator.check_token(user, token): # Store the token in the session and redirect to the @@ -180,9 +181,11 @@ def activate_user(request, uidb64, token): if token == activation_url_token: session_token = request.session.get(activation_session_token) form = forms.ActivationForm(user=user, data=request.POST) - if form.is_valid() and default_token_generator.check_token(user, session_token): + if form.is_valid() and default_token_generator.check_token( + user, session_token + ): with transaction.atomic(): - user.set_password(form.cleaned_data['password1']) + user.set_password(form.cleaned_data["password1"]) user.is_active = True # Check legacy credentials check_legacy_credentials(user, user.email) @@ -192,14 +195,15 @@ def activate_user(request, uidb64, token): email.is_verified = True email.save() request.session.pop(activation_session_token) - logger.info('User activated - {0}'.format(user.email)) - messages.success(request, 'The account has been activated.') + logger.info("User activated - {0}".format(user.email)) + messages.success(request, "The account has been activated.") login(request, user) - return redirect('project_home') - return render(request, 'user/activate_user.html', {'form': form, - 'title': title}) + return redirect("project_home") + return render( + request, "user/activate_user.html", {"form": form, "title": title} + ) - return render(request, 'user/activate_user_complete.html', context) + return render(request, "user/activate_user_complete.html", context) def check_legacy_credentials(user, email): @@ -207,14 +211,13 @@ def check_legacy_credentials(user, email): Check whether a user has already beeen credentialed on the old pn site. If so, credential their account and mark the migration. """ - legacy_credential = LegacyCredential.objects.filter(email=email, - migrated=False) + legacy_credential = LegacyCredential.objects.filter(email=email, migrated=False) if legacy_credential: legacy_credential = legacy_credential.get() user.is_credentialed = True # All of them are mimic credentialed - month, day, year = legacy_credential.mimic_approval_date.split('/') - dt = datetime(int(year), int(month), int(day)) + month, day, year = legacy_credential.mimic_approval_date.split("/") + dt = datetime(int(year), int(month), int(day)) dt = pytz.timezone(timezone.get_default_timezone_name()).localize(dt) user.credential_datetime = dt legacy_credential.migrated = True @@ -223,6 +226,7 @@ def check_legacy_credentials(user, email): legacy_credential.save() user.save() + def remove_email(request, email_id): "Remove a non-primary email associated with a user" user = request.user @@ -232,36 +236,53 @@ def remove_email(request, email_id): email = associated_email.email associated_email.delete() logger.info(f"Removed email {email} from user {user.id}") - messages.success(request, f"The email address ({email}) has been removed from your account.") + messages.success( + request, + f"The email address ({email}) has been removed from your account.", + ) except AssociatedEmail.DoesNotExist: - messages.success(request, "The email address has been removed from your account.") + messages.success( + request, "The email address has been removed from your account." + ) def set_primary_email(request, primary_email_form): "Set the selected email as the primary email" user = request.user if primary_email_form.is_valid(): - associated_email = primary_email_form.cleaned_data['associated_email'] + associated_email = primary_email_form.cleaned_data["associated_email"] # Only do something if they selected a different email if associated_email.email != user.email: - logger.info('Primary email changed from: {0} to {1}'.format(user.email, associated_email.email)) + logger.info( + "Primary email changed from: {0} to {1}".format( + user.email, associated_email.email + ) + ) user.email = associated_email.email - user.save(update_fields=['email']) + user.save(update_fields=["email"]) # Change the email field of author objects belonging to # the user. Warn them if they are the corresponding # author of any projects authors = Author.objects.filter(user=user) authors.update(corresponding_email=associated_email) - messages.success(request, 'Your email: {0} has been set as your new primary email.'.format(user.email)) + messages.success( + request, + "Your email: {0} has been set as your new primary email.".format( + user.email + ), + ) if authors.filter(is_corresponding=True): - messages.info(request, 'The corresponding email in all your authoring projects has been set to your new primary email.') + messages.info( + request, + "The corresponding email in all your authoring projects has been set to your new primary email.", + ) def set_public_email(request, public_email_form): "Set the selected email as the public email" user = request.user if public_email_form.is_valid(): - associated_email = public_email_form.cleaned_data['associated_email'] + associated_email = public_email_form.cleaned_data["associated_email"] current_public_email = user.associated_emails.filter(is_public=True).first() # Only do something if they selected a different email if associated_email != current_public_email: @@ -272,33 +293,55 @@ def set_public_email(request, public_email_form): if associated_email: associated_email.is_public = True associated_email.save() - messages.success(request, 'Your email: {0} has been set to public.'.format(associated_email.email)) + messages.success( + request, + "Your email: {0} has been set to public.".format( + associated_email.email + ), + ) else: - messages.success(request, 'Your email: {0} has been set to private.'.format(current_public_email.email)) + messages.success( + request, + "Your email: {0} has been set to private.".format( + current_public_email.email + ), + ) + def add_email(request, add_email_form): user = request.user if add_email_form.is_valid(): token = get_random_string(20) - associated_email = AssociatedEmail.objects.create(user=user, - email=add_email_form.cleaned_data['email'], - verification_token=token) + associated_email = AssociatedEmail.objects.create( + user=user, + email=add_email_form.cleaned_data["email"], + verification_token=token, + ) # Send an email to the newly added email with a verification link uidb64 = force_str(urlsafe_base64_encode(force_bytes(associated_email.pk))) subject = f"{settings.SITE_NAME} Email Verification" context = { - 'name': user.get_full_name(), - 'domain': get_current_site(request), - 'url_prefix': get_url_prefix(request), - 'uidb64': uidb64, - 'token': token, - 'SITE_NAME': settings.SITE_NAME, + "name": user.get_full_name(), + "domain": get_current_site(request), + "url_prefix": get_url_prefix(request), + "uidb64": uidb64, + "token": token, + "SITE_NAME": settings.SITE_NAME, } - body = loader.render_to_string('user/email/verify_email_email.html', context) - send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, - [add_email_form.cleaned_data['email']], fail_silently=False) - messages.success(request, 'A verification link has been sent to: {0}'.format(associated_email.email)) + body = loader.render_to_string("user/email/verify_email_email.html", context) + send_mail( + subject, + body, + settings.DEFAULT_FROM_EMAIL, + [add_email_form.cleaned_data["email"]], + fail_silently=False, + ) + messages.success( + request, + "A verification link has been sent to: {0}".format(associated_email.email), + ) + @login_required def edit_emails(request): @@ -307,56 +350,65 @@ def edit_emails(request): """ user = request.user - associated_emails = AssociatedEmail.objects.filter( - user=user).order_by('-is_verified', '-is_primary_email') + associated_emails = AssociatedEmail.objects.filter(user=user).order_by( + "-is_verified", "-is_primary_email" + ) total_associated_emails = associated_emails.count() max_associated_emails_allowed = settings.MAX_EMAILS_PER_USER - primary_email_form = forms.AssociatedEmailChoiceForm(user=user, - selection_type='primary') - public_email_form = forms.AssociatedEmailChoiceForm(user=user, - selection_type='public') + primary_email_form = forms.AssociatedEmailChoiceForm( + user=user, selection_type="primary" + ) + public_email_form = forms.AssociatedEmailChoiceForm( + user=user, selection_type="public" + ) add_email_form = forms.AddEmailForm() - if request.method == 'POST': - if 'remove_email' in request.POST: + if request.method == "POST": + if "remove_email" in request.POST: # No form. Just get button value. - email_id = int(request.POST['remove_email']) + email_id = int(request.POST["remove_email"]) remove_email(request, email_id) # Update the associated_emails count after removing an email total_associated_emails = AssociatedEmail.objects.filter(user=user).count() - elif 'set_primary_email' in request.POST: - primary_email_form = forms.AssociatedEmailChoiceForm(user=user, - selection_type='primary', data=request.POST) + elif "set_primary_email" in request.POST: + primary_email_form = forms.AssociatedEmailChoiceForm( + user=user, selection_type="primary", data=request.POST + ) set_primary_email(request, primary_email_form) - elif 'set_public_email' in request.POST: - public_email_form = forms.AssociatedEmailChoiceForm(user=user, - selection_type='public', data=request.POST) + elif "set_public_email" in request.POST: + public_email_form = forms.AssociatedEmailChoiceForm( + user=user, selection_type="public", data=request.POST + ) set_public_email(request, public_email_form) - elif 'add_email' in request.POST: + elif "add_email" in request.POST: if associated_emails.count() >= settings.MAX_EMAILS_PER_USER: messages.error( request, - 'You have reached the maximum number of email addresses allowed.' + "You have reached the maximum number of email addresses allowed.", ) else: add_email_form = forms.AddEmailForm(request.POST) add_email(request, add_email_form) # Update the associated_emails count after adding a new email - total_associated_emails = AssociatedEmail.objects.filter(user=user).count() + total_associated_emails = AssociatedEmail.objects.filter( + user=user + ).count() - context = {'associated_emails': associated_emails, - 'primary_email_form': primary_email_form, - 'add_email_form': add_email_form, - 'public_email_form': public_email_form, - 'total_associated_emails': total_associated_emails, - 'max_associated_emails_allowed': max_associated_emails_allowed} + context = { + "associated_emails": associated_emails, + "primary_email_form": primary_email_form, + "add_email_form": add_email_form, + "public_email_form": public_email_form, + "total_associated_emails": total_associated_emails, + "max_associated_emails_allowed": max_associated_emails_allowed, + } - context['messages'] = messages.get_messages(request) + context["messages"] = messages.get_messages(request) - return render(request, 'user/edit_emails.html', context) + return render(request, "user/edit_emails.html", context) @login_required @@ -367,29 +419,31 @@ def edit_profile(request): profile = request.user.profile form = forms.ProfileForm(instance=profile) - if request.method == 'POST': + if request.method == "POST": if settings.SYSTEM_MAINTENANCE_NO_UPLOAD: # Allow submitting the form, but do not allow the photo to # be modified. - if 'delete_photo' in request.POST or request.FILES: + if "delete_photo" in request.POST or request.FILES: raise ServiceUnavailable() - if 'edit_profile' in request.POST: + if "edit_profile" in request.POST: # Update the profile and return to the same page. Place a message # at the top of the page: 'your profile has been updated' - form = forms.ProfileForm(data=request.POST, files=request.FILES, - instance=profile) + form = forms.ProfileForm( + data=request.POST, files=request.FILES, instance=profile + ) if form.is_valid(): form.save() - messages.success(request, 'Your profile has been updated.') - elif 'delete_photo' in request.POST: + messages.success(request, "Your profile has been updated.") + elif "delete_photo" in request.POST: profile.delete_photo() - messages.success(request, 'Your profile photo has been deleted.') + messages.success(request, "Your profile photo has been deleted.") if not form.errors: form = forms.ProfileForm(instance=profile) - return render(request, 'user/edit_profile.html', {'form':form}) + return render(request, "user/edit_profile.html", {"form": form}) + @login_required def edit_orcid(request): @@ -399,8 +453,8 @@ def edit_orcid(request): ORCID account from their account. """ - if request.method == 'POST': - if 'request_orcid' in request.POST: + if request.method == "POST": + if "request_orcid" in request.POST: client_id = settings.ORCID_CLIENT_ID redirect_uri = settings.ORCID_REDIRECT_URI scope = list(settings.ORCID_SCOPE.split(",")) @@ -409,12 +463,15 @@ def edit_orcid(request): return redirect(authorization_url) - if 'remove_orcid' in request.POST: + if "remove_orcid" in request.POST: try: Orcid.objects.get(user=request.user).delete() orcid_html = None except ObjectDoesNotExist: - messages.error(request, 'Object Does Not Exist Error: tried to unlink an object which does not exist.') + messages.error( + request, + "Object Does Not Exist Error: tried to unlink an object which does not exist.", + ) orcid_html = None else: @@ -423,7 +480,8 @@ def edit_orcid(request): except ObjectDoesNotExist: orcid_html = None - return render(request, 'user/edit_orcid.html', {'orcid': orcid_html}) + return render(request, "user/edit_orcid.html", {"orcid": orcid_html}) + @login_required @disallow_during_maintenance @@ -441,37 +499,44 @@ def auth_orcid(request): client_secret = settings.ORCID_CLIENT_SECRET redirect_uri = settings.ORCID_REDIRECT_URI scope = list(settings.ORCID_SCOPE.split(",")) - oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, - scope=scope) + oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scope) params = request.GET.copy() - code = params['code'] + code = params["code"] try: - token = oauth.fetch_token(settings.ORCID_TOKEN_URL, code=code, - include_client_id=True, client_secret=client_secret) + token = oauth.fetch_token( + settings.ORCID_TOKEN_URL, + code=code, + include_client_id=True, + client_secret=client_secret, + ) try: - validators.validate_orcid_token(token['access_token']) + validators.validate_orcid_token(token["access_token"]) token_valid = True except ValidationError: - messages.error(request, 'Validation Error: ORCID token validation failed.') + messages.error(request, "Validation Error: ORCID token validation failed.") token_valid = False except InvalidGrantError: - messages.error(request, 'Invalid Grant Error: authorization code may be expired or invalid.') + messages.error( + request, + "Invalid Grant Error: authorization code may be expired or invalid.", + ) token_valid = False if token_valid: orcid_profile, _ = Orcid.objects.get_or_create(user=request.user) - orcid_profile.orcid_id = token.get('orcid') - orcid_profile.name = token.get('name') - orcid_profile.access_token = token.get('access_token') - orcid_profile.refresh_token = token.get('refresh_token') - orcid_profile.token_type = token.get('token_type') - orcid_profile.token_scope = token.get('scope') - orcid_profile.token_expiration = token.get('expires_at') + orcid_profile.orcid_id = token.get("orcid") + orcid_profile.name = token.get("name") + orcid_profile.access_token = token.get("access_token") + orcid_profile.refresh_token = token.get("refresh_token") + orcid_profile.token_type = token.get("token_type") + orcid_profile.token_scope = token.get("scope") + orcid_profile.token_expiration = token.get("expires_at") orcid_profile.full_clean() orcid_profile.save() - return redirect('edit_orcid') + return redirect("edit_orcid") + @login_required def edit_password_complete(request): @@ -479,7 +544,7 @@ def edit_password_complete(request): After password has successfully been changed. Need this view because we can't control the edit password view to show a success message. """ - return render(request, 'user/edit_password_complete.html') + return render(request, "user/edit_password_complete.html") def public_profile(request, username): @@ -493,12 +558,20 @@ def public_profile(request, username): raise Http404() # get list of projects - projects = PublishedProject.objects.filter(authors__user=public_user).order_by('-publish_datetime') - + projects = PublishedProject.objects.filter(authors__user=public_user).order_by( + "-publish_datetime" + ) - return render(request, 'user/public_profile.html', { - 'public_user':public_user, 'profile':public_user.profile, - 'public_email':public_email, 'projects':projects}) + return render( + request, + "user/public_profile.html", + { + "public_user": public_user, + "profile": public_user.profile, + "public_email": public_email, + "projects": projects, + }, + ) def profile_photo(request, username): @@ -522,9 +595,9 @@ def register(request): """ user = request.user if user.is_authenticated: - return redirect('home') + return redirect("home") - if request.method == 'POST': + if request.method == "POST": form = forms.RegistrationForm(request=request, data=request.POST) if form.is_valid(): # Create the new user @@ -534,19 +607,17 @@ def register(request): form.full_clean() if form.is_valid(): raise - user = User.objects.get(username=form.data['username']) + user = User.objects.get(username=form.data["username"]) else: - uidb64 = force_str(urlsafe_base64_encode(force_bytes( - user.pk))) + uidb64 = force_str(urlsafe_base64_encode(force_bytes(user.pk))) token = default_token_generator.make_token(user) notify_account_registration(request, user, uidb64, token) - return render(request, 'user/register_done.html', { - 'email': user.email}) + return render(request, "user/register_done.html", {"email": user.email}) else: form = forms.RegistrationForm(request=request) - response = render(request, 'user/register.html', {'form': form}) + response = render(request, "user/register.html", {"form": form}) form.set_response_cookies(response) return response @@ -557,7 +628,7 @@ def user_settings(request): Settings. Redirect to default - settings/profile Don't call this 'settings' because there's an import called 'settings' """ - return redirect('edit_profile') + return redirect("edit_profile") @login_required @@ -581,14 +652,21 @@ def verify_email(request, uidb64, token): associated_email.save() if not user.is_credentialed: check_legacy_credentials(user, associated_email.email) - logger.info('User {0} verified another email {1}'.format(user.id, associated_email)) - messages.success(request, 'The email address {} has been verified.'.format( - associated_email)) - return redirect('edit_emails') + logger.info( + "User {0} verified another email {1}".format(user.id, associated_email) + ) + messages.success( + request, + "The email address {} has been verified.".format(associated_email), + ) + return redirect("edit_emails") - logger.warning('Invalid Verification Link') - return render(request, 'user/verify_email.html', - {'title':'Invalid Verification Link', 'isvalid':False}) + logger.warning("Invalid Verification Link") + return render( + request, + "user/verify_email.html", + {"title": "Invalid Verification Link", "isvalid": False}, + ) @login_required @@ -599,17 +677,16 @@ def edit_username(request): user = request.user form = forms.UsernameChangeForm(instance=user) - if request.method == 'POST': + if request.method == "POST": form = forms.UsernameChangeForm(instance=user, data=request.POST) if form.is_valid(): form.save() - messages.success(request, 'Your username has been updated.') + messages.success(request, "Your username has been updated.") else: user = User.objects.get(id=user.id) - return render(request, 'user/edit_username.html', {'form':form, - 'user':user}) + return render(request, "user/edit_username.html", {"form": form, "user": user}) @login_required @@ -633,21 +710,28 @@ def edit_credentialing(request): ticket_system_url = None applications = CredentialApplication.objects.filter(user=request.user) - current_application = applications.filter(status=CredentialApplication.Status.PENDING).first() + current_application = applications.filter( + status=CredentialApplication.Status.PENDING + ).first() - if request.method == 'POST' and 'withdraw_credentialing' in request.POST: + if request.method == "POST" and "withdraw_credentialing" in request.POST: if current_application: current_application.withdraw(responder=request.user) - return render(request, 'user/withdraw_credentialing_success.html') + return render(request, "user/withdraw_credentialing_success.html") else: - messages.error(request, 'The application has already been processed.') + messages.error(request, "The application has already been processed.") - return render(request, 'user/edit_credentialing.html', { - 'applications': applications, - 'pause_applications': pause_applications, - 'pause_message': pause_message, - 'current_application': current_application, - 'ticket_system_url': ticket_system_url}) + return render( + request, + "user/edit_credentialing.html", + { + "applications": applications, + "pause_applications": pause_applications, + "pause_message": pause_message, + "current_application": current_application, + "ticket_system_url": ticket_system_url, + }, + ) @login_required @@ -655,11 +739,15 @@ def user_credential_applications(request): """ All the credential applications made by a user """ - applications = CredentialApplication.objects.filter( - user=request.user).order_by('-application_datetime') + applications = CredentialApplication.objects.filter(user=request.user).order_by( + "-application_datetime" + ) - return render(request, 'user/user_credential_applications.html', - {'applications': applications, 'CredentialApplication': CredentialApplication}) + return render( + request, + "user/user_credential_applications.html", + {"applications": applications, "CredentialApplication": CredentialApplication}, + ) @login_required @@ -669,23 +757,31 @@ def credential_application(request): """ user = request.user if user.is_credentialed or CredentialApplication.objects.filter( - user=user, status=CredentialApplication.Status.PENDING): - return redirect('edit_credentialing') + user=user, status=CredentialApplication.Status.PENDING + ): + return redirect("edit_credentialing") if settings.SYSTEM_MAINTENANCE_NO_UPLOAD: raise ServiceUnavailable() - if request.method == 'POST': + if request.method == "POST": # We use the individual forms to render the errors in the template # if not all valid - personal_form = forms.PersonalCAF(user=user, data=request.POST, prefix="application") + personal_form = forms.PersonalCAF( + user=user, data=request.POST, prefix="application" + ) research_form = forms.ResearchCAF(data=request.POST, prefix="application") - reference_form = forms.ReferenceCAF(data=request.POST, prefix="application", user=user) + reference_form = forms.ReferenceCAF( + data=request.POST, prefix="application", user=user + ) - form = forms.CredentialApplicationForm(user=user, data=request.POST, - files=request.FILES, prefix="application") + form = forms.CredentialApplicationForm( + user=user, data=request.POST, files=request.FILES, prefix="application" + ) - if (personal_form.is_valid() and reference_form.is_valid() and form.is_valid()) and research_form.is_valid(): + if ( + personal_form.is_valid() and reference_form.is_valid() and form.is_valid() + ) and research_form.is_valid(): application = form.save() credential_application_request(request, application) @@ -694,9 +790,9 @@ def credential_application(request): user=request.user, ) - return render(request, 'user/credential_application_complete.html') + return render(request, "user/credential_application_complete.html") else: - messages.error(request, 'Invalid submission. See errors below.') + messages.error(request, "Invalid submission. See errors below.") else: personal_form = forms.PersonalCAF(user=user, prefix="application") reference_form = forms.ReferenceCAF(prefix="application", user=user) @@ -707,15 +803,17 @@ def credential_application(request): return render( request, - 'user/credential_application.html', + "user/credential_application.html", { - 'form': form, - 'personal_form': personal_form, - 'reference_form': reference_form, - 'research_form': research_form, - 'code_of_conduct': code_of_conduct, + "form": form, + "personal_form": personal_form, + "reference_form": reference_form, + "research_form": research_form, + "code_of_conduct": code_of_conduct, }, ) + + @login_required def edit_course(request): """ @@ -726,75 +824,83 @@ def edit_course(request): else: ticket_system_url = None - if request.method == 'POST': + if request.method == "POST": training_form = forms.TrainingForm( - user=request.user, data=request.POST, files=request.FILES, training_type=request.POST.get('training_type') + user=request.user, + data=request.POST, + files=request.FILES, + training_type=request.POST.get("training_type"), ) if training_form.is_valid(): training_form.save() - messages.success(request, 'The training has been submitted successfully.') + messages.success(request, "The training has been submitted successfully.") training_application_request(request, training_form) training_form = forms.TrainingForm(user=request.user) else: - messages.error(request, 'Invalid submission. Check the errors below.') + messages.error(request, "Invalid submission. Check the errors below.") else: - training_type = request.GET.get('trainingType') + training_type = request.GET.get("trainingType") if training_type: - training_form = forms.TrainingForm(user=request.user, training_type=training_type) + training_form = forms.TrainingForm( + user=request.user, training_type=training_type + ) else: training_form = forms.TrainingForm(user=request.user) return render( request, - 'user/edit_course.html', - { - 'training_form': training_form, - 'ticket_system_url': ticket_system_url}, + "user/edit_course.html", + {"training_form": training_form, "ticket_system_url": ticket_system_url}, ) + @login_required def edit_training(request): """ Training settings page. """ - if settings.TICKET_SYSTEM_URL: - ticket_system_url = settings.TICKET_SYSTEM_URL - else: - ticket_system_url = None - if request.method == 'POST': + if request.method == "POST": training_form = forms.TrainingForm( - user=request.user, data=request.POST, files=request.FILES, training_type=request.POST.get('training_type') + user=request.user, + data=request.POST, + files=request.FILES, + training_type=request.POST.get("training_type"), ) if training_form.is_valid(): training_form.save() - messages.success(request, 'The training has been submitted successfully.') + messages.success(request, "The training has been submitted successfully.") training_application_request(request, training_form) training_form = forms.TrainingForm(user=request.user) else: - messages.error(request, 'Invalid submission. Check the errors below.') + messages.error(request, "Invalid submission. Check the errors below.") else: - training_type = request.GET.get('trainingType') + training_type = request.GET.get("trainingType") if training_type: - training_form = forms.TrainingForm(user=request.user, training_type=training_type) + training_form = forms.TrainingForm( + user=request.user, training_type=training_type + ) else: training_form = forms.TrainingForm(user=request.user) - training = Training.objects.select_related('training_type').filter(user=request.user).order_by('-status') + training = ( + Training.objects.select_related("training_type") + .filter(user=request.user) + .order_by("-status") + ) training_by_status = { - 'under review': training.get_review(), - 'active': training.get_valid(), - 'expired': training.get_expired(), - 'rejected': training.get_rejected(), - } + "under review": training.get_review(), + "active": training.get_valid(), + "expired": training.get_expired(), + "rejected": training.get_rejected(), + } return render( request, - 'user/edit_training.html', - { - 'training_by_status': training_by_status}, + "user/edit_training.html", + {"training_by_status": training_by_status}, ) @@ -802,12 +908,12 @@ def edit_training(request): def edit_training_detail(request, training_id): training = get_object_or_404(Training, pk=training_id, user=request.user) - if request.method == 'POST': - if request.POST.get('withdraw') is not None and not training.is_withdrawn(): + if request.method == "POST": + if request.POST.get("withdraw") is not None and not training.is_withdrawn(): training.withdraw() - messages.success(request, 'The training has been withdrawn.') + messages.success(request, "The training has been withdrawn.") - return render(request, 'user/edit_training_detail.html', {'training': training}) + return render(request, "user/edit_training_detail.html", {"training": training}) @login_required @@ -816,7 +922,7 @@ def training_report(request, training_id): Serve a training report file """ trainings = Training.objects.all() - if not request.user.has_perm('user.change_credentialapplication'): + if not request.user.has_perm("user.change_credentialapplication"): trainings = trainings.filter(user=request.user) training = get_object_or_404(trainings, id=training_id) @@ -834,31 +940,39 @@ def credential_reference(request, application_slug): Page for a reference to verify or reject a credential application """ application = CredentialApplication.objects.filter( - slug=application_slug, reference_response_datetime=None) + slug=application_slug, reference_response_datetime=None + ) if not application: - return redirect('/') + return redirect("/") application = application.get() form = forms.CredentialReferenceForm(instance=application) - if request.method == 'POST': + if request.method == "POST": form = forms.CredentialReferenceForm(data=request.POST, instance=application) if form.is_valid(): application = form.save() # Automated email notifying that their reference has denied # their application. if application.reference_response == 1: - process_credential_complete(request, application, - include_comments=False) + process_credential_complete( + request, application, include_comments=False + ) - response = 'verifying' if application.reference_response == 2 else 'denying' - return render(request, 'user/credential_reference_complete.html', - {'response': response, 'application': application}) + response = "verifying" if application.reference_response == 2 else "denying" + return render( + request, + "user/credential_reference_complete.html", + {"response": response, "application": application}, + ) else: - messages.error(request, 'Invalid submission. See errors below.') + messages.error(request, "Invalid submission. See errors below.") - return render(request, 'user/credential_reference.html', - {'form': form, 'application': application}) + return render( + request, + "user/credential_reference.html", + {"form": form, "application": application}, + ) def credential_reference_verification(request, application_slug, verification_token): @@ -867,31 +981,42 @@ def credential_reference_verification(request, application_slug, verification_to `credential_reference` that uses an additional verification token. """ application = CredentialApplication.objects.filter( - slug=application_slug, reference_response_datetime=None, status=0, - reference_verification_token=verification_token) + slug=application_slug, + reference_response_datetime=None, + status=0, + reference_verification_token=verification_token, + ) if not application: - return redirect('/') + return redirect("/") application = application.get() form = forms.CredentialReferenceForm(instance=application) - if request.method == 'POST': + if request.method == "POST": form = forms.CredentialReferenceForm(data=request.POST, instance=application) if form.is_valid(): application = form.save() # Automated email notifying that their reference has denied # their application. if application.reference_response == 1: - process_credential_complete(request, application, - include_comments=False) + process_credential_complete( + request, application, include_comments=False + ) - response = 'verifying' if application.reference_response == 2 else 'denying' - return render(request, 'user/credential_reference_complete.html', - {'response': response, 'application': application}) + response = "verifying" if application.reference_response == 2 else "denying" + return render( + request, + "user/credential_reference_complete.html", + {"response": response, "application": application}, + ) else: - messages.error(request, 'Invalid submission. See errors below.') + messages.error(request, "Invalid submission. See errors below.") - return render(request, 'user/credential_reference.html', {'form': form, 'application': application}) + return render( + request, + "user/credential_reference.html", + {"form": form, "application": application}, + ) @login_required @@ -902,15 +1027,16 @@ def edit_cloud(request): user = request.user cloud_info = CloudInformation.objects.get_or_create(user=user)[0] form = forms.CloudForm(instance=cloud_info) - if request.method == 'POST': + if request.method == "POST": form = forms.CloudForm(instance=cloud_info, data=request.POST) if form.is_valid(): form.save() - messages.success(request, 'Your cloud information has been saved.') + messages.success(request, "Your cloud information has been saved.") else: - messages.error(request, 'Invalid submission. See errors below.') + messages.error(request, "Invalid submission. See errors below.") + + return render(request, "user/edit_cloud.html", {"form": form, "user": user}) - return render(request, 'user/edit_cloud.html', {'form':form, 'user':user}) @login_required def view_agreements(request): @@ -918,19 +1044,24 @@ def view_agreements(request): View a list of signed agreements in the user profile. """ user = request.user - signed_agreements = DUASignature.objects.filter(user=user).order_by('-sign_datetime') - signed_code_of_conducts = CodeOfConductSignature.objects.filter(user=user).order_by('-sign_datetime') + signed_agreements = DUASignature.objects.filter(user=user).order_by( + "-sign_datetime" + ) + signed_code_of_conducts = CodeOfConductSignature.objects.filter(user=user).order_by( + "-sign_datetime" + ) return render( request, - 'user/view_agreements.html', + "user/view_agreements.html", { - 'user': user, - 'signed_agreements': signed_agreements, - 'signed_code_of_conducts': signed_code_of_conducts, + "user": user, + "signed_agreements": signed_agreements, + "signed_code_of_conducts": signed_code_of_conducts, }, ) + @login_required def view_signed_agreement(request, dua_signature_id): """ @@ -939,5 +1070,6 @@ def view_signed_agreement(request, dua_signature_id): user = request.user signed = get_object_or_404(DUASignature, user=user, id=dua_signature_id) - return render(request, 'user/view_signed_agreement.html', - {'user': user, 'signed': signed}) + return render( + request, "user/view_signed_agreement.html", {"user": user, "signed": signed} + ) From 41c9439f2af0082b58b411c85d97d7c919ed4357 Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Mon, 11 Sep 2023 12:51:46 -0400 Subject: [PATCH 3/6] Restructred the training information into training and certification --- .../templates/user/edit_certification.html | 30 ++++++++ .../user/templates/user/edit_course.html | 51 ------------- .../user/templates/user/edit_training.html | 73 ++++++++----------- .../user/templatetags/user_templatetags.py | 2 +- physionet-django/user/urls.py | 4 +- physionet-django/user/views.py | 12 +-- 6 files changed, 71 insertions(+), 101 deletions(-) create mode 100644 physionet-django/user/templates/user/edit_certification.html delete mode 100644 physionet-django/user/templates/user/edit_course.html diff --git a/physionet-django/user/templates/user/edit_certification.html b/physionet-django/user/templates/user/edit_certification.html new file mode 100644 index 0000000000..584fb045df --- /dev/null +++ b/physionet-django/user/templates/user/edit_certification.html @@ -0,0 +1,30 @@ +{% extends "user/settings.html" %} + +{% block title %}{{ SITE_NAME }} Training{% endblock %} + +{% block main_content %} +

Certification

+
+

To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

+
To submit a new completion report, please go to the Training page.
+
+ + {% for status, group in training_by_status.items %} +
+

{{ status|capfirst }}

+
    + {% for course in group %} +
  • +
    + {{ course.training_type.name }} +
    + View +
  • + {% empty %} +

    N/A

    + {% endfor %} +
+
+ {% endfor %} + +{% endblock %} diff --git a/physionet-django/user/templates/user/edit_course.html b/physionet-django/user/templates/user/edit_course.html deleted file mode 100644 index 93e35632be..0000000000 --- a/physionet-django/user/templates/user/edit_course.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends "user/settings.html" %} - -{% block title %}{{ SITE_NAME }} Courses{% endblock %} - -{% block main_content %} -

Courses

-
-

To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

-
-

Submit evidence of a completed course

- -

Please use the form below to submit a new completion report.

-

For CITI training, please refer to our step-by-step instructions and - be sure to upload the training report, not the certificate. Please ensure that you complete the "Data or - Specimens Only Research" course (with HIPAA module). - {% if ticket_system_url %}To raise a support request, please click here. - {% endif %}

- -
- {% include "descriptive_inline_form_snippet.html" with form=training_form %} - - -
- -{% endblock %} - -{% block local_js_bottom %} - -{% endblock %} diff --git a/physionet-django/user/templates/user/edit_training.html b/physionet-django/user/templates/user/edit_training.html index d8a5ac6f48..807b626995 100644 --- a/physionet-django/user/templates/user/edit_training.html +++ b/physionet-django/user/templates/user/edit_training.html @@ -6,50 +6,39 @@

Training


To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

- - {% for status, group in training_by_status.items %} - {% if not status == 'under review' %} -
-

{{ status|capfirst }}

-
    - {% for course in group %} -
  • -
    - {{ course.training_type.name }} -
    - View -
  • - {% empty %} -

    N/A

    - {% endfor %} -
-
- {% endif %} - {% endfor %} -
- - {% for status, group in training_by_status.items %} - {% if status == 'under review' and group %} -
-
- The following training is under review: -
    - {% for course in group %} -
  • -
    - {{ course.training_type.name }} -
    - View -
  • - {% endfor %} +

    Submit evidence of a completed Training

    + +

    Please use the form below to submit a new completion report.

    +

    For CITI training, please refer to our step-by-step instructions and + be sure to upload the training report, not the certificate. Please ensure that you complete the "Data or + Specimens Only Research" course (with HIPAA module). + {% if ticket_system_url %}To raise a support request, please click here. + {% endif %}

    + +
    + {% include "descriptive_inline_form_snippet.html" with form=training_form %} + +
-
- {% endif %} - {% endfor %} - -
To submit a new completion report, please go to the Courses page.
+
+ {% endblock %} diff --git a/physionet-django/user/templatetags/user_templatetags.py b/physionet-django/user/templatetags/user_templatetags.py index f86c6e4108..c24199d2f2 100644 --- a/physionet-django/user/templatetags/user_templatetags.py +++ b/physionet-django/user/templatetags/user_templatetags.py @@ -13,8 +13,8 @@ def settings_tabs(hide_password_settings: bool): "Cloud", "ORCID", "Credentialing", - "Course", "Training", + "Certification", "Agreements", ] if not hide_password_settings: diff --git a/physionet-django/user/urls.py b/physionet-django/user/urls.py index 8547e7bd11..8723f37e62 100644 --- a/physionet-django/user/urls.py +++ b/physionet-django/user/urls.py @@ -25,8 +25,10 @@ views.user_credential_applications, name="user_credential_applications", ), + path( + "settings/certification/", views.edit_certification, name="edit_certification" + ), path("settings/training/", views.edit_training, name="edit_training"), - path("settings/course/", views.edit_course, name="edit_course"), path( "settings/training//", views.edit_training_detail, diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 4875a02fd8..7888f83ca4 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -815,9 +815,9 @@ def credential_application(request): @login_required -def edit_course(request): +def edit_training(request): """ - Courses settings page. + Trainings settings page. """ if settings.TICKET_SYSTEM_URL: ticket_system_url = settings.TICKET_SYSTEM_URL @@ -850,15 +850,15 @@ def edit_course(request): return render( request, - "user/edit_course.html", + "user/edit_training.html", {"training_form": training_form, "ticket_system_url": ticket_system_url}, ) @login_required -def edit_training(request): +def edit_certification(request): """ - Training settings page. + Certifications page. """ if request.method == "POST": @@ -899,7 +899,7 @@ def edit_training(request): return render( request, - "user/edit_training.html", + "user/edit_certification.html", {"training_by_status": training_by_status}, ) From fc9bea6d2f93e211f5281f5a9ae94acb4add6741 Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Mon, 11 Sep 2023 12:57:13 -0400 Subject: [PATCH 4/6] Changinh site header for cerification tab --- physionet-django/user/templates/user/edit_certification.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/physionet-django/user/templates/user/edit_certification.html b/physionet-django/user/templates/user/edit_certification.html index 584fb045df..715a0cfebb 100644 --- a/physionet-django/user/templates/user/edit_certification.html +++ b/physionet-django/user/templates/user/edit_certification.html @@ -1,6 +1,6 @@ {% extends "user/settings.html" %} -{% block title %}{{ SITE_NAME }} Training{% endblock %} +{% block title %}{{ SITE_NAME }} Certification{% endblock %} {% block main_content %}

Certification

From 8cd66603eaec0efe0af3eb4eb65276e694a889cb Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Tue, 12 Sep 2023 10:39:35 -0400 Subject: [PATCH 5/6] Added the ref to certification page --- physionet-django/user/templates/user/edit_training.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/physionet-django/user/templates/user/edit_training.html b/physionet-django/user/templates/user/edit_training.html index 807b626995..f397848c8d 100644 --- a/physionet-django/user/templates/user/edit_training.html +++ b/physionet-django/user/templates/user/edit_training.html @@ -5,7 +5,9 @@ {% block main_content %}

Training


-

To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

+

To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

+ +
You can view the status of your training submissions on the Certification page.

Submit evidence of a completed Training

From 122b71ec1d63c7c127faf570c79e60731052eeb9 Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Tue, 12 Sep 2023 13:44:00 -0400 Subject: [PATCH 6/6] Fixed the styling changes Fixed the styling changes --- .../user/templatetags/user_templatetags.py | 21 +- physionet-django/user/views.py | 550 +++++++----------- 2 files changed, 220 insertions(+), 351 deletions(-) diff --git a/physionet-django/user/templatetags/user_templatetags.py b/physionet-django/user/templatetags/user_templatetags.py index c24199d2f2..ff6efa1fe7 100644 --- a/physionet-django/user/templatetags/user_templatetags.py +++ b/physionet-django/user/templatetags/user_templatetags.py @@ -4,24 +4,15 @@ register = template.Library() -@register.inclusion_tag("user/settings_tabs.html") +@register.inclusion_tag('user/settings_tabs.html') def settings_tabs(hide_password_settings: bool): - default_tabs = [ - "Profile", - "Emails", - "Username", - "Cloud", - "ORCID", - "Credentialing", - "Training", - "Certification", - "Agreements", - ] + default_tabs = ['Profile', 'Emails', 'Username', 'Cloud', 'ORCID', 'Credentialing', 'Training', + 'Certification', 'Agreements'] if not hide_password_settings: - default_tabs.insert(1, "Password") - return {"settings_tabs": default_tabs} + default_tabs.insert(1, 'Password') + return {'settings_tabs': default_tabs} -@register.filter(name="has_group") +@register.filter(name='has_group') def has_group(user, group_name): return user.groups.filter(name=group_name).exists() diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 7888f83ca4..fab5386d72 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -63,30 +63,30 @@ logger = logging.getLogger(__name__) -@method_decorator(allow_post_during_maintenance, "dispatch") +@method_decorator(allow_post_during_maintenance, 'dispatch') class LoginView(auth_views.LoginView): - template_name = "user/login.html" + template_name = 'user/login.html' authentication_form = forms.LoginForm redirect_authenticated_user = True -@method_decorator(allow_post_during_maintenance, "dispatch") +@method_decorator(allow_post_during_maintenance, 'dispatch') class SSOLoginView(auth_views.LoginView): - template_name = "sso/login.html" + template_name = 'sso/login.html' authentication_form = forms.LoginForm redirect_authenticated_user = True def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) try: - login_static_page = StaticPage.objects.get(url="/about/login/") + login_static_page = StaticPage.objects.get(url='/about/login/') instruction_sections = Section.objects.filter(static_page=login_static_page) except StaticPage.DoesNotExist: instruction_sections = [] sso_extra_context = { - "sso_login_button_text": settings.SSO_LOGIN_BUTTON_TEXT, - "login_instruction_sections": instruction_sections, + 'sso_login_button_text': settings.SSO_LOGIN_BUTTON_TEXT, + 'login_instruction_sections': instruction_sections, } return {**context, **sso_extra_context} @@ -97,33 +97,33 @@ class LogoutView(auth_views.LogoutView): # Request password reset class PasswordResetView(auth_views.PasswordResetView): - template_name = "user/reset_password_request.html" - success_url = reverse_lazy("reset_password_sent") - email_template_name = "user/email/reset_password_email.html" - extra_email_context = {"SITE_NAME": settings.SITE_NAME} + template_name = 'user/reset_password_request.html' + success_url = reverse_lazy('reset_password_sent') + email_template_name = 'user/email/reset_password_email.html' + extra_email_context = {'SITE_NAME': settings.SITE_NAME} # Page shown after reset email has been sent class PasswordResetDoneView(auth_views.PasswordResetDoneView): - template_name = "user/reset_password_sent.html" + template_name = 'user/reset_password_sent.html' # Prompt user to enter new password and carry out password reset (if # url is valid) -@method_decorator(disallow_during_maintenance, "dispatch") +@method_decorator(disallow_during_maintenance, 'dispatch') class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): - template_name = "user/reset_password_confirm.html" - success_url = reverse_lazy("reset_password_complete") + template_name = 'user/reset_password_confirm.html' + success_url = reverse_lazy('reset_password_complete') # Password reset successfully carried out class PasswordResetCompleteView(auth_views.PasswordResetCompleteView): - template_name = "user/reset_password_complete.html" + template_name = 'user/reset_password_complete.html' class PasswordChangeView(auth_views.PasswordChangeView): - success_url = reverse_lazy("edit_password_complete") - template_name = "user/edit_password.html" + success_url = reverse_lazy('edit_password_complete') + template_name = 'user/edit_password.html' login = LoginView.as_view() @@ -136,7 +136,7 @@ class PasswordChangeView(auth_views.PasswordChangeView): edit_password = PasswordChangeView.as_view() -@sensitive_post_parameters("password1", "password2") +@sensitive_post_parameters('password1', 'password2') @disallow_during_maintenance def activate_user(request, uidb64, token): """ @@ -144,10 +144,10 @@ def activate_user(request, uidb64, token): The user will create the password at this stage and then logged in. """ - activation_session_token = "_activation_reset_token" - activation_url_token = "user-activation" + activation_session_token = '_activation_reset_token' + activation_url_token = 'user-activation' title = "Account activation" - context = {"title": "Invalid Activation Link", "isvalid": False} + context = {'title': 'Invalid Activation Link', 'isvalid': False} try: uid = force_str(urlsafe_base64_decode(uidb64)) @@ -156,18 +156,17 @@ def activate_user(request, uidb64, token): user = None if user and user.is_active: - messages.success(request, "The account is active.") - return redirect("login") + messages.success(request, 'The account is active.') + return redirect('login') - if request.method == "GET": + if request.method == 'GET': if token == activation_url_token: session_token = request.session.get(activation_session_token) if default_token_generator.check_token(user, session_token): # If the token is valid, display the password reset form. form = forms.ActivationForm(user=user) - return render( - request, "user/activate_user.html", {"form": form, "title": title} - ) + return render(request, 'user/activate_user.html', { + 'form': form, 'title': title}) else: if default_token_generator.check_token(user, token): # Store the token in the session and redirect to the @@ -181,11 +180,9 @@ def activate_user(request, uidb64, token): if token == activation_url_token: session_token = request.session.get(activation_session_token) form = forms.ActivationForm(user=user, data=request.POST) - if form.is_valid() and default_token_generator.check_token( - user, session_token - ): + if form.is_valid() and default_token_generator.check_token(user, session_token): with transaction.atomic(): - user.set_password(form.cleaned_data["password1"]) + user.set_password(form.cleaned_data['password1']) user.is_active = True # Check legacy credentials check_legacy_credentials(user, user.email) @@ -195,15 +192,14 @@ def activate_user(request, uidb64, token): email.is_verified = True email.save() request.session.pop(activation_session_token) - logger.info("User activated - {0}".format(user.email)) - messages.success(request, "The account has been activated.") + logger.info('User activated - {0}'.format(user.email)) + messages.success(request, 'The account has been activated.') login(request, user) - return redirect("project_home") - return render( - request, "user/activate_user.html", {"form": form, "title": title} - ) + return redirect('project_home') + return render(request, 'user/activate_user.html', {'form': form, + 'title': title}) - return render(request, "user/activate_user_complete.html", context) + return render(request, 'user/activate_user_complete.html', context) def check_legacy_credentials(user, email): @@ -211,13 +207,14 @@ def check_legacy_credentials(user, email): Check whether a user has already beeen credentialed on the old pn site. If so, credential their account and mark the migration. """ - legacy_credential = LegacyCredential.objects.filter(email=email, migrated=False) + legacy_credential = LegacyCredential.objects.filter(email=email, + migrated=False) if legacy_credential: legacy_credential = legacy_credential.get() user.is_credentialed = True # All of them are mimic credentialed - month, day, year = legacy_credential.mimic_approval_date.split("/") - dt = datetime(int(year), int(month), int(day)) + month, day, year = legacy_credential.mimic_approval_date.split('/') + dt = datetime(int(year), int(month), int(day)) dt = pytz.timezone(timezone.get_default_timezone_name()).localize(dt) user.credential_datetime = dt legacy_credential.migrated = True @@ -226,7 +223,6 @@ def check_legacy_credentials(user, email): legacy_credential.save() user.save() - def remove_email(request, email_id): "Remove a non-primary email associated with a user" user = request.user @@ -236,53 +232,36 @@ def remove_email(request, email_id): email = associated_email.email associated_email.delete() logger.info(f"Removed email {email} from user {user.id}") - messages.success( - request, - f"The email address ({email}) has been removed from your account.", - ) + messages.success(request, f"The email address ({email}) has been removed from your account.") except AssociatedEmail.DoesNotExist: - messages.success( - request, "The email address has been removed from your account." - ) + messages.success(request, "The email address has been removed from your account.") def set_primary_email(request, primary_email_form): "Set the selected email as the primary email" user = request.user if primary_email_form.is_valid(): - associated_email = primary_email_form.cleaned_data["associated_email"] + associated_email = primary_email_form.cleaned_data['associated_email'] # Only do something if they selected a different email if associated_email.email != user.email: - logger.info( - "Primary email changed from: {0} to {1}".format( - user.email, associated_email.email - ) - ) + logger.info('Primary email changed from: {0} to {1}'.format(user.email, associated_email.email)) user.email = associated_email.email - user.save(update_fields=["email"]) + user.save(update_fields=['email']) # Change the email field of author objects belonging to # the user. Warn them if they are the corresponding # author of any projects authors = Author.objects.filter(user=user) authors.update(corresponding_email=associated_email) - messages.success( - request, - "Your email: {0} has been set as your new primary email.".format( - user.email - ), - ) + messages.success(request, 'Your email: {0} has been set as your new primary email.'.format(user.email)) if authors.filter(is_corresponding=True): - messages.info( - request, - "The corresponding email in all your authoring projects has been set to your new primary email.", - ) + messages.info(request, 'The corresponding email in all your authoring projects has been set to your new primary email.') def set_public_email(request, public_email_form): "Set the selected email as the public email" user = request.user if public_email_form.is_valid(): - associated_email = public_email_form.cleaned_data["associated_email"] + associated_email = public_email_form.cleaned_data['associated_email'] current_public_email = user.associated_emails.filter(is_public=True).first() # Only do something if they selected a different email if associated_email != current_public_email: @@ -293,55 +272,33 @@ def set_public_email(request, public_email_form): if associated_email: associated_email.is_public = True associated_email.save() - messages.success( - request, - "Your email: {0} has been set to public.".format( - associated_email.email - ), - ) + messages.success(request, 'Your email: {0} has been set to public.'.format(associated_email.email)) else: - messages.success( - request, - "Your email: {0} has been set to private.".format( - current_public_email.email - ), - ) - + messages.success(request, 'Your email: {0} has been set to private.'.format(current_public_email.email)) def add_email(request, add_email_form): user = request.user if add_email_form.is_valid(): token = get_random_string(20) - associated_email = AssociatedEmail.objects.create( - user=user, - email=add_email_form.cleaned_data["email"], - verification_token=token, - ) + associated_email = AssociatedEmail.objects.create(user=user, + email=add_email_form.cleaned_data['email'], + verification_token=token) # Send an email to the newly added email with a verification link uidb64 = force_str(urlsafe_base64_encode(force_bytes(associated_email.pk))) subject = f"{settings.SITE_NAME} Email Verification" context = { - "name": user.get_full_name(), - "domain": get_current_site(request), - "url_prefix": get_url_prefix(request), - "uidb64": uidb64, - "token": token, - "SITE_NAME": settings.SITE_NAME, + 'name': user.get_full_name(), + 'domain': get_current_site(request), + 'url_prefix': get_url_prefix(request), + 'uidb64': uidb64, + 'token': token, + 'SITE_NAME': settings.SITE_NAME, } - body = loader.render_to_string("user/email/verify_email_email.html", context) - send_mail( - subject, - body, - settings.DEFAULT_FROM_EMAIL, - [add_email_form.cleaned_data["email"]], - fail_silently=False, - ) - messages.success( - request, - "A verification link has been sent to: {0}".format(associated_email.email), - ) - + body = loader.render_to_string('user/email/verify_email_email.html', context) + send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, + [add_email_form.cleaned_data['email']], fail_silently=False) + messages.success(request, 'A verification link has been sent to: {0}'.format(associated_email.email)) @login_required def edit_emails(request): @@ -350,65 +307,56 @@ def edit_emails(request): """ user = request.user - associated_emails = AssociatedEmail.objects.filter(user=user).order_by( - "-is_verified", "-is_primary_email" - ) + associated_emails = AssociatedEmail.objects.filter( + user=user).order_by('-is_verified', '-is_primary_email') total_associated_emails = associated_emails.count() max_associated_emails_allowed = settings.MAX_EMAILS_PER_USER - primary_email_form = forms.AssociatedEmailChoiceForm( - user=user, selection_type="primary" - ) - public_email_form = forms.AssociatedEmailChoiceForm( - user=user, selection_type="public" - ) + primary_email_form = forms.AssociatedEmailChoiceForm(user=user, + selection_type='primary') + public_email_form = forms.AssociatedEmailChoiceForm(user=user, + selection_type='public') add_email_form = forms.AddEmailForm() - if request.method == "POST": - if "remove_email" in request.POST: + if request.method == 'POST': + if 'remove_email' in request.POST: # No form. Just get button value. - email_id = int(request.POST["remove_email"]) + email_id = int(request.POST['remove_email']) remove_email(request, email_id) # Update the associated_emails count after removing an email total_associated_emails = AssociatedEmail.objects.filter(user=user).count() - elif "set_primary_email" in request.POST: - primary_email_form = forms.AssociatedEmailChoiceForm( - user=user, selection_type="primary", data=request.POST - ) + elif 'set_primary_email' in request.POST: + primary_email_form = forms.AssociatedEmailChoiceForm(user=user, + selection_type='primary', data=request.POST) set_primary_email(request, primary_email_form) - elif "set_public_email" in request.POST: - public_email_form = forms.AssociatedEmailChoiceForm( - user=user, selection_type="public", data=request.POST - ) + elif 'set_public_email' in request.POST: + public_email_form = forms.AssociatedEmailChoiceForm(user=user, + selection_type='public', data=request.POST) set_public_email(request, public_email_form) - elif "add_email" in request.POST: + elif 'add_email' in request.POST: if associated_emails.count() >= settings.MAX_EMAILS_PER_USER: messages.error( request, - "You have reached the maximum number of email addresses allowed.", + 'You have reached the maximum number of email addresses allowed.' ) else: add_email_form = forms.AddEmailForm(request.POST) add_email(request, add_email_form) # Update the associated_emails count after adding a new email - total_associated_emails = AssociatedEmail.objects.filter( - user=user - ).count() - - context = { - "associated_emails": associated_emails, - "primary_email_form": primary_email_form, - "add_email_form": add_email_form, - "public_email_form": public_email_form, - "total_associated_emails": total_associated_emails, - "max_associated_emails_allowed": max_associated_emails_allowed, - } + total_associated_emails = AssociatedEmail.objects.filter(user=user).count() + + context = {'associated_emails': associated_emails, + 'primary_email_form': primary_email_form, + 'add_email_form': add_email_form, + 'public_email_form': public_email_form, + 'total_associated_emails': total_associated_emails, + 'max_associated_emails_allowed': max_associated_emails_allowed} - context["messages"] = messages.get_messages(request) + context['messages'] = messages.get_messages(request) - return render(request, "user/edit_emails.html", context) + return render(request, 'user/edit_emails.html', context) @login_required @@ -419,31 +367,29 @@ def edit_profile(request): profile = request.user.profile form = forms.ProfileForm(instance=profile) - if request.method == "POST": + if request.method == 'POST': if settings.SYSTEM_MAINTENANCE_NO_UPLOAD: # Allow submitting the form, but do not allow the photo to # be modified. - if "delete_photo" in request.POST or request.FILES: + if 'delete_photo' in request.POST or request.FILES: raise ServiceUnavailable() - if "edit_profile" in request.POST: + if 'edit_profile' in request.POST: # Update the profile and return to the same page. Place a message # at the top of the page: 'your profile has been updated' - form = forms.ProfileForm( - data=request.POST, files=request.FILES, instance=profile - ) + form = forms.ProfileForm(data=request.POST, files=request.FILES, + instance=profile) if form.is_valid(): form.save() - messages.success(request, "Your profile has been updated.") - elif "delete_photo" in request.POST: + messages.success(request, 'Your profile has been updated.') + elif 'delete_photo' in request.POST: profile.delete_photo() - messages.success(request, "Your profile photo has been deleted.") + messages.success(request, 'Your profile photo has been deleted.') if not form.errors: form = forms.ProfileForm(instance=profile) - return render(request, "user/edit_profile.html", {"form": form}) - + return render(request, 'user/edit_profile.html', {'form':form}) @login_required def edit_orcid(request): @@ -453,8 +399,8 @@ def edit_orcid(request): ORCID account from their account. """ - if request.method == "POST": - if "request_orcid" in request.POST: + if request.method == 'POST': + if 'request_orcid' in request.POST: client_id = settings.ORCID_CLIENT_ID redirect_uri = settings.ORCID_REDIRECT_URI scope = list(settings.ORCID_SCOPE.split(",")) @@ -463,15 +409,12 @@ def edit_orcid(request): return redirect(authorization_url) - if "remove_orcid" in request.POST: + if 'remove_orcid' in request.POST: try: Orcid.objects.get(user=request.user).delete() orcid_html = None except ObjectDoesNotExist: - messages.error( - request, - "Object Does Not Exist Error: tried to unlink an object which does not exist.", - ) + messages.error(request, 'Object Does Not Exist Error: tried to unlink an object which does not exist.') orcid_html = None else: @@ -480,8 +423,7 @@ def edit_orcid(request): except ObjectDoesNotExist: orcid_html = None - return render(request, "user/edit_orcid.html", {"orcid": orcid_html}) - + return render(request, 'user/edit_orcid.html', {'orcid': orcid_html}) @login_required @disallow_during_maintenance @@ -499,44 +441,37 @@ def auth_orcid(request): client_secret = settings.ORCID_CLIENT_SECRET redirect_uri = settings.ORCID_REDIRECT_URI scope = list(settings.ORCID_SCOPE.split(",")) - oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scope) + oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, + scope=scope) params = request.GET.copy() - code = params["code"] + code = params['code'] try: - token = oauth.fetch_token( - settings.ORCID_TOKEN_URL, - code=code, - include_client_id=True, - client_secret=client_secret, - ) + token = oauth.fetch_token(settings.ORCID_TOKEN_URL, code=code, + include_client_id=True, client_secret=client_secret) try: - validators.validate_orcid_token(token["access_token"]) + validators.validate_orcid_token(token['access_token']) token_valid = True except ValidationError: - messages.error(request, "Validation Error: ORCID token validation failed.") + messages.error(request, 'Validation Error: ORCID token validation failed.') token_valid = False except InvalidGrantError: - messages.error( - request, - "Invalid Grant Error: authorization code may be expired or invalid.", - ) + messages.error(request, 'Invalid Grant Error: authorization code may be expired or invalid.') token_valid = False if token_valid: orcid_profile, _ = Orcid.objects.get_or_create(user=request.user) - orcid_profile.orcid_id = token.get("orcid") - orcid_profile.name = token.get("name") - orcid_profile.access_token = token.get("access_token") - orcid_profile.refresh_token = token.get("refresh_token") - orcid_profile.token_type = token.get("token_type") - orcid_profile.token_scope = token.get("scope") - orcid_profile.token_expiration = token.get("expires_at") + orcid_profile.orcid_id = token.get('orcid') + orcid_profile.name = token.get('name') + orcid_profile.access_token = token.get('access_token') + orcid_profile.refresh_token = token.get('refresh_token') + orcid_profile.token_type = token.get('token_type') + orcid_profile.token_scope = token.get('scope') + orcid_profile.token_expiration = token.get('expires_at') orcid_profile.full_clean() orcid_profile.save() - return redirect("edit_orcid") - + return redirect('edit_orcid') @login_required def edit_password_complete(request): @@ -544,7 +479,7 @@ def edit_password_complete(request): After password has successfully been changed. Need this view because we can't control the edit password view to show a success message. """ - return render(request, "user/edit_password_complete.html") + return render(request, 'user/edit_password_complete.html') def public_profile(request, username): @@ -558,20 +493,12 @@ def public_profile(request, username): raise Http404() # get list of projects - projects = PublishedProject.objects.filter(authors__user=public_user).order_by( - "-publish_datetime" - ) + projects = PublishedProject.objects.filter(authors__user=public_user).order_by('-publish_datetime') - return render( - request, - "user/public_profile.html", - { - "public_user": public_user, - "profile": public_user.profile, - "public_email": public_email, - "projects": projects, - }, - ) + + return render(request, 'user/public_profile.html', { + 'public_user':public_user, 'profile':public_user.profile, + 'public_email':public_email, 'projects':projects}) def profile_photo(request, username): @@ -595,9 +522,9 @@ def register(request): """ user = request.user if user.is_authenticated: - return redirect("home") + return redirect('home') - if request.method == "POST": + if request.method == 'POST': form = forms.RegistrationForm(request=request, data=request.POST) if form.is_valid(): # Create the new user @@ -607,17 +534,19 @@ def register(request): form.full_clean() if form.is_valid(): raise - user = User.objects.get(username=form.data["username"]) + user = User.objects.get(username=form.data['username']) else: - uidb64 = force_str(urlsafe_base64_encode(force_bytes(user.pk))) + uidb64 = force_str(urlsafe_base64_encode(force_bytes( + user.pk))) token = default_token_generator.make_token(user) notify_account_registration(request, user, uidb64, token) - return render(request, "user/register_done.html", {"email": user.email}) + return render(request, 'user/register_done.html', { + 'email': user.email}) else: form = forms.RegistrationForm(request=request) - response = render(request, "user/register.html", {"form": form}) + response = render(request, 'user/register.html', {'form': form}) form.set_response_cookies(response) return response @@ -628,7 +557,7 @@ def user_settings(request): Settings. Redirect to default - settings/profile Don't call this 'settings' because there's an import called 'settings' """ - return redirect("edit_profile") + return redirect('edit_profile') @login_required @@ -652,21 +581,14 @@ def verify_email(request, uidb64, token): associated_email.save() if not user.is_credentialed: check_legacy_credentials(user, associated_email.email) - logger.info( - "User {0} verified another email {1}".format(user.id, associated_email) - ) - messages.success( - request, - "The email address {} has been verified.".format(associated_email), - ) - return redirect("edit_emails") + logger.info('User {0} verified another email {1}'.format(user.id, associated_email)) + messages.success(request, 'The email address {} has been verified.'.format( + associated_email)) + return redirect('edit_emails') - logger.warning("Invalid Verification Link") - return render( - request, - "user/verify_email.html", - {"title": "Invalid Verification Link", "isvalid": False}, - ) + logger.warning('Invalid Verification Link') + return render(request, 'user/verify_email.html', + {'title':'Invalid Verification Link', 'isvalid':False}) @login_required @@ -677,16 +599,17 @@ def edit_username(request): user = request.user form = forms.UsernameChangeForm(instance=user) - if request.method == "POST": + if request.method == 'POST': form = forms.UsernameChangeForm(instance=user, data=request.POST) if form.is_valid(): form.save() - messages.success(request, "Your username has been updated.") + messages.success(request, 'Your username has been updated.') else: user = User.objects.get(id=user.id) - return render(request, "user/edit_username.html", {"form": form, "user": user}) + return render(request, 'user/edit_username.html', {'form':form, + 'user':user}) @login_required @@ -710,28 +633,21 @@ def edit_credentialing(request): ticket_system_url = None applications = CredentialApplication.objects.filter(user=request.user) - current_application = applications.filter( - status=CredentialApplication.Status.PENDING - ).first() + current_application = applications.filter(status=CredentialApplication.Status.PENDING).first() - if request.method == "POST" and "withdraw_credentialing" in request.POST: + if request.method == 'POST' and 'withdraw_credentialing' in request.POST: if current_application: current_application.withdraw(responder=request.user) - return render(request, "user/withdraw_credentialing_success.html") + return render(request, 'user/withdraw_credentialing_success.html') else: - messages.error(request, "The application has already been processed.") + messages.error(request, 'The application has already been processed.') - return render( - request, - "user/edit_credentialing.html", - { - "applications": applications, - "pause_applications": pause_applications, - "pause_message": pause_message, - "current_application": current_application, - "ticket_system_url": ticket_system_url, - }, - ) + return render(request, 'user/edit_credentialing.html', { + 'applications': applications, + 'pause_applications': pause_applications, + 'pause_message': pause_message, + 'current_application': current_application, + 'ticket_system_url': ticket_system_url}) @login_required @@ -739,15 +655,11 @@ def user_credential_applications(request): """ All the credential applications made by a user """ - applications = CredentialApplication.objects.filter(user=request.user).order_by( - "-application_datetime" - ) + applications = CredentialApplication.objects.filter( + user=request.user).order_by('-application_datetime') - return render( - request, - "user/user_credential_applications.html", - {"applications": applications, "CredentialApplication": CredentialApplication}, - ) + return render(request, 'user/user_credential_applications.html', + {'applications': applications, 'CredentialApplication': CredentialApplication}) @login_required @@ -757,31 +669,23 @@ def credential_application(request): """ user = request.user if user.is_credentialed or CredentialApplication.objects.filter( - user=user, status=CredentialApplication.Status.PENDING - ): - return redirect("edit_credentialing") + user=user, status=CredentialApplication.Status.PENDING): + return redirect('edit_credentialing') if settings.SYSTEM_MAINTENANCE_NO_UPLOAD: raise ServiceUnavailable() - if request.method == "POST": + if request.method == 'POST': # We use the individual forms to render the errors in the template # if not all valid - personal_form = forms.PersonalCAF( - user=user, data=request.POST, prefix="application" - ) + personal_form = forms.PersonalCAF(user=user, data=request.POST, prefix="application") research_form = forms.ResearchCAF(data=request.POST, prefix="application") - reference_form = forms.ReferenceCAF( - data=request.POST, prefix="application", user=user - ) + reference_form = forms.ReferenceCAF(data=request.POST, prefix="application", user=user) - form = forms.CredentialApplicationForm( - user=user, data=request.POST, files=request.FILES, prefix="application" - ) + form = forms.CredentialApplicationForm(user=user, data=request.POST, + files=request.FILES, prefix="application") - if ( - personal_form.is_valid() and reference_form.is_valid() and form.is_valid() - ) and research_form.is_valid(): + if (personal_form.is_valid() and reference_form.is_valid() and form.is_valid()) and research_form.is_valid(): application = form.save() credential_application_request(request, application) @@ -790,9 +694,9 @@ def credential_application(request): user=request.user, ) - return render(request, "user/credential_application_complete.html") + return render(request, 'user/credential_application_complete.html') else: - messages.error(request, "Invalid submission. See errors below.") + messages.error(request, 'Invalid submission. See errors below.') else: personal_form = forms.PersonalCAF(user=user, prefix="application") reference_form = forms.ReferenceCAF(prefix="application", user=user) @@ -803,13 +707,13 @@ def credential_application(request): return render( request, - "user/credential_application.html", + 'user/credential_application.html', { - "form": form, - "personal_form": personal_form, - "reference_form": reference_form, - "research_form": research_form, - "code_of_conduct": code_of_conduct, + 'form': form, + 'personal_form': personal_form, + 'reference_form': reference_form, + 'research_form': research_form, + 'code_of_conduct': code_of_conduct, }, ) @@ -908,12 +812,12 @@ def edit_certification(request): def edit_training_detail(request, training_id): training = get_object_or_404(Training, pk=training_id, user=request.user) - if request.method == "POST": - if request.POST.get("withdraw") is not None and not training.is_withdrawn(): + if request.method == 'POST': + if request.POST.get('withdraw') is not None and not training.is_withdrawn(): training.withdraw() - messages.success(request, "The training has been withdrawn.") + messages.success(request, 'The training has been withdrawn.') - return render(request, "user/edit_training_detail.html", {"training": training}) + return render(request, 'user/edit_training_detail.html', {'training': training}) @login_required @@ -922,7 +826,7 @@ def training_report(request, training_id): Serve a training report file """ trainings = Training.objects.all() - if not request.user.has_perm("user.change_credentialapplication"): + if not request.user.has_perm('user.change_credentialapplication'): trainings = trainings.filter(user=request.user) training = get_object_or_404(trainings, id=training_id) @@ -940,39 +844,31 @@ def credential_reference(request, application_slug): Page for a reference to verify or reject a credential application """ application = CredentialApplication.objects.filter( - slug=application_slug, reference_response_datetime=None - ) + slug=application_slug, reference_response_datetime=None) if not application: - return redirect("/") + return redirect('/') application = application.get() form = forms.CredentialReferenceForm(instance=application) - if request.method == "POST": + if request.method == 'POST': form = forms.CredentialReferenceForm(data=request.POST, instance=application) if form.is_valid(): application = form.save() # Automated email notifying that their reference has denied # their application. if application.reference_response == 1: - process_credential_complete( - request, application, include_comments=False - ) + process_credential_complete(request, application, + include_comments=False) - response = "verifying" if application.reference_response == 2 else "denying" - return render( - request, - "user/credential_reference_complete.html", - {"response": response, "application": application}, - ) + response = 'verifying' if application.reference_response == 2 else 'denying' + return render(request, 'user/credential_reference_complete.html', + {'response': response, 'application': application}) else: - messages.error(request, "Invalid submission. See errors below.") + messages.error(request, 'Invalid submission. See errors below.') - return render( - request, - "user/credential_reference.html", - {"form": form, "application": application}, - ) + return render(request, 'user/credential_reference.html', + {'form': form, 'application': application}) def credential_reference_verification(request, application_slug, verification_token): @@ -981,42 +877,31 @@ def credential_reference_verification(request, application_slug, verification_to `credential_reference` that uses an additional verification token. """ application = CredentialApplication.objects.filter( - slug=application_slug, - reference_response_datetime=None, - status=0, - reference_verification_token=verification_token, - ) + slug=application_slug, reference_response_datetime=None, status=0, + reference_verification_token=verification_token) if not application: - return redirect("/") + return redirect('/') application = application.get() form = forms.CredentialReferenceForm(instance=application) - if request.method == "POST": + if request.method == 'POST': form = forms.CredentialReferenceForm(data=request.POST, instance=application) if form.is_valid(): application = form.save() # Automated email notifying that their reference has denied # their application. if application.reference_response == 1: - process_credential_complete( - request, application, include_comments=False - ) + process_credential_complete(request, application, + include_comments=False) - response = "verifying" if application.reference_response == 2 else "denying" - return render( - request, - "user/credential_reference_complete.html", - {"response": response, "application": application}, - ) + response = 'verifying' if application.reference_response == 2 else 'denying' + return render(request, 'user/credential_reference_complete.html', + {'response': response, 'application': application}) else: - messages.error(request, "Invalid submission. See errors below.") + messages.error(request, 'Invalid submission. See errors below.') - return render( - request, - "user/credential_reference.html", - {"form": form, "application": application}, - ) + return render(request, 'user/credential_reference.html', {'form': form, 'application': application}) @login_required @@ -1027,16 +912,15 @@ def edit_cloud(request): user = request.user cloud_info = CloudInformation.objects.get_or_create(user=user)[0] form = forms.CloudForm(instance=cloud_info) - if request.method == "POST": + if request.method == 'POST': form = forms.CloudForm(instance=cloud_info, data=request.POST) if form.is_valid(): form.save() - messages.success(request, "Your cloud information has been saved.") + messages.success(request, 'Your cloud information has been saved.') else: - messages.error(request, "Invalid submission. See errors below.") - - return render(request, "user/edit_cloud.html", {"form": form, "user": user}) + messages.error(request, 'Invalid submission. See errors below.') + return render(request, 'user/edit_cloud.html', {'form':form, 'user':user}) @login_required def view_agreements(request): @@ -1044,24 +928,19 @@ def view_agreements(request): View a list of signed agreements in the user profile. """ user = request.user - signed_agreements = DUASignature.objects.filter(user=user).order_by( - "-sign_datetime" - ) - signed_code_of_conducts = CodeOfConductSignature.objects.filter(user=user).order_by( - "-sign_datetime" - ) + signed_agreements = DUASignature.objects.filter(user=user).order_by('-sign_datetime') + signed_code_of_conducts = CodeOfConductSignature.objects.filter(user=user).order_by('-sign_datetime') return render( request, - "user/view_agreements.html", + 'user/view_agreements.html', { - "user": user, - "signed_agreements": signed_agreements, - "signed_code_of_conducts": signed_code_of_conducts, + 'user': user, + 'signed_agreements': signed_agreements, + 'signed_code_of_conducts': signed_code_of_conducts, }, ) - @login_required def view_signed_agreement(request, dua_signature_id): """ @@ -1070,6 +949,5 @@ def view_signed_agreement(request, dua_signature_id): user = request.user signed = get_object_or_404(DUASignature, user=user, id=dua_signature_id) - return render( - request, "user/view_signed_agreement.html", {"user": user, "signed": signed} - ) + return render(request, 'user/view_signed_agreement.html', + {'user': user, 'signed': signed})