diff --git a/cfl_common/common/app_settings.py b/cfl_common/common/app_settings.py index 035be8623..33bcfe6b5 100644 --- a/cfl_common/common/app_settings.py +++ b/cfl_common/common/app_settings.py @@ -8,7 +8,6 @@ # Dotmailer URLs for adding users to the newsletter address book DOTMAILER_CREATE_CONTACT_URL = getattr(settings, "DOTMAILER_CREATE_CONTACT_URL", "") -DOTMAILER_MAIN_ADDRESS_BOOK_URL = getattr(settings, "DOTMAILER_MAIN_ADDRESS_BOOK_URL", "") DOTMAILER_TEACHER_ADDRESS_BOOK_URL = getattr(settings, "DOTMAILER_TEACHER_ADDRESS_BOOK_URL", "") DOTMAILER_STUDENT_ADDRESS_BOOK_URL = getattr(settings, "DOTMAILER_STUDENT_ADDRESS_BOOK_URL", "") DOTMAILER_NO_ACCOUNT_ADDRESS_BOOK_URL = getattr(settings, "DOTMAILER_NO_ACCOUNT_ADDRESS_BOOK_URL", "") diff --git a/cfl_common/common/helpers/emails.py b/cfl_common/common/helpers/emails.py index b97c27c1d..e1d5fb1a1 100644 --- a/cfl_common/common/helpers/emails.py +++ b/cfl_common/common/helpers/emails.py @@ -5,7 +5,12 @@ import jwt from common import app_settings -from common.mail import campaign_ids, django_send_email, send_dotdigital_email +from common.mail import ( + address_book_ids, + campaign_ids, + django_send_email, + send_dotdigital_email, +) from common.models import Student, Teacher from django.conf import settings from django.contrib.auth.models import User @@ -15,9 +20,15 @@ from requests import delete, get, post, put from requests.exceptions import RequestException -NOTIFICATION_EMAIL = "Code For Life Notification <" + app_settings.EMAIL_ADDRESS + ">" -VERIFICATION_EMAIL = "Code For Life Verification <" + app_settings.EMAIL_ADDRESS + ">" -PASSWORD_RESET_EMAIL = "Code For Life Password Reset <" + app_settings.EMAIL_ADDRESS + ">" +NOTIFICATION_EMAIL = ( + "Code For Life Notification <" + app_settings.EMAIL_ADDRESS + ">" +) +VERIFICATION_EMAIL = ( + "Code For Life Verification <" + app_settings.EMAIL_ADDRESS + ">" +) +PASSWORD_RESET_EMAIL = ( + "Code For Life Password Reset <" + app_settings.EMAIL_ADDRESS + ">" +) INVITE_FROM = "Code For Life Invitation <" + app_settings.EMAIL_ADDRESS + ">" @@ -41,7 +52,9 @@ def generate_token_for_email(email: str, new_email: str = ""): "email": email, "new_email": new_email, "email_verification_token": uuid4().hex[:30], - "expires": (timezone.now() + datetime.timedelta(hours=1)).timestamp(), + "expires": ( + timezone.now() + datetime.timedelta(hours=1) + ).timestamp(), }, settings.SECRET_KEY, algorithm="HS256", @@ -62,10 +75,21 @@ def send_email( plaintext_template="email.txt", html_template="email.html", ): - django_send_email(sender, recipients, subject, text_content, title, replace_url, plaintext_template, html_template) + django_send_email( + sender, + recipients, + subject, + text_content, + title, + replace_url, + plaintext_template, + html_template, + ) -def send_verification_email(request, user, data, new_email=None, age=None, school=None): +def send_verification_email( + request, user, data, new_email=None, age=None, school=None +): """ Sends emails relating to email address verification. @@ -98,18 +122,30 @@ def send_verification_email(request, user, data, new_email=None, age=None, schoo url = f"{request.build_absolute_uri(reverse('verify_email', kwargs={'token': verification}))}" send_dotdigital_email( - campaign_ids["verify_released_student"], [user.email], - personalization_values={"VERIFICATION_LINK": url, "SCHOOL_NAME": school.name} + campaign_ids["verify_released_student"], + [user.email], + personalization_values={ + "VERIFICATION_LINK": url, + "SCHOOL_NAME": school.name, + }, ) else: url = f"{request.build_absolute_uri(reverse('verify_email', kwargs={'token': verification}))}" send_dotdigital_email( - campaign_ids["verify_new_user"], [user.email], personalization_values={"VERIFICATION_LINK": url} + campaign_ids["verify_new_user"], + [user.email], + personalization_values={"VERIFICATION_LINK": url}, ) if _newsletter_ticked(data): - add_to_dotmailer(user.first_name, user.last_name, user.email, DotmailerUserType.TEACHER) + add_to_dotmailer( + user.first_name, + user.last_name, + user.email, + address_book_ids["newsletter"], + DotmailerUserType.TEACHER, + ) # if the user is an independent student else: if age < 13: @@ -117,50 +153,50 @@ def send_verification_email(request, user, data, new_email=None, age=None, schoo send_dotdigital_email( campaign_ids["verify_new_user_via_parent"], [user.email], - personalization_values={"FIRST_NAME": user.first_name, "ACTIVATION_LINK": url}, + personalization_values={ + "FIRST_NAME": user.first_name, + "ACTIVATION_LINK": url, + }, ) else: url = f"{request.build_absolute_uri(reverse('verify_email', kwargs={'token': verification}))}" send_dotdigital_email( - campaign_ids["verify_new_user"], [user.email], personalization_values={"VERIFICATION_LINK": url} + campaign_ids["verify_new_user"], + [user.email], + personalization_values={"VERIFICATION_LINK": url}, ) if _newsletter_ticked(data): - add_to_dotmailer(user.first_name, user.last_name, user.email, DotmailerUserType.STUDENT) + add_to_dotmailer( + user.first_name, + user.last_name, + user.email, + address_book_ids["newsletter"], + DotmailerUserType.STUDENT, + ) # verifying change of email address. else: verification = generate_token(user, new_email) url = f"{request.build_absolute_uri(reverse('verify_email', kwargs={'token': verification}))}" send_dotdigital_email( - campaign_ids["email_change_verification"], [new_email], personalization_values={"VERIFICATION_LINK": url} + campaign_ids["email_change_verification"], + [new_email], + personalization_values={"VERIFICATION_LINK": url}, ) -def add_to_dotmailer(first_name: str, last_name: str, email: str, user_type: DotmailerUserType): - try: - create_contact(first_name, last_name, email) - add_contact_to_address_book(first_name, last_name, email, user_type) - except RequestException: - return HttpResponse(status=404) - - -def add_donor_to_dotmailer(first_name: str, last_name: str, email: str): +def add_to_dotmailer( + first_name: str, + last_name: str, + email: str, + address_book_id: int, + user_type: DotmailerUserType = None, +): try: create_contact(first_name, last_name, email) - main_address_book_url = "https://r1-api.dotmailer.com/v2/address-books/37649245/contacts" - - body = { - "email": email, - "optInType": "VerifiedDouble", - "emailType": "Html", - "dataFields": [ - {"key": "FIRSTNAME", "value": first_name}, - {"key": "LASTNAME", "value": last_name}, - {"key": "FULLNAME", "value": f"{first_name} {last_name}"}, - ], - } - - post(main_address_book_url, json=body, auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD)) + add_contact_to_address_book( + first_name, last_name, email, address_book_id, user_type + ) except RequestException: return HttpResponse(status=404) @@ -178,15 +214,34 @@ def create_contact(first_name, last_name, email): {"key": "FULLNAME", "value": f"{first_name} {last_name}"}, ], }, - "consentFields": [{"fields": [{"key": "DATETIMECONSENTED", "value": datetime.datetime.now().__str__()}]}], + "consentFields": [ + { + "fields": [ + { + "key": "DATETIMECONSENTED", + "value": datetime.datetime.now().__str__(), + } + ] + } + ], "preferences": app_settings.DOTMAILER_DEFAULT_PREFERENCES, } - post(url, json=body, auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD)) + post( + url, + json=body, + auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD), + ) -def add_contact_to_address_book(first_name: str, last_name: str, email: str, user_type: DotmailerUserType): - main_address_book_url = app_settings.DOTMAILER_MAIN_ADDRESS_BOOK_URL +def add_contact_to_address_book( + first_name: str, + last_name: str, + email: str, + address_book_id: int, + user_type: DotmailerUserType = None, +): + main_address_book_url = f"https://r1-api.dotmailer.com/v2/address-books/{address_book_id}/contacts" body = { "email": email, @@ -199,16 +254,31 @@ def add_contact_to_address_book(first_name: str, last_name: str, email: str, use ], } - post(main_address_book_url, json=body, auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD)) - - specific_address_book_url = app_settings.DOTMAILER_NO_ACCOUNT_ADDRESS_BOOK_URL + post( + main_address_book_url, + json=body, + auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD), + ) - if user_type == DotmailerUserType.TEACHER: - specific_address_book_url = app_settings.DOTMAILER_TEACHER_ADDRESS_BOOK_URL - elif user_type == DotmailerUserType.STUDENT: - specific_address_book_url = app_settings.DOTMAILER_STUDENT_ADDRESS_BOOK_URL + if user_type is not None: + specific_address_book_url = ( + app_settings.DOTMAILER_NO_ACCOUNT_ADDRESS_BOOK_URL + ) - post(specific_address_book_url, json=body, auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD)) + if user_type == DotmailerUserType.TEACHER: + specific_address_book_url = ( + app_settings.DOTMAILER_TEACHER_ADDRESS_BOOK_URL + ) + elif user_type == DotmailerUserType.STUDENT: + specific_address_book_url = ( + app_settings.DOTMAILER_STUDENT_ADDRESS_BOOK_URL + ) + + post( + specific_address_book_url, + json=body, + auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD), + ) def delete_contact(email: str): @@ -216,8 +286,16 @@ def delete_contact(email: str): user = get_dotmailer_user_by_email(email) user_id = user.get("id") if user_id: - url = app_settings.DOTMAILER_DELETE_USER_BY_ID_URL.replace("ID", str(user_id)) - delete(url, auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD)) + url = app_settings.DOTMAILER_DELETE_USER_BY_ID_URL.replace( + "ID", str(user_id) + ) + delete( + url, + auth=( + app_settings.DOTMAILER_USER, + app_settings.DOTMAILER_PASSWORD, + ), + ) except RequestException: return HttpResponse(status=404) @@ -225,7 +303,9 @@ def delete_contact(email: str): def get_dotmailer_user_by_email(email): url = app_settings.DOTMAILER_GET_USER_BY_EMAIL_URL.replace("EMAIL", email) - response = get(url, auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD)) + response = get( + url, auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD) + ) return json.loads(response.content) @@ -233,7 +313,9 @@ def get_dotmailer_user_by_email(email): def add_consent_record_to_dotmailer_user(user): consent_date_time = datetime.datetime.now().__str__() - url = app_settings.DOTMAILER_PUT_CONSENT_DATA_URL.replace("USER_ID", str(user["id"])) + url = app_settings.DOTMAILER_PUT_CONSENT_DATA_URL.replace( + "USER_ID", str(user["id"]) + ) body = { "contact": { "email": user["email"], @@ -241,10 +323,20 @@ def add_consent_record_to_dotmailer_user(user): "emailType": user["emailType"], "dataFields": user["dataFields"], }, - "consentFields": [{"fields": [{"key": "DATETIMECONSENTED", "value": consent_date_time}]}], + "consentFields": [ + { + "fields": [ + {"key": "DATETIMECONSENTED", "value": consent_date_time} + ] + } + ], } - put(url, json=body, auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD)) + put( + url, + json=body, + auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD), + ) def send_dotmailer_consent_confirmation_email_to_user(user): @@ -252,7 +344,11 @@ def send_dotmailer_consent_confirmation_email_to_user(user): campaign_id = app_settings.DOTMAILER_THANKS_FOR_STAYING_CAMPAIGN_ID body = {"campaignID": campaign_id, "contactIds": [str(user["id"])]} - post(url, json=body, auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD)) + post( + url, + json=body, + auth=(app_settings.DOTMAILER_USER, app_settings.DOTMAILER_PASSWORD), + ) def update_indy_email(user, request, data): diff --git a/cfl_common/common/mail.py b/cfl_common/common/mail.py index 948df31a0..961c98228 100644 --- a/cfl_common/common/mail.py +++ b/cfl_common/common/mail.py @@ -32,6 +32,11 @@ "inactive_users_on_website_final_reminder": 1606215, } +address_book_ids = { + "newsletter": 9705772, + "donors": 37649245, +} + def add_contact(email: str): """Add a new contact to Dotdigital.""" diff --git a/portal/tests/test_emails.py b/portal/tests/test_emails.py index beb03cd26..f4fa626f7 100644 --- a/portal/tests/test_emails.py +++ b/portal/tests/test_emails.py @@ -10,6 +10,7 @@ send_dotmailer_consent_confirmation_email_to_user, DotmailerUserType, ) +from common.mail import address_book_ids from django.test import Client from django.urls import reverse @@ -21,7 +22,6 @@ DOTMAILER_THANKS_FOR_STAYING_CAMPAIGN_ID, DOTMAILER_CREATE_CONTACT_URL, DOTMAILER_DELETE_USER_BY_ID_URL, - DOTMAILER_MAIN_ADDRESS_BOOK_URL, DOTMAILER_TEACHER_ADDRESS_BOOK_URL, DOTMAILER_STUDENT_ADDRESS_BOOK_URL, DOTMAILER_NO_ACCOUNT_ADDRESS_BOOK_URL, @@ -47,7 +47,11 @@ def test_newsletter_calls_correct_requests(mocker, monkeypatch): ) add_to_dotmailer( - "Ray", "Charles", "ray.charles@example.com", DotmailerUserType.TEACHER + "Ray", + "Charles", + "ray.charles@example.com", + address_book_ids["newsletter"], + DotmailerUserType.TEACHER, ) mocked_create_contact.assert_called_once() @@ -121,17 +125,15 @@ def test_newsletter_sends_correct_request_data( ) add_contact_to_address_book( - "Ray", "Charles", "ray.charles@example.com", DotmailerUserType.TEACHER + "Ray", + "Charles", + "ray.charles@example.com", + address_book_ids["newsletter"], + DotmailerUserType.TEACHER, ) assert mocked_post.call_count == 3 - mocked_post.assert_any_call( - DOTMAILER_MAIN_ADDRESS_BOOK_URL, - auth=(DOTMAILER_USER, DOTMAILER_PASSWORD), - json=expected_body2, - ) - mocked_post.assert_any_call( DOTMAILER_TEACHER_ADDRESS_BOOK_URL, auth=(DOTMAILER_USER, DOTMAILER_PASSWORD), @@ -141,17 +143,15 @@ def test_newsletter_sends_correct_request_data( mocked_post.reset_mock() add_contact_to_address_book( - "Ray", "Charles", "ray.charles@example.com", DotmailerUserType.STUDENT + "Ray", + "Charles", + "ray.charles@example.com", + address_book_ids["newsletter"], + DotmailerUserType.STUDENT, ) assert mocked_post.call_count == 2 - mocked_post.assert_any_call( - DOTMAILER_MAIN_ADDRESS_BOOK_URL, - auth=(DOTMAILER_USER, DOTMAILER_PASSWORD), - json=expected_body2, - ) - mocked_post.assert_any_call( DOTMAILER_STUDENT_ADDRESS_BOOK_URL, auth=(DOTMAILER_USER, DOTMAILER_PASSWORD), @@ -164,17 +164,12 @@ def test_newsletter_sends_correct_request_data( "Ray", "Charles", "ray.charles@example.com", + address_book_ids["newsletter"], DotmailerUserType.NO_ACCOUNT, ) assert mocked_post.call_count == 2 - mocked_post.assert_any_call( - DOTMAILER_MAIN_ADDRESS_BOOK_URL, - auth=(DOTMAILER_USER, DOTMAILER_PASSWORD), - json=expected_body2, - ) - mocked_post.assert_any_call( DOTMAILER_NO_ACCOUNT_ADDRESS_BOOK_URL, auth=(DOTMAILER_USER, DOTMAILER_PASSWORD), diff --git a/portal/views/dotmailer.py b/portal/views/dotmailer.py index 7229999b0..7261470a5 100644 --- a/portal/views/dotmailer.py +++ b/portal/views/dotmailer.py @@ -1,11 +1,11 @@ from common.helpers.emails import ( - add_donor_to_dotmailer, add_to_dotmailer, get_dotmailer_user_by_email, send_dotmailer_consent_confirmation_email_to_user, add_consent_record_to_dotmailer_user, DotmailerUserType, ) +from common.mail import address_book_ids from django.contrib import messages as messages from django.http import HttpResponseRedirect, HttpResponse from django.shortcuts import render @@ -21,7 +21,13 @@ def process_newsletter_form(request): newsletter_form = NewsletterForm(data=request.POST) if newsletter_form.is_valid(): user_email = newsletter_form.cleaned_data["email"] - add_to_dotmailer("", "", user_email, DotmailerUserType.NO_ACCOUNT) + add_to_dotmailer( + "", + "", + user_email, + address_book_ids["newsletter"], + DotmailerUserType.NO_ACCOUNT, + ) messages.success(request, "Thank you for signing up! 🎉") return HttpResponseRedirect(reverse_lazy("home")) messages.error( @@ -40,7 +46,7 @@ def process_donate_form(request): donate_form = DonateForm(data=request.POST) if donate_form.is_valid(): user_email = request.POST.get("email", "") - add_donor_to_dotmailer("", "", user_email) + add_to_dotmailer("", "", user_email, address_book_ids["donors"]) messages.success( request, "Thank you for registering your interest! 🎉" ) diff --git a/portal/views/teacher/dashboard.py b/portal/views/teacher/dashboard.py index 3a8082513..88ed4f8c0 100644 --- a/portal/views/teacher/dashboard.py +++ b/portal/views/teacher/dashboard.py @@ -2,16 +2,13 @@ from uuid import uuid4 from common.helpers.emails import ( - INVITE_FROM, - NOTIFICATION_EMAIL, DotmailerUserType, add_to_dotmailer, generate_token, - send_email, update_email, ) from common.helpers.generators import get_random_username -from common.mail import campaign_ids, send_dotdigital_email +from common.mail import address_book_ids, campaign_ids, send_dotdigital_email from common.models import ( Class, JoinReleaseStudent, @@ -50,7 +47,6 @@ RATELIMIT_METHOD, clear_ratelimit_cache_for_user, ) - from .teach import create_class @@ -92,11 +88,19 @@ def dashboard_teacher_view(request, is_admin): update_school_form = None if school: - coworkers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name") + coworkers = Teacher.objects.filter(school=school).order_by( + "new_user__last_name", "new_user__first_name" + ) - sent_invites = SchoolTeacherInvitation.objects.filter(school=school) if teacher.is_admin else [] + sent_invites = ( + SchoolTeacherInvitation.objects.filter(school=school) + if teacher.is_admin + else [] + ) - update_school_form = OrganisationForm(user=request.user, current_school=school) + update_school_form = OrganisationForm( + user=request.user, current_school=school + ) update_school_form.fields["name"].initial = school.name update_school_form.fields["country"].initial = school.country update_school_form.fields["county"].initial = school.county @@ -121,7 +125,9 @@ def dashboard_teacher_view(request, is_admin): if request.method == "POST": if can_process_update_school_form(request, is_admin): anchor = "school-details" - update_school_form = OrganisationForm(request.POST, user=request.user, current_school=school) + update_school_form = OrganisationForm( + request.POST, user=request.user, current_school=school + ) anchor = process_update_school_form(request, school, anchor) elif "create_class" in request.POST: @@ -131,14 +137,23 @@ def dashboard_teacher_view(request, is_admin): class_teacher = teacher # If the logged in teacher is an admin, then get the class teacher from the selected dropdown if teacher.is_admin: - class_teacher = get_object_or_404(Teacher, id=create_class_form.cleaned_data["teacher"]) - created_class = create_class(create_class_form, class_teacher, class_creator=teacher) + class_teacher = get_object_or_404( + Teacher, id=create_class_form.cleaned_data["teacher"] + ) + created_class = create_class( + create_class_form, class_teacher, class_creator=teacher + ) messages.success( request, - "The class '{className}' has been created successfully.".format(className=created_class.name), + "The class '{className}' has been created successfully.".format( + className=created_class.name + ), ) return HttpResponseRedirect( - reverse_lazy("view_class", kwargs={"access_code": created_class.access_code}) + reverse_lazy( + "view_class", + kwargs={"access_code": created_class.access_code}, + ) ) elif request.POST.get("show_onboarding_complete") == "1": @@ -165,11 +180,11 @@ def dashboard_teacher_view(request, is_admin): expiry=timezone.now() + timedelta(days=30), ) - account_exists = User.objects.filter(email=invited_teacher_email).exists() + account_exists = User.objects.filter( + email=invited_teacher_email + ).exists() - registration_link = ( - f"{request.build_absolute_uri(reverse('invited_teacher', kwargs={'token': token}))} " - ) + registration_link = f"{request.build_absolute_uri(reverse('invited_teacher', kwargs={'token': token}))} " campaign_id = ( campaign_ids["invite_teacher_with_account"] @@ -180,7 +195,10 @@ def dashboard_teacher_view(request, is_admin): send_dotdigital_email( campaign_id, [invited_teacher_email], - personalization_values={"SCHOOL_NAME": school.name, "REGISTRATION_LINK": registration_link}, + personalization_values={ + "SCHOOL_NAME": school.name, + "REGISTRATION_LINK": registration_link, + }, ) messages.success( @@ -194,15 +212,23 @@ def dashboard_teacher_view(request, is_admin): elif "delete_account" in request.POST: delete_account_form = DeleteAccountForm(request.user, request.POST) if not delete_account_form.is_valid(): - messages.warning(request, "Your account was not deleted due to incorrect password.") + messages.warning( + request, + "Your account was not deleted due to incorrect password.", + ) else: delete_account_confirm = True else: anchor = "account" - update_account_form = TeacherEditAccountForm(request.user, request.POST) - (changing_email, new_email, changing_password, anchor) = process_update_account_form( - request, teacher, anchor + update_account_form = TeacherEditAccountForm( + request.user, request.POST ) + ( + changing_email, + new_email, + changing_password, + anchor, + ) = process_update_account_form(request, teacher, anchor) if changing_email: logout(request) messages.success( @@ -210,19 +236,33 @@ def dashboard_teacher_view(request, is_admin): "Your email will be changed once you have verified it, until then " "you can still log in with your old email.", ) - return render(request, "portal/email_verification_needed.html", {"usertype": "TEACHER"}) + return render( + request, + "portal/email_verification_needed.html", + {"usertype": "TEACHER"}, + ) if changing_password: logout(request) - messages.success(request, "Please login using your new password.") + messages.success( + request, "Please login using your new password." + ) return HttpResponseRedirect(reverse_lazy("teacher_login")) if teacher.is_admin: # Making sure the current teacher classes come up first classes = school.classes() - [classes.insert(0, classes.pop(i)) for i in range(len(classes)) if classes[i].teacher.id == teacher.id] + [ + classes.insert(0, classes.pop(i)) + for i in range(len(classes)) + if classes[i].teacher.id == teacher.id + ] - requests = list(Student.objects.filter(pending_class_request__teacher__school=school)) + requests = list( + Student.objects.filter( + pending_class_request__teacher__school=school + ) + ) [ requests.insert(0, requests.pop(i)) for i in range(len(requests)) @@ -231,7 +271,9 @@ def dashboard_teacher_view(request, is_admin): else: classes = Class.objects.filter(teacher=teacher) - requests = Student.objects.filter(pending_class_request__teacher=teacher) + requests = Student.objects.filter( + pending_class_request__teacher=teacher + ) return render( request, @@ -265,7 +307,9 @@ def check_backup_tokens(request): # For teachers using 2FA, find out how many backup tokens they have if using_two_factor(request.user): try: - backup_tokens = request.user.staticdevice_set.all()[0].token_set.count() + backup_tokens = request.user.staticdevice_set.all()[ + 0 + ].token_set.count() except Exception: backup_tokens = 0 @@ -273,7 +317,9 @@ def check_backup_tokens(request): def process_update_school_form(request, school, old_anchor): - update_school_form = OrganisationForm(request.POST, user=request.user, current_school=school) + update_school_form = OrganisationForm( + request.POST, user=request.user, current_school=school + ) if update_school_form.is_valid(): data = update_school_form.cleaned_data name = data.get("name", "") @@ -286,7 +332,10 @@ def process_update_school_form(request, school, old_anchor): anchor = "#" - messages.success(request, "You have updated the details for your school or club successfully.") + messages.success( + request, + "You have updated the details for your school or club successfully.", + ) else: anchor = old_anchor @@ -302,7 +351,9 @@ def process_update_account_form(request, teacher, old_anchor): data = update_account_form.cleaned_data # check not default value for CharField - changing_password = check_update_password(update_account_form, teacher.new_user, request, data) + changing_password = check_update_password( + update_account_form, teacher.new_user, request, data + ) teacher.new_user.first_name = data["first_name"] teacher.new_user.last_name = data["last_name"] @@ -317,7 +368,9 @@ def process_update_account_form(request, teacher, old_anchor): # Reset ratelimit cache after successful account details update clear_ratelimit_cache_for_user(teacher.new_user.username) - messages.success(request, "Your account details have been successfully changed.") + messages.success( + request, "Your account details have been successfully changed." + ) else: anchor = old_anchor @@ -349,7 +402,9 @@ def organisation_kick(request, pk): check_teacher_is_authorised(teacher, user) - success_message = "The teacher has been successfully removed from your school or club." + success_message = ( + "The teacher has been successfully removed from your school or club." + ) classes = Class.objects.filter(teacher=teacher) for klass in classes: @@ -359,10 +414,14 @@ def organisation_kick(request, pk): klass.teacher = new_teacher klass.save() - success_message = success_message.replace(".", " and their classes were successfully transferred.") + success_message = success_message.replace( + ".", " and their classes were successfully transferred." + ) classes = Class.objects.filter(teacher=teacher) - teachers = Teacher.objects.filter(school=teacher.school).exclude(id=teacher.id) + teachers = Teacher.objects.filter(school=teacher.school).exclude( + id=teacher.id + ) if classes.exists(): messages.info( @@ -404,18 +463,24 @@ def invite_toggle_admin(request, invite_id): invite.save() if invite.invited_teacher_is_admin: - messages.success(request, "Administrator invite status has been given successfully") + messages.success( + request, "Administrator invite status has been given successfully" + ) send_dotdigital_email( campaign_ids["admin_given"], [invite.invited_teacher_email], personalization_values={ "SCHOOL_CLUB_NAME": invite.school, - "MANAGEMENT_LINK": request.build_absolute_uri(reverse("dashboard")), + "MANAGEMENT_LINK": request.build_absolute_uri( + reverse("dashboard") + ), }, ) else: - messages.success(request, "Administrator invite status has been revoked successfully") + messages.success( + request, "Administrator invite status has been revoked successfully" + ) send_dotdigital_email( campaign_ids["admin_revoked"], [invite.invited_teacher_email], @@ -438,13 +503,17 @@ def organisation_toggle_admin(request, pk): teacher.save() if teacher.is_admin: - messages.success(request, "Administrator status has been given successfully.") + messages.success( + request, "Administrator status has been given successfully." + ) send_dotdigital_email( campaign_ids["admin_given"], [teacher.new_user.email], personalization_values={ "SCHOOL_CLUB_NAME": teacher.school.name, - "MANAGEMENT_LINK": request.build_absolute_uri(reverse("dashboard")), + "MANAGEMENT_LINK": request.build_absolute_uri( + reverse("dashboard") + ), }, ) else: @@ -452,9 +521,12 @@ def organisation_toggle_admin(request, pk): [ unshare_level(level, teacher.new_user) for level in levels_shared_with(teacher.new_user) - if hasattr(level.owner, "student") and not teacher.teaches(level.owner) + if hasattr(level.owner, "student") + and not teacher.teaches(level.owner) ] - messages.success(request, "Administrator status has been revoked successfully.") + messages.success( + request, "Administrator status has been revoked successfully." + ) send_dotdigital_email( campaign_ids["admin_revoked"], [teacher.new_user.email], @@ -474,7 +546,11 @@ def teacher_disable_2FA(request, pk): if teacher.school != user.school or not user.is_admin: raise Http404 - [device.delete() for device in devices_for_user(teacher.new_user) if request.method == "POST"] + [ + device.delete() + for device in devices_for_user(teacher.new_user) + if request.method == "POST" + ] return HttpResponseRedirect(reverse_lazy("dashboard")) @@ -487,10 +563,14 @@ def teacher_accept_student_request(request, pk): check_student_request_can_be_handled(request, student) - students = Student.objects.filter(class_field=student.pending_class_request).order_by("new_user__first_name") + students = Student.objects.filter( + class_field=student.pending_class_request + ).order_by("new_user__first_name") if request.method == "POST": - form = TeacherAddExternalStudentForm(student.pending_class_request, request.POST) + form = TeacherAddExternalStudentForm( + student.pending_class_request, request.POST + ) if form.is_valid(): data = form.cleaned_data student.class_field = student.pending_class_request @@ -505,7 +585,9 @@ def teacher_accept_student_request(request, pk): student.new_user.userprofile.save() # log the data - joinrelease = JoinReleaseStudent.objects.create(student=student, action_type=JoinReleaseStudent.JOIN) + joinrelease = JoinReleaseStudent.objects.create( + student=student, action_type=JoinReleaseStudent.JOIN + ) joinrelease.save() return render( @@ -515,13 +597,19 @@ def teacher_accept_student_request(request, pk): ) else: form = TeacherAddExternalStudentForm( - student.pending_class_request, initial={"name": student.new_user.first_name} + student.pending_class_request, + initial={"name": student.new_user.first_name}, ) return render( request, "portal/teach/teacher_add_external_student.html", - {"students": students, "class": student.pending_class_request, "student": student, "form": form}, + { + "students": students, + "class": student.pending_class_request, + "student": student, + "form": form, + }, ) @@ -556,7 +644,10 @@ def teacher_reject_student_request(request, pk): student.pending_class_request = None student.save() - messages.success(request, "Request from external/independent student has been rejected successfully.") + messages.success( + request, + "Request from external/independent student has been rejected successfully.", + ) return HttpResponseRedirect(reverse_lazy("dashboard")) @@ -571,11 +662,17 @@ def delete_teacher_invite(request, token): # auth the user before deletion if invite is None or teacher.school != invite.school: - messages.error(request, "You do not have permission to perform this action or the invite does not exist") + messages.error( + request, + "You do not have permission to perform this action or the invite does not exist", + ) else: invite_teacher_first_name = invite.invited_teacher_first_name invite.anonymise() - messages.success(request, f"Invite for {invite_teacher_first_name} successfully deleted") + messages.success( + request, + f"Invite for {invite_teacher_first_name} successfully deleted", + ) return HttpResponseRedirect(reverse_lazy("dashboard")) @@ -589,7 +686,10 @@ def resend_invite_teacher(request, token): # auth the user before deletion if invite is None or teacher.school != invite.school: - messages.error(request, "You do not have permission to perform this action or the invite does not exist") + messages.error( + request, + "You do not have permission to perform this action or the invite does not exist", + ) else: invite.expiry = timezone.now() + timedelta(days=30) invite.save() @@ -608,7 +708,10 @@ def resend_invite_teacher(request, token): send_dotdigital_email( campaign_id, [invite.invited_teacher_email], - personalization_values={"SCHOOL_NAME": invite.school, "REGISTRATION_LINK": registration_link}, + personalization_values={ + "SCHOOL_NAME": invite.school, + "REGISTRATION_LINK": registration_link, + }, ) return HttpResponseRedirect(reverse_lazy("dashboard")) @@ -620,7 +723,10 @@ def invited_teacher(request, token): if request.method == "POST": invited_teacher_form = InvitedTeacherForm(request.POST) if invited_teacher_form.is_valid(): - messages.success(request, "Your account has been created successfully, please log in.") + messages.success( + request, + "Your account has been created successfully, please log in.", + ) return HttpResponseRedirect(reverse_lazy("teacher_login")) else: invited_teacher_form = InvitedTeacherForm() @@ -628,13 +734,18 @@ def invited_teacher(request, token): return render( request, "portal/teach/invited.html", - {"invited_teacher_form": invited_teacher_form, "error_message": error_message}, + { + "invited_teacher_form": invited_teacher_form, + "error_message": error_message, + }, ) def process_teacher_invitation(request, token): try: - invitation = SchoolTeacherInvitation.objects.get(token=token, expiry__gt=timezone.now()) + invitation = SchoolTeacherInvitation.objects.get( + token=token, expiry__gt=timezone.now() + ) except SchoolTeacherInvitation.DoesNotExist: return "Uh oh, the Invitation does not exist or it has expired. 😞" @@ -670,7 +781,13 @@ def process_teacher_invitation(request, token): # Add to Dotmailer if they ticked the box if newsletter_ticked: user = invited_teacher.user.user - add_to_dotmailer(user.first_name, user.last_name, user.email, DotmailerUserType.TEACHER) + add_to_dotmailer( + user.first_name, + user.last_name, + user.email, + address_book_ids["newsletter"], + DotmailerUserType.TEACHER, + ) # Anonymise the invitation invitation.anonymise()