codeforlife-portal 6.41.4__py2.py3-none-any.whl → 6.41.6__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/app_settings.py +3 -0
- cfl_common/common/email_messages.py +0 -58
- cfl_common/common/helpers/data_migration_loader.py +3 -4
- cfl_common/common/helpers/emails.py +27 -45
- cfl_common/common/helpers/generators.py +1 -1
- cfl_common/common/mail.py +116 -0
- cfl_common/common/migrations/0002_emailverification.py +1 -3
- cfl_common/common/migrations/0005_add_worksheets.py +1 -5
- cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +1 -5
- cfl_common/common/migrations/0008_unlock_worksheet_3.py +1 -5
- cfl_common/common/migrations/0017_copy_email_to_username.py +2 -8
- cfl_common/common/migrations/0021_school_is_active.py +7 -7
- cfl_common/common/migrations/0022_school_cleanup.py +9 -9
- cfl_common/common/migrations/0023_userprofile_aimmo_badges.py +4 -4
- cfl_common/common/migrations/0025_schoolteacherinvitation.py +29 -13
- cfl_common/common/migrations/0026_teacher_remove_join_request.py +5 -5
- cfl_common/common/migrations/0027_class_created_by.py +10 -4
- cfl_common/common/migrations/0028_coding_club_downloads.py +5 -5
- cfl_common/common/migrations/0029_dynamicelement.py +6 -6
- cfl_common/common/migrations/0030_add_maintenance_banner.py +1 -3
- cfl_common/common/migrations/0031_improve_admin_panel.py +32 -14
- cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py +3 -3
- cfl_common/common/migrations/0033_password_reset_tracking_fields.py +5 -5
- cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +3 -3
- cfl_common/common/migrations/0035_rename_lockout_fields.py +10 -10
- cfl_common/common/migrations/0037_migrate_email_verification.py +2 -2
- cfl_common/common/migrations/0038_delete_emailverification.py +2 -2
- cfl_common/common/migrations/0039_copy_email_to_username.py +1 -6
- cfl_common/common/migrations/0040_school_county.py +3 -3
- cfl_common/common/migrations/0042_totalactivity.py +7 -7
- cfl_common/common/migrations/0044_update_activity_models.py +9 -9
- cfl_common/common/migrations/0045_otp.py +5 -5
- cfl_common/common/migrations/0046_alter_school_country.py +3 -3
- cfl_common/common/tests/utils/email.py +14 -34
- cfl_common/common/tests/utils/student.py +8 -8
- cfl_common/common/tests/utils/teacher.py +8 -8
- {codeforlife_portal-6.41.4.dist-info → codeforlife_portal-6.41.6.dist-info}/METADATA +2 -2
- {codeforlife_portal-6.41.4.dist-info → codeforlife_portal-6.41.6.dist-info}/RECORD +52 -51
- example_project/portal_test_settings.py +5 -1
- portal/__init__.py +1 -1
- portal/static/portal/img/logo_cfl.png +0 -0
- portal/static/portal/img/logo_cfl_reminder_cards.jpg +0 -0
- portal/tests/test_independent_student.py +30 -17
- portal/tests/test_ratelimit.py +15 -12
- portal/tests/test_teacher.py +35 -21
- portal/tests/test_teacher_student.py +13 -3
- portal/tests/test_views.py +55 -194
- portal/views/cron/user.py +12 -49
- portal/views/teacher/teach.py +6 -3
- {codeforlife_portal-6.41.4.dist-info → codeforlife_portal-6.41.6.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-6.41.4.dist-info → codeforlife_portal-6.41.6.dist-info}/WHEEL +0 -0
- {codeforlife_portal-6.41.4.dist-info → codeforlife_portal-6.41.6.dist-info}/top_level.txt +0 -0
portal/tests/test_views.py
CHANGED
|
@@ -7,7 +7,6 @@ from unittest.mock import ANY, Mock, patch
|
|
|
7
7
|
import PyPDF2
|
|
8
8
|
import pytest
|
|
9
9
|
from aimmo.models import Game
|
|
10
|
-
from common.helpers.emails import NOTIFICATION_EMAIL
|
|
11
10
|
from common.models import (
|
|
12
11
|
Class,
|
|
13
12
|
DailyActivity,
|
|
@@ -55,9 +54,7 @@ class TestTeacherViews(TestCase):
|
|
|
55
54
|
def setUpTestData(cls):
|
|
56
55
|
cls.email, cls.password = signup_teacher_directly()
|
|
57
56
|
_, _, cls.class_access_code = create_class_directly(cls.email)
|
|
58
|
-
_, _, cls.student = create_school_student_directly(
|
|
59
|
-
cls.class_access_code
|
|
60
|
-
)
|
|
57
|
+
_, _, cls.student = create_school_student_directly(cls.class_access_code)
|
|
61
58
|
|
|
62
59
|
def login(self):
|
|
63
60
|
c = Client()
|
|
@@ -66,9 +63,7 @@ class TestTeacherViews(TestCase):
|
|
|
66
63
|
|
|
67
64
|
def test_reminder_cards(self):
|
|
68
65
|
c = self.login()
|
|
69
|
-
url = reverse(
|
|
70
|
-
"teacher_print_reminder_cards", args=[self.class_access_code]
|
|
71
|
-
)
|
|
66
|
+
url = reverse("teacher_print_reminder_cards", args=[self.class_access_code])
|
|
72
67
|
|
|
73
68
|
# First test with 2 dummy students
|
|
74
69
|
NAME1 = "Test name"
|
|
@@ -102,9 +97,7 @@ class TestTeacherViews(TestCase):
|
|
|
102
97
|
# page number
|
|
103
98
|
students_per_page = REMINDER_CARDS_PDF_ROWS * REMINDER_CARDS_PDF_COLUMNS
|
|
104
99
|
for _ in range(len(studentlist), students_per_page + 1):
|
|
105
|
-
studentlist.append(
|
|
106
|
-
{"name": NAME1, "password": PASSWORD1, "login_url": URL}
|
|
107
|
-
)
|
|
100
|
+
studentlist.append({"name": NAME1, "password": PASSWORD1, "login_url": URL})
|
|
108
101
|
|
|
109
102
|
assert len(studentlist) == students_per_page + 1
|
|
110
103
|
|
|
@@ -143,9 +136,7 @@ class TestTeacherViews(TestCase):
|
|
|
143
136
|
reader = csv.reader(io.StringIO(content))
|
|
144
137
|
|
|
145
138
|
access_code = self.class_access_code
|
|
146
|
-
class_url = reverse(
|
|
147
|
-
"student_login", kwargs={"access_code": access_code}
|
|
148
|
-
)
|
|
139
|
+
class_url = reverse("student_login", kwargs={"access_code": access_code})
|
|
149
140
|
row0 = next(reader)
|
|
150
141
|
assert row0[0].strip() == access_code
|
|
151
142
|
assert class_url in row0[1].strip()
|
|
@@ -184,9 +175,7 @@ class TestTeacherViews(TestCase):
|
|
|
184
175
|
|
|
185
176
|
def test_daily_activity_student_details(self):
|
|
186
177
|
c = self.login()
|
|
187
|
-
url = reverse(
|
|
188
|
-
"teacher_print_reminder_cards", args=[self.class_access_code]
|
|
189
|
-
)
|
|
178
|
+
url = reverse("teacher_print_reminder_cards", args=[self.class_access_code])
|
|
190
179
|
|
|
191
180
|
data = {
|
|
192
181
|
"data": json.dumps(
|
|
@@ -249,9 +238,7 @@ class TestLoginViews(TestCase):
|
|
|
249
238
|
teacher_email, teacher_password = signup_teacher_directly()
|
|
250
239
|
create_organisation_directly(teacher_email)
|
|
251
240
|
_, _, class_access_code = create_class_directly(teacher_email)
|
|
252
|
-
student_name, student_password, _ = create_school_student_directly(
|
|
253
|
-
class_access_code
|
|
254
|
-
)
|
|
241
|
+
student_name, student_password, _ = create_school_student_directly(class_access_code)
|
|
255
242
|
|
|
256
243
|
return (
|
|
257
244
|
teacher_email,
|
|
@@ -284,16 +271,9 @@ class TestLoginViews(TestCase):
|
|
|
284
271
|
_, _, name, password, class_access_code = self._set_up_test_data()
|
|
285
272
|
|
|
286
273
|
if next_url:
|
|
287
|
-
url = (
|
|
288
|
-
reverse(
|
|
289
|
-
"student_login", kwargs={"access_code": class_access_code}
|
|
290
|
-
)
|
|
291
|
-
+ "?next=/"
|
|
292
|
-
)
|
|
274
|
+
url = reverse("student_login", kwargs={"access_code": class_access_code}) + "?next=/"
|
|
293
275
|
else:
|
|
294
|
-
url = reverse(
|
|
295
|
-
"student_login", kwargs={"access_code": class_access_code}
|
|
296
|
-
)
|
|
276
|
+
url = reverse("student_login", kwargs={"access_code": class_access_code})
|
|
297
277
|
|
|
298
278
|
c = Client()
|
|
299
279
|
response = c.post(url, {"username": name, "password": password})
|
|
@@ -332,9 +312,7 @@ class TestLoginViews(TestCase):
|
|
|
332
312
|
|
|
333
313
|
def _get_user_class(self, name, class_access_code):
|
|
334
314
|
klass = Class.objects.get(access_code=class_access_code)
|
|
335
|
-
students = Student.objects.filter(
|
|
336
|
-
new_user__first_name__iexact=name, class_field=klass
|
|
337
|
-
)
|
|
315
|
+
students = Student.objects.filter(new_user__first_name__iexact=name, class_field=klass)
|
|
338
316
|
assert len(students) == 1
|
|
339
317
|
user = students[0].new_user
|
|
340
318
|
return user, klass
|
|
@@ -376,9 +354,7 @@ class TestLoginViews(TestCase):
|
|
|
376
354
|
_, _, name, password, class_access_code = self._set_up_test_data()
|
|
377
355
|
|
|
378
356
|
c = Client()
|
|
379
|
-
url = reverse(
|
|
380
|
-
"student_login", kwargs={"access_code": class_access_code}
|
|
381
|
-
)
|
|
357
|
+
url = reverse("student_login", kwargs={"access_code": class_access_code})
|
|
382
358
|
c.post(url, {"username": name, "password": password})
|
|
383
359
|
|
|
384
360
|
# check if there's a UserSession data within the last 10 secs
|
|
@@ -399,9 +375,7 @@ class TestLoginViews(TestCase):
|
|
|
399
375
|
randomname = "randomname"
|
|
400
376
|
|
|
401
377
|
c = Client()
|
|
402
|
-
url = reverse(
|
|
403
|
-
"student_login", kwargs={"access_code": class_access_code}
|
|
404
|
-
)
|
|
378
|
+
url = reverse("student_login", kwargs={"access_code": class_access_code})
|
|
405
379
|
c.post(url, {"username": randomname, "password": "xx"})
|
|
406
380
|
|
|
407
381
|
# check if there's a UserSession data within the last 10 secs
|
|
@@ -427,9 +401,7 @@ class TestLoginViews(TestCase):
|
|
|
427
401
|
|
|
428
402
|
def test_student_direct_login(self):
|
|
429
403
|
_, _, _, _, class_access_code = self._set_up_test_data()
|
|
430
|
-
student, login_id, _, _ = create_student_with_direct_login(
|
|
431
|
-
class_access_code
|
|
432
|
-
)
|
|
404
|
+
student, login_id, _, _ = create_student_with_direct_login(class_access_code)
|
|
433
405
|
|
|
434
406
|
c = Client()
|
|
435
407
|
assert c.login(user_id=student.new_user.id, login_id=login_id) == True
|
|
@@ -551,9 +523,7 @@ class TestViews(TestCase):
|
|
|
551
523
|
c = Client()
|
|
552
524
|
|
|
553
525
|
# Login and check initial data
|
|
554
|
-
url = reverse(
|
|
555
|
-
"student_login", kwargs={"access_code": class_access_code}
|
|
556
|
-
)
|
|
526
|
+
url = reverse("student_login", kwargs={"access_code": class_access_code})
|
|
557
527
|
c.post(url, {"username": student_name, "password": student_password})
|
|
558
528
|
|
|
559
529
|
student_dashboard_url = reverse("student_details")
|
|
@@ -631,9 +601,7 @@ class TestViews(TestCase):
|
|
|
631
601
|
|
|
632
602
|
# try again with the correct password
|
|
633
603
|
url = reverse("delete_account")
|
|
634
|
-
response = c.post(
|
|
635
|
-
url, {"password": password, "unsubscribe_newsletter": "on"}
|
|
636
|
-
)
|
|
604
|
+
response = c.post(url, {"password": password, "unsubscribe_newsletter": "on"})
|
|
637
605
|
|
|
638
606
|
assert response.status_code == 302
|
|
639
607
|
assert response.url == reverse("home")
|
|
@@ -710,9 +678,7 @@ class TestViews(TestCase):
|
|
|
710
678
|
|
|
711
679
|
school_id = school.id
|
|
712
680
|
school_name = school.name
|
|
713
|
-
teachers = Teacher.objects.filter(school=school).order_by(
|
|
714
|
-
"new_user__last_name", "new_user__first_name"
|
|
715
|
-
)
|
|
681
|
+
teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name")
|
|
716
682
|
assert len(teachers) == 3
|
|
717
683
|
|
|
718
684
|
# one of the remaining teachers should be admin (the second in our case, as it's alphabetical)
|
|
@@ -742,9 +708,7 @@ class TestViews(TestCase):
|
|
|
742
708
|
c.post(url, {"password": password3})
|
|
743
709
|
|
|
744
710
|
# 2 teachers left
|
|
745
|
-
teachers = Teacher.objects.filter(school=school).order_by(
|
|
746
|
-
"new_user__last_name", "new_user__first_name"
|
|
747
|
-
)
|
|
711
|
+
teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name")
|
|
748
712
|
assert len(teachers) == 2
|
|
749
713
|
|
|
750
714
|
# teacher2 should still be admin, teacher4 is not passed admin role because there is teacher2
|
|
@@ -756,9 +720,7 @@ class TestViews(TestCase):
|
|
|
756
720
|
# delete teacher4
|
|
757
721
|
anonymise(user4)
|
|
758
722
|
|
|
759
|
-
teachers = Teacher.objects.filter(school=school).order_by(
|
|
760
|
-
"new_user__last_name", "new_user__first_name"
|
|
761
|
-
)
|
|
723
|
+
teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name")
|
|
762
724
|
assert len(teachers) == 1
|
|
763
725
|
u = User.objects.get(id=usrid2)
|
|
764
726
|
assert u.new_teacher.is_admin
|
|
@@ -814,15 +776,14 @@ class TestViews(TestCase):
|
|
|
814
776
|
|
|
815
777
|
c.logout()
|
|
816
778
|
|
|
817
|
-
|
|
779
|
+
@patch("common.helpers.emails.send_dotdigital_email")
|
|
780
|
+
def test_registrations_increment_data(self, mock_send_dotdigital_email: Mock):
|
|
818
781
|
c = Client()
|
|
819
782
|
|
|
820
783
|
total_activity = TotalActivity.objects.get(id=1)
|
|
821
784
|
teacher_registration_count = total_activity.teacher_registrations
|
|
822
785
|
student_registration_count = total_activity.student_registrations
|
|
823
|
-
independent_registration_count =
|
|
824
|
-
total_activity.independent_registrations
|
|
825
|
-
)
|
|
786
|
+
independent_registration_count = total_activity.independent_registrations
|
|
826
787
|
|
|
827
788
|
response = c.post(
|
|
828
789
|
reverse("register"),
|
|
@@ -838,13 +799,11 @@ class TestViews(TestCase):
|
|
|
838
799
|
)
|
|
839
800
|
|
|
840
801
|
assert response.status_code == 302
|
|
802
|
+
mock_send_dotdigital_email.assert_called_once()
|
|
841
803
|
|
|
842
804
|
total_activity = TotalActivity.objects.get(id=1)
|
|
843
805
|
|
|
844
|
-
assert
|
|
845
|
-
total_activity.teacher_registrations
|
|
846
|
-
== teacher_registration_count + 1
|
|
847
|
-
)
|
|
806
|
+
assert total_activity.teacher_registrations == teacher_registration_count + 1
|
|
848
807
|
|
|
849
808
|
response = c.post(
|
|
850
809
|
reverse("register"),
|
|
@@ -862,13 +821,11 @@ class TestViews(TestCase):
|
|
|
862
821
|
)
|
|
863
822
|
|
|
864
823
|
assert response.status_code == 302
|
|
824
|
+
mock_send_dotdigital_email.assert_called()
|
|
865
825
|
|
|
866
826
|
total_activity = TotalActivity.objects.get(id=1)
|
|
867
827
|
|
|
868
|
-
assert
|
|
869
|
-
total_activity.independent_registrations
|
|
870
|
-
== independent_registration_count + 1
|
|
871
|
-
)
|
|
828
|
+
assert total_activity.independent_registrations == independent_registration_count + 1
|
|
872
829
|
|
|
873
830
|
teacher_email, teacher_password = signup_teacher_directly()
|
|
874
831
|
create_organisation_directly(teacher_email)
|
|
@@ -884,10 +841,7 @@ class TestViews(TestCase):
|
|
|
884
841
|
|
|
885
842
|
total_activity = TotalActivity.objects.get(id=1)
|
|
886
843
|
|
|
887
|
-
assert
|
|
888
|
-
total_activity.student_registrations
|
|
889
|
-
== student_registration_count + 3
|
|
890
|
-
)
|
|
844
|
+
assert total_activity.student_registrations == student_registration_count + 3
|
|
891
845
|
|
|
892
846
|
|
|
893
847
|
# CRON view tests
|
|
@@ -906,12 +860,8 @@ class CronTestClient(APIClient):
|
|
|
906
860
|
secure=False,
|
|
907
861
|
**extra,
|
|
908
862
|
):
|
|
909
|
-
wsgi_response = super().generic(
|
|
910
|
-
|
|
911
|
-
)
|
|
912
|
-
assert (
|
|
913
|
-
200 <= wsgi_response.status_code < 300
|
|
914
|
-
), f"Response has error status code: {wsgi_response.status_code}"
|
|
863
|
+
wsgi_response = super().generic(method, path, data, content_type, secure, **extra)
|
|
864
|
+
assert 200 <= wsgi_response.status_code < 300, f"Response has error status code: {wsgi_response.status_code}"
|
|
915
865
|
|
|
916
866
|
return wsgi_response
|
|
917
867
|
|
|
@@ -930,34 +880,27 @@ class TestUser(CronTestCase):
|
|
|
930
880
|
indy_email, _, _ = create_independent_student_directly()
|
|
931
881
|
|
|
932
882
|
self.teacher_user = User.objects.get(email=teacher_email)
|
|
933
|
-
self.teacher_user_profile = UserProfile.objects.get(
|
|
934
|
-
user=self.teacher_user
|
|
935
|
-
)
|
|
883
|
+
self.teacher_user_profile = UserProfile.objects.get(user=self.teacher_user)
|
|
936
884
|
|
|
937
885
|
self.indy_user = User.objects.get(email=indy_email)
|
|
938
886
|
self.indy_user_profile = UserProfile.objects.get(user=self.indy_user)
|
|
939
887
|
|
|
940
888
|
self.student_user: User = student.new_user
|
|
941
889
|
|
|
890
|
+
@patch("portal.views.cron.user.send_dotdigital_email")
|
|
942
891
|
def send_verify_email_reminder(
|
|
943
892
|
self,
|
|
944
893
|
days: int,
|
|
945
894
|
is_verified: bool,
|
|
946
895
|
view_name: str,
|
|
947
|
-
send_email: Mock,
|
|
948
896
|
assert_called: bool,
|
|
897
|
+
mock_send_dotdigital_email: Mock,
|
|
949
898
|
):
|
|
950
|
-
self.teacher_user.date_joined = timezone.now() - timedelta(
|
|
951
|
-
days=days, hours=12
|
|
952
|
-
)
|
|
899
|
+
self.teacher_user.date_joined = timezone.now() - timedelta(days=days, hours=12)
|
|
953
900
|
self.teacher_user.save()
|
|
954
|
-
self.student_user.date_joined = timezone.now() - timedelta(
|
|
955
|
-
days=days, hours=12
|
|
956
|
-
)
|
|
901
|
+
self.student_user.date_joined = timezone.now() - timedelta(days=days, hours=12)
|
|
957
902
|
self.student_user.save()
|
|
958
|
-
self.indy_user.date_joined = timezone.now() - timedelta(
|
|
959
|
-
days=days, hours=12
|
|
960
|
-
)
|
|
903
|
+
self.indy_user.date_joined = timezone.now() - timedelta(days=days, hours=12)
|
|
961
904
|
self.indy_user.save()
|
|
962
905
|
|
|
963
906
|
self.teacher_user_profile.is_verified = is_verified
|
|
@@ -968,100 +911,34 @@ class TestUser(CronTestCase):
|
|
|
968
911
|
self.client.get(reverse(view_name))
|
|
969
912
|
|
|
970
913
|
if assert_called:
|
|
971
|
-
|
|
972
|
-
sender=NOTIFICATION_EMAIL,
|
|
973
|
-
recipients=[self.teacher_user.email],
|
|
974
|
-
subject=ANY,
|
|
975
|
-
title=ANY,
|
|
976
|
-
text_content=ANY,
|
|
977
|
-
replace_url=ANY,
|
|
978
|
-
)
|
|
914
|
+
mock_send_dotdigital_email.assert_any_call(ANY, [self.teacher_user.email], personalization_values=ANY)
|
|
979
915
|
|
|
980
|
-
|
|
981
|
-
sender=NOTIFICATION_EMAIL,
|
|
982
|
-
recipients=[self.indy_user.email],
|
|
983
|
-
subject=ANY,
|
|
984
|
-
title=ANY,
|
|
985
|
-
text_content=ANY,
|
|
986
|
-
replace_url=ANY,
|
|
987
|
-
)
|
|
916
|
+
mock_send_dotdigital_email.assert_any_call(ANY, [self.indy_user.email], personalization_values=ANY)
|
|
988
917
|
|
|
989
918
|
# Check only two emails are sent - the student should never be included.
|
|
990
|
-
assert
|
|
919
|
+
assert mock_send_dotdigital_email.call_count == 2
|
|
991
920
|
else:
|
|
992
|
-
|
|
921
|
+
mock_send_dotdigital_email.assert_not_called()
|
|
993
922
|
|
|
994
|
-
|
|
923
|
+
mock_send_dotdigital_email.reset_mock()
|
|
995
924
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
self.send_verify_email_reminder(
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
view_name="first-verify-email-reminder",
|
|
1002
|
-
send_email=send_email,
|
|
1003
|
-
assert_called=False,
|
|
1004
|
-
)
|
|
1005
|
-
self.send_verify_email_reminder(
|
|
1006
|
-
days=7,
|
|
1007
|
-
is_verified=False,
|
|
1008
|
-
view_name="first-verify-email-reminder",
|
|
1009
|
-
send_email=send_email,
|
|
1010
|
-
assert_called=True,
|
|
1011
|
-
)
|
|
1012
|
-
self.send_verify_email_reminder(
|
|
1013
|
-
days=7,
|
|
1014
|
-
is_verified=True,
|
|
1015
|
-
view_name="first-verify-email-reminder",
|
|
1016
|
-
send_email=send_email,
|
|
1017
|
-
assert_called=False,
|
|
1018
|
-
)
|
|
1019
|
-
self.send_verify_email_reminder(
|
|
1020
|
-
days=8,
|
|
1021
|
-
is_verified=False,
|
|
1022
|
-
view_name="first-verify-email-reminder",
|
|
1023
|
-
send_email=send_email,
|
|
1024
|
-
assert_called=False,
|
|
1025
|
-
)
|
|
925
|
+
def test_first_verify_email_reminder_view(self):
|
|
926
|
+
self.send_verify_email_reminder(6, False, "first-verify-email-reminder", False)
|
|
927
|
+
self.send_verify_email_reminder(7, False, "first-verify-email-reminder", True)
|
|
928
|
+
self.send_verify_email_reminder(7, True, "first-verify-email-reminder", False)
|
|
929
|
+
self.send_verify_email_reminder(8, False, "first-verify-email-reminder", False)
|
|
1026
930
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
self.send_verify_email_reminder(
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
view_name="second-verify-email-reminder",
|
|
1033
|
-
send_email=send_email,
|
|
1034
|
-
assert_called=False,
|
|
1035
|
-
)
|
|
1036
|
-
self.send_verify_email_reminder(
|
|
1037
|
-
days=14,
|
|
1038
|
-
is_verified=False,
|
|
1039
|
-
view_name="second-verify-email-reminder",
|
|
1040
|
-
send_email=send_email,
|
|
1041
|
-
assert_called=True,
|
|
1042
|
-
)
|
|
1043
|
-
self.send_verify_email_reminder(
|
|
1044
|
-
days=14,
|
|
1045
|
-
is_verified=True,
|
|
1046
|
-
view_name="second-verify-email-reminder",
|
|
1047
|
-
send_email=send_email,
|
|
1048
|
-
assert_called=False,
|
|
1049
|
-
)
|
|
1050
|
-
self.send_verify_email_reminder(
|
|
1051
|
-
days=15,
|
|
1052
|
-
is_verified=False,
|
|
1053
|
-
view_name="second-verify-email-reminder",
|
|
1054
|
-
send_email=send_email,
|
|
1055
|
-
assert_called=False,
|
|
1056
|
-
)
|
|
931
|
+
def test_second_verify_email_reminder_view(self):
|
|
932
|
+
self.send_verify_email_reminder(13, False, "second-verify-email-reminder", False)
|
|
933
|
+
self.send_verify_email_reminder(14, False, "second-verify-email-reminder", True)
|
|
934
|
+
self.send_verify_email_reminder(14, True, "second-verify-email-reminder", False)
|
|
935
|
+
self.send_verify_email_reminder(15, False, "second-verify-email-reminder", False)
|
|
1057
936
|
|
|
1058
937
|
def test_anonymise_unverified_accounts_view(self):
|
|
1059
938
|
now = timezone.now()
|
|
1060
939
|
|
|
1061
940
|
for user in [self.teacher_user, self.indy_user, self.student_user]:
|
|
1062
|
-
user.date_joined = now - timedelta(
|
|
1063
|
-
days=USER_DELETE_UNVERIFIED_ACCOUNT_DAYS + 1
|
|
1064
|
-
)
|
|
941
|
+
user.date_joined = now - timedelta(days=USER_DELETE_UNVERIFIED_ACCOUNT_DAYS + 1)
|
|
1065
942
|
user.save()
|
|
1066
943
|
|
|
1067
944
|
for user_profile in [self.teacher_user_profile, self.indy_user_profile]:
|
|
@@ -1126,9 +1003,7 @@ class TestUser(CronTestCase):
|
|
|
1126
1003
|
new_user=indy_user,
|
|
1127
1004
|
)
|
|
1128
1005
|
|
|
1129
|
-
activity_today = DailyActivity.objects.get_or_create(
|
|
1130
|
-
date=datetime.now().date()
|
|
1131
|
-
)[0]
|
|
1006
|
+
activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
|
|
1132
1007
|
daily_teacher_count = activity_today.anonymised_unverified_teachers
|
|
1133
1008
|
daily_indy_count = activity_today.anonymised_unverified_independents
|
|
1134
1009
|
|
|
@@ -1151,30 +1026,16 @@ class TestUser(CronTestCase):
|
|
|
1151
1026
|
assert indy_user_active == assert_active
|
|
1152
1027
|
assert student_user_active
|
|
1153
1028
|
|
|
1154
|
-
activity_today = DailyActivity.objects.get_or_create(
|
|
1155
|
-
date=datetime.now().date()
|
|
1156
|
-
)[0]
|
|
1029
|
+
activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
|
|
1157
1030
|
total_activity = TotalActivity.objects.get(id=1)
|
|
1158
1031
|
|
|
1159
1032
|
if not teacher_user_active:
|
|
1160
|
-
assert
|
|
1161
|
-
|
|
1162
|
-
== daily_teacher_count + 1
|
|
1163
|
-
)
|
|
1164
|
-
assert (
|
|
1165
|
-
total_activity.anonymised_unverified_teachers
|
|
1166
|
-
== total_teacher_count + 1
|
|
1167
|
-
)
|
|
1033
|
+
assert activity_today.anonymised_unverified_teachers == daily_teacher_count + 1
|
|
1034
|
+
assert total_activity.anonymised_unverified_teachers == total_teacher_count + 1
|
|
1168
1035
|
|
|
1169
1036
|
if not indy_user_active:
|
|
1170
|
-
assert
|
|
1171
|
-
|
|
1172
|
-
== daily_indy_count + 1
|
|
1173
|
-
)
|
|
1174
|
-
assert (
|
|
1175
|
-
total_activity.anonymised_unverified_independents
|
|
1176
|
-
== total_indy_count + 1
|
|
1177
|
-
)
|
|
1037
|
+
assert activity_today.anonymised_unverified_independents == daily_indy_count + 1
|
|
1038
|
+
assert total_activity.anonymised_unverified_independents == total_indy_count + 1
|
|
1178
1039
|
|
|
1179
1040
|
teacher_user.delete()
|
|
1180
1041
|
indy_user.delete()
|
portal/views/cron/user.py
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from datetime import
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
3
|
|
|
4
|
-
from common.helpers.emails import
|
|
5
|
-
|
|
6
|
-
generate_token_for_email,
|
|
7
|
-
send_email,
|
|
8
|
-
)
|
|
4
|
+
from common.helpers.emails import generate_token_for_email
|
|
5
|
+
from common.mail import campaign_ids, send_dotdigital_email
|
|
9
6
|
from common.models import DailyActivity, TotalActivity
|
|
10
7
|
from django.contrib.auth.models import User
|
|
11
8
|
from django.db.models import F
|
|
@@ -14,29 +11,14 @@ from django.urls import reverse
|
|
|
14
11
|
from django.utils import timezone
|
|
15
12
|
from rest_framework.response import Response
|
|
16
13
|
from rest_framework.views import APIView
|
|
14
|
+
|
|
17
15
|
from portal.views.api import anonymise
|
|
18
16
|
|
|
19
17
|
from ...mixins import CronMixin
|
|
20
18
|
|
|
21
19
|
# TODO: move email templates to DotDigital.
|
|
22
20
|
USER_1ST_VERIFY_EMAIL_REMINDER_DAYS = 7
|
|
23
|
-
USER_1ST_VERIFY_EMAIL_REMINDER_TEXT = (
|
|
24
|
-
"Please go to the link below to verify your email address:"
|
|
25
|
-
"\n{email_verification_url}."
|
|
26
|
-
"\nYou will not be able to use your account until it is verified."
|
|
27
|
-
"\n\nBy activating the account you confirm that you have read and agreed to"
|
|
28
|
-
" our terms ({terms_url}) and our privacy notice ({privacy_notice_url}). If"
|
|
29
|
-
" your account is not verified within 12 days we will delete it."
|
|
30
|
-
)
|
|
31
21
|
USER_2ND_VERIFY_EMAIL_REMINDER_DAYS = 14
|
|
32
|
-
USER_2ND_VERIFY_EMAIL_REMINDER_TEXT = (
|
|
33
|
-
"Please go to the link below to verify your email address:"
|
|
34
|
-
"\n{email_verification_url}."
|
|
35
|
-
"\nYou will not be able to use your account until it is verified."
|
|
36
|
-
"\n\nBy activating the account you confirm that you have read and agreed to"
|
|
37
|
-
" our terms ({terms_url}) and our privacy notice ({privacy_notice_url}). If"
|
|
38
|
-
" your account is not verified within 5 days we will delete it."
|
|
39
|
-
)
|
|
40
22
|
USER_DELETE_UNVERIFIED_ACCOUNT_DAYS = 19
|
|
41
23
|
|
|
42
24
|
|
|
@@ -87,9 +69,6 @@ class FirstVerifyEmailReminderView(CronMixin, APIView):
|
|
|
87
69
|
logging.info(f"{user_count} emails unverified.")
|
|
88
70
|
|
|
89
71
|
if user_count > 0:
|
|
90
|
-
terms_url = build_absolute_google_uri(request, reverse("terms"))
|
|
91
|
-
privacy_notice_url = build_absolute_google_uri(request, reverse("privacy_notice"))
|
|
92
|
-
|
|
93
72
|
sent_email_count = 0
|
|
94
73
|
for email in user_queryset.values_list("email", flat=True).iterator(chunk_size=500):
|
|
95
74
|
email_verification_url = build_absolute_google_uri(
|
|
@@ -101,17 +80,10 @@ class FirstVerifyEmailReminderView(CronMixin, APIView):
|
|
|
101
80
|
)
|
|
102
81
|
|
|
103
82
|
try:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
title="Awaiting verification",
|
|
109
|
-
text_content=USER_1ST_VERIFY_EMAIL_REMINDER_TEXT.format(
|
|
110
|
-
email_verification_url=email_verification_url,
|
|
111
|
-
terms_url=terms_url,
|
|
112
|
-
privacy_notice_url=privacy_notice_url,
|
|
113
|
-
),
|
|
114
|
-
replace_url={"verify_url": email_verification_url},
|
|
83
|
+
send_dotdigital_email(
|
|
84
|
+
campaign_ids["verify_new_user_first_reminder"],
|
|
85
|
+
[email],
|
|
86
|
+
personalization_values={"VERIFICATION_LINK": email_verification_url},
|
|
115
87
|
)
|
|
116
88
|
|
|
117
89
|
sent_email_count += 1
|
|
@@ -135,8 +107,6 @@ class SecondVerifyEmailReminderView(CronMixin, APIView):
|
|
|
135
107
|
logging.info(f"{user_count} emails unverified.")
|
|
136
108
|
|
|
137
109
|
if user_count > 0:
|
|
138
|
-
terms_url = build_absolute_google_uri(request, reverse("terms"))
|
|
139
|
-
privacy_notice_url = build_absolute_google_uri(request, reverse("privacy_notice"))
|
|
140
110
|
|
|
141
111
|
sent_email_count = 0
|
|
142
112
|
for email in user_queryset.values_list("email", flat=True).iterator(chunk_size=500):
|
|
@@ -149,17 +119,10 @@ class SecondVerifyEmailReminderView(CronMixin, APIView):
|
|
|
149
119
|
)
|
|
150
120
|
|
|
151
121
|
try:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
title="Your account needs verification",
|
|
157
|
-
text_content=USER_2ND_VERIFY_EMAIL_REMINDER_TEXT.format(
|
|
158
|
-
email_verification_url=email_verification_url,
|
|
159
|
-
terms_url=terms_url,
|
|
160
|
-
privacy_notice_url=privacy_notice_url,
|
|
161
|
-
),
|
|
162
|
-
replace_url={"verify_url": email_verification_url},
|
|
122
|
+
send_dotdigital_email(
|
|
123
|
+
campaign_ids["verify_new_user_second_reminder"],
|
|
124
|
+
[email],
|
|
125
|
+
personalization_values={"VERIFICATION_LINK": email_verification_url},
|
|
163
126
|
)
|
|
164
127
|
|
|
165
128
|
sent_email_count += 1
|
portal/views/teacher/teach.py
CHANGED
|
@@ -804,16 +804,19 @@ def teacher_print_reminder_cards(request, access_code):
|
|
|
804
804
|
p.setStrokeColor(black)
|
|
805
805
|
p.rect(left, bottom, CARD_WIDTH, CARD_HEIGHT)
|
|
806
806
|
|
|
807
|
+
card_logo_height = CARD_HEIGHT - INTER_CARD_MARGIN * 2
|
|
808
|
+
|
|
807
809
|
# logo
|
|
808
810
|
p.drawImage(
|
|
809
811
|
logo_image,
|
|
810
|
-
left,
|
|
812
|
+
left + INTER_CARD_MARGIN,
|
|
811
813
|
bottom + INTER_CARD_MARGIN,
|
|
812
|
-
height=
|
|
814
|
+
height=card_logo_height,
|
|
813
815
|
preserveAspectRatio=True,
|
|
816
|
+
anchor="w",
|
|
814
817
|
)
|
|
815
818
|
|
|
816
|
-
text_left = left + logo_image.getSize()[0]
|
|
819
|
+
text_left = left + INTER_CARD_MARGIN + (logo_image.getSize()[0] / logo_image.getSize()[1]) * card_logo_height
|
|
817
820
|
|
|
818
821
|
# student details
|
|
819
822
|
p.setFillColor(black)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|