codeforlife-portal 6.43.2__py2.py3-none-any.whl → 6.43.3__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of codeforlife-portal might be problematic. Click here for more details.

cfl_common/common/mail.py CHANGED
@@ -13,7 +13,15 @@ campaign_ids = {
13
13
  "delete_account": 1567477,
14
14
  "email_change_notification": 1551600,
15
15
  "email_change_verification": 1551594,
16
+ "invite_teacher_with_account": 1569599,
17
+ "invite_teacher_without_account": 1569607,
18
+ "level_creation": 1570259,
16
19
  "reset_password": 1557153,
20
+ "student_join_request_notification": 1569486,
21
+ "student_join_request_rejected": 1569470,
22
+ "student_join_request_sent": 1569477,
23
+ "teacher_released": 1569537,
24
+ "user_already_registered": 1569539,
17
25
  "verify_new_user": 1551577,
18
26
  "verify_new_user_first_reminder": 1557170,
19
27
  "verify_new_user_second_reminder": 1557173,
@@ -16,8 +16,8 @@ def follow_verify_email_link_to_login(page, url, user_type):
16
16
  return go_to_independent_student_login_page(page.browser)
17
17
 
18
18
 
19
- def follow_duplicate_account_link_to_login(page, email, user_type):
20
- _follow_duplicate_account_email_link(page, email)
19
+ def follow_duplicate_account_link_to_login(page, url, user_type):
20
+ page.browser.get(url)
21
21
 
22
22
  if user_type == "teacher":
23
23
  return go_to_teacher_login_page(page.browser)
@@ -25,15 +25,6 @@ def follow_duplicate_account_link_to_login(page, email, user_type):
25
25
  return go_to_independent_student_login_page(page.browser)
26
26
 
27
27
 
28
- def _follow_duplicate_account_email_link(page, email):
29
- message = str(email.message())
30
- prefix = 'please login: <a href="'
31
- i = str.find(message, prefix) + len(prefix)
32
- suffix = '" rel="nofollow">'
33
- j = str.find(message, suffix, i)
34
- page.browser.get(message[i:j])
35
-
36
-
37
28
  def follow_reset_email_link(browser, link):
38
29
  browser.get(link)
39
30
 
@@ -5,7 +5,6 @@ from unittest.mock import patch
5
5
  from common.helpers.emails import generate_token
6
6
  from common.helpers.generators import generate_login_id
7
7
  from common.models import Class, Student
8
- from django.core import mail
9
8
 
10
9
  from . import email
11
10
 
@@ -102,23 +101,6 @@ def generate_independent_student_details():
102
101
  generate_independent_student_details.next_id = 1
103
102
 
104
103
 
105
- def signup_duplicate_independent_student_fail(page, duplicate_email=None):
106
- page = page.go_to_signup_page()
107
-
108
- name, username, email_address, password = generate_independent_student_details()
109
-
110
- if not duplicate_email:
111
- duplicate_email = email_address
112
-
113
- page = page.independent_student_signup(name, duplicate_email, password=password, confirm_password=password)
114
-
115
- page = page.return_to_home_page()
116
-
117
- page = email.follow_duplicate_account_link_to_login(page, mail.outbox[0], "independent")
118
-
119
- return page, name, username, email_address, password
120
-
121
-
122
104
  @patch("common.helpers.emails.send_dotdigital_email")
123
105
  def create_independent_student(page, mock_send_dotdigital_email):
124
106
  page = page.go_to_signup_page()
@@ -4,7 +4,6 @@ from unittest.mock import patch
4
4
 
5
5
  from common.helpers.emails import generate_token
6
6
  from common.models import Teacher
7
- from django.core import mail
8
7
 
9
8
  from . import email
10
9
 
@@ -34,7 +33,8 @@ def signup_teacher_directly(preverified=True, **kwargs):
34
33
  return email_address, password
35
34
 
36
35
 
37
- def signup_duplicate_teacher_fail(page, duplicate_email):
36
+ @patch("portal.views.home.send_dotdigital_email")
37
+ def signup_duplicate_teacher_fail(page, duplicate_email, mock_send_dotdigital_email):
38
38
  page = page.go_to_signup_page()
39
39
 
40
40
  first_name, last_name, email_address, password = generate_details()
@@ -42,8 +42,9 @@ def signup_duplicate_teacher_fail(page, duplicate_email):
42
42
 
43
43
  page = page.return_to_home_page()
44
44
 
45
- page = email.follow_duplicate_account_link_to_login(page, mail.outbox[0], "teacher")
46
- mail.outbox = []
45
+ login_link = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["LOGIN_URL"]
46
+
47
+ page = email.follow_duplicate_account_link_to_login(page, login_link, "teacher")
47
48
 
48
49
  return page, email_address, password
49
50
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codeforlife-portal
3
- Version: 6.43.2
3
+ Version: 6.43.3
4
4
  Classifier: Programming Language :: Python
5
5
  Classifier: Programming Language :: Python :: 3.8
6
6
  Classifier: Framework :: Django
@@ -24,7 +24,7 @@ Requires-Dist: django-classy-tags ==2.0.0
24
24
  Requires-Dist: libsass ==0.23.0
25
25
  Requires-Dist: phonenumbers ==8.12.12
26
26
  Requires-Dist: more-itertools ==8.7.0
27
- Requires-Dist: cfl-common ==6.43.2
27
+ Requires-Dist: cfl-common ==6.43.3
28
28
  Requires-Dist: django-ratelimit ==3.0.1
29
29
  Requires-Dist: django-preventconcurrentlogins ==0.8.2
30
30
  Requires-Dist: django-csp ==3.7
@@ -5,8 +5,7 @@ cfl_common/common/app_settings.py,sha256=x2ROLY5Xl5LgqjxyTiChZvQorZYUXpFzEkaLsjh
5
5
  cfl_common/common/apps.py,sha256=49UXZ3bSkFKvIEOL4zM7y1sAhccQJyRtsoOg5XVd_8Y,129
6
6
  cfl_common/common/context_processors.py,sha256=X0iuX5qu9kMWa7q8osE9CJ2LgM7pPOYQFGdjm8X3rk0,236
7
7
  cfl_common/common/csp_config.py,sha256=sZT6s9zMT5FFIqNODsURT0ifxbDgXpDlki8UxaBq2iE,2940
8
- cfl_common/common/email_messages.py,sha256=J57n4krBAnwqacRjIzkO-qhbdlqNWwyJiRd8ozitm-M,3148
9
- cfl_common/common/mail.py,sha256=11WqmB770MmGJcATa4dOeyGHSYLceV8zZDPx_4kK-zI,6021
8
+ cfl_common/common/mail.py,sha256=XPImcfZKcW5mxov04_0jc3xkx_u1SJE6Hhn8K2kPgoA,6354
10
9
  cfl_common/common/models.py,sha256=EunFsc7sOWfWiFf4IQwuy56gu8pu3YpPoOgVtsMhbRM,14958
11
10
  cfl_common/common/permissions.py,sha256=gC6RQGZI2QDBbglx-xr_V4Hl2C2nf1V2_uPmEuoEcJo,2416
12
11
  cfl_common/common/utils.py,sha256=Nn2Npao9Uqad5Js_IdHwF-ow6wrPNpBLW4AO1LxoEBc,1727
@@ -78,10 +77,10 @@ cfl_common/common/tests/test_migration_verify_returning_users.py,sha256=n8JGW-Tm
78
77
  cfl_common/common/tests/test_models.py,sha256=xMdzonW5CADMjas_zfg8V1YPQpUetleyn6TE95hbO9k,3723
79
78
  cfl_common/common/tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
79
  cfl_common/common/tests/utils/classes.py,sha256=ZA2pp9Pyx3rwi0VFwtuUA2Pys9xQJ-L_zE0u2tpwEH4,1094
81
- cfl_common/common/tests/utils/email.py,sha256=x3DjWCT997wwj33YuiA3mDqkNahr63zVmz_mX7XIomo,2094
80
+ cfl_common/common/tests/utils/email.py,sha256=RljsVjIob4Uqi3O5YhP2ifqfc4cMcdP4Gv0EaL-sHXo,1780
82
81
  cfl_common/common/tests/utils/organisation.py,sha256=vNgKFtU3VPcWRnZfh82yCS90PLAK1XTYJNIxGwfgUI4,966
83
- cfl_common/common/tests/utils/student.py,sha256=XlgWT0TdbIY6w9uB4SqOoXmhxxCRnucEcPY9Q5Xva0U,4415
84
- cfl_common/common/tests/utils/teacher.py,sha256=kY9LuP1mTEj_andYxF9k54xEHiJ36a6dokHxA9cB9f0,2500
82
+ cfl_common/common/tests/utils/student.py,sha256=PLd980iSlxmMoB8J3C2pVjNC5xHdVxfAkJXzhv_dRhg,3814
83
+ cfl_common/common/tests/utils/teacher.py,sha256=bJAYLzZdksCIHNxUJJYGZNLHj-axYro4pd3_sXvXbno,2625
85
84
  cfl_common/common/tests/utils/user.py,sha256=NvLzZLVP4jy5Hn1iztOYF_BTQ9WsbSmuWMEzGzhAsRU,919
86
85
  deploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
86
  deploy/captcha.py,sha256=MbOBuGnbT_SOIltSjP1XMOLrfo1DldCilaVAEim0vM4,23
@@ -106,7 +105,7 @@ example_project/portal_test_settings.py,sha256=frp_XMpd-z1g3VFCRxB2w7AaFW2ivRVKn
106
105
  example_project/settings.py,sha256=XRZZvASoIl5a9xe3masTq_CUBleuJq9ByHx8f_e2UFc,5613
107
106
  example_project/urls.py,sha256=OVeRQ-TCpzHISBRuzqD0yd3ewF7H5U3c-f2p2alfUD0,430
108
107
  example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
109
- portal/__init__.py,sha256=hjhQopF2UeY6Ek3KDmWtiEswta3fxltdCEalD4TaXFA,23
108
+ portal/__init__.py,sha256=lS9jJlgjO1rzcyujvAxw3NGfAlOy9t3JYoNPpGZka5Q,23
110
109
  portal/admin.py,sha256=k5Hsiln43DlVPoufnrx5AXWu_RijX8xi_n7wwBuuCJo,5132
111
110
  portal/app_settings.py,sha256=DhWLQOwM0zVOXE3O5TNKbMM9K6agfLuCsHOdr1J7xEI,651
112
111
  portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
@@ -544,7 +543,7 @@ portal/tests/test_class.py,sha256=V6Fkc6PqdisefKD3xs9PbfE2pKp-9e0gwQVkPUiu6bk,14
544
543
  portal/tests/test_daily_activities.py,sha256=-siDCMGBD1ijjccHVk7eEmrk4bgTsvbh0B6hDoj2fo0,1803
545
544
  portal/tests/test_emails.py,sha256=Y26VjhPOzc2aptHBSmOW9R-do3k1QaRqUc5MiwgsAQk,9156
546
545
  portal/tests/test_helper_methods.py,sha256=-SQCDZm2XUtyXGEp0CHIb_SSC9CPD-XOSnpnY8QclHk,890
547
- portal/tests/test_independent_student.py,sha256=rW3OTFWxeEHm5tSumr1L4MRkUsEgESfhapU6M3bfNhc,26437
546
+ portal/tests/test_independent_student.py,sha256=jyNpHyisCfkD-CDsjIfSxWnWTxDmkReJ-BdXEBdJoMo,27081
548
547
  portal/tests/test_invite_teacher.py,sha256=oeOaoJV1IqJSYPlaPFjnhVXdB2mq8otCTLp_lfjuCfk,12224
549
548
  portal/tests/test_middleware.py,sha256=b6jfNmiRZ2snqLKsyJUG-RivoX5fmrqLlQkG9MeVnqM,8034
550
549
  portal/tests/test_newsletter_footer.py,sha256=MdVUX53mEoDTa4Krq-jg9LFNo-QyghqvTvhHeNXBGnE,838
@@ -618,7 +617,7 @@ portal/views/admin.py,sha256=4Xt3zEyQH7sUwQSrwuRtoCodWidjOzd7gJUwWU96pXY,957
618
617
  portal/views/api.py,sha256=lCwiclR98G-yTgK55u8IjkueIH8iremeiZSa3jAvO-M,6990
619
618
  portal/views/dotmailer.py,sha256=_subSoy5f1j5sAcRrjE_xMejdarjHIY1d_jwSrf7_o0,2299
620
619
  portal/views/email.py,sha256=dSgTp0kEZRTBRwPbtCrMoWhiOqZ1dPZT1YMvR6f1GQU,3899
621
- portal/views/home.py,sha256=J_kb0Kv5Ubk9yQ4r0IAvhPV5o_iezcaqA4Z5bd0-Idc,9869
620
+ portal/views/home.py,sha256=J4KY66p2nRgRu1O_0IYwdgfZoTYuhQS0qpfiA1b8PTE,9855
622
621
  portal/views/legal.py,sha256=nUunsTHnhMcXBcDlg1GmUal86k9Vhinne4A2FWfq78M,342
623
622
  portal/views/organisation.py,sha256=sPDbiM7hdtpF8GKyh_4n4VPl2a-WnAgnF4q9aSvQCVI,3341
624
623
  portal/views/play_landing_page.py,sha256=FFmjUFub3ZdlbMqkB8yX3jAImCzqrUqgb8AZcpKywZ4,308
@@ -634,16 +633,16 @@ portal/views/login/student.py,sha256=dt6cMfWepBJsVCRcADltfYSHVpyeP1WGLKSogMJ22E0
634
633
  portal/views/login/teacher.py,sha256=kRugP7TPbZIb_BmYMYxFeugxZy8UbCry_q0_jJDJ_Mw,1975
635
634
  portal/views/student/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
636
635
  portal/views/student/edit_account_details.py,sha256=Ba-3D_zzKbX5N01NG5qqBS0ud10B8D5SN8hOpLiGa9U,8468
637
- portal/views/student/play.py,sha256=-v9lBjHF3_PAKRgWcCGGt_bEOpIkmJDJnzgR5JvqrMo,8908
636
+ portal/views/student/play.py,sha256=r5TADH_wYn3d1beezfvkYBiendQ9qLys9dUJwHqF_44,8581
638
637
  portal/views/teacher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
639
- portal/views/teacher/dashboard.py,sha256=nNA7XxylunLjyCpKmq7h_CYITi7wM3YnmjqzUL0A-bI,25236
638
+ portal/views/teacher/dashboard.py,sha256=8WglspwuHF__2LtoX5_XvoW1ulICSupjKv--MtjrvJk,25714
640
639
  portal/views/teacher/teach.py,sha256=B71jReMJ4BYFmo7NtJVK3-4DeXEwxfu_WA3Ij1RYzdI,34725
641
640
  portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
642
641
  portal/views/two_factor/core.py,sha256=O_wcBeFqdPYSGNGv-pT_vbs5-Dj1Z-Jfkd6f9-E5yZI,760
643
642
  portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
644
643
  portal/views/two_factor/profile.py,sha256=tkl_ludo8arMtd5LKNmohM66vpC_YQiP-0nspTSJiJ4,383
645
- codeforlife_portal-6.43.2.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
646
- codeforlife_portal-6.43.2.dist-info/METADATA,sha256=kUAWYPqcsp2m4LS4Kh6PwIr_K7GbBPgZA6qvvbO3XQE,1137
647
- codeforlife_portal-6.43.2.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
648
- codeforlife_portal-6.43.2.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
649
- codeforlife_portal-6.43.2.dist-info/RECORD,,
644
+ codeforlife_portal-6.43.3.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
645
+ codeforlife_portal-6.43.3.dist-info/METADATA,sha256=d1x7eE5cAtisbjaj-f49hoXXduEJXJg7agpKyvtX7q0,1137
646
+ codeforlife_portal-6.43.3.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
647
+ codeforlife_portal-6.43.3.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
648
+ codeforlife_portal-6.43.3.dist-info/RECORD,,
portal/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "6.43.2"
1
+ __version__ = "6.43.3"
@@ -16,7 +16,7 @@ from common.tests.utils.student import (
16
16
  create_independent_student,
17
17
  create_independent_student_directly,
18
18
  create_school_student_directly,
19
- signup_duplicate_independent_student_fail,
19
+ generate_independent_student_details,
20
20
  verify_email,
21
21
  )
22
22
  from common.tests.utils.teacher import signup_teacher_directly
@@ -222,20 +222,30 @@ class TestIndependentStudentFrontend(BaseTest):
222
222
  page, _, _, _, _ = create_independent_student(page)
223
223
  assert is_email_verified_message_showing(self.selenium)
224
224
 
225
- def test_signup_duplicate_email_failure(self):
225
+ @patch("portal.views.home.send_dotdigital_email")
226
+ def test_signup_duplicate_email_failure(self, mock_send_dotdigital_email):
226
227
  page = self.go_to_homepage()
227
228
  page, _, _, email, _ = create_independent_student(page)
228
229
  assert is_email_verified_message_showing(self.selenium)
229
230
 
230
- page = self.go_to_homepage()
231
- page, _, _, _, _ = signup_duplicate_independent_student_fail(page, duplicate_email=email)
231
+ page = self.go_to_homepage().go_to_signup_page()
232
+
233
+ name, username, email_address, password = generate_independent_student_details()
234
+ page = page.independent_student_signup(name, email, password=password, confirm_password=password)
235
+ page = page.return_to_home_page()
236
+
237
+ mock_send_dotdigital_email.assert_called_once_with(
238
+ campaign_ids["user_already_registered"], ANY, personalization_values=ANY
239
+ )
240
+
241
+ login_link = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["LOGIN_URL"]
232
242
 
233
- assert len(mail.outbox) == 1
234
- assert mail.outbox[0].subject == "Duplicate account"
243
+ page = email_utils.follow_duplicate_account_link_to_login(page, login_link, "independent")
235
244
 
236
245
  assert self.is_login_page(page)
237
246
 
238
- def test_signup_duplicate_email_with_teacher(self):
247
+ @patch("portal.views.home.send_dotdigital_email")
248
+ def test_signup_duplicate_email_with_teacher(self, mock_send_dotdigital_email: Mock):
239
249
  teacher_email, _ = signup_teacher_directly()
240
250
 
241
251
  page = self.go_to_homepage()
@@ -248,8 +258,9 @@ class TestIndependentStudentFrontend(BaseTest):
248
258
 
249
259
  page.return_to_home_page()
250
260
 
251
- assert len(mail.outbox) == 1
252
- assert mail.outbox[0].subject == "Duplicate account"
261
+ mock_send_dotdigital_email.assert_called_once_with(
262
+ campaign_ids["user_already_registered"], ANY, personalization_values=ANY
263
+ )
253
264
 
254
265
  def test_login_failure(self):
255
266
  page = self.go_to_homepage()
portal/views/home.py CHANGED
@@ -1,13 +1,13 @@
1
1
  import logging
2
2
  import math
3
3
 
4
- from common import email_messages
5
4
  from common.helpers.emails import (
6
5
  NOTIFICATION_EMAIL,
7
6
  send_email,
8
7
  send_verification_email,
9
8
  )
10
- from common.models import Student, Teacher, DynamicElement, TotalActivity
9
+ from common.mail import campaign_ids, send_dotdigital_email
10
+ from common.models import DynamicElement, Student, Teacher, TotalActivity
11
11
  from common.permissions import logged_in_as_student, logged_in_as_teacher
12
12
  from common.utils import _using_two_factor
13
13
  from django.contrib import messages as messages
@@ -34,8 +34,7 @@ from portal.helpers.ratelimit import (
34
34
  from portal.strings.coding_club import CODING_CLUB_BANNER
35
35
  from portal.strings.home_learning import HOME_LEARNING_BANNER
36
36
  from portal.templatetags.app_tags import cloud_storage
37
- from portal.views.teacher.teach import DownloadType
38
- from portal.views.teacher.teach import count_student_pack_downloads_click
37
+ from portal.views.teacher.teach import DownloadType, count_student_pack_downloads_click
39
38
 
40
39
  LOGGER = logging.getLogger(__name__)
41
40
 
@@ -126,7 +125,7 @@ def process_signup_form(request, data):
126
125
  email = data["teacher_email"]
127
126
 
128
127
  if email and User.objects.filter(email=email).exists():
129
- email_message = email_messages.userAlreadyRegisteredEmail(request, email)
128
+
130
129
  is_email_ratelimited = is_ratelimited(
131
130
  request=request,
132
131
  group=RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP,
@@ -136,12 +135,13 @@ def process_signup_form(request, data):
136
135
  )
137
136
 
138
137
  if not is_email_ratelimited:
139
- send_email(
140
- NOTIFICATION_EMAIL,
138
+ send_dotdigital_email(
139
+ campaign_ids["user_already_registered"],
141
140
  [email],
142
- email_message["subject"],
143
- email_message["message"],
144
- email_message["subject"],
141
+ personalization_values={
142
+ "EMAIL": email,
143
+ "LOGIN_URL": request.build_absolute_uri(reverse("teacher_login")),
144
+ },
145
145
  )
146
146
  else:
147
147
  LOGGER.warn(f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}")
@@ -164,7 +164,6 @@ def process_independent_student_signup_form(request, data):
164
164
  email = data["email"]
165
165
 
166
166
  if email and User.objects.filter(email=email).exists():
167
- email_message = email_messages.userAlreadyRegisteredEmail(request, email, is_independent_student=True)
168
167
  is_email_ratelimited = is_ratelimited(
169
168
  request=request,
170
169
  group=RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP,
@@ -174,12 +173,13 @@ def process_independent_student_signup_form(request, data):
174
173
  )
175
174
 
176
175
  if not is_email_ratelimited:
177
- send_email(
178
- NOTIFICATION_EMAIL,
176
+ send_dotdigital_email(
177
+ campaign_ids["user_already_registered"],
179
178
  [email],
180
- email_message["subject"],
181
- email_message["message"],
182
- email_message["subject"],
179
+ personalization_values={
180
+ "EMAIL": email,
181
+ "LOGIN_URL": request.build_absolute_uri(reverse("independent_student_login")),
182
+ },
183
183
  )
184
184
  else:
185
185
  LOGGER.warning(f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}")
@@ -1,7 +1,7 @@
1
1
  from typing import Any, Dict, List, Optional
2
2
 
3
- from common import email_messages
4
3
  from common.helpers.emails import NOTIFICATION_EMAIL, send_email
4
+ from common.mail import campaign_ids, send_dotdigital_email
5
5
  from common.models import Student
6
6
  from common.permissions import (
7
7
  logged_in_as_independent_student,
@@ -22,9 +22,7 @@ from game.models import Attempt, Level
22
22
  from portal.forms.play import StudentJoinOrganisationForm
23
23
 
24
24
 
25
- class SchoolStudentDashboard(
26
- LoginRequiredNoErrorMixin, UserPassesTestMixin, TemplateView
27
- ):
25
+ class SchoolStudentDashboard(LoginRequiredNoErrorMixin, UserPassesTestMixin, TemplateView):
28
26
  template_name = "portal/play/student_dashboard.html"
29
27
  login_url = reverse_lazy("student_login_access_code")
30
28
 
@@ -50,16 +48,10 @@ class SchoolStudentDashboard(
50
48
  custom_levels = student.new_user.shared.filter(owner=teacher)
51
49
 
52
50
  if custom_levels:
53
- custom_levels_data = _compute_rapid_router_scores(
54
- student, custom_levels
55
- )
51
+ custom_levels_data = _compute_rapid_router_scores(student, custom_levels)
56
52
 
57
- context_data["total_custom_score"] = custom_levels_data[
58
- "total_score"
59
- ]
60
- context_data["total_custom_available_score"] = custom_levels_data[
61
- "total_available_score"
62
- ]
53
+ context_data["total_custom_score"] = custom_levels_data["total_score"]
54
+ context_data["total_custom_available_score"] = custom_levels_data["total_available_score"]
63
55
 
64
56
  # Get Kurono game info if the class has a game linked to it
65
57
  aimmo_game = klass.active_game
@@ -72,9 +64,7 @@ class SchoolStudentDashboard(
72
64
  return context_data
73
65
 
74
66
 
75
- class IndependentStudentDashboard(
76
- LoginRequiredNoErrorMixin, UserPassesTestMixin, TemplateView, FormView
77
- ):
67
+ class IndependentStudentDashboard(LoginRequiredNoErrorMixin, UserPassesTestMixin, TemplateView, FormView):
78
68
  template_name = "portal/play/independent_student_dashboard.html"
79
69
  login_url = reverse_lazy("independent_student_login")
80
70
 
@@ -91,9 +81,7 @@ class IndependentStudentDashboard(
91
81
  )
92
82
 
93
83
 
94
- def _compute_rapid_router_scores(
95
- student: Student, levels: List[Level] or QuerySet
96
- ) -> Dict[str, int]:
84
+ def _compute_rapid_router_scores(student: Student, levels: List[Level] or QuerySet) -> Dict[str, int]:
97
85
  """
98
86
  Finds Rapid Router progress and score data for a specific student and a specific
99
87
  set of levels. This is used to show quick score data to the student on their
@@ -112,9 +100,9 @@ def _compute_rapid_router_scores(
112
100
  num_completed = num_top_scores = total_available_score = 0
113
101
  total_score = 0.0
114
102
  # Get a QuerySet of best attempts for each level
115
- best_attempts = Attempt.objects.filter(
116
- level__in=levels, student=student, is_best_attempt=True
117
- ).select_related("level")
103
+ best_attempts = Attempt.objects.filter(level__in=levels, student=student, is_best_attempt=True).select_related(
104
+ "level"
105
+ )
118
106
 
119
107
  for level in levels:
120
108
  total_available_score += _get_max_score_for_level(level)
@@ -122,10 +110,7 @@ def _compute_rapid_router_scores(
122
110
  # For each level, compare best attempt's score with level's max score and
123
111
  # increment variables as needed
124
112
  if best_attempts:
125
- attempts_dict = {
126
- best_attempt.level.id: best_attempt
127
- for best_attempt in best_attempts
128
- }
113
+ attempts_dict = {best_attempt.level.id: best_attempt for best_attempt in best_attempts}
129
114
  for level in levels:
130
115
  attempt = attempts_dict.get(level.id)
131
116
 
@@ -155,12 +140,7 @@ def _get_max_score_for_level(level: Level) -> int:
155
140
  """
156
141
  return (
157
142
  10
158
- if level.id > 12
159
- and (
160
- level.disable_route_score
161
- or level.disable_algorithm_score
162
- or not level.episode
163
- )
143
+ if level.id > 12 and (level.disable_route_score or level.disable_algorithm_score or not level.episode)
164
144
  else 20
165
145
  )
166
146
 
@@ -207,31 +187,23 @@ def process_join_organisation_form(request_form, request, student):
207
187
  student.pending_class_request = request_form.klass
208
188
  student.save()
209
189
 
210
- email_message = email_messages.studentJoinRequestSentEmail(
211
- request,
212
- request_form.klass.teacher.school.name,
213
- request_form.klass.access_code,
214
- )
215
- send_email(
216
- NOTIFICATION_EMAIL,
190
+ send_dotdigital_email(
191
+ campaign_ids["student_join_request_sent"],
217
192
  [student.new_user.email],
218
- email_message["subject"],
219
- email_message["message"],
220
- email_message["subject"],
193
+ personalization_values={
194
+ "SCHOOL_CLUB_NAME": request_form.klass.teacher.school.name,
195
+ "ACCESS_CODE": request_form.klass.access_code,
196
+ },
221
197
  )
222
198
 
223
- email_message = email_messages.studentJoinRequestNotifyEmail(
224
- request,
225
- student.new_user.username,
226
- student.new_user.email,
227
- student.pending_class_request.access_code,
228
- )
229
- send_email(
230
- NOTIFICATION_EMAIL,
199
+ send_dotdigital_email(
200
+ campaign_ids["student_join_request_notification"],
231
201
  [student.pending_class_request.teacher.new_user.email],
232
- email_message["subject"],
233
- email_message["message"],
234
- email_message["subject"],
202
+ personalization_values={
203
+ "USERNAME": student.new_user.username,
204
+ "EMAIL": student.new_user.email,
205
+ "ACCESS_CODE": student.pending_class_request.access_code,
206
+ },
235
207
  )
236
208
 
237
209
  messages.success(
@@ -1,7 +1,6 @@
1
1
  from datetime import timedelta
2
2
  from uuid import uuid4
3
3
 
4
- from common import email_messages
5
4
  from common.helpers.emails import (
6
5
  INVITE_FROM,
7
6
  NOTIFICATION_EMAIL,
@@ -167,9 +166,21 @@ def dashboard_teacher_view(request, is_admin):
167
166
  )
168
167
 
169
168
  account_exists = User.objects.filter(email=invited_teacher_email).exists()
170
- message = email_messages.inviteTeacherEmail(request, school.name, token, account_exists)
171
- send_email(
172
- INVITE_FROM, [invited_teacher_email], message["subject"], message["message"], message["subject"]
169
+
170
+ registration_link = (
171
+ f"{request.build_absolute_uri(reverse('invited_teacher', kwargs={'token': token}))} "
172
+ )
173
+
174
+ campaign_id = (
175
+ campaign_ids["invite_teacher_with_account"]
176
+ if account_exists
177
+ else campaign_ids["invite_teacher_without_account"]
178
+ )
179
+
180
+ send_dotdigital_email(
181
+ campaign_id,
182
+ [invited_teacher_email],
183
+ personalization_values={"SCHOOL_NAME": school.name, "REGISTRATION_LINK": registration_link},
173
184
  )
174
185
 
175
186
  messages.success(
@@ -375,14 +386,10 @@ def organisation_kick(request, pk):
375
386
 
376
387
  messages.success(request, success_message)
377
388
 
378
- emailMessage = email_messages.kickedEmail(request, user.school.name)
379
-
380
- send_email(
381
- NOTIFICATION_EMAIL,
389
+ send_dotdigital_email(
390
+ campaign_ids["teacher_released"],
382
391
  [teacher.new_user.email],
383
- emailMessage["subject"],
384
- emailMessage["message"],
385
- emailMessage["subject"],
392
+ personalization_values={"SCHOOL_CLUB_NAME": user.school.name},
386
393
  )
387
394
 
388
395
  return HttpResponseRedirect(reverse_lazy("dashboard"))
@@ -537,15 +544,13 @@ def teacher_reject_student_request(request, pk):
537
544
 
538
545
  check_student_request_can_be_handled(request, student)
539
546
 
540
- emailMessage = email_messages.studentJoinRequestRejectedEmail(
541
- request, student.pending_class_request.teacher.school.name, student.pending_class_request.access_code
542
- )
543
- send_email(
544
- NOTIFICATION_EMAIL,
547
+ send_dotdigital_email(
548
+ campaign_ids["student_join_request_rejected"],
545
549
  [student.new_user.email],
546
- emailMessage["subject"],
547
- emailMessage["message"],
548
- emailMessage["subject"],
550
+ personalization_values={
551
+ "SCHOOL_CLUB_NAME": student.pending_class_request.teacher.school.name,
552
+ "ACCESS_CODE": student.pending_class_request.access_code,
553
+ },
549
554
  )
550
555
 
551
556
  student.pending_class_request = None
@@ -591,10 +596,21 @@ def resend_invite_teacher(request, token):
591
596
  teacher = Teacher.objects.filter(id=invite.from_teacher.id)[0]
592
597
 
593
598
  messages.success(request, "Teacher re-invited!")
594
- message = email_messages.inviteTeacherEmail(request, invite.school, token, not (invite.is_expired))
595
- send_email(
596
- INVITE_FROM, [invite.invited_teacher_email], message["subject"], message["message"], message["subject"]
599
+
600
+ registration_link = f"{request.build_absolute_uri(reverse('invited_teacher', kwargs={'token': token}))} "
601
+
602
+ campaign_id = (
603
+ campaign_ids["invite_teacher_with_account"]
604
+ if teacher.exists()
605
+ else campaign_ids["invite_teacher_without_account"]
597
606
  )
607
+
608
+ send_dotdigital_email(
609
+ campaign_id,
610
+ [invite.invited_teacher_email],
611
+ personalization_values={"SCHOOL_NAME": invite.school, "REGISTRATION_LINK": registration_link},
612
+ )
613
+
598
614
  return HttpResponseRedirect(reverse_lazy("dashboard"))
599
615
 
600
616
 
@@ -1,83 +0,0 @@
1
- from django.urls import reverse
2
-
3
-
4
- def userAlreadyRegisteredEmail(request, email, is_independent_student=False):
5
- if is_independent_student:
6
- login_url = reverse("independent_student_login")
7
- else:
8
- login_url = reverse("teacher_login")
9
-
10
- return {
11
- "subject": f"Duplicate account",
12
- "message": (
13
- f"A user is already registered with this email address: {email}.\n"
14
- f"If you've already registered, please login: "
15
- f"{request.build_absolute_uri(login_url)}.\n"
16
- f"Otherwise please register with a different email address."
17
- ),
18
- }
19
-
20
-
21
- def kickedEmail(request, schoolName):
22
- return {
23
- "subject": f"You were successfully released from your school or club",
24
- "message": (
25
- f"You have been released from the school or club '{schoolName}'. "
26
- f"If you think this was an error, please contact the administrator of that "
27
- f"school or club."
28
- ),
29
- }
30
-
31
-
32
- def studentJoinRequestSentEmail(request, schoolName, accessCode):
33
- return {
34
- "subject": f"School or club join request sent",
35
- "message": (
36
- f"Your request to join the school or club '{schoolName}' in class "
37
- f"{accessCode} has been sent to that class's teacher, who will either "
38
- f"accept or deny your request."
39
- ),
40
- }
41
-
42
-
43
- def studentJoinRequestNotifyEmail(request, username, email, accessCode):
44
- return {
45
- "subject": f"School or club join request",
46
- "message": (
47
- f"There is a request waiting from student with username '{username}' and "
48
- f"email {email} to join your class {accessCode}. "
49
- f"Please log in to your dashboard to review the request."
50
- ),
51
- }
52
-
53
-
54
- def studentJoinRequestRejectedEmail(request, schoolName, accessCode):
55
- return {
56
- "subject": f"School or club join request rejected",
57
- "message": (
58
- f"Your request to join the school or club '{schoolName}' in class "
59
- f"{accessCode} has been rejected. Speak to your teacher if you think this "
60
- f"is an error."
61
- ),
62
- }
63
-
64
-
65
- def inviteTeacherEmail(request, schoolName, token, account_exists):
66
- url = f"{request.build_absolute_uri(reverse('invited_teacher', kwargs={'token': token}))} "
67
-
68
- if account_exists:
69
- message = (
70
- f"A teacher at the school '{schoolName}' has invited you to join Code for Life. 🎉 Unfortunately, you "
71
- f"already have an account with this email address, so you will need to either delete it first or change "
72
- f"the email registered to your other account. After that, you can complete the registration process, by "
73
- f"following the link below.\n\n"
74
- f"{url}"
75
- )
76
- else:
77
- message = (
78
- f"A teacher at the school '{schoolName}' has invited you to join Code for Life. 🎉 To complete the "
79
- f"registration process, please create a password by following the link below.\n\n"
80
- f"{url}"
81
- )
82
-
83
- return {"subject": f"You've been invited to join Code for Life", "message": message}