codeforlife-portal 6.44.10__py2.py3-none-any.whl → 6.45.0__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/helpers/emails.py +18 -10
- cfl_common/common/mail.py +1 -0
- {codeforlife_portal-6.44.10.dist-info → codeforlife_portal-6.45.0.dist-info}/METADATA +2 -2
- {codeforlife_portal-6.44.10.dist-info → codeforlife_portal-6.45.0.dist-info}/RECORD +12 -12
- portal/__init__.py +1 -1
- portal/forms/play.py +10 -6
- portal/tests/test_independent_student.py +7 -3
- portal/tests/test_views.py +188 -45
- portal/views/teacher/teach.py +3 -2
- {codeforlife_portal-6.44.10.dist-info → codeforlife_portal-6.45.0.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-6.44.10.dist-info → codeforlife_portal-6.45.0.dist-info}/WHEEL +0 -0
- {codeforlife_portal-6.44.10.dist-info → codeforlife_portal-6.45.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import json
|
|
3
|
-
import re
|
|
4
3
|
from enum import Enum, auto
|
|
5
4
|
from uuid import uuid4
|
|
6
5
|
|
|
@@ -66,13 +65,14 @@ def send_email(
|
|
|
66
65
|
django_send_email(sender, recipients, subject, text_content, title, replace_url, plaintext_template, html_template)
|
|
67
66
|
|
|
68
67
|
|
|
69
|
-
def send_verification_email(request, user, data, new_email=None, age=None):
|
|
68
|
+
def send_verification_email(request, user, data, new_email=None, age=None, school=None):
|
|
70
69
|
"""
|
|
71
70
|
Sends emails relating to email address verification.
|
|
72
71
|
|
|
73
72
|
On registration:
|
|
74
73
|
- if the user is under 13, send a verification email addressed to the parent / guardian
|
|
75
74
|
- if the user is over 13, send a regular verification email
|
|
75
|
+
- if the user is a student who just got released, send a verification email explaining the situation
|
|
76
76
|
- if the user is a student who has requested to sign up to the newsletter, handle their Dotmailer subscription
|
|
77
77
|
|
|
78
78
|
On email address update:
|
|
@@ -88,20 +88,28 @@ def send_verification_email(request, user, data, new_email=None, age=None):
|
|
|
88
88
|
student)
|
|
89
89
|
"""
|
|
90
90
|
|
|
91
|
-
# verifying first email address (registration)
|
|
91
|
+
# verifying first email address (registration or unverified login attempt)
|
|
92
92
|
if not new_email:
|
|
93
93
|
verification = generate_token(user)
|
|
94
94
|
|
|
95
|
-
# if the user is a teacher
|
|
96
95
|
if age is None:
|
|
97
|
-
|
|
96
|
+
# if the user is a released student
|
|
97
|
+
if hasattr(user, "new_student") and school is not None:
|
|
98
|
+
url = f"{request.build_absolute_uri(reverse('verify_email', kwargs={'token': verification}))}"
|
|
99
|
+
|
|
100
|
+
send_dotdigital_email(
|
|
101
|
+
campaign_ids["verify_released_student"], [user.email],
|
|
102
|
+
personalization_values={"VERIFICATION_LINK": url, "SCHOOL_NAME": school.name}
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
url = f"{request.build_absolute_uri(reverse('verify_email', kwargs={'token': verification}))}"
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
send_dotdigital_email(
|
|
108
|
+
campaign_ids["verify_new_user"], [user.email], personalization_values={"VERIFICATION_LINK": url}
|
|
109
|
+
)
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
if _newsletter_ticked(data):
|
|
112
|
+
add_to_dotmailer(user.first_name, user.last_name, user.email, DotmailerUserType.TEACHER)
|
|
105
113
|
# if the user is an independent student
|
|
106
114
|
else:
|
|
107
115
|
if age < 13:
|
cfl_common/common/mail.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: codeforlife-portal
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.45.0
|
|
4
4
|
Classifier: Programming Language :: Python
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.8
|
|
6
6
|
Classifier: Framework :: Django
|
|
@@ -25,7 +25,7 @@ Requires-Dist: django-classy-tags ==2.0.0
|
|
|
25
25
|
Requires-Dist: libsass ==0.23.0
|
|
26
26
|
Requires-Dist: phonenumbers ==8.12.12
|
|
27
27
|
Requires-Dist: more-itertools ==8.7.0
|
|
28
|
-
Requires-Dist: cfl-common ==6.
|
|
28
|
+
Requires-Dist: cfl-common ==6.45.0
|
|
29
29
|
Requires-Dist: django-ratelimit ==3.0.1
|
|
30
30
|
Requires-Dist: django-preventconcurrentlogins ==0.8.2
|
|
31
31
|
Requires-Dist: django-csp ==3.7
|
|
@@ -5,13 +5,13 @@ 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/mail.py,sha256=
|
|
8
|
+
cfl_common/common/mail.py,sha256=Lmn7CepceKw1UqxjxTR_24fo7izecw0ihBJ3W_9tcco,6394
|
|
9
9
|
cfl_common/common/models.py,sha256=1e_3zHf8h_K812-2cQymRLZAKoA73_5-t4LQGPQlifE,16946
|
|
10
10
|
cfl_common/common/permissions.py,sha256=gC6RQGZI2QDBbglx-xr_V4Hl2C2nf1V2_uPmEuoEcJo,2416
|
|
11
11
|
cfl_common/common/utils.py,sha256=Nn2Npao9Uqad5Js_IdHwF-ow6wrPNpBLW4AO1LxoEBc,1727
|
|
12
12
|
cfl_common/common/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
cfl_common/common/helpers/data_migration_loader.py,sha256=_BhS5lPmhcuVUbryBmJytlWdHyT02KYyxPkHar32mOE,1748
|
|
14
|
-
cfl_common/common/helpers/emails.py,sha256=
|
|
14
|
+
cfl_common/common/helpers/emails.py,sha256=Gu4YAd977k7v_4qezcYFwWYEBUAV3r8pXZYDVxJL3qw,10700
|
|
15
15
|
cfl_common/common/helpers/generators.py,sha256=kTL5e91I8wgmjJ-mu4jr9vIacjccUZ5pZSAz5cUNhdM,1505
|
|
16
16
|
cfl_common/common/helpers/organisation.py,sha256=e-JKumKoXrkMTzZPv0H4ViWL8vtCt7oXJjn_zZ1ec00,427
|
|
17
17
|
cfl_common/common/migrations/0001_initial.py,sha256=Y2kt2xmdCbrmDXCgqmhXeacicNg26Zj7L7SANSsgAAI,9664
|
|
@@ -106,7 +106,7 @@ example_project/portal_test_settings.py,sha256=frp_XMpd-z1g3VFCRxB2w7AaFW2ivRVKn
|
|
|
106
106
|
example_project/settings.py,sha256=XRZZvASoIl5a9xe3masTq_CUBleuJq9ByHx8f_e2UFc,5613
|
|
107
107
|
example_project/urls.py,sha256=OVeRQ-TCpzHISBRuzqD0yd3ewF7H5U3c-f2p2alfUD0,430
|
|
108
108
|
example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
|
|
109
|
-
portal/__init__.py,sha256=
|
|
109
|
+
portal/__init__.py,sha256=oVokL7bm_I6q-ZEmSiMqfc1pQbhzWmTkE47idKwv0l4,23
|
|
110
110
|
portal/admin.py,sha256=on1-zNRnZvf2cwBN6GVRVYRhkaksrCgfzX8XPWtkvz8,6062
|
|
111
111
|
portal/app_settings.py,sha256=DhWLQOwM0zVOXE3O5TNKbMM9K6agfLuCsHOdr1J7xEI,651
|
|
112
112
|
portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
|
|
@@ -123,7 +123,7 @@ portal/forms/dotmailer.py,sha256=McD9_u8yxUfE7PSVG3MPMilRJtx9GTe9QsNzDDV3uuI,761
|
|
|
123
123
|
portal/forms/error_messages.py,sha256=8d3z_3e2L-5zwj5hFhnUByC5k2CEpIVVuJg2nYkCUQ8,148
|
|
124
124
|
portal/forms/invite_teacher.py,sha256=jkDNcCfkts4_lXRzhcI3xBam21Zn2yX9wMpMVhDtW1w,880
|
|
125
125
|
portal/forms/organisation.py,sha256=QcQyd7AiqBmvt4y8uQSQylguUbKOKqo2pjqWIkpWjDg,7433
|
|
126
|
-
portal/forms/play.py,sha256=
|
|
126
|
+
portal/forms/play.py,sha256=z9P5LzyS3jjYcnfco84d2x8ptgLxRmh94Dnj05plmbY,11505
|
|
127
127
|
portal/forms/registration.py,sha256=gWcY7rllhWO3c9as6QHUDWZx1Jme7DqtGHYaKcvxe-U,5990
|
|
128
128
|
portal/forms/teach.py,sha256=-3dMQxIQtYq2xg5DgtIJMpN7RajNhTvc56Clr5QjsHo,20440
|
|
129
129
|
portal/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -545,7 +545,7 @@ portal/tests/test_class.py,sha256=V6Fkc6PqdisefKD3xs9PbfE2pKp-9e0gwQVkPUiu6bk,14
|
|
|
545
545
|
portal/tests/test_daily_activities.py,sha256=-siDCMGBD1ijjccHVk7eEmrk4bgTsvbh0B6hDoj2fo0,1803
|
|
546
546
|
portal/tests/test_emails.py,sha256=xNgOt592r2nrQu7VeBdc8kvSnw6Z5fPJqMx2-UMcyyk,9482
|
|
547
547
|
portal/tests/test_helper_methods.py,sha256=-SQCDZm2XUtyXGEp0CHIb_SSC9CPD-XOSnpnY8QclHk,890
|
|
548
|
-
portal/tests/test_independent_student.py,sha256=
|
|
548
|
+
portal/tests/test_independent_student.py,sha256=mRbWZVwbZKPqHajTd9bYrCAn9ZJLpexpk-YSiZuDAbM,27540
|
|
549
549
|
portal/tests/test_invite_teacher.py,sha256=oeOaoJV1IqJSYPlaPFjnhVXdB2mq8otCTLp_lfjuCfk,12224
|
|
550
550
|
portal/tests/test_middleware.py,sha256=b6jfNmiRZ2snqLKsyJUG-RivoX5fmrqLlQkG9MeVnqM,8034
|
|
551
551
|
portal/tests/test_newsletter_footer.py,sha256=MdVUX53mEoDTa4Krq-jg9LFNo-QyghqvTvhHeNXBGnE,838
|
|
@@ -556,7 +556,7 @@ portal/tests/test_school_student.py,sha256=bFZwY4twaFHQLp0cltMq8cLNDZGgCHTZBCZHK
|
|
|
556
556
|
portal/tests/test_security.py,sha256=FGrlRfnzi-Xx2_bn4fTZlYORKm7w_GhGkD3havvplwc,3239
|
|
557
557
|
portal/tests/test_teacher.py,sha256=_VmQCWq07uCFbvq6Vd7GN00mE7vY7WNMeQTk6bHxFPI,36898
|
|
558
558
|
portal/tests/test_teacher_student.py,sha256=NWITbUw1kijqu3c8eRHLHJKaYQMOsOMvl7PAVx5QghI,21567
|
|
559
|
-
portal/tests/test_views.py,sha256=
|
|
559
|
+
portal/tests/test_views.py,sha256=g6WQtexZ-UfwpNxpTmfiB-RTBtLFiYBmjAa5oFCSBDk,42073
|
|
560
560
|
portal/tests/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
561
561
|
portal/tests/migrations/test_migration_make_portaladmin_teacher.py,sha256=ekMRb6cU97oT0k9gCKW7IUB7oPuGmv4uWJCqInQN7x8,2589
|
|
562
562
|
portal/tests/migrations/test_migration_preview_user_remove.py,sha256=K6D-FZT9YFEA8oMxHz9VTglVV6MZOTRYVlvwWwXc2vU,555
|
|
@@ -638,13 +638,13 @@ portal/views/student/edit_account_details.py,sha256=Ba-3D_zzKbX5N01NG5qqBS0ud10B
|
|
|
638
638
|
portal/views/student/play.py,sha256=r5TADH_wYn3d1beezfvkYBiendQ9qLys9dUJwHqF_44,8581
|
|
639
639
|
portal/views/teacher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
640
640
|
portal/views/teacher/dashboard.py,sha256=8WglspwuHF__2LtoX5_XvoW1ulICSupjKv--MtjrvJk,25714
|
|
641
|
-
portal/views/teacher/teach.py,sha256=
|
|
641
|
+
portal/views/teacher/teach.py,sha256=PJAUjLeIBELWc5e2Eek0mr8kYleYWb1MH9FF4O2q7Ok,34779
|
|
642
642
|
portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
643
643
|
portal/views/two_factor/core.py,sha256=O_wcBeFqdPYSGNGv-pT_vbs5-Dj1Z-Jfkd6f9-E5yZI,760
|
|
644
644
|
portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
|
|
645
645
|
portal/views/two_factor/profile.py,sha256=tkl_ludo8arMtd5LKNmohM66vpC_YQiP-0nspTSJiJ4,383
|
|
646
|
-
codeforlife_portal-6.
|
|
647
|
-
codeforlife_portal-6.
|
|
648
|
-
codeforlife_portal-6.
|
|
649
|
-
codeforlife_portal-6.
|
|
650
|
-
codeforlife_portal-6.
|
|
646
|
+
codeforlife_portal-6.45.0.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
|
|
647
|
+
codeforlife_portal-6.45.0.dist-info/METADATA,sha256=wLw9Jy35iptYZbEN8byRqGpjW7IWIUl9iDNyDJTyBjs,3474
|
|
648
|
+
codeforlife_portal-6.45.0.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
|
|
649
|
+
codeforlife_portal-6.45.0.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
|
|
650
|
+
codeforlife_portal-6.45.0.dist-info/RECORD,,
|
portal/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "6.
|
|
1
|
+
__version__ = "6.45.0"
|
portal/forms/play.py
CHANGED
|
@@ -277,15 +277,19 @@ class StudentJoinOrganisationForm(forms.Form):
|
|
|
277
277
|
|
|
278
278
|
def clean(self):
|
|
279
279
|
access_code = self.cleaned_data.get("access_code", None)
|
|
280
|
+
join_error_text = "The class code you entered either does not exist or is not currently accepting join requests. Please double check that you have entered the correct class code and contact the teacher of the class to ensure their class is currently accepting join requests."
|
|
280
281
|
|
|
281
282
|
if access_code:
|
|
282
283
|
classes = Class.objects.filter(access_code=access_code)
|
|
283
284
|
if len(classes) != 1:
|
|
284
|
-
raise forms.ValidationError(
|
|
285
|
+
raise forms.ValidationError(join_error_text)
|
|
286
|
+
|
|
285
287
|
self.klass = classes[0]
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
|
|
289
|
+
if not self.klass.always_accept_requests and (
|
|
290
|
+
self.klass.accept_requests_until is None
|
|
291
|
+
or self.klass.accept_requests_until - timezone.now()
|
|
292
|
+
< timedelta()
|
|
293
|
+
):
|
|
294
|
+
raise forms.ValidationError(join_error_text)
|
|
291
295
|
return self.cleaned_data
|
|
@@ -283,7 +283,7 @@ class TestIndependentStudentFrontend(BaseTest):
|
|
|
283
283
|
page = page.go_to_independent_student_login_page()
|
|
284
284
|
page = page.independent_student_login_failure(username, password)
|
|
285
285
|
|
|
286
|
-
|
|
286
|
+
page.has_login_failed("independent_student_login_form", INVALID_LOGIN_MESSAGE)
|
|
287
287
|
assert page.has_login_failed("independent_student_login_form", INVALID_LOGIN_MESSAGE)
|
|
288
288
|
|
|
289
289
|
verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
|
|
@@ -471,7 +471,9 @@ class TestIndependentStudentFrontend(BaseTest):
|
|
|
471
471
|
)
|
|
472
472
|
|
|
473
473
|
assert self.is_join_class_page(page)
|
|
474
|
-
assert page.has_join_request_failed(
|
|
474
|
+
assert page.has_join_request_failed(
|
|
475
|
+
"The class code you entered either does not exist or is not currently accepting join requests. Please double check that you have entered the correct class code and contact the teacher of the class to ensure their class is currently accepting join requests."
|
|
476
|
+
)
|
|
475
477
|
|
|
476
478
|
def test_join_class_not_accepting_requests(self):
|
|
477
479
|
teacher_email, _ = signup_teacher_directly()
|
|
@@ -490,7 +492,9 @@ class TestIndependentStudentFrontend(BaseTest):
|
|
|
490
492
|
)
|
|
491
493
|
|
|
492
494
|
assert self.is_join_class_page(page)
|
|
493
|
-
assert page.has_join_request_failed(
|
|
495
|
+
assert page.has_join_request_failed(
|
|
496
|
+
"The class code you entered either does not exist or is not currently accepting join requests. Please double check that you have entered the correct class code and contact the teacher of the class to ensure their class is currently accepting join requests."
|
|
497
|
+
)
|
|
494
498
|
|
|
495
499
|
def test_join_class_revoked(self):
|
|
496
500
|
teacher_email, _ = signup_teacher_directly()
|
portal/tests/test_views.py
CHANGED
|
@@ -53,8 +53,11 @@ class TestTeacherViews(TestCase):
|
|
|
53
53
|
@classmethod
|
|
54
54
|
def setUpTestData(cls):
|
|
55
55
|
cls.email, cls.password = signup_teacher_directly()
|
|
56
|
+
cls.school = create_organisation_directly(cls.email)
|
|
56
57
|
_, _, cls.class_access_code = create_class_directly(cls.email)
|
|
57
|
-
_,
|
|
58
|
+
_, cls.password_student, cls.student = create_school_student_directly(
|
|
59
|
+
cls.class_access_code
|
|
60
|
+
)
|
|
58
61
|
|
|
59
62
|
def login(self):
|
|
60
63
|
c = Client()
|
|
@@ -63,7 +66,9 @@ class TestTeacherViews(TestCase):
|
|
|
63
66
|
|
|
64
67
|
def test_reminder_cards(self):
|
|
65
68
|
c = self.login()
|
|
66
|
-
url = reverse(
|
|
69
|
+
url = reverse(
|
|
70
|
+
"teacher_print_reminder_cards", args=[self.class_access_code]
|
|
71
|
+
)
|
|
67
72
|
|
|
68
73
|
# First test with 2 dummy students
|
|
69
74
|
NAME1 = "Test name"
|
|
@@ -97,7 +102,9 @@ class TestTeacherViews(TestCase):
|
|
|
97
102
|
# page number
|
|
98
103
|
students_per_page = REMINDER_CARDS_PDF_ROWS * REMINDER_CARDS_PDF_COLUMNS
|
|
99
104
|
for _ in range(len(studentlist), students_per_page + 1):
|
|
100
|
-
studentlist.append(
|
|
105
|
+
studentlist.append(
|
|
106
|
+
{"name": NAME1, "password": PASSWORD1, "login_url": URL}
|
|
107
|
+
)
|
|
101
108
|
|
|
102
109
|
assert len(studentlist) == students_per_page + 1
|
|
103
110
|
|
|
@@ -136,7 +143,9 @@ class TestTeacherViews(TestCase):
|
|
|
136
143
|
reader = csv.reader(io.StringIO(content))
|
|
137
144
|
|
|
138
145
|
access_code = self.class_access_code
|
|
139
|
-
class_url = reverse(
|
|
146
|
+
class_url = reverse(
|
|
147
|
+
"student_login", kwargs={"access_code": access_code}
|
|
148
|
+
)
|
|
140
149
|
row0 = next(reader)
|
|
141
150
|
assert row0[0].strip() == access_code
|
|
142
151
|
assert class_url in row0[1].strip()
|
|
@@ -175,7 +184,9 @@ class TestTeacherViews(TestCase):
|
|
|
175
184
|
|
|
176
185
|
def test_daily_activity_student_details(self):
|
|
177
186
|
c = self.login()
|
|
178
|
-
url = reverse(
|
|
187
|
+
url = reverse(
|
|
188
|
+
"teacher_print_reminder_cards", args=[self.class_access_code]
|
|
189
|
+
)
|
|
179
190
|
|
|
180
191
|
data = {
|
|
181
192
|
"data": json.dumps(
|
|
@@ -221,6 +232,48 @@ class TestTeacherViews(TestCase):
|
|
|
221
232
|
with pytest.raises(Exception):
|
|
222
233
|
count_student_details_click("Wrong download method")
|
|
223
234
|
|
|
235
|
+
def test_release_verified_student(self):
|
|
236
|
+
c = Client()
|
|
237
|
+
student_login_url = reverse(
|
|
238
|
+
"student_login", args=[self.class_access_code]
|
|
239
|
+
)
|
|
240
|
+
response = c.post(
|
|
241
|
+
student_login_url,
|
|
242
|
+
{
|
|
243
|
+
"username": self.student.new_user.first_name,
|
|
244
|
+
"password": self.password_student,
|
|
245
|
+
},
|
|
246
|
+
)
|
|
247
|
+
assert response.status_code == 302
|
|
248
|
+
|
|
249
|
+
student = Student.objects.get(pk=self.student.pk)
|
|
250
|
+
assert student.user.is_verified
|
|
251
|
+
|
|
252
|
+
c.logout()
|
|
253
|
+
c.login(username=self.email, password=self.password)
|
|
254
|
+
|
|
255
|
+
release_url = reverse(
|
|
256
|
+
"teacher_dismiss_students", args=[self.class_access_code]
|
|
257
|
+
)
|
|
258
|
+
response = c.post(
|
|
259
|
+
release_url,
|
|
260
|
+
{
|
|
261
|
+
"form-TOTAL_FORMS": 1,
|
|
262
|
+
"form-INITIAL_FORMS": 1,
|
|
263
|
+
"form-MIN_NUM_FORMS": 0,
|
|
264
|
+
"form-MAX_NUM_FORMS": 1000,
|
|
265
|
+
"form-0-orig_name": self.student.new_user.first_name,
|
|
266
|
+
"form-0-name": self.student.new_user.first_name,
|
|
267
|
+
"form-0-email": "independent@gmail.com",
|
|
268
|
+
"form-0-confirm_email": "independent@gmail.com",
|
|
269
|
+
"submit_dismiss": "",
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
assert response.status_code == 302
|
|
273
|
+
|
|
274
|
+
student = Student.objects.get(pk=self.student.pk)
|
|
275
|
+
assert not student.user.is_verified
|
|
276
|
+
|
|
224
277
|
|
|
225
278
|
class TestLoginViews(TestCase):
|
|
226
279
|
@classmethod
|
|
@@ -238,7 +291,9 @@ class TestLoginViews(TestCase):
|
|
|
238
291
|
teacher_email, teacher_password = signup_teacher_directly()
|
|
239
292
|
create_organisation_directly(teacher_email)
|
|
240
293
|
_, _, class_access_code = create_class_directly(teacher_email)
|
|
241
|
-
student_name, student_password, _ = create_school_student_directly(
|
|
294
|
+
student_name, student_password, _ = create_school_student_directly(
|
|
295
|
+
class_access_code
|
|
296
|
+
)
|
|
242
297
|
|
|
243
298
|
return (
|
|
244
299
|
teacher_email,
|
|
@@ -271,9 +326,16 @@ class TestLoginViews(TestCase):
|
|
|
271
326
|
_, _, name, password, class_access_code = self._set_up_test_data()
|
|
272
327
|
|
|
273
328
|
if next_url:
|
|
274
|
-
url =
|
|
329
|
+
url = (
|
|
330
|
+
reverse(
|
|
331
|
+
"student_login", kwargs={"access_code": class_access_code}
|
|
332
|
+
)
|
|
333
|
+
+ "?next=/"
|
|
334
|
+
)
|
|
275
335
|
else:
|
|
276
|
-
url = reverse(
|
|
336
|
+
url = reverse(
|
|
337
|
+
"student_login", kwargs={"access_code": class_access_code}
|
|
338
|
+
)
|
|
277
339
|
|
|
278
340
|
c = Client()
|
|
279
341
|
response = c.post(url, {"username": name, "password": password})
|
|
@@ -312,7 +374,9 @@ class TestLoginViews(TestCase):
|
|
|
312
374
|
|
|
313
375
|
def _get_user_class(self, name, class_access_code):
|
|
314
376
|
klass = Class.objects.get(access_code=class_access_code)
|
|
315
|
-
students = Student.objects.filter(
|
|
377
|
+
students = Student.objects.filter(
|
|
378
|
+
new_user__first_name__iexact=name, class_field=klass
|
|
379
|
+
)
|
|
316
380
|
assert len(students) == 1
|
|
317
381
|
user = students[0].new_user
|
|
318
382
|
return user, klass
|
|
@@ -354,7 +418,9 @@ class TestLoginViews(TestCase):
|
|
|
354
418
|
_, _, name, password, class_access_code = self._set_up_test_data()
|
|
355
419
|
|
|
356
420
|
c = Client()
|
|
357
|
-
url = reverse(
|
|
421
|
+
url = reverse(
|
|
422
|
+
"student_login", kwargs={"access_code": class_access_code}
|
|
423
|
+
)
|
|
358
424
|
c.post(url, {"username": name, "password": password})
|
|
359
425
|
|
|
360
426
|
# check if there's a UserSession data within the last 10 secs
|
|
@@ -375,7 +441,9 @@ class TestLoginViews(TestCase):
|
|
|
375
441
|
randomname = "randomname"
|
|
376
442
|
|
|
377
443
|
c = Client()
|
|
378
|
-
url = reverse(
|
|
444
|
+
url = reverse(
|
|
445
|
+
"student_login", kwargs={"access_code": class_access_code}
|
|
446
|
+
)
|
|
379
447
|
c.post(url, {"username": randomname, "password": "xx"})
|
|
380
448
|
|
|
381
449
|
# check if there's a UserSession data within the last 10 secs
|
|
@@ -401,7 +469,9 @@ class TestLoginViews(TestCase):
|
|
|
401
469
|
|
|
402
470
|
def test_student_direct_login(self):
|
|
403
471
|
_, _, _, _, class_access_code = self._set_up_test_data()
|
|
404
|
-
student, login_id, _, _ = create_student_with_direct_login(
|
|
472
|
+
student, login_id, _, _ = create_student_with_direct_login(
|
|
473
|
+
class_access_code
|
|
474
|
+
)
|
|
405
475
|
|
|
406
476
|
c = Client()
|
|
407
477
|
assert c.login(user_id=student.new_user.id, login_id=login_id) == True
|
|
@@ -523,7 +593,9 @@ class TestViews(TestCase):
|
|
|
523
593
|
c = Client()
|
|
524
594
|
|
|
525
595
|
# Login and check initial data
|
|
526
|
-
url = reverse(
|
|
596
|
+
url = reverse(
|
|
597
|
+
"student_login", kwargs={"access_code": class_access_code}
|
|
598
|
+
)
|
|
527
599
|
c.post(url, {"username": student_name, "password": student_password})
|
|
528
600
|
|
|
529
601
|
student_dashboard_url = reverse("student_details")
|
|
@@ -602,7 +674,9 @@ class TestViews(TestCase):
|
|
|
602
674
|
|
|
603
675
|
# try again with the correct password
|
|
604
676
|
url = reverse("delete_account")
|
|
605
|
-
response = c.post(
|
|
677
|
+
response = c.post(
|
|
678
|
+
url, {"password": password, "unsubscribe_newsletter": "on"}
|
|
679
|
+
)
|
|
606
680
|
|
|
607
681
|
assert response.status_code == 302
|
|
608
682
|
mock_send_dotdigital_email.assert_called_once()
|
|
@@ -684,7 +758,9 @@ class TestViews(TestCase):
|
|
|
684
758
|
|
|
685
759
|
school_id = school.id
|
|
686
760
|
school_name = school.name
|
|
687
|
-
teachers = Teacher.objects.filter(school=school).order_by(
|
|
761
|
+
teachers = Teacher.objects.filter(school=school).order_by(
|
|
762
|
+
"new_user__last_name", "new_user__first_name"
|
|
763
|
+
)
|
|
688
764
|
assert len(teachers) == 3
|
|
689
765
|
|
|
690
766
|
# one of the remaining teachers should be admin (the second in our case, as it's alphabetical)
|
|
@@ -715,7 +791,9 @@ class TestViews(TestCase):
|
|
|
715
791
|
self.assertEqual(mock_send_dotdigital_email.call_count, 2)
|
|
716
792
|
|
|
717
793
|
# 2 teachers left
|
|
718
|
-
teachers = Teacher.objects.filter(school=school).order_by(
|
|
794
|
+
teachers = Teacher.objects.filter(school=school).order_by(
|
|
795
|
+
"new_user__last_name", "new_user__first_name"
|
|
796
|
+
)
|
|
719
797
|
assert len(teachers) == 2
|
|
720
798
|
|
|
721
799
|
# teacher2 should still be admin, teacher4 is not passed admin role because there is teacher2
|
|
@@ -727,7 +805,9 @@ class TestViews(TestCase):
|
|
|
727
805
|
# delete teacher4
|
|
728
806
|
anonymise(user4)
|
|
729
807
|
|
|
730
|
-
teachers = Teacher.objects.filter(school=school).order_by(
|
|
808
|
+
teachers = Teacher.objects.filter(school=school).order_by(
|
|
809
|
+
"new_user__last_name", "new_user__first_name"
|
|
810
|
+
)
|
|
731
811
|
assert len(teachers) == 1
|
|
732
812
|
u = User.objects.get(id=usrid2)
|
|
733
813
|
assert u.new_teacher.is_admin
|
|
@@ -785,13 +865,17 @@ class TestViews(TestCase):
|
|
|
785
865
|
c.logout()
|
|
786
866
|
|
|
787
867
|
@patch("common.helpers.emails.send_dotdigital_email")
|
|
788
|
-
def test_registrations_increment_data(
|
|
868
|
+
def test_registrations_increment_data(
|
|
869
|
+
self, mock_send_dotdigital_email: Mock
|
|
870
|
+
):
|
|
789
871
|
c = Client()
|
|
790
872
|
|
|
791
873
|
total_activity = TotalActivity.objects.get(id=1)
|
|
792
874
|
teacher_registration_count = total_activity.teacher_registrations
|
|
793
875
|
student_registration_count = total_activity.student_registrations
|
|
794
|
-
independent_registration_count =
|
|
876
|
+
independent_registration_count = (
|
|
877
|
+
total_activity.independent_registrations
|
|
878
|
+
)
|
|
795
879
|
|
|
796
880
|
response = c.post(
|
|
797
881
|
reverse("register"),
|
|
@@ -811,7 +895,10 @@ class TestViews(TestCase):
|
|
|
811
895
|
|
|
812
896
|
total_activity = TotalActivity.objects.get(id=1)
|
|
813
897
|
|
|
814
|
-
assert
|
|
898
|
+
assert (
|
|
899
|
+
total_activity.teacher_registrations
|
|
900
|
+
== teacher_registration_count + 1
|
|
901
|
+
)
|
|
815
902
|
|
|
816
903
|
response = c.post(
|
|
817
904
|
reverse("register"),
|
|
@@ -833,7 +920,10 @@ class TestViews(TestCase):
|
|
|
833
920
|
|
|
834
921
|
total_activity = TotalActivity.objects.get(id=1)
|
|
835
922
|
|
|
836
|
-
assert
|
|
923
|
+
assert (
|
|
924
|
+
total_activity.independent_registrations
|
|
925
|
+
== independent_registration_count + 1
|
|
926
|
+
)
|
|
837
927
|
|
|
838
928
|
teacher_email, teacher_password = signup_teacher_directly()
|
|
839
929
|
create_organisation_directly(teacher_email)
|
|
@@ -849,7 +939,10 @@ class TestViews(TestCase):
|
|
|
849
939
|
|
|
850
940
|
total_activity = TotalActivity.objects.get(id=1)
|
|
851
941
|
|
|
852
|
-
assert
|
|
942
|
+
assert (
|
|
943
|
+
total_activity.student_registrations
|
|
944
|
+
== student_registration_count + 3
|
|
945
|
+
)
|
|
853
946
|
|
|
854
947
|
|
|
855
948
|
# CRON view tests
|
|
@@ -868,8 +961,12 @@ class CronTestClient(APIClient):
|
|
|
868
961
|
secure=False,
|
|
869
962
|
**extra,
|
|
870
963
|
):
|
|
871
|
-
wsgi_response = super().generic(
|
|
872
|
-
|
|
964
|
+
wsgi_response = super().generic(
|
|
965
|
+
method, path, data, content_type, secure, **extra
|
|
966
|
+
)
|
|
967
|
+
assert (
|
|
968
|
+
200 <= wsgi_response.status_code < 300
|
|
969
|
+
), f"Response has error status code: {wsgi_response.status_code}"
|
|
873
970
|
|
|
874
971
|
return wsgi_response
|
|
875
972
|
|
|
@@ -888,7 +985,9 @@ class TestUser(CronTestCase):
|
|
|
888
985
|
indy_email, _, _ = create_independent_student_directly()
|
|
889
986
|
|
|
890
987
|
self.teacher_user = User.objects.get(email=teacher_email)
|
|
891
|
-
self.teacher_user_profile = UserProfile.objects.get(
|
|
988
|
+
self.teacher_user_profile = UserProfile.objects.get(
|
|
989
|
+
user=self.teacher_user
|
|
990
|
+
)
|
|
892
991
|
|
|
893
992
|
self.indy_user = User.objects.get(email=indy_email)
|
|
894
993
|
self.indy_user_profile = UserProfile.objects.get(user=self.indy_user)
|
|
@@ -904,11 +1003,17 @@ class TestUser(CronTestCase):
|
|
|
904
1003
|
assert_called: bool,
|
|
905
1004
|
mock_send_dotdigital_email: Mock,
|
|
906
1005
|
):
|
|
907
|
-
self.teacher_user.date_joined = timezone.now() - timedelta(
|
|
1006
|
+
self.teacher_user.date_joined = timezone.now() - timedelta(
|
|
1007
|
+
days=days, hours=12
|
|
1008
|
+
)
|
|
908
1009
|
self.teacher_user.save()
|
|
909
|
-
self.student_user.date_joined = timezone.now() - timedelta(
|
|
1010
|
+
self.student_user.date_joined = timezone.now() - timedelta(
|
|
1011
|
+
days=days, hours=12
|
|
1012
|
+
)
|
|
910
1013
|
self.student_user.save()
|
|
911
|
-
self.indy_user.date_joined = timezone.now() - timedelta(
|
|
1014
|
+
self.indy_user.date_joined = timezone.now() - timedelta(
|
|
1015
|
+
days=days, hours=12
|
|
1016
|
+
)
|
|
912
1017
|
self.indy_user.save()
|
|
913
1018
|
|
|
914
1019
|
self.teacher_user_profile.is_verified = is_verified
|
|
@@ -919,9 +1024,13 @@ class TestUser(CronTestCase):
|
|
|
919
1024
|
self.client.get(reverse(view_name))
|
|
920
1025
|
|
|
921
1026
|
if assert_called:
|
|
922
|
-
mock_send_dotdigital_email.assert_any_call(
|
|
1027
|
+
mock_send_dotdigital_email.assert_any_call(
|
|
1028
|
+
ANY, [self.teacher_user.email], personalization_values=ANY
|
|
1029
|
+
)
|
|
923
1030
|
|
|
924
|
-
mock_send_dotdigital_email.assert_any_call(
|
|
1031
|
+
mock_send_dotdigital_email.assert_any_call(
|
|
1032
|
+
ANY, [self.indy_user.email], personalization_values=ANY
|
|
1033
|
+
)
|
|
925
1034
|
|
|
926
1035
|
# Check only two emails are sent - the student should never be included.
|
|
927
1036
|
assert mock_send_dotdigital_email.call_count == 2
|
|
@@ -931,22 +1040,40 @@ class TestUser(CronTestCase):
|
|
|
931
1040
|
mock_send_dotdigital_email.reset_mock()
|
|
932
1041
|
|
|
933
1042
|
def test_first_verify_email_reminder_view(self):
|
|
934
|
-
self.send_verify_email_reminder(
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
self.send_verify_email_reminder(
|
|
1043
|
+
self.send_verify_email_reminder(
|
|
1044
|
+
6, False, "first-verify-email-reminder", False
|
|
1045
|
+
)
|
|
1046
|
+
self.send_verify_email_reminder(
|
|
1047
|
+
7, False, "first-verify-email-reminder", True
|
|
1048
|
+
)
|
|
1049
|
+
self.send_verify_email_reminder(
|
|
1050
|
+
7, True, "first-verify-email-reminder", False
|
|
1051
|
+
)
|
|
1052
|
+
self.send_verify_email_reminder(
|
|
1053
|
+
8, False, "first-verify-email-reminder", False
|
|
1054
|
+
)
|
|
938
1055
|
|
|
939
1056
|
def test_second_verify_email_reminder_view(self):
|
|
940
|
-
self.send_verify_email_reminder(
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
self.send_verify_email_reminder(
|
|
1057
|
+
self.send_verify_email_reminder(
|
|
1058
|
+
13, False, "second-verify-email-reminder", False
|
|
1059
|
+
)
|
|
1060
|
+
self.send_verify_email_reminder(
|
|
1061
|
+
14, False, "second-verify-email-reminder", True
|
|
1062
|
+
)
|
|
1063
|
+
self.send_verify_email_reminder(
|
|
1064
|
+
14, True, "second-verify-email-reminder", False
|
|
1065
|
+
)
|
|
1066
|
+
self.send_verify_email_reminder(
|
|
1067
|
+
15, False, "second-verify-email-reminder", False
|
|
1068
|
+
)
|
|
944
1069
|
|
|
945
1070
|
def test_anonymise_unverified_accounts_view(self):
|
|
946
1071
|
now = timezone.now()
|
|
947
1072
|
|
|
948
1073
|
for user in [self.teacher_user, self.indy_user, self.student_user]:
|
|
949
|
-
user.date_joined = now - timedelta(
|
|
1074
|
+
user.date_joined = now - timedelta(
|
|
1075
|
+
days=USER_DELETE_UNVERIFIED_ACCOUNT_DAYS + 1
|
|
1076
|
+
)
|
|
950
1077
|
user.save()
|
|
951
1078
|
|
|
952
1079
|
for user_profile in [self.teacher_user_profile, self.indy_user_profile]:
|
|
@@ -1011,7 +1138,9 @@ class TestUser(CronTestCase):
|
|
|
1011
1138
|
new_user=indy_user,
|
|
1012
1139
|
)
|
|
1013
1140
|
|
|
1014
|
-
activity_today = DailyActivity.objects.get_or_create(
|
|
1141
|
+
activity_today = DailyActivity.objects.get_or_create(
|
|
1142
|
+
date=datetime.now().date()
|
|
1143
|
+
)[0]
|
|
1015
1144
|
daily_teacher_count = activity_today.anonymised_unverified_teachers
|
|
1016
1145
|
daily_indy_count = activity_today.anonymised_unverified_independents
|
|
1017
1146
|
|
|
@@ -1034,16 +1163,30 @@ class TestUser(CronTestCase):
|
|
|
1034
1163
|
assert indy_user_active == assert_active
|
|
1035
1164
|
assert student_user_active
|
|
1036
1165
|
|
|
1037
|
-
activity_today = DailyActivity.objects.get_or_create(
|
|
1166
|
+
activity_today = DailyActivity.objects.get_or_create(
|
|
1167
|
+
date=datetime.now().date()
|
|
1168
|
+
)[0]
|
|
1038
1169
|
total_activity = TotalActivity.objects.get(id=1)
|
|
1039
1170
|
|
|
1040
1171
|
if not teacher_user_active:
|
|
1041
|
-
assert
|
|
1042
|
-
|
|
1172
|
+
assert (
|
|
1173
|
+
activity_today.anonymised_unverified_teachers
|
|
1174
|
+
== daily_teacher_count + 1
|
|
1175
|
+
)
|
|
1176
|
+
assert (
|
|
1177
|
+
total_activity.anonymised_unverified_teachers
|
|
1178
|
+
== total_teacher_count + 1
|
|
1179
|
+
)
|
|
1043
1180
|
|
|
1044
1181
|
if not indy_user_active:
|
|
1045
|
-
assert
|
|
1046
|
-
|
|
1182
|
+
assert (
|
|
1183
|
+
activity_today.anonymised_unverified_independents
|
|
1184
|
+
== daily_indy_count + 1
|
|
1185
|
+
)
|
|
1186
|
+
assert (
|
|
1187
|
+
total_activity.anonymised_unverified_independents
|
|
1188
|
+
== total_indy_count + 1
|
|
1189
|
+
)
|
|
1047
1190
|
|
|
1048
1191
|
teacher_user.delete()
|
|
1049
1192
|
indy_user.delete()
|
portal/views/teacher/teach.py
CHANGED
|
@@ -552,15 +552,16 @@ def process_dismiss_student_form(request, formset, klass, access_code):
|
|
|
552
552
|
student.new_user.first_name = data["name"]
|
|
553
553
|
student.new_user.username = data["email"]
|
|
554
554
|
student.new_user.email = data["email"]
|
|
555
|
+
student.user.is_verified = False
|
|
555
556
|
student.save()
|
|
556
557
|
student.new_user.save()
|
|
557
|
-
student.
|
|
558
|
+
student.user.save()
|
|
558
559
|
|
|
559
560
|
# log the data
|
|
560
561
|
joinrelease = JoinReleaseStudent.objects.create(student=student, action_type=JoinReleaseStudent.RELEASE)
|
|
561
562
|
joinrelease.save()
|
|
562
563
|
|
|
563
|
-
send_verification_email(request, student.new_user, data)
|
|
564
|
+
send_verification_email(request, student.new_user, data, school=klass.teacher.school)
|
|
564
565
|
|
|
565
566
|
if not failed_users:
|
|
566
567
|
messages.success(request, "The students have been released successfully from the class.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|