codeforlife-portal 6.46.1__py2.py3-none-any.whl → 7.1.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.

Files changed (69) hide show
  1. cfl_common/common/csp_config.py +0 -2
  2. cfl_common/common/mail.py +31 -6
  3. cfl_common/common/migrations/0005_add_worksheets.py +1 -5
  4. cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +1 -5
  5. cfl_common/common/migrations/0054_delete_aimmo_models.py +20 -0
  6. cfl_common/common/models.py +0 -25
  7. {codeforlife_portal-6.46.1.dist-info → codeforlife_portal-7.1.0.dist-info}/METADATA +3 -4
  8. {codeforlife_portal-6.46.1.dist-info → codeforlife_portal-7.1.0.dist-info}/RECORD +44 -68
  9. example_project/portal_test_settings.py +0 -1
  10. example_project/settings.py +0 -1
  11. example_project/urls.py +0 -2
  12. portal/__init__.py +1 -1
  13. portal/static/portal/sass/partials/_banners.scss +0 -177
  14. portal/static/portal/sass/partials/_buttons.scss +0 -12
  15. portal/static/portal/sass/partials/_grids.scss +0 -53
  16. portal/static/portal/sass/partials/_text.scss +1 -10
  17. portal/static/portal/sass/styles.scss +0 -1
  18. portal/strings/play.py +1 -2
  19. portal/strings/teacher_resources.py +0 -10
  20. portal/templates/portal/about.html +91 -60
  21. portal/templates/portal/contribute.html +45 -49
  22. portal/templates/portal/partials/header.html +0 -12
  23. portal/templates/portal/play/independent_student_dashboard.html +12 -25
  24. portal/templates/portal/play/student_dashboard.html +16 -34
  25. portal/templates/portal/play.html +36 -49
  26. portal/templates/portal/register.html +1 -1
  27. portal/templates/portal/teach.html +37 -55
  28. portal/templates/portal/ten_year_map.html +9 -9
  29. portal/templatetags/app_tags.py +13 -28
  30. portal/tests/conftest.py +4 -16
  31. portal/tests/pageObjects/portal/base_page.py +20 -20
  32. portal/tests/snapshots/snap_test_partials.py +0 -452
  33. portal/tests/test_class.py +213 -45
  34. portal/tests/test_independent_student.py +0 -9
  35. portal/tests/test_partials.py +6 -56
  36. portal/tests/test_teacher.py +221 -285
  37. portal/tests/test_views.py +257 -73
  38. portal/urls.py +38 -20
  39. portal/views/cron/user.py +158 -15
  40. portal/views/student/play.py +36 -25
  41. portal/views/teacher/teach.py +0 -5
  42. cfl_common/common/tests/test_migration_aimmo_characters.py +0 -29
  43. portal/forms/add_game.py +0 -29
  44. portal/static/portal/img/kurono_hero.jpg +0 -0
  45. portal/static/portal/img/kurono_landing_hero.png +0 -0
  46. portal/static/portal/img/kurono_logo.svg +0 -1
  47. portal/static/portal/img/kurono_logo_grey_background.svg +0 -1
  48. portal/static/portal/img/kurono_logo_mark.svg +0 -1
  49. portal/static/portal/img/kurono_resources_hero.jpg +0 -0
  50. portal/static/portal/img/kurono_story.png +0 -0
  51. portal/static/portal/img/thumbnail_educate_kurono.png +0 -0
  52. portal/static/portal/img/thumbnail_kurono_resources.png +0 -0
  53. portal/static/portal/img/thumbnail_play_kurono.png +0 -0
  54. portal/static/portal/js/aimmoGame.js +0 -106
  55. portal/static/portal/sass/partials/_videos.scss +0 -10
  56. portal/static/portal/video/aimmo_play_now_background_video.mp4 +0 -0
  57. portal/strings/student_aimmo_dashboard.py +0 -6
  58. portal/templates/portal/partials/aimmo_games_table.html +0 -89
  59. portal/templates/portal/play/student_aimmo_dashboard.html +0 -46
  60. portal/templates/portal/teach/teacher_aimmo_dashboard.html +0 -95
  61. portal/templatetags/character_list_tags.py +0 -16
  62. portal/tests/pageObjects/portal/kurono_teacher_dashboard_page.py +0 -49
  63. portal/tests/test_aimmo_dashboards.py +0 -206
  64. portal/tests/utils/aimmo_games.py +0 -30
  65. portal/views/aimmo/__init__.py +0 -0
  66. portal/views/aimmo/dashboard.py +0 -105
  67. {codeforlife_portal-6.46.1.dist-info → codeforlife_portal-7.1.0.dist-info}/LICENSE.md +0 -0
  68. {codeforlife_portal-6.46.1.dist-info → codeforlife_portal-7.1.0.dist-info}/WHEEL +0 -0
  69. {codeforlife_portal-6.46.1.dist-info → codeforlife_portal-7.1.0.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,6 @@ from unittest.mock import ANY, Mock, patch
6
6
 
7
7
  import PyPDF2
8
8
  import pytest
9
- from aimmo.models import Game
10
9
  from common.models import (
11
10
  Class,
12
11
  DailyActivity,
@@ -17,6 +16,7 @@ from common.models import (
17
16
  UserProfile,
18
17
  UserSession,
19
18
  )
19
+ from common.mail import campaign_ids
20
20
  from common.tests.utils.classes import create_class_directly
21
21
  from common.tests.utils.organisation import (
22
22
  create_organisation_directly,
@@ -55,7 +55,9 @@ class TestTeacherViews(TestCase):
55
55
  cls.email, cls.password = signup_teacher_directly()
56
56
  cls.school = create_organisation_directly(cls.email)
57
57
  _, _, cls.class_access_code = create_class_directly(cls.email)
58
- _, cls.password_student, cls.student = create_school_student_directly(cls.class_access_code)
58
+ _, cls.password_student, cls.student = create_school_student_directly(
59
+ cls.class_access_code
60
+ )
59
61
 
60
62
  def login(self):
61
63
  c = Client()
@@ -64,7 +66,9 @@ class TestTeacherViews(TestCase):
64
66
 
65
67
  def test_reminder_cards(self):
66
68
  c = self.login()
67
- url = reverse("teacher_print_reminder_cards", args=[self.class_access_code])
69
+ url = reverse(
70
+ "teacher_print_reminder_cards", args=[self.class_access_code]
71
+ )
68
72
 
69
73
  # First test with 2 dummy students
70
74
  NAME1 = "Test name"
@@ -98,7 +102,9 @@ class TestTeacherViews(TestCase):
98
102
  # page number
99
103
  students_per_page = REMINDER_CARDS_PDF_ROWS * REMINDER_CARDS_PDF_COLUMNS
100
104
  for _ in range(len(studentlist), students_per_page + 1):
101
- studentlist.append({"name": NAME1, "password": PASSWORD1, "login_url": URL})
105
+ studentlist.append(
106
+ {"name": NAME1, "password": PASSWORD1, "login_url": URL}
107
+ )
102
108
 
103
109
  assert len(studentlist) == students_per_page + 1
104
110
 
@@ -137,7 +143,9 @@ class TestTeacherViews(TestCase):
137
143
  reader = csv.reader(io.StringIO(content))
138
144
 
139
145
  access_code = self.class_access_code
140
- class_url = reverse("student_login", kwargs={"access_code": access_code})
146
+ class_url = reverse(
147
+ "student_login", kwargs={"access_code": access_code}
148
+ )
141
149
  row0 = next(reader)
142
150
  assert row0[0].strip() == access_code
143
151
  assert class_url in row0[1].strip()
@@ -176,7 +184,9 @@ class TestTeacherViews(TestCase):
176
184
 
177
185
  def test_daily_activity_student_details(self):
178
186
  c = self.login()
179
- url = reverse("teacher_print_reminder_cards", args=[self.class_access_code])
187
+ url = reverse(
188
+ "teacher_print_reminder_cards", args=[self.class_access_code]
189
+ )
180
190
 
181
191
  data = {
182
192
  "data": json.dumps(
@@ -224,7 +234,9 @@ class TestTeacherViews(TestCase):
224
234
 
225
235
  def test_release_verified_student(self):
226
236
  c = Client()
227
- student_login_url = reverse("student_login", args=[self.class_access_code])
237
+ student_login_url = reverse(
238
+ "student_login", args=[self.class_access_code]
239
+ )
228
240
  response = c.post(
229
241
  student_login_url,
230
242
  {
@@ -240,7 +252,9 @@ class TestTeacherViews(TestCase):
240
252
  c.logout()
241
253
  c.login(username=self.email, password=self.password)
242
254
 
243
- release_url = reverse("teacher_dismiss_students", args=[self.class_access_code])
255
+ release_url = reverse(
256
+ "teacher_dismiss_students", args=[self.class_access_code]
257
+ )
244
258
  response = c.post(
245
259
  release_url,
246
260
  {
@@ -277,7 +291,9 @@ class TestLoginViews(TestCase):
277
291
  teacher_email, teacher_password = signup_teacher_directly()
278
292
  create_organisation_directly(teacher_email)
279
293
  _, _, class_access_code = create_class_directly(teacher_email)
280
- student_name, student_password, _ = create_school_student_directly(class_access_code)
294
+ student_name, student_password, _ = create_school_student_directly(
295
+ class_access_code
296
+ )
281
297
 
282
298
  return (
283
299
  teacher_email,
@@ -310,9 +326,16 @@ class TestLoginViews(TestCase):
310
326
  _, _, name, password, class_access_code = self._set_up_test_data()
311
327
 
312
328
  if next_url:
313
- url = reverse("student_login", kwargs={"access_code": class_access_code}) + "?next=/"
329
+ url = (
330
+ reverse(
331
+ "student_login", kwargs={"access_code": class_access_code}
332
+ )
333
+ + "?next=/"
334
+ )
314
335
  else:
315
- url = reverse("student_login", kwargs={"access_code": class_access_code})
336
+ url = reverse(
337
+ "student_login", kwargs={"access_code": class_access_code}
338
+ )
316
339
 
317
340
  c = Client()
318
341
  response = c.post(url, {"username": name, "password": password})
@@ -351,7 +374,9 @@ class TestLoginViews(TestCase):
351
374
 
352
375
  def _get_user_class(self, name, class_access_code):
353
376
  klass = Class.objects.get(access_code=class_access_code)
354
- students = Student.objects.filter(new_user__first_name__iexact=name, class_field=klass)
377
+ students = Student.objects.filter(
378
+ new_user__first_name__iexact=name, class_field=klass
379
+ )
355
380
  assert len(students) == 1
356
381
  user = students[0].new_user
357
382
  return user, klass
@@ -393,7 +418,9 @@ class TestLoginViews(TestCase):
393
418
  _, _, name, password, class_access_code = self._set_up_test_data()
394
419
 
395
420
  c = Client()
396
- url = reverse("student_login", kwargs={"access_code": class_access_code})
421
+ url = reverse(
422
+ "student_login", kwargs={"access_code": class_access_code}
423
+ )
397
424
  c.post(url, {"username": name, "password": password})
398
425
 
399
426
  # check if there's a UserSession data within the last 10 secs
@@ -414,7 +441,9 @@ class TestLoginViews(TestCase):
414
441
  randomname = "randomname"
415
442
 
416
443
  c = Client()
417
- url = reverse("student_login", kwargs={"access_code": class_access_code})
444
+ url = reverse(
445
+ "student_login", kwargs={"access_code": class_access_code}
446
+ )
418
447
  c.post(url, {"username": randomname, "password": "xx"})
419
448
 
420
449
  # check if there's a UserSession data within the last 10 secs
@@ -440,7 +469,9 @@ class TestLoginViews(TestCase):
440
469
 
441
470
  def test_student_direct_login(self):
442
471
  _, _, _, _, class_access_code = self._set_up_test_data()
443
- student, login_id, _, _ = create_student_with_direct_login(class_access_code)
472
+ student, login_id, _, _ = create_student_with_direct_login(
473
+ class_access_code
474
+ )
444
475
 
445
476
  c = Client()
446
477
  assert c.login(user_id=student.new_user.id, login_id=login_id) == True
@@ -543,7 +574,8 @@ class TestViews(TestCase):
543
574
  "total_available_score": 2040,
544
575
  }
545
576
 
546
- # Expected context data when a student has also attempted some custom RR levels
577
+ # Expected context data when a student has also attempted some custom RR
578
+ # levels
547
579
  EXPECTED_DATA_WITH_CUSTOM_ATTEMPTS = {
548
580
  "num_completed": 2,
549
581
  "num_top_scores": 1,
@@ -553,22 +585,12 @@ class TestViews(TestCase):
553
585
  "total_custom_available_score": 20,
554
586
  }
555
587
 
556
- # Expected context data when a student also has access to a Kurono game
557
- EXPECTED_DATA_WITH_KURONO_GAME = {
558
- "num_completed": 2,
559
- "num_top_scores": 1,
560
- "total_score": 39,
561
- "total_available_score": 2040,
562
- "total_custom_score": 10,
563
- "total_custom_available_score": 20,
564
- "worksheet_id": 3,
565
- "worksheet_image": "images/worksheets/ancient.jpg",
566
- }
567
-
568
588
  c = Client()
569
589
 
570
590
  # Login and check initial data
571
- url = reverse("student_login", kwargs={"access_code": class_access_code})
591
+ url = reverse(
592
+ "student_login", kwargs={"access_code": class_access_code}
593
+ )
572
594
  c.post(url, {"username": student_name, "password": student_password})
573
595
 
574
596
  student_dashboard_url = reverse("student_details")
@@ -589,9 +611,9 @@ class TestViews(TestCase):
589
611
  assert response.status_code == 200
590
612
  assert response.context_data == EXPECTED_DATA_WITH_ATTEMPTS
591
613
 
592
- # Teacher creates 3 custom levels, only shares the first 2 with the student.
593
- # Check that the total available score only includes the levels shared with the
594
- # student. Student attempts one level only.
614
+ # Teacher creates 3 custom levels, only shares the first 2 with the
615
+ # student. Check that the total available score only includes the
616
+ # levels shared with the student. Student attempts one level only.
595
617
  custom_level1_id = create_save_level(student.class_field.teacher)
596
618
  custom_level2_id = create_save_level(student.class_field.teacher)
597
619
  create_save_level(student.class_field.teacher)
@@ -608,15 +630,6 @@ class TestViews(TestCase):
608
630
  assert response.status_code == 200
609
631
  assert response.context_data == EXPECTED_DATA_WITH_CUSTOM_ATTEMPTS
610
632
 
611
- # Link Kurono game to student's class
612
- game = Game(game_class=klass, worksheet_id=3)
613
- game.save()
614
-
615
- response = c.get(student_dashboard_url)
616
-
617
- assert response.status_code == 200
618
- assert response.context_data == EXPECTED_DATA_WITH_KURONO_GAME
619
-
620
633
  @patch("portal.views.registration.send_dotdigital_email")
621
634
  def test_delete_account(self, mock_send_dotdigital_email: Mock):
622
635
  email, password = signup_teacher_directly()
@@ -647,7 +660,9 @@ class TestViews(TestCase):
647
660
 
648
661
  # try again with the correct password
649
662
  url = reverse("delete_account")
650
- response = c.post(url, {"password": password, "unsubscribe_newsletter": "on"})
663
+ response = c.post(
664
+ url, {"password": password, "unsubscribe_newsletter": "on"}
665
+ )
651
666
 
652
667
  assert response.status_code == 302
653
668
  mock_send_dotdigital_email.assert_called_once()
@@ -729,7 +744,9 @@ class TestViews(TestCase):
729
744
 
730
745
  school_id = school.id
731
746
  school_name = school.name
732
- teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name")
747
+ teachers = Teacher.objects.filter(school=school).order_by(
748
+ "new_user__last_name", "new_user__first_name"
749
+ )
733
750
  assert len(teachers) == 3
734
751
 
735
752
  # one of the remaining teachers should be admin (the second in our case, as it's alphabetical)
@@ -760,7 +777,9 @@ class TestViews(TestCase):
760
777
  self.assertEqual(mock_send_dotdigital_email.call_count, 2)
761
778
 
762
779
  # 2 teachers left
763
- teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name")
780
+ teachers = Teacher.objects.filter(school=school).order_by(
781
+ "new_user__last_name", "new_user__first_name"
782
+ )
764
783
  assert len(teachers) == 2
765
784
 
766
785
  # teacher2 should still be admin, teacher4 is not passed admin role because there is teacher2
@@ -772,7 +791,9 @@ class TestViews(TestCase):
772
791
  # delete teacher4
773
792
  anonymise(user4)
774
793
 
775
- teachers = Teacher.objects.filter(school=school).order_by("new_user__last_name", "new_user__first_name")
794
+ teachers = Teacher.objects.filter(school=school).order_by(
795
+ "new_user__last_name", "new_user__first_name"
796
+ )
776
797
  assert len(teachers) == 1
777
798
  u = User.objects.get(id=usrid2)
778
799
  assert u.new_teacher.is_admin
@@ -830,13 +851,17 @@ class TestViews(TestCase):
830
851
  c.logout()
831
852
 
832
853
  @patch("common.helpers.emails.send_dotdigital_email")
833
- def test_registrations_increment_data(self, mock_send_dotdigital_email: Mock):
854
+ def test_registrations_increment_data(
855
+ self, mock_send_dotdigital_email: Mock
856
+ ):
834
857
  c = Client()
835
858
 
836
859
  total_activity = TotalActivity.objects.get(id=1)
837
860
  teacher_registration_count = total_activity.teacher_registrations
838
861
  student_registration_count = total_activity.student_registrations
839
- independent_registration_count = total_activity.independent_registrations
862
+ independent_registration_count = (
863
+ total_activity.independent_registrations
864
+ )
840
865
 
841
866
  response = c.post(
842
867
  reverse("register"),
@@ -856,7 +881,10 @@ class TestViews(TestCase):
856
881
 
857
882
  total_activity = TotalActivity.objects.get(id=1)
858
883
 
859
- assert total_activity.teacher_registrations == teacher_registration_count + 1
884
+ assert (
885
+ total_activity.teacher_registrations
886
+ == teacher_registration_count + 1
887
+ )
860
888
 
861
889
  response = c.post(
862
890
  reverse("register"),
@@ -878,7 +906,10 @@ class TestViews(TestCase):
878
906
 
879
907
  total_activity = TotalActivity.objects.get(id=1)
880
908
 
881
- assert total_activity.independent_registrations == independent_registration_count + 1
909
+ assert (
910
+ total_activity.independent_registrations
911
+ == independent_registration_count + 1
912
+ )
882
913
 
883
914
  teacher_email, teacher_password = signup_teacher_directly()
884
915
  create_organisation_directly(teacher_email)
@@ -894,7 +925,10 @@ class TestViews(TestCase):
894
925
 
895
926
  total_activity = TotalActivity.objects.get(id=1)
896
927
 
897
- assert total_activity.student_registrations == student_registration_count + 3
928
+ assert (
929
+ total_activity.student_registrations
930
+ == student_registration_count + 3
931
+ )
898
932
 
899
933
 
900
934
  # CRON view tests
@@ -913,8 +947,12 @@ class CronTestClient(APIClient):
913
947
  secure=False,
914
948
  **extra,
915
949
  ):
916
- wsgi_response = super().generic(method, path, data, content_type, secure, **extra)
917
- assert 200 <= wsgi_response.status_code < 300, f"Response has error status code: {wsgi_response.status_code}"
950
+ wsgi_response = super().generic(
951
+ method, path, data, content_type, secure, **extra
952
+ )
953
+ assert (
954
+ 200 <= wsgi_response.status_code < 300
955
+ ), f"Response has error status code: {wsgi_response.status_code}"
918
956
 
919
957
  return wsgi_response
920
958
 
@@ -933,7 +971,9 @@ class TestUser(CronTestCase):
933
971
  indy_email, _, _ = create_independent_student_directly()
934
972
 
935
973
  self.teacher_user = User.objects.get(email=teacher_email)
936
- self.teacher_user_profile = UserProfile.objects.get(user=self.teacher_user)
974
+ self.teacher_user_profile = UserProfile.objects.get(
975
+ user=self.teacher_user
976
+ )
937
977
 
938
978
  self.indy_user = User.objects.get(email=indy_email)
939
979
  self.indy_user_profile = UserProfile.objects.get(user=self.indy_user)
@@ -949,11 +989,17 @@ class TestUser(CronTestCase):
949
989
  assert_called: bool,
950
990
  mock_send_dotdigital_email: Mock,
951
991
  ):
952
- self.teacher_user.date_joined = timezone.now() - timedelta(days=days, hours=12)
992
+ self.teacher_user.date_joined = timezone.now() - timedelta(
993
+ days=days, hours=12
994
+ )
953
995
  self.teacher_user.save()
954
- self.student_user.date_joined = timezone.now() - timedelta(days=days, hours=12)
996
+ self.student_user.date_joined = timezone.now() - timedelta(
997
+ days=days, hours=12
998
+ )
955
999
  self.student_user.save()
956
- self.indy_user.date_joined = timezone.now() - timedelta(days=days, hours=12)
1000
+ self.indy_user.date_joined = timezone.now() - timedelta(
1001
+ days=days, hours=12
1002
+ )
957
1003
  self.indy_user.save()
958
1004
 
959
1005
  self.teacher_user_profile.is_verified = is_verified
@@ -964,9 +1010,13 @@ class TestUser(CronTestCase):
964
1010
  self.client.get(reverse(view_name))
965
1011
 
966
1012
  if assert_called:
967
- mock_send_dotdigital_email.assert_any_call(ANY, [self.teacher_user.email], personalization_values=ANY)
1013
+ mock_send_dotdigital_email.assert_any_call(
1014
+ ANY, [self.teacher_user.email], personalization_values=ANY
1015
+ )
968
1016
 
969
- mock_send_dotdigital_email.assert_any_call(ANY, [self.indy_user.email], personalization_values=ANY)
1017
+ mock_send_dotdigital_email.assert_any_call(
1018
+ ANY, [self.indy_user.email], personalization_values=ANY
1019
+ )
970
1020
 
971
1021
  # Check only two emails are sent - the student should never be included.
972
1022
  assert mock_send_dotdigital_email.call_count == 2
@@ -976,22 +1026,40 @@ class TestUser(CronTestCase):
976
1026
  mock_send_dotdigital_email.reset_mock()
977
1027
 
978
1028
  def test_first_verify_email_reminder_view(self):
979
- self.send_verify_email_reminder(6, False, "first-verify-email-reminder", False)
980
- self.send_verify_email_reminder(7, False, "first-verify-email-reminder", True)
981
- self.send_verify_email_reminder(7, True, "first-verify-email-reminder", False)
982
- self.send_verify_email_reminder(8, False, "first-verify-email-reminder", False)
1029
+ self.send_verify_email_reminder(
1030
+ 6, False, "first-verify-email-reminder", False
1031
+ )
1032
+ self.send_verify_email_reminder(
1033
+ 7, False, "first-verify-email-reminder", True
1034
+ )
1035
+ self.send_verify_email_reminder(
1036
+ 7, True, "first-verify-email-reminder", False
1037
+ )
1038
+ self.send_verify_email_reminder(
1039
+ 8, False, "first-verify-email-reminder", False
1040
+ )
983
1041
 
984
1042
  def test_second_verify_email_reminder_view(self):
985
- self.send_verify_email_reminder(13, False, "second-verify-email-reminder", False)
986
- self.send_verify_email_reminder(14, False, "second-verify-email-reminder", True)
987
- self.send_verify_email_reminder(14, True, "second-verify-email-reminder", False)
988
- self.send_verify_email_reminder(15, False, "second-verify-email-reminder", False)
1043
+ self.send_verify_email_reminder(
1044
+ 13, False, "second-verify-email-reminder", False
1045
+ )
1046
+ self.send_verify_email_reminder(
1047
+ 14, False, "second-verify-email-reminder", True
1048
+ )
1049
+ self.send_verify_email_reminder(
1050
+ 14, True, "second-verify-email-reminder", False
1051
+ )
1052
+ self.send_verify_email_reminder(
1053
+ 15, False, "second-verify-email-reminder", False
1054
+ )
989
1055
 
990
1056
  def test_anonymise_unverified_accounts_view(self):
991
1057
  now = timezone.now()
992
1058
 
993
1059
  for user in [self.teacher_user, self.indy_user, self.student_user]:
994
- user.date_joined = now - timedelta(days=USER_DELETE_UNVERIFIED_ACCOUNT_DAYS + 1)
1060
+ user.date_joined = now - timedelta(
1061
+ days=USER_DELETE_UNVERIFIED_ACCOUNT_DAYS + 1
1062
+ )
995
1063
  user.save()
996
1064
 
997
1065
  for user_profile in [self.teacher_user_profile, self.indy_user_profile]:
@@ -1056,7 +1124,9 @@ class TestUser(CronTestCase):
1056
1124
  new_user=indy_user,
1057
1125
  )
1058
1126
 
1059
- activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
1127
+ activity_today = DailyActivity.objects.get_or_create(
1128
+ date=datetime.now().date()
1129
+ )[0]
1060
1130
  daily_teacher_count = activity_today.anonymised_unverified_teachers
1061
1131
  daily_indy_count = activity_today.anonymised_unverified_independents
1062
1132
 
@@ -1079,16 +1149,30 @@ class TestUser(CronTestCase):
1079
1149
  assert indy_user_active == assert_active
1080
1150
  assert student_user_active
1081
1151
 
1082
- activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
1152
+ activity_today = DailyActivity.objects.get_or_create(
1153
+ date=datetime.now().date()
1154
+ )[0]
1083
1155
  total_activity = TotalActivity.objects.get(id=1)
1084
1156
 
1085
1157
  if not teacher_user_active:
1086
- assert activity_today.anonymised_unverified_teachers == daily_teacher_count + 1
1087
- assert total_activity.anonymised_unverified_teachers == total_teacher_count + 1
1158
+ assert (
1159
+ activity_today.anonymised_unverified_teachers
1160
+ == daily_teacher_count + 1
1161
+ )
1162
+ assert (
1163
+ total_activity.anonymised_unverified_teachers
1164
+ == total_teacher_count + 1
1165
+ )
1088
1166
 
1089
1167
  if not indy_user_active:
1090
- assert activity_today.anonymised_unverified_independents == daily_indy_count + 1
1091
- assert total_activity.anonymised_unverified_independents == total_indy_count + 1
1168
+ assert (
1169
+ activity_today.anonymised_unverified_independents
1170
+ == daily_indy_count + 1
1171
+ )
1172
+ assert (
1173
+ total_activity.anonymised_unverified_independents
1174
+ == total_indy_count + 1
1175
+ )
1092
1176
 
1093
1177
  teacher_user.delete()
1094
1178
  indy_user.delete()
@@ -1114,3 +1198,103 @@ class TestUser(CronTestCase):
1114
1198
  is_verified=False,
1115
1199
  assert_active=False,
1116
1200
  )
1201
+
1202
+ @patch("portal.views.cron.user.send_dotdigital_email")
1203
+ def send_inactivity_reminder(
1204
+ self,
1205
+ days: int,
1206
+ view_name: str,
1207
+ assert_called: bool,
1208
+ campaign_name: str,
1209
+ mock_send_dotdigital_email: Mock,
1210
+ ):
1211
+ self.teacher_user.date_joined = timezone.now() - timedelta(
1212
+ days=days, hours=12
1213
+ )
1214
+ self.teacher_user.save()
1215
+ self.student_user.date_joined = timezone.now() - timedelta(
1216
+ days=days, hours=12
1217
+ )
1218
+ self.student_user.save()
1219
+ self.indy_user.last_login = timezone.now() - timedelta(
1220
+ days=days, hours=12
1221
+ )
1222
+ self.indy_user.save()
1223
+
1224
+ self.client.get(reverse(view_name))
1225
+
1226
+ if assert_called:
1227
+ mock_send_dotdigital_email.assert_any_call(
1228
+ campaign_ids[campaign_name], [self.teacher_user.email]
1229
+ )
1230
+
1231
+ mock_send_dotdigital_email.assert_any_call(
1232
+ campaign_ids[campaign_name], [self.indy_user.email]
1233
+ )
1234
+
1235
+ # Check only two emails are sent - the student should never be included.
1236
+ assert mock_send_dotdigital_email.call_count == 2
1237
+ else:
1238
+ mock_send_dotdigital_email.assert_not_called()
1239
+
1240
+ mock_send_dotdigital_email.reset_mock()
1241
+
1242
+ def test_first_inactivity_reminder_view(self):
1243
+ self.send_inactivity_reminder(
1244
+ 729,
1245
+ "first-inactivity-reminder",
1246
+ False,
1247
+ "inactive_users_on_website_first_reminder",
1248
+ )
1249
+ self.send_inactivity_reminder(
1250
+ 730,
1251
+ "first-inactivity-reminder",
1252
+ True,
1253
+ "inactive_users_on_website_first_reminder",
1254
+ )
1255
+ self.send_inactivity_reminder(
1256
+ 731,
1257
+ "first-inactivity-reminder",
1258
+ False,
1259
+ "inactive_users_on_website_first_reminder",
1260
+ )
1261
+
1262
+ def test_second_inactivity_reminder_view(self):
1263
+ self.send_inactivity_reminder(
1264
+ 972,
1265
+ "second-inactivity-reminder",
1266
+ False,
1267
+ "inactive_users_on_website_second_reminder",
1268
+ )
1269
+ self.send_inactivity_reminder(
1270
+ 973,
1271
+ "second-inactivity-reminder",
1272
+ True,
1273
+ "inactive_users_on_website_second_reminder",
1274
+ )
1275
+ self.send_inactivity_reminder(
1276
+ 974,
1277
+ "second-inactivity-reminder",
1278
+ False,
1279
+ "inactive_users_on_website_second_reminder",
1280
+ )
1281
+
1282
+ def test_final_inactivity_reminder_view(self):
1283
+ self.send_inactivity_reminder(
1284
+ 1064,
1285
+ "final-inactivity-reminder",
1286
+ False,
1287
+ "inactive_users_on_website_final_reminder",
1288
+ )
1289
+ self.send_inactivity_reminder(
1290
+ 1065,
1291
+ "final-inactivity-reminder",
1292
+ True,
1293
+ "inactive_users_on_website_final_reminder",
1294
+ )
1295
+ self.send_inactivity_reminder(
1296
+ 1066,
1297
+ "final-inactivity-reminder",
1298
+ False,
1299
+ "inactive_users_on_website_final_reminder",
1300
+ )