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 +8 -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.2.dist-info → codeforlife_portal-6.43.3.dist-info}/METADATA +2 -2
- {codeforlife_portal-6.43.2.dist-info → codeforlife_portal-6.43.3.dist-info}/RECORD +14 -15
- portal/__init__.py +1 -1
- portal/tests/test_independent_student.py +20 -9
- portal/views/home.py +16 -16
- portal/views/student/play.py +25 -53
- portal/views/teacher/dashboard.py +38 -22
- cfl_common/common/email_messages.py +0 -83
- {codeforlife_portal-6.43.2.dist-info → codeforlife_portal-6.43.3.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-6.43.2.dist-info → codeforlife_portal-6.43.3.dist-info}/WHEEL +0 -0
- {codeforlife_portal-6.43.2.dist-info → codeforlife_portal-6.43.3.dist-info}/top_level.txt +0 -0
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,
|
|
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=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=
|
|
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
|
|
@@ -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=
|
|
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
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
-
|
|
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.
|
|
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/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,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}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|