From 81bf6eac81029976f20b9ea36acc7b0ae47424ee Mon Sep 17 00:00:00 2001 From: faucomte97 Date: Mon, 20 May 2024 12:09:24 +0100 Subject: [PATCH] fix: Remove legacy weekly stats email --- portal/urls.py | 292 ++++++++++++++++++++++++++++++++++-------- portal/views/email.py | 77 ++++------- 2 files changed, 263 insertions(+), 106 deletions(-) diff --git a/portal/urls.py b/portal/urls.py index b83f7fdbf..2511810f9 100644 --- a/portal/urls.py +++ b/portal/urls.py @@ -1,13 +1,18 @@ from aimmo.urls import HOMEPAGE_REGEX from common.permissions import teacher_verified from django.conf.urls import include, url -from django.urls import path from django.http import HttpResponse +from django.urls import path from django.views.generic import RedirectView from django.views.generic.base import TemplateView from django.views.i18n import JavaScriptCatalog from game.views.level import play_default_level -from two_factor.views import BackupTokensView, ProfileView, QRGeneratorView, SetupCompleteView +from two_factor.views import ( + BackupTokensView, + ProfileView, + QRGeneratorView, + SetupCompleteView, +) from portal.helpers.decorators import ratelimit from portal.helpers.ratelimit import ( @@ -18,9 +23,16 @@ ) from portal.helpers.ratelimit import school_student_key from portal.helpers.regexes import ACCESS_CODE_REGEX, JWT_REGEX +from portal.views import cron from portal.views.about import about, getinvolved, contribute -from portal.views.admin import AdminChangePasswordDoneView, AdminChangePasswordView -from portal.views.aimmo.dashboard import StudentAimmoDashboard, TeacherAimmoDashboard +from portal.views.admin import ( + AdminChangePasswordDoneView, + AdminChangePasswordView, +) +from portal.views.aimmo.dashboard import ( + StudentAimmoDashboard, + TeacherAimmoDashboard, +) from portal.views.api import ( AnonymiseOrphanSchoolsView, InactiveUsersView, @@ -29,8 +41,11 @@ number_users_per_country, registered_users, ) -from portal.views.dotmailer import dotmailer_consent_form, process_newsletter_form -from portal.views.email import send_new_users_report, verify_email +from portal.views.dotmailer import ( + dotmailer_consent_form, + process_newsletter_form, +) +from portal.views.email import verify_email from portal.views.home import ( coding_club, download_student_pack, @@ -43,7 +58,11 @@ from portal.views.legal import privacy_notice, terms from portal.views.login import old_login_form_redirect from portal.views.login.independent_student import IndependentStudentLoginView -from portal.views.login.student import StudentLoginView, StudentClassCodeView, student_direct_login +from portal.views.login.student import ( + StudentLoginView, + StudentClassCodeView, + student_direct_login, +) from portal.views.login.teacher import TeacherLoginView from portal.views.organisation import organisation_leave, organisation_manage from portal.views.play_landing_page import play_landing_page @@ -59,7 +78,11 @@ SchoolStudentEditAccountView, student_edit_account, ) -from portal.views.student.play import SchoolStudentDashboard, IndependentStudentDashboard, student_join_organisation +from portal.views.student.play import ( + SchoolStudentDashboard, + IndependentStudentDashboard, + student_join_organisation, +) from portal.views.teach import teach from portal.views.teacher.dashboard import ( dashboard_manage, @@ -88,20 +111,36 @@ teacher_download_csv, teacher_view_class, ) -from portal.views import cron - from portal.views.two_factor.core import CustomSetupView from portal.views.two_factor.profile import CustomDisableView js_info_dict = {"packages": ("conf.locale",)} two_factor_patterns = [ - url(r"^account/two_factor/setup/$", CustomSetupView.as_view(), name="setup"), + url( + r"^account/two_factor/setup/$", CustomSetupView.as_view(), name="setup" + ), url(r"^account/two_factor/qrcode/$", QRGeneratorView.as_view(), name="qr"), - url(r"^account/two_factor/setup/complete/$", SetupCompleteView.as_view(), name="setup_complete"), - url(r"^account/two_factor/backup/tokens/$", teacher_verified(BackupTokensView.as_view()), name="backup_tokens"), - url(r"^account/two_factor/$", teacher_verified(ProfileView.as_view()), name="profile"), - url(r"^account/two_factor/disable/$", teacher_verified(CustomDisableView.as_view()), name="disable"), + url( + r"^account/two_factor/setup/complete/$", + SetupCompleteView.as_view(), + name="setup_complete", + ), + url( + r"^account/two_factor/backup/tokens/$", + teacher_verified(BackupTokensView.as_view()), + name="backup_tokens", + ), + url( + r"^account/two_factor/$", + teacher_verified(ProfileView.as_view()), + name="profile", + ), + url( + r"^account/two_factor/disable/$", + teacher_verified(CustomDisableView.as_view()), + name="disable", + ), ] @@ -136,22 +175,51 @@ ), ), url(HOMEPAGE_REGEX, include("aimmo.urls")), - url(r"^teach/kurono/dashboard/$", TeacherAimmoDashboard.as_view(), name="teacher_aimmo_dashboard"), - url(r"^play/kurono/dashboard/$", StudentAimmoDashboard.as_view(), name="student_aimmo_dashboard"), - url(r"^favicon\.ico$", RedirectView.as_view(url="/static/portal/img/favicon.ico", permanent=True)), - url(r"^administration/password_change/$", AdminChangePasswordView.as_view(), name="administration_password_change"), + url( + r"^teach/kurono/dashboard/$", + TeacherAimmoDashboard.as_view(), + name="teacher_aimmo_dashboard", + ), + url( + r"^play/kurono/dashboard/$", + StudentAimmoDashboard.as_view(), + name="student_aimmo_dashboard", + ), + url( + r"^favicon\.ico$", + RedirectView.as_view( + url="/static/portal/img/favicon.ico", permanent=True + ), + ), + url( + r"^administration/password_change/$", + AdminChangePasswordView.as_view(), + name="administration_password_change", + ), url( r"^administration/password_change_done/$", AdminChangePasswordDoneView.as_view(), name="administration_password_change_done", ), - url(r"^mail/weekly/", send_new_users_report, name="send_new_users_report"), - url(r"^users/inactive/", InactiveUsersView.as_view(), name="inactive_users"), - url(r"^locked_out/$", TemplateView.as_view(template_name="portal/locked_out.html"), name="locked_out"), - url(r"^", include((two_factor_patterns, "two_factor"), namespace="two_factor")), + url( + r"^users/inactive/", InactiveUsersView.as_view(), name="inactive_users" + ), + url( + r"^locked_out/$", + TemplateView.as_view(template_name="portal/locked_out.html"), + name="locked_out", + ), + url( + r"^", + include((two_factor_patterns, "two_factor"), namespace="two_factor"), + ), url(r"^i18n/", include("django.conf.urls.i18n")), url(r"^jsi18n/$", JavaScriptCatalog.as_view(), js_info_dict), - url(r"^(?P[A-Z0-9]+)/$", play_default_level, name="play_default_level"), + url( + r"^(?P[A-Z0-9]+)/$", + play_default_level, + name="play_default_level", + ), url(r"^$", home, name="home"), url(r"^home-learning", home_learning, name="home-learning"), url(r"^register_form", register_view, name="register"), @@ -181,8 +249,16 @@ )(StudentLoginView.as_view()), name="student_login", ), - url(r"^login/student/$", StudentClassCodeView.as_view(), name="student_login_access_code"), - url(r"^u/(?P[0-9]+)/(?P[a-z0-9]+)/$", student_direct_login, name="student_direct_login"), + url( + r"^login/student/$", + StudentClassCodeView.as_view(), + name="student_login_access_code", + ), + url( + r"^u/(?P[0-9]+)/(?P[a-z0-9]+)/$", + student_direct_login, + name="student_direct_login", + ), url( r"^login/independent/$", ratelimit( @@ -197,32 +273,70 @@ ), url(r"^login_form", old_login_form_redirect, name="old_login_form"), url(r"^logout/$", logout_view, name="logout_view"), - url(r"^news_signup/$", process_newsletter_form, name="process_newsletter_form"), + url( + r"^news_signup/$", + process_newsletter_form, + name="process_newsletter_form", + ), url(r"^consent_form/$", dotmailer_consent_form, name="consent_form"), url( r"^verify_email/$", - TemplateView.as_view(template_name="portal/email_verification_needed.html"), + TemplateView.as_view( + template_name="portal/email_verification_needed.html" + ), name="email_verification", ), - url(rf"^verify_email/(?P{JWT_REGEX})/$", verify_email, name="verify_email"), - url(r"^user/password/reset/student/$", student_password_reset, name="student_password_reset"), - url(r"^user/password/reset/teacher/$", teacher_password_reset, name="teacher_password_reset"), - url(r"^user/password/reset/done/$", password_reset_done, name="reset_password_email_sent"), + url( + rf"^verify_email/(?P{JWT_REGEX})/$", + verify_email, + name="verify_email", + ), + url( + r"^user/password/reset/student/$", + student_password_reset, + name="student_password_reset", + ), + url( + r"^user/password/reset/teacher/$", + teacher_password_reset, + name="teacher_password_reset", + ), + url( + r"^user/password/reset/done/$", + password_reset_done, + name="reset_password_email_sent", + ), url( r"^user/password/reset/(?P[0-9A-Za-z]+)-(?P.+)/$", password_reset_check_and_confirm, name="password_reset_check_and_confirm", ), - url(r"^user/reset_screentime_warning/$", reset_screentime_warning, name="reset_screentime_warning"), - url(r"^user/reset_session_time/$", lambda _: HttpResponse(status=204), name="reset_session_time"), + url( + r"^user/reset_screentime_warning/$", + reset_screentime_warning, + name="reset_screentime_warning", + ), + url( + r"^user/reset_session_time/$", + lambda _: HttpResponse(status=204), + name="reset_session_time", + ), url( r"^teacher/password/reset/complete/$", TemplateView.as_view(template_name="portal/reset_password_done.html"), name="password_reset_complete", ), url(r"^teach/$", teach, name="teach"), - url(r"^teach/onboarding-organisation/$", organisation_manage, name="onboarding-organisation"), - url(r"^teach/onboarding-classes", teacher_onboarding_create_class, name="onboarding-classes"), + url( + r"^teach/onboarding-organisation/$", + organisation_manage, + name="onboarding-organisation", + ), + url( + r"^teach/onboarding-classes", + teacher_onboarding_create_class, + name="onboarding-classes", + ), url( rf"^teach/onboarding-class/(?P{ACCESS_CODE_REGEX})$", teacher_onboarding_edit_class, @@ -238,10 +352,22 @@ teacher_download_csv, name="teacher_download_csv", ), - url(r"^invited_teacher/(?P[0-9a-f]+)/$", invited_teacher, name="invited_teacher"), + url( + r"^invited_teacher/(?P[0-9a-f]+)/$", + invited_teacher, + name="invited_teacher", + ), url(r"^play/$", play_landing_page, name="play"), - url(r"^play/details/$", SchoolStudentDashboard.as_view(), name="student_details"), - url(r"^play/details/independent$", IndependentStudentDashboard.as_view(), name="independent_student_details"), + url( + r"^play/details/$", + SchoolStudentDashboard.as_view(), + name="student_details", + ), + url( + r"^play/details/independent$", + IndependentStudentDashboard.as_view(), + name="independent_student_details", + ), url(r"^play/account/$", student_edit_account, name="student_edit_account"), url( r"^play/account/independent/$", @@ -255,19 +381,45 @@ )(independentStudentEditAccountView), name="independent_edit_account", ), - url(r"^play/account/school_student/$", SchoolStudentEditAccountView.as_view(), name="school_student_edit_account"), - url(r"^play/join/$", student_join_organisation, name="student_join_organisation"), + url( + r"^play/account/school_student/$", + SchoolStudentEditAccountView.as_view(), + name="school_student_edit_account", + ), + url( + r"^play/join/$", + student_join_organisation, + name="student_join_organisation", + ), url(r"^about", about, name="about"), url(r"^getinvolved", getinvolved, name="getinvolved"), url(r"^contribute", contribute, name="contribute"), url(r"^terms", terms, name="terms"), url(r"^privacy-notice/$", privacy_notice, name="privacy_notice"), - url(r"^privacy-policy/$", privacy_notice, name="privacy_policy"), # Keeping this to route from old URL + url( + r"^privacy-policy/$", privacy_notice, name="privacy_policy" + ), # Keeping this to route from old URL url(r"^teach/dashboard/$", dashboard_manage, name="dashboard"), - url(r"^teach/dashboard/kick/(?P[0-9]+)/$", organisation_kick, name="organisation_kick"), - url(r"^teach/dashboard/toggle_admin/(?P[0-9]+)/$", organisation_toggle_admin, name="organisation_toggle_admin"), - url(r"^teach/dashboard/disable_2FA/(?P[0-9]+)/$", teacher_disable_2FA, name="teacher_disable_2FA"), - url(r"^teach/dashboard/school/leave/$", organisation_leave, name="organisation_leave"), + url( + r"^teach/dashboard/kick/(?P[0-9]+)/$", + organisation_kick, + name="organisation_kick", + ), + url( + r"^teach/dashboard/toggle_admin/(?P[0-9]+)/$", + organisation_toggle_admin, + name="organisation_toggle_admin", + ), + url( + r"^teach/dashboard/disable_2FA/(?P[0-9]+)/$", + teacher_disable_2FA, + name="teacher_disable_2FA", + ), + url( + r"^teach/dashboard/school/leave/$", + organisation_leave, + name="organisation_leave", + ), url( r"^teach/dashboard/student/accept/(?P[0-9]+)/$", teacher_accept_student_request, @@ -278,17 +430,31 @@ teacher_reject_student_request, name="teacher_reject_student_request", ), - url(rf"^teach/class/(?P{ACCESS_CODE_REGEX})$", teacher_view_class, name="view_class"), url( - rf"^teach/class/delete/(?P{ACCESS_CODE_REGEX})$", teacher_delete_class, name="teacher_delete_class" + rf"^teach/class/(?P{ACCESS_CODE_REGEX})$", + teacher_view_class, + name="view_class", + ), + url( + rf"^teach/class/delete/(?P{ACCESS_CODE_REGEX})$", + teacher_delete_class, + name="teacher_delete_class", ), url( rf"^teach/class/(?P{ACCESS_CODE_REGEX})/students/delete/$", teacher_delete_students, name="teacher_delete_students", ), - url(rf"^teach/class/edit/(?P{ACCESS_CODE_REGEX})$", teacher_edit_class, name="teacher_edit_class"), - url(r"^teach/class/student/edit/(?P[0-9]+)/$", teacher_edit_student, name="teacher_edit_student"), + url( + rf"^teach/class/edit/(?P{ACCESS_CODE_REGEX})$", + teacher_edit_class, + name="teacher_edit_class", + ), + url( + r"^teach/class/student/edit/(?P[0-9]+)/$", + teacher_edit_student, + name="teacher_edit_student", + ), url( rf"^teach/class/(?P{ACCESS_CODE_REGEX})/password_reset/$", teacher_class_password_reset, @@ -304,9 +470,15 @@ teacher_move_students, name="teacher_move_students", ), - url(r"^teach/dashboard/resend_invite/(?P[0-9a-f]+)/$", resend_invite_teacher, name="resend_invite_teacher"), url( - r"^teach/dashboard/toggle_admin_invite/(?P[0-9]+)/$", invite_toggle_admin, name="invite_toggle_admin" + r"^teach/dashboard/resend_invite/(?P[0-9a-f]+)/$", + resend_invite_teacher, + name="resend_invite_teacher", + ), + url( + r"^teach/dashboard/toggle_admin_invite/(?P[0-9]+)/$", + invite_toggle_admin, + name="invite_toggle_admin", ), url( r"^teach/dashboard/delete_teacher_invite/(?P[0-9a-f]+)$", @@ -320,7 +492,9 @@ ), url(r"^delete/account/$", delete_account, name="delete_account"), url( - r"^schools/anonymise/(?P\d+)/", AnonymiseOrphanSchoolsView.as_view(), name="anonymise_orphan_schools" + r"^schools/anonymise/(?P\d+)/", + AnonymiseOrphanSchoolsView.as_view(), + name="anonymise_orphan_schools", ), url( r"^api/", @@ -345,6 +519,14 @@ ), ), url(r"^codingClub/$", coding_club, name="codingClub"), - url(r"^codingClub/(?P[3-4])/", download_student_pack, name="download_student_pack"), - url(r"^removeFakeAccounts/", RemoveFakeAccounts.as_view(), name="remove_fake_accounts"), + url( + r"^codingClub/(?P[3-4])/", + download_student_pack, + name="download_student_pack", + ), + url( + r"^removeFakeAccounts/", + RemoveFakeAccounts.as_view(), + name="remove_fake_accounts", + ), ] diff --git a/portal/views/email.py b/portal/views/email.py index 3d31a643d..671b0dd88 100644 --- a/portal/views/email.py +++ b/portal/views/email.py @@ -1,38 +1,47 @@ from datetime import timedelta import jwt -from common.helpers.emails import NOTIFICATION_EMAIL, send_email -from common.models import School, Student, Teacher +from common.models import Teacher from common.permissions import logged_in_as_independent_student from django.conf import settings from django.contrib import messages as messages from django.contrib.auth.models import User -from django.db.models import Count -from django.http import HttpResponse, HttpResponseRedirect +from django.http import HttpResponseRedirect from django.shortcuts import render from django.urls import reverse_lazy from django.utils import timezone -from django_countries import countries - -from portal.app_settings import CONTACT_FORM_EMAILS def verify_email(request, token): decoded_jwt = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) user_found = User.objects.filter(email=decoded_jwt["email"]).first() - usertype = "TEACHER" if Teacher.objects.filter(new_user=user_found).exists() else "INDEP_STUDENT" + usertype = ( + "TEACHER" + if Teacher.objects.filter(new_user=user_found).exists() + else "INDEP_STUDENT" + ) is_expired = decoded_jwt["expires"] < timezone.now().timestamp() is_changing_email = decoded_jwt["new_email"] != "" - updated_email = decoded_jwt["new_email"] if is_changing_email else decoded_jwt["email"] + updated_email = ( + decoded_jwt["new_email"] if is_changing_email else decoded_jwt["email"] + ) if not user_found or is_expired: - return render(request, "portal/email_verification_failed.html", {"usertype": usertype}) + return render( + request, + "portal/email_verification_failed.html", + {"usertype": usertype}, + ) is_user_verified = user_found.userprofile.is_verified if is_user_verified and not is_changing_email: - return render(request, "portal/email_verification_failed.html", {"usertype": usertype}) + return render( + request, + "portal/email_verification_failed.html", + {"usertype": usertype}, + ) if not is_user_verified or is_changing_email: user_found.email = updated_email @@ -41,7 +50,9 @@ def verify_email(request, token): user_found.userprofile.save() user_found.save() - messages.success(request, "Your email address was successfully verified, please log in.") + messages.success( + request, "Your email address was successfully verified, please log in." + ) if logged_in_as_independent_student(user_found): login_url = "independent_student_login" @@ -53,43 +64,7 @@ def verify_email(request, token): def has_verification_failed(verifications): return ( - len(verifications) > 1 or verifications[0].verified or (verifications[0].expiry - timezone.now()) < timedelta() - ) - - -def send_new_users_report(request): - new_users_count = User.objects.filter(date_joined__gte=timezone.now() - timedelta(days=7)).count() - users_count = User.objects.count() - active_users = User.objects.filter(last_login__gte=timezone.now() - timedelta(days=7)).count() - school_count = School.objects.count() - teacher_count = Teacher.objects.count() - student_count = Student.objects.count() - schools_countries = School.objects.values("country").annotate(nb_countries=Count("id")).order_by("-nb_countries") - nb_countries = schools_countries.count() - countries_count = "\n".join( - "{}: {}".format(dict(countries)[k["country"]], k["nb_countries"]) for k in schools_countries[:3] - ) - send_email( - NOTIFICATION_EMAIL, - CONTACT_FORM_EMAILS, - "new users", - "There are {new_users} new users this week!\n" - "The total number of registered users is now: {users}\n" - "Current number of schools: {schools}\n" - "Current number of teachers: {teachers}\n" - "Current number of students: {students}\n" - "Number of users that last logged in during the last week: {active_users}\n" - "Number of countries with registered schools: {countries}\n" - "Top 3 - schools per country:\n{countries_counter}".format( - new_users=new_users_count, - users=users_count, - schools=school_count, - teachers=teacher_count, - students=student_count, - countries=nb_countries, - active_users=active_users, - countries_counter=countries_count, - ), - "new users", + len(verifications) > 1 + or verifications[0].verified + or (verifications[0].expiry - timezone.now()) < timedelta() ) - return HttpResponse("success")