codeforlife-portal 6.43.1__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 +9 -0
- cfl_common/common/tests/utils/email.py +2 -11
- cfl_common/common/tests/utils/student.py +0 -18
- cfl_common/common/tests/utils/teacher.py +5 -4
- {codeforlife_portal-6.43.1.dist-info → codeforlife_portal-6.43.3.dist-info}/METADATA +2 -2
- {codeforlife_portal-6.43.1.dist-info → codeforlife_portal-6.43.3.dist-info}/RECORD +17 -18
- portal/__init__.py +1 -1
- portal/tests/test_independent_student.py +23 -11
- portal/tests/test_views.py +11 -3
- portal/views/home.py +16 -16
- portal/views/registration.py +11 -20
- portal/views/student/edit_account_details.py +8 -6
- portal/views/student/play.py +25 -53
- portal/views/teacher/dashboard.py +38 -22
- cfl_common/common/email_messages.py +0 -96
- {codeforlife_portal-6.43.1.dist-info → codeforlife_portal-6.43.3.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-6.43.1.dist-info → codeforlife_portal-6.43.3.dist-info}/WHEEL +0 -0
- {codeforlife_portal-6.43.1.dist-info → codeforlife_portal-6.43.3.dist-info}/top_level.txt +0 -0
cfl_common/common/mail.py
CHANGED
|
@@ -10,9 +10,18 @@ from django.template import loader
|
|
|
10
10
|
campaign_ids = {
|
|
11
11
|
"admin_given": 1569057,
|
|
12
12
|
"admin_revoked": 1569071,
|
|
13
|
+
"delete_account": 1567477,
|
|
13
14
|
"email_change_notification": 1551600,
|
|
14
15
|
"email_change_verification": 1551594,
|
|
16
|
+
"invite_teacher_with_account": 1569599,
|
|
17
|
+
"invite_teacher_without_account": 1569607,
|
|
18
|
+
"level_creation": 1570259,
|
|
15
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,
|
|
16
25
|
"verify_new_user": 1551577,
|
|
17
26
|
"verify_new_user_first_reminder": 1557170,
|
|
18
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,
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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.
|
|
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.
|
|
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/
|
|
9
|
-
cfl_common/common/mail.py,sha256=qgsgBc0drFIG8ik4ZBjaqjSvk0LVDIJV2fNYWFrbzHU,5990
|
|
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=
|
|
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=
|
|
84
|
-
cfl_common/common/tests/utils/teacher.py,sha256=
|
|
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=
|
|
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=
|
|
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
|
|
@@ -555,7 +554,7 @@ portal/tests/test_school_student.py,sha256=bFZwY4twaFHQLp0cltMq8cLNDZGgCHTZBCZHK
|
|
|
555
554
|
portal/tests/test_security.py,sha256=FGrlRfnzi-Xx2_bn4fTZlYORKm7w_GhGkD3havvplwc,3239
|
|
556
555
|
portal/tests/test_teacher.py,sha256=_VmQCWq07uCFbvq6Vd7GN00mE7vY7WNMeQTk6bHxFPI,36898
|
|
557
556
|
portal/tests/test_teacher_student.py,sha256=NWITbUw1kijqu3c8eRHLHJKaYQMOsOMvl7PAVx5QghI,21567
|
|
558
|
-
portal/tests/test_views.py,sha256=
|
|
557
|
+
portal/tests/test_views.py,sha256=IElQJnp9fhlmqR-54nBycul2uUIjsG9dTRnYy5SxXJ4,39278
|
|
559
558
|
portal/tests/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
560
559
|
portal/tests/migrations/test_migration_make_portaladmin_teacher.py,sha256=ekMRb6cU97oT0k9gCKW7IUB7oPuGmv4uWJCqInQN7x8,2589
|
|
561
560
|
portal/tests/migrations/test_migration_preview_user_remove.py,sha256=K6D-FZT9YFEA8oMxHz9VTglVV6MZOTRYVlvwWwXc2vU,555
|
|
@@ -618,11 +617,11 @@ 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=
|
|
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
|
|
625
|
-
portal/views/registration.py,sha256=
|
|
624
|
+
portal/views/registration.py,sha256=L9AzIG2nOU946cSOXmUMQRtDo3uxApHX-0ceXopbOCw,10888
|
|
626
625
|
portal/views/teach.py,sha256=nzlyTcgq9ImAjnqrF3esqi212qBLH5Ww1LKE2gSjoRY,210
|
|
627
626
|
portal/views/aimmo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
628
627
|
portal/views/aimmo/dashboard.py,sha256=YMOzonNE87OEP5lThY4BrF0rNyvIYpDInh_sM-n38AM,4046
|
|
@@ -633,17 +632,17 @@ portal/views/login/independent_student.py,sha256=3dFULhwMAlX4VDrJl-Znril6a9M5xKB
|
|
|
633
632
|
portal/views/login/student.py,sha256=dt6cMfWepBJsVCRcADltfYSHVpyeP1WGLKSogMJ22E0,5539
|
|
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
|
-
portal/views/student/edit_account_details.py,sha256=
|
|
637
|
-
portal/views/student/play.py,sha256
|
|
635
|
+
portal/views/student/edit_account_details.py,sha256=Ba-3D_zzKbX5N01NG5qqBS0ud10B8D5SN8hOpLiGa9U,8468
|
|
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=
|
|
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.
|
|
646
|
-
codeforlife_portal-6.43.
|
|
647
|
-
codeforlife_portal-6.43.
|
|
648
|
-
codeforlife_portal-6.43.
|
|
649
|
-
codeforlife_portal-6.43.
|
|
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.
|
|
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
|
-
|
|
19
|
+
generate_independent_student_details,
|
|
20
20
|
verify_email,
|
|
21
21
|
)
|
|
22
22
|
from common.tests.utils.teacher import signup_teacher_directly
|
|
@@ -169,7 +169,8 @@ class TestIndependentStudent(TestCase):
|
|
|
169
169
|
|
|
170
170
|
# Class for Selenium tests. We plan to replace these and turn them into Cypress tests
|
|
171
171
|
class TestIndependentStudentFrontend(BaseTest):
|
|
172
|
-
|
|
172
|
+
@patch("portal.views.registration.send_dotdigital_email")
|
|
173
|
+
def test_delete_indy_account(self, mock_send_dotdigital_email: Mock):
|
|
173
174
|
page = self.go_to_homepage()
|
|
174
175
|
page, _, _, email, password = create_independent_student(page)
|
|
175
176
|
page = page.independent_student_login(email, password)
|
|
@@ -214,27 +215,37 @@ class TestIndependentStudentFrontend(BaseTest):
|
|
|
214
215
|
assert not User.objects.get(id=user_id).is_active
|
|
215
216
|
|
|
216
217
|
# check if email has been sent
|
|
217
|
-
|
|
218
|
+
mock_send_dotdigital_email.assert_called_once_with(campaign_ids["delete_account"], ANY)
|
|
218
219
|
|
|
219
220
|
def test_signup_without_newsletter(self):
|
|
220
221
|
page = self.go_to_homepage()
|
|
221
222
|
page, _, _, _, _ = create_independent_student(page)
|
|
222
223
|
assert is_email_verified_message_showing(self.selenium)
|
|
223
224
|
|
|
224
|
-
|
|
225
|
+
@patch("portal.views.home.send_dotdigital_email")
|
|
226
|
+
def test_signup_duplicate_email_failure(self, mock_send_dotdigital_email):
|
|
225
227
|
page = self.go_to_homepage()
|
|
226
228
|
page, _, _, email, _ = create_independent_student(page)
|
|
227
229
|
assert is_email_verified_message_showing(self.selenium)
|
|
228
230
|
|
|
229
|
-
page = self.go_to_homepage()
|
|
230
|
-
|
|
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"]
|
|
231
242
|
|
|
232
|
-
|
|
233
|
-
assert mail.outbox[0].subject == "Duplicate account"
|
|
243
|
+
page = email_utils.follow_duplicate_account_link_to_login(page, login_link, "independent")
|
|
234
244
|
|
|
235
245
|
assert self.is_login_page(page)
|
|
236
246
|
|
|
237
|
-
|
|
247
|
+
@patch("portal.views.home.send_dotdigital_email")
|
|
248
|
+
def test_signup_duplicate_email_with_teacher(self, mock_send_dotdigital_email: Mock):
|
|
238
249
|
teacher_email, _ = signup_teacher_directly()
|
|
239
250
|
|
|
240
251
|
page = self.go_to_homepage()
|
|
@@ -247,8 +258,9 @@ class TestIndependentStudentFrontend(BaseTest):
|
|
|
247
258
|
|
|
248
259
|
page.return_to_home_page()
|
|
249
260
|
|
|
250
|
-
|
|
251
|
-
|
|
261
|
+
mock_send_dotdigital_email.assert_called_once_with(
|
|
262
|
+
campaign_ids["user_already_registered"], ANY, personalization_values=ANY
|
|
263
|
+
)
|
|
252
264
|
|
|
253
265
|
def test_login_failure(self):
|
|
254
266
|
page = self.go_to_homepage()
|
portal/tests/test_views.py
CHANGED
|
@@ -572,7 +572,8 @@ class TestViews(TestCase):
|
|
|
572
572
|
assert response.status_code == 200
|
|
573
573
|
assert response.context_data == EXPECTED_DATA_WITH_KURONO_GAME
|
|
574
574
|
|
|
575
|
-
|
|
575
|
+
@patch("portal.views.registration.send_dotdigital_email")
|
|
576
|
+
def test_delete_account(self, mock_send_dotdigital_email: Mock):
|
|
576
577
|
email, password = signup_teacher_directly()
|
|
577
578
|
u = User.objects.get(email=email)
|
|
578
579
|
usrid = u.id
|
|
@@ -593,7 +594,7 @@ class TestViews(TestCase):
|
|
|
593
594
|
response = c.post(url, {"password": "wrongPassword"})
|
|
594
595
|
|
|
595
596
|
assert response.status_code == 302
|
|
596
|
-
|
|
597
|
+
mock_send_dotdigital_email.assert_not_called()
|
|
597
598
|
|
|
598
599
|
# user has not been anonymised
|
|
599
600
|
u = User.objects.get(email=email)
|
|
@@ -604,6 +605,7 @@ class TestViews(TestCase):
|
|
|
604
605
|
response = c.post(url, {"password": password, "unsubscribe_newsletter": "on"})
|
|
605
606
|
|
|
606
607
|
assert response.status_code == 302
|
|
608
|
+
mock_send_dotdigital_email.assert_called_once()
|
|
607
609
|
assert response.url == reverse("home")
|
|
608
610
|
|
|
609
611
|
# user has been anonymised
|
|
@@ -611,7 +613,10 @@ class TestViews(TestCase):
|
|
|
611
613
|
assert u.first_name == "Deleted"
|
|
612
614
|
assert not u.is_active
|
|
613
615
|
|
|
614
|
-
|
|
616
|
+
assert c.login(username=email, password=password) == False
|
|
617
|
+
|
|
618
|
+
@patch("portal.views.registration.send_dotdigital_email")
|
|
619
|
+
def test_delete_account_admin(self, mock_send_dotdigital_email: Mock):
|
|
615
620
|
"""test the passing of admin role after deletion of an admin account"""
|
|
616
621
|
|
|
617
622
|
email1, password1 = signup_teacher_directly()
|
|
@@ -666,6 +671,7 @@ class TestViews(TestCase):
|
|
|
666
671
|
# delete teacher1 account
|
|
667
672
|
url = reverse("delete_account")
|
|
668
673
|
c.post(url, {"password": password1})
|
|
674
|
+
mock_send_dotdigital_email.assert_called_once()
|
|
669
675
|
|
|
670
676
|
# user has been anonymised
|
|
671
677
|
u = User.objects.get(id=usrid1)
|
|
@@ -706,6 +712,7 @@ class TestViews(TestCase):
|
|
|
706
712
|
# now delete teacher3 account
|
|
707
713
|
url = reverse("delete_account")
|
|
708
714
|
c.post(url, {"password": password3})
|
|
715
|
+
self.assertEqual(mock_send_dotdigital_email.call_count, 2)
|
|
709
716
|
|
|
710
717
|
# 2 teachers left
|
|
711
718
|
teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name")
|
|
@@ -738,6 +745,7 @@ class TestViews(TestCase):
|
|
|
738
745
|
|
|
739
746
|
url = reverse("delete_account")
|
|
740
747
|
c.post(url, {"password": password2})
|
|
748
|
+
self.assertEqual(mock_send_dotdigital_email.call_count, 3)
|
|
741
749
|
|
|
742
750
|
# school should be anonymised
|
|
743
751
|
school = School._base_manager.get(id=school_id)
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
138
|
+
send_dotdigital_email(
|
|
139
|
+
campaign_ids["user_already_registered"],
|
|
141
140
|
[email],
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
178
|
-
|
|
176
|
+
send_dotdigital_email(
|
|
177
|
+
campaign_ids["user_already_registered"],
|
|
179
178
|
[email],
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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}")
|
portal/views/registration.py
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
import re
|
|
3
|
-
|
|
4
|
-
from common.email_messages import accountDeletionEmail
|
|
5
|
-
from portal.views.login import has_user_lockout_expired
|
|
6
|
-
|
|
7
|
-
from django.contrib.auth.models import User
|
|
8
3
|
from datetime import datetime
|
|
4
|
+
|
|
9
5
|
from common.helpers.emails import (
|
|
10
|
-
delete_contact,
|
|
11
6
|
NOTIFICATION_EMAIL,
|
|
12
7
|
PASSWORD_RESET_EMAIL,
|
|
8
|
+
delete_contact,
|
|
13
9
|
send_email,
|
|
14
10
|
)
|
|
15
|
-
from common.
|
|
16
|
-
from common.
|
|
17
|
-
|
|
11
|
+
from common.mail import campaign_ids, send_dotdigital_email
|
|
12
|
+
from common.models import DailyActivity, Student, Teacher
|
|
13
|
+
from common.permissions import not_fully_logged_in, not_logged_in
|
|
18
14
|
from django.contrib import messages as messages
|
|
19
15
|
from django.contrib.auth import get_user_model
|
|
20
|
-
from django.contrib.auth.decorators import
|
|
16
|
+
from django.contrib.auth.decorators import login_required, user_passes_test
|
|
21
17
|
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
|
|
18
|
+
from django.contrib.auth.models import User
|
|
22
19
|
from django.contrib.auth.tokens import default_token_generator
|
|
23
20
|
from django.http import HttpResponseRedirect
|
|
24
21
|
from django.shortcuts import render
|
|
@@ -35,14 +32,15 @@ from django.views.decorators.http import require_POST
|
|
|
35
32
|
from deploy import captcha
|
|
36
33
|
from portal import app_settings
|
|
37
34
|
from portal.forms.registration import (
|
|
38
|
-
TeacherPasswordResetForm,
|
|
39
|
-
TeacherPasswordResetSetPasswordForm,
|
|
40
35
|
StudentPasswordResetForm,
|
|
41
36
|
StudentPasswordResetSetPasswordForm,
|
|
37
|
+
TeacherPasswordResetForm,
|
|
38
|
+
TeacherPasswordResetSetPasswordForm,
|
|
42
39
|
)
|
|
43
40
|
from portal.helpers.captcha import remove_captcha_from_form
|
|
44
41
|
from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
45
42
|
from portal.views.api import anonymise
|
|
43
|
+
from portal.views.login import has_user_lockout_expired
|
|
46
44
|
|
|
47
45
|
|
|
48
46
|
@user_passes_test(not_logged_in, login_url=reverse_lazy("home"))
|
|
@@ -309,13 +307,6 @@ def delete_account(request):
|
|
|
309
307
|
delete_contact(email)
|
|
310
308
|
|
|
311
309
|
# send confirmation email
|
|
312
|
-
|
|
313
|
-
send_email(
|
|
314
|
-
NOTIFICATION_EMAIL,
|
|
315
|
-
[email],
|
|
316
|
-
message["subject"],
|
|
317
|
-
message["message"],
|
|
318
|
-
message["title"],
|
|
319
|
-
)
|
|
310
|
+
send_dotdigital_email(campaign_ids["delete_account"], [email])
|
|
320
311
|
|
|
321
312
|
return HttpResponseRedirect(reverse_lazy("home"))
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
from common.
|
|
2
|
-
|
|
1
|
+
from common.helpers.emails import (
|
|
2
|
+
NOTIFICATION_EMAIL,
|
|
3
|
+
delete_contact,
|
|
4
|
+
send_email,
|
|
5
|
+
update_indy_email,
|
|
6
|
+
)
|
|
3
7
|
from common.models import Student
|
|
4
8
|
from common.permissions import logged_in_as_student
|
|
5
9
|
from django.contrib import messages as messages
|
|
@@ -7,17 +11,15 @@ from django.contrib.auth import logout
|
|
|
7
11
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
|
8
12
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
9
13
|
from django.http import HttpResponseRedirect
|
|
14
|
+
from django.shortcuts import render
|
|
10
15
|
from django.urls import reverse_lazy
|
|
11
16
|
from django.views.generic.edit import FormView
|
|
12
|
-
from django.shortcuts import render
|
|
13
17
|
|
|
14
|
-
from portal.forms.play import
|
|
18
|
+
from portal.forms.play import IndependentStudentEditAccountForm, StudentEditAccountForm
|
|
15
19
|
from portal.forms.registration import DeleteAccountForm
|
|
16
|
-
|
|
17
20
|
from portal.helpers.password import check_update_password
|
|
18
21
|
from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
19
22
|
from portal.views.api import anonymise
|
|
20
|
-
from django.contrib import messages as messages
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def _get_form(self, form_class):
|
portal/views/student/play.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
117
|
-
)
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
send_email(
|
|
381
|
-
NOTIFICATION_EMAIL,
|
|
389
|
+
send_dotdigital_email(
|
|
390
|
+
campaign_ids["teacher_released"],
|
|
382
391
|
[teacher.new_user.email],
|
|
383
|
-
|
|
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
|
-
|
|
541
|
-
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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,96 +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}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def accountDeletionEmail(request):
|
|
87
|
-
return {
|
|
88
|
-
"subject": f"We are sorry to see you go",
|
|
89
|
-
"title": "Your account was successfully deleted",
|
|
90
|
-
"message": (
|
|
91
|
-
f"If you have a moment before you leave us completely, please "
|
|
92
|
-
f"let us know the reason through our super short survey below."
|
|
93
|
-
f"\n\nGive feedback: https://usabi.li/do/d8e0313a31d7/5bef"
|
|
94
|
-
f"\n\nThank you for being part of the Code for Life community!"
|
|
95
|
-
),
|
|
96
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|