codeforlife-portal 6.41.5__py2.py3-none-any.whl → 6.41.7__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 (51) hide show
  1. cfl_common/common/app_settings.py +3 -0
  2. cfl_common/common/email_messages.py +0 -58
  3. cfl_common/common/helpers/data_migration_loader.py +3 -4
  4. cfl_common/common/helpers/emails.py +27 -45
  5. cfl_common/common/helpers/generators.py +1 -1
  6. cfl_common/common/mail.py +116 -0
  7. cfl_common/common/migrations/0002_emailverification.py +1 -3
  8. cfl_common/common/migrations/0005_add_worksheets.py +1 -5
  9. cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +1 -5
  10. cfl_common/common/migrations/0008_unlock_worksheet_3.py +1 -5
  11. cfl_common/common/migrations/0017_copy_email_to_username.py +2 -8
  12. cfl_common/common/migrations/0021_school_is_active.py +7 -7
  13. cfl_common/common/migrations/0022_school_cleanup.py +9 -9
  14. cfl_common/common/migrations/0023_userprofile_aimmo_badges.py +4 -4
  15. cfl_common/common/migrations/0025_schoolteacherinvitation.py +29 -13
  16. cfl_common/common/migrations/0026_teacher_remove_join_request.py +5 -5
  17. cfl_common/common/migrations/0027_class_created_by.py +10 -4
  18. cfl_common/common/migrations/0028_coding_club_downloads.py +5 -5
  19. cfl_common/common/migrations/0029_dynamicelement.py +6 -6
  20. cfl_common/common/migrations/0030_add_maintenance_banner.py +1 -3
  21. cfl_common/common/migrations/0031_improve_admin_panel.py +32 -14
  22. cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py +3 -3
  23. cfl_common/common/migrations/0033_password_reset_tracking_fields.py +5 -5
  24. cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +3 -3
  25. cfl_common/common/migrations/0035_rename_lockout_fields.py +10 -10
  26. cfl_common/common/migrations/0037_migrate_email_verification.py +2 -2
  27. cfl_common/common/migrations/0038_delete_emailverification.py +2 -2
  28. cfl_common/common/migrations/0039_copy_email_to_username.py +1 -6
  29. cfl_common/common/migrations/0040_school_county.py +3 -3
  30. cfl_common/common/migrations/0042_totalactivity.py +7 -7
  31. cfl_common/common/migrations/0044_update_activity_models.py +9 -9
  32. cfl_common/common/migrations/0045_otp.py +5 -5
  33. cfl_common/common/migrations/0046_alter_school_country.py +3 -3
  34. cfl_common/common/tests/utils/email.py +14 -34
  35. cfl_common/common/tests/utils/student.py +8 -8
  36. cfl_common/common/tests/utils/teacher.py +8 -8
  37. {codeforlife_portal-6.41.5.dist-info → codeforlife_portal-6.41.7.dist-info}/METADATA +2 -2
  38. {codeforlife_portal-6.41.5.dist-info → codeforlife_portal-6.41.7.dist-info}/RECORD +51 -50
  39. example_project/portal_test_settings.py +5 -1
  40. portal/__init__.py +1 -1
  41. portal/templates/portal/mouseflow.html +1 -1
  42. portal/templates/portal/privacy_notice.html +5 -0
  43. portal/tests/test_independent_student.py +30 -17
  44. portal/tests/test_ratelimit.py +15 -12
  45. portal/tests/test_teacher.py +35 -21
  46. portal/tests/test_teacher_student.py +13 -3
  47. portal/tests/test_views.py +55 -194
  48. portal/views/cron/user.py +12 -49
  49. {codeforlife_portal-6.41.5.dist-info → codeforlife_portal-6.41.7.dist-info}/LICENSE.md +0 -0
  50. {codeforlife_portal-6.41.5.dist-info → codeforlife_portal-6.41.7.dist-info}/WHEEL +0 -0
  51. {codeforlife_portal-6.41.5.dist-info → codeforlife_portal-6.41.7.dist-info}/top_level.txt +0 -0
@@ -1,27 +1,28 @@
1
1
  cfl_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  cfl_common/setup.py,sha256=5Rk-FXyWToTujXqGRYqeA0A5nJ4NC5woXxyb6NLLbpo,818
3
3
  cfl_common/common/__init__.py,sha256=XlncBOpKp_gekbKH7Y_i6yu1qy5tJc3Y8sn8cDy-Vgk,48
4
- cfl_common/common/app_settings.py,sha256=IlFosu8P-m3ZOWnlX9MZupuLe2tFUDzQarBjYNDQ3kg,2491
4
+ cfl_common/common/app_settings.py,sha256=x2ROLY5Xl5LgqjxyTiChZvQorZYUXpFzEkaLsjh8UHo,2586
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/email_messages.py,sha256=YlzJ98Fjv3Qe8XkIIakXOQx93yKFNIsS_vdrqGG7LeQ,7702
8
+ cfl_common/common/email_messages.py,sha256=res-Uh_0KihJdvXKZg9EbvQ4ohQwS2rlR2wNHcazLoo,5009
9
+ cfl_common/common/mail.py,sha256=EsqghHcRbrvCHI-eVcd88PrVVvsz1ZMbUzHrWne3nyE,4192
9
10
  cfl_common/common/models.py,sha256=vnvy8U-sHopyaxgJK9wTxelbKsCnYMjuEu3HIuAEkrs,14974
10
11
  cfl_common/common/permissions.py,sha256=gC6RQGZI2QDBbglx-xr_V4Hl2C2nf1V2_uPmEuoEcJo,2416
11
12
  cfl_common/common/utils.py,sha256=Nn2Npao9Uqad5Js_IdHwF-ow6wrPNpBLW4AO1LxoEBc,1727
12
13
  cfl_common/common/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- cfl_common/common/helpers/data_migration_loader.py,sha256=05OteSWSuCLt2BRIlRhHCNWVigYjyoiaMw323ZKFZPk,1778
14
- cfl_common/common/helpers/emails.py,sha256=R1QVmAA2Nwcu3Lyxt02N2yjQzCg9fcVN-Pyl1gRaIrI,11316
15
- cfl_common/common/helpers/generators.py,sha256=wJ90XBe5zN-JXEPSpnQhCG0sdpBLEBUj1MG2K_GOWPk,1505
14
+ cfl_common/common/helpers/data_migration_loader.py,sha256=_BhS5lPmhcuVUbryBmJytlWdHyT02KYyxPkHar32mOE,1748
15
+ cfl_common/common/helpers/emails.py,sha256=u2X2brjHIlUDNIgQ6-Ld23Y4zyouJGTFH-G7HNQDBYs,11041
16
+ cfl_common/common/helpers/generators.py,sha256=kTL5e91I8wgmjJ-mu4jr9vIacjccUZ5pZSAz5cUNhdM,1505
16
17
  cfl_common/common/helpers/organisation.py,sha256=e-JKumKoXrkMTzZPv0H4ViWL8vtCt7oXJjn_zZ1ec00,427
17
18
  cfl_common/common/migrations/0001_initial.py,sha256=Y2kt2xmdCbrmDXCgqmhXeacicNg26Zj7L7SANSsgAAI,9664
18
- cfl_common/common/migrations/0002_emailverification.py,sha256=_tbAHuICs2y5Rdahry98GQ5plBh46SQ-0nZGrfTQAlY,2156
19
+ cfl_common/common/migrations/0002_emailverification.py,sha256=csWNasKNB5HQfrg5Owa6ovwx9f-v3ved-1B6QSu2_3s,2094
19
20
  cfl_common/common/migrations/0003_aimmocharacter.py,sha256=ZS_-sJCVUnFdhDjyH75P5bryg5QKHPDfe-SXXK7k9KM,922
20
21
  cfl_common/common/migrations/0004_add_aimmocharacters.py,sha256=543WKEphsvdKo8Z7f8wPgZHGxRPWMdo288O5_7edawo,430
21
- cfl_common/common/migrations/0005_add_worksheets.py,sha256=Y9cN3d0K-NLDwyjNE0QGOnD9UUHBNtp2AOlbp_p7FXQ,412
22
+ cfl_common/common/migrations/0005_add_worksheets.py,sha256=lIfjiNEOGiGEsrJJCN-BVaOcyidlT3s9uhwpCfyHFV0,376
22
23
  cfl_common/common/migrations/0006_update_aimmo_character_image_path.py,sha256=De1d02dAYE2KT8uW1qPi0NmWp45_zXVNH7kjWg3f2ko,431
23
- cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py,sha256=xoJQig-M4KXww9wHaSQNDoK2cc6dpfKd4qccD8wGZR8,431
24
- cfl_common/common/migrations/0008_unlock_worksheet_3.py,sha256=AxMwnF7sBsWq83e-FqtOwQ3f58FrYLzECjjNBBamvfA,381
24
+ cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py,sha256=dQBI3VzqS8CpnpmQfXx_sNVDynhQL-KxwJNl38cgJi4,395
25
+ cfl_common/common/migrations/0008_unlock_worksheet_3.py,sha256=WzTdGbSjYNlyxNLVRMPWOhNBkR5e-lRewu0xBy5d69c,345
25
26
  cfl_common/common/migrations/0009_add_blocked_time_to_teacher_and_student.py,sha256=hdff316HkAVCB0EW61qbR2lxwkxSTsD7OBK66PLBUUQ,599
26
27
  cfl_common/common/migrations/0010_remove_teacher_title.py,sha256=dMX-wgD-nzRx2AmrvuLMtb1X7TIv1gZ9icacFgI1LNc,396
27
28
  cfl_common/common/migrations/0011_student_login_id.py,sha256=0sMdbzshOu_hT3s7uC0ILKd4PA1waw14y83Re736788,400
@@ -30,36 +31,36 @@ cfl_common/common/migrations/0013_class_school.py,sha256=n5slnrrOFnrBsjAmkdcRxIU
30
31
  cfl_common/common/migrations/0014_login_type.py,sha256=_liGPnErUKWj2hfErZjkw--2BM8pZCYIvdg96uJts08,817
31
32
  cfl_common/common/migrations/0015_dailyactivity.py,sha256=jbzA7hwcEd7mg-w0he-OMpVGSUrjYuWW_QFXY2RWzjk,901
32
33
  cfl_common/common/migrations/0016_joinreleasestudent.py,sha256=bu7s-artUHMRPxP0Rnbw10cQy47c2_GYOD8fzzHgXQ8,1208
33
- cfl_common/common/migrations/0017_copy_email_to_username.py,sha256=nx1Y30Ngj_FuuBCR1C1hUZjYV-_hCTKoyoQyulVBzog,637
34
+ cfl_common/common/migrations/0017_copy_email_to_username.py,sha256=oon4z_fHDxABJ-J3T3IeucAJPl2lYCu75zcZk0a1xfs,587
34
35
  cfl_common/common/migrations/0018_update_aimmo_character_image_path.py,sha256=WKrxcjGjQ2pE4lcubkU37nsThmHbeVni9tCNSQWm8Sk,387
35
36
  cfl_common/common/migrations/0019_aimmocharacter_alt.py,sha256=vhcO1-1TFNYJ2ILLj-tHq_z7jlhd9URwColHqTh11m8,418
36
37
  cfl_common/common/migrations/0020_class_is_active_and_null_access_code.py,sha256=g0frY5T5AuVwCPIhj9UEtyB0lyMktl285eBJtLpN-zk,555
37
- cfl_common/common/migrations/0021_school_is_active.py,sha256=3hHNUkBRtKlqVNuOu4ZL3bJvFcX1QO276Hz8IjIhBNg,736
38
- cfl_common/common/migrations/0022_school_cleanup.py,sha256=JsIHrz2w-JD5BiAYs-Evnqht6_mQ37YxTs9oLjlcH0A,661
39
- cfl_common/common/migrations/0023_userprofile_aimmo_badges.py,sha256=FQP_Q-phm1PnI05sy5KkEH02v0vhury4oNNrDJiyBbM,515
38
+ cfl_common/common/migrations/0021_school_is_active.py,sha256=S1HjZIalqLSbqrTu7AF0xvqZEbzcvLCCa1rMa7Pnc-w,736
39
+ cfl_common/common/migrations/0022_school_cleanup.py,sha256=r3YYnC6gJtBy5Ewa8_cjDxvK-W3oEm1IKopn-UXVyXg,661
40
+ cfl_common/common/migrations/0023_userprofile_aimmo_badges.py,sha256=NsOOw2XB03k4U5-2wDzuvN7P69IHLNKcjsPLMxmJbPM,515
40
41
  cfl_common/common/migrations/0024_teacher_invited_by.py,sha256=UxsKaGB16y7NjH6mrqZceNLi3tERgLb7gxS14ozsTjw,632
41
- cfl_common/common/migrations/0025_schoolteacherinvitation.py,sha256=xX4SiJ4d6t30YewNYrEZmcOGTRLQwdnuRO2MIcewyVw,1463
42
- cfl_common/common/migrations/0026_teacher_remove_join_request.py,sha256=81rqW8d_x6pXX61U24TQdh6aj_Bf-nibSQFHxXCK5fQ,528
43
- cfl_common/common/migrations/0027_class_created_by.py,sha256=rKITmkyvX2iiQs-CFLOXrsoMEnWv6PqXz5G2GyGSDxo,537
44
- cfl_common/common/migrations/0028_coding_club_downloads.py,sha256=b-d7cNTk2d2cZzAhtfcrzV7aIn0xuPkRNXooZcEaBOI,606
45
- cfl_common/common/migrations/0029_dynamicelement.py,sha256=os-svhr2Xfnw6pR_pseAFdCzvTULexl7_LLG7IK60mc,684
46
- cfl_common/common/migrations/0030_add_maintenance_banner.py,sha256=8DxUo9YJXC_2yAPXP1t0fFU2r3I593zl6k3hB_Yapao,865
47
- cfl_common/common/migrations/0031_improve_admin_panel.py,sha256=GdhVIkd7HzqcklrbBi4iGWQX2qqU4okghyFxENls5-s,1380
48
- cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py,sha256=eI5DgJLxlNrNPZiaeleRauMG3qTDgywkLAmVnrz7JL4,417
49
- cfl_common/common/migrations/0033_password_reset_tracking_fields.py,sha256=Xoz5Qvhjw1x2fNO1yJ4-LjBXspqtHYKoJDNCTKjtg78,619
50
- cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py,sha256=hnZoJf1_GwhIPzjrQMp72-CCXFXtuWGhaClNM-1gmw8,438
51
- cfl_common/common/migrations/0035_rename_lockout_fields.py,sha256=LCB0Cl-HhEsGazXVJDw6m3RLStRAKsHHtYEVEC4t-xw,808
42
+ cfl_common/common/migrations/0025_schoolteacherinvitation.py,sha256=Ba2gpIfeCkPGDnB6JqbM1DXlixkLqsbE3voKVK9Gvok,1819
43
+ cfl_common/common/migrations/0026_teacher_remove_join_request.py,sha256=U7hfnmNwQ0c6yzkiXhMrm2AdgrQszTjBal4U-v0H_VE,528
44
+ cfl_common/common/migrations/0027_class_created_by.py,sha256=H9T78SxvnrM0mjh5l_msa060MsJoWZ_bV-VizBElNpc,632
45
+ cfl_common/common/migrations/0028_coding_club_downloads.py,sha256=RLp7-M95MhwHCZfvo2qHfA_KZXa4pqXBni1Kaf26kOw,606
46
+ cfl_common/common/migrations/0029_dynamicelement.py,sha256=T48j7Uwik_H0bg8eFpSxB-PgxEfmPlPgrr19UFETrj4,684
47
+ cfl_common/common/migrations/0030_add_maintenance_banner.py,sha256=gZAVSbUPfZBTXHlJ_vjO4XwlAgIm6JcR59xZWawS4Ns,851
48
+ cfl_common/common/migrations/0031_improve_admin_panel.py,sha256=UAOBZUS5jR071ciQjO-CoEN6y4BJqMSD1YvYwJBd3FI,1665
49
+ cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py,sha256=E6KiHkUXs1ka0SknETP_HXRTr1cKELPBIj5WgVdZNS8,417
50
+ cfl_common/common/migrations/0033_password_reset_tracking_fields.py,sha256=s2kqLCJBeAIeYJjdlRdXHlmNwZb7Czq7omJp51VKc8M,619
51
+ cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py,sha256=j3OKxJZTK4NQKVx2JkcK4o9Ie6C9yqbQHfZRsn4NIZg,438
52
+ cfl_common/common/migrations/0035_rename_lockout_fields.py,sha256=1UbvNJ1Qf2SNqGRRJCjXhxHi_rnJuToUW8DEF-tH0ZM,808
52
53
  cfl_common/common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py,sha256=wmfENAwFqegcBJYk0wcFdgvY-m2BlGAFQjt-wNO6kBM,396
53
- cfl_common/common/migrations/0037_migrate_email_verification.py,sha256=kyGg5gvkaiwE-OFQBycKzykackLrkCTGFZcKCHfFMSM,1022
54
- cfl_common/common/migrations/0038_delete_emailverification.py,sha256=3u-vRF52IrpQvzkgUI7offAKCbBf2RKDxW-s_32c4S8,314
55
- cfl_common/common/migrations/0039_copy_email_to_username.py,sha256=us1CmVFxE_ntYhZpgtDerEzcQKBa0ytzGsjERG_F_uA,589
56
- cfl_common/common/migrations/0040_school_county.py,sha256=vnHuXh52NCTYhg4Os_AIwmwmtfdLSFKp26SAVCKw-i4,411
54
+ cfl_common/common/migrations/0037_migrate_email_verification.py,sha256=zC8rZ3xFhkbHqr3oUyTO2XHleqbx8o9wA2S_24_Y6kY,1018
55
+ cfl_common/common/migrations/0038_delete_emailverification.py,sha256=JTjGGrcJQY7sagMWLTn8jWP4Ldm99LwWo8Sn7QgfIgw,314
56
+ cfl_common/common/migrations/0039_copy_email_to_username.py,sha256=h2NwkcMJ1pBCVKtEj9spM72hG4zm0JnWKXkQI7i9p48,552
57
+ cfl_common/common/migrations/0040_school_county.py,sha256=LuPMnxLXOVZDlIeFtKr5wNrxBvVVrcjbnXyCjWgq4QQ,411
57
58
  cfl_common/common/migrations/0041_populate_gb_counties.py,sha256=74pFXOqUcPh8fCyhlOGQNUd0yzfDCUDR-Y2aO6UyP_Y,898
58
- cfl_common/common/migrations/0042_totalactivity.py,sha256=AqYs9BkYsG3xDzJ-yj3JBsxSyrOQSZnDwOR-DY_NFPY,810
59
+ cfl_common/common/migrations/0042_totalactivity.py,sha256=U8nY0lcnreU4ugWpt8xi8lSDDRDg3cnV7liYVU_MxNw,810
59
60
  cfl_common/common/migrations/0043_add_total_activity.py,sha256=9dCx3-uCrmG5XS9snhTeFEOpaqFWZvjIvkgukYldZ4I,1065
60
- cfl_common/common/migrations/0044_update_activity_models.py,sha256=xP-rLNuWVk81SzTiPW1TS9RkqTSydmHH-Gp53IiwHiw,997
61
- cfl_common/common/migrations/0045_otp.py,sha256=a-D6ScRLcQ9U3RuyjNBcQzaPSRemFcPvLSkr1ItgzMg,599
62
- cfl_common/common/migrations/0046_alter_school_country.py,sha256=otxmKezNM_Qvpfe49EQ_V2zLn2SkB9RD8qjHFaUhR3c,437
61
+ cfl_common/common/migrations/0044_update_activity_models.py,sha256=9ZwggtsiMB8LzKQEoyeDSIaADbz_wFk55bt-C9wSooY,997
62
+ cfl_common/common/migrations/0045_otp.py,sha256=_GmCOFOINqFMBqPBvdBaR1nwAI_FkzIlMTq_kfaN1IQ,599
63
+ cfl_common/common/migrations/0046_alter_school_country.py,sha256=dg_lexw7ALB-jlOm_EBQauk9mI4VbqUGv0qQsHo0b5s,437
63
64
  cfl_common/common/migrations/0047_delete_school_postcode.py,sha256=GPV0hLfXmbPpx4-G5OaaLy6aalKvSnZLH0aGggYx9u0,331
64
65
  cfl_common/common/migrations/0048_unique_school_names.py,sha256=pu5xiuesvFNGngD-hl0OQ6Gi2r6pEY9fPCayKyb9n04,1433
65
66
  cfl_common/common/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -71,10 +72,10 @@ cfl_common/common/tests/test_migration_remove_teacher_title.py,sha256=wwm6tayb75
71
72
  cfl_common/common/tests/test_models.py,sha256=xMdzonW5CADMjas_zfg8V1YPQpUetleyn6TE95hbO9k,3723
72
73
  cfl_common/common/tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
74
  cfl_common/common/tests/utils/classes.py,sha256=ZA2pp9Pyx3rwi0VFwtuUA2Pys9xQJ-L_zE0u2tpwEH4,1094
74
- cfl_common/common/tests/utils/email.py,sha256=bS4xnwhKiSD5S0-IlQtSwvs2ynZNMh93mrIBvUpAb6M,2927
75
+ cfl_common/common/tests/utils/email.py,sha256=lNLl1SoQUnRAI0Z7nuaUS8HQwt9YHVc-YB9rqdYzyrw,2190
75
76
  cfl_common/common/tests/utils/organisation.py,sha256=vNgKFtU3VPcWRnZfh82yCS90PLAK1XTYJNIxGwfgUI4,966
76
- cfl_common/common/tests/utils/student.py,sha256=h7w2UPGQHl1dNruzIfEnbeMbD4mhPYt2bj_8iqLkH_4,4239
77
- cfl_common/common/tests/utils/teacher.py,sha256=lBhNrusANKhPqMKgQN_6xg3hHS2tjGkqrsRvLjhekPo,2324
77
+ cfl_common/common/tests/utils/student.py,sha256=XlgWT0TdbIY6w9uB4SqOoXmhxxCRnucEcPY9Q5Xva0U,4415
78
+ cfl_common/common/tests/utils/teacher.py,sha256=kY9LuP1mTEj_andYxF9k54xEHiJ36a6dokHxA9cB9f0,2500
78
79
  cfl_common/common/tests/utils/user.py,sha256=NvLzZLVP4jy5Hn1iztOYF_BTQ9WsbSmuWMEzGzhAsRU,919
79
80
  deploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
81
  deploy/captcha.py,sha256=MbOBuGnbT_SOIltSjP1XMOLrfo1DldCilaVAEim0vM4,23
@@ -95,11 +96,11 @@ deploy/static/robots.txt,sha256=5cS4RITuQhbpNzvpk4AyDCXdlIBfmfCoBYRvCHY2VT8,24
95
96
  deploy/templates/deploy/csrf_failure.html,sha256=-pBRPn4Y7nUdYHGpTHCokT9Boi-isuwuivF8V2K1SgM,412
96
97
  example_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
98
  example_project/manage.py,sha256=EUgybZlZ7xk2Zf2KCwBbK_z7gf7Ifqs0_bl4Kijhdgo,242
98
- example_project/portal_test_settings.py,sha256=dxxdij_LiYDSQIsnoeuJ6y0Kob0rCC0CNJIrBoQ4Xyk,7181
99
+ example_project/portal_test_settings.py,sha256=fkbeU36YhWvNbR_hm28Rq4pV6ln-dGHK1VdML_1CToY,7227
99
100
  example_project/settings.py,sha256=GLelTfsnhAuf_rqjXUoIWoLtzKvr-9l8UQDQ23rxPQc,5580
100
101
  example_project/urls.py,sha256=OVeRQ-TCpzHISBRuzqD0yd3ewF7H5U3c-f2p2alfUD0,430
101
102
  example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
102
- portal/__init__.py,sha256=W7a69GHPeZkX6OrrDZxmHgUlk8Qd3F0Pwexx_RKDOhM,23
103
+ portal/__init__.py,sha256=A8V28UAPxnOJDbbZ7kfC9uVQv4XuMebzK2I__dZqK0w,23
103
104
  portal/admin.py,sha256=k5Hsiln43DlVPoufnrx5AXWu_RijX8xi_n7wwBuuCJo,5132
104
105
  portal/app_settings.py,sha256=DhWLQOwM0zVOXE3O5TNKbMM9K6agfLuCsHOdr1J7xEI,651
105
106
  portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
@@ -443,9 +444,9 @@ portal/templates/portal/home.html,sha256=pHXbzXAfCgaZnGDvofvNcb63dkZ309XGKg35dxZ
443
444
  portal/templates/portal/home_learning.html,sha256=AdKDe0dlbWUrL1BOCjJpdQcOsC6SR-0Ciby1dxJ6iXU,9707
444
445
  portal/templates/portal/locked_out.html,sha256=JeiFHFyueFX03D54k82swjxvKY78cFG8Vre-bKKv_7Y,894
445
446
  portal/templates/portal/locked_out_school_student.html,sha256=bcpLtu4mkHR-zILLuArtHH2OfUSjHiqntcdbTMlx5Kg,684
446
- portal/templates/portal/mouseflow.html,sha256=b6xMVYlpHNihfkhRgtWxczskdhDuxkCADU1RSfHUTOo,361
447
+ portal/templates/portal/mouseflow.html,sha256=ji9SWR86nqC59xdmivfdPNG5OzFk8gJEUeOUUh52alk,361
447
448
  portal/templates/portal/play.html,sha256=P5riFKSqaz_6aycgGC7-ql8u_ut6Igsa2SX9kMv8qM0,5162
448
- portal/templates/portal/privacy_notice.html,sha256=JK7la7WF0or0gt30tci4Gi0DQOidNjb3htkyoQUuLfM,55276
449
+ portal/templates/portal/privacy_notice.html,sha256=W35cFgpYd--iPjEGO3mKAjbrTAYbjOfu42KwQu2Ra4I,55807
449
450
  portal/templates/portal/register.html,sha256=9AFNejCCcG2VwNCJ8AU5r3Wk0u_ftK_NbVfxZb5weh0,12547
450
451
  portal/templates/portal/reset_password.html,sha256=YzsREz5D2OwhicMLahVOVDXiNDxoHlPqU5iu96i36W0,1373
451
452
  portal/templates/portal/reset_password_confirm.html,sha256=jPHSDatezRXzCG4zH_5BQPWAxLblidqro0hzvsH54ho,3499
@@ -537,18 +538,18 @@ portal/tests/test_class.py,sha256=V6Fkc6PqdisefKD3xs9PbfE2pKp-9e0gwQVkPUiu6bk,14
537
538
  portal/tests/test_daily_activities.py,sha256=-siDCMGBD1ijjccHVk7eEmrk4bgTsvbh0B6hDoj2fo0,1803
538
539
  portal/tests/test_emails.py,sha256=-rI3FlJO7n9qfZ8Vz_Fe3DmjOngr4r23PCpjIoRxNY0,9133
539
540
  portal/tests/test_helper_methods.py,sha256=-SQCDZm2XUtyXGEp0CHIb_SSC9CPD-XOSnpnY8QclHk,890
540
- portal/tests/test_independent_student.py,sha256=FB7YaQaio_NJGvPPVxl7XZk-zN2Fu0itdQb7W8psudo,25003
541
+ portal/tests/test_independent_student.py,sha256=Kl2dyZh6-4TdlwWpsi2w1q54SoC_P2YWEvjJXrd6LdY,25857
541
542
  portal/tests/test_invite_teacher.py,sha256=oeOaoJV1IqJSYPlaPFjnhVXdB2mq8otCTLp_lfjuCfk,12224
542
543
  portal/tests/test_middleware.py,sha256=b6jfNmiRZ2snqLKsyJUG-RivoX5fmrqLlQkG9MeVnqM,8034
543
544
  portal/tests/test_newsletter_footer.py,sha256=MdVUX53mEoDTa4Krq-jg9LFNo-QyghqvTvhHeNXBGnE,838
544
545
  portal/tests/test_organisation.py,sha256=fOtck-0MkPM2F0V4RFH-QUeWEk6yUIXDv_GI5cl8sdg,7649
545
546
  portal/tests/test_partials.py,sha256=ydh1nef6BqvMfah2BSBS9QDiKY0xopY74k_W1YVobAE,3687
546
- portal/tests/test_ratelimit.py,sha256=WWirzWPuFJp5K8PyXIa-QuBk5rFysTRg37w8Zznb_0M,18735
547
+ portal/tests/test_ratelimit.py,sha256=-nlQHOT0Mk_9IKnKYa_t82Y6EL6oZD8uVZ5rof4C1O0,19144
547
548
  portal/tests/test_school_student.py,sha256=bFZwY4twaFHQLp0cltMq8cLNDZGgCHTZBCZHK0JcV8s,8604
548
549
  portal/tests/test_security.py,sha256=FGrlRfnzi-Xx2_bn4fTZlYORKm7w_GhGkD3havvplwc,3239
549
- portal/tests/test_teacher.py,sha256=T9ZUgbAmi3lwEVJy8zUsMwkLfcgBPjU0tORjPz9dxIE,35299
550
- portal/tests/test_teacher_student.py,sha256=B7mX6tuJPxXLFC3mjV1G-uyDSSLTGBauuuRxAYydGXM,21228
551
- portal/tests/test_views.py,sha256=ZWQE9DQnZxvhjK3ygyFh5JH9sndUaiiI8zJNVjrWrdI,41080
550
+ portal/tests/test_teacher.py,sha256=mft_xw97AkNi5OGprafpMMdGM1L1J_sMedBBg_Unb9k,36139
551
+ portal/tests/test_teacher_student.py,sha256=NWITbUw1kijqu3c8eRHLHJKaYQMOsOMvl7PAVx5QghI,21567
552
+ portal/tests/test_views.py,sha256=6y4ICpo5KPTWQed3J1Hg74ZGBKI2y3-HNHAowOsge80,38769
552
553
  portal/tests/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
553
554
  portal/tests/migrations/test_migration_make_portaladmin_teacher.py,sha256=ekMRb6cU97oT0k9gCKW7IUB7oPuGmv4uWJCqInQN7x8,2589
554
555
  portal/tests/migrations/test_migration_preview_user_remove.py,sha256=K6D-FZT9YFEA8oMxHz9VTglVV6MZOTRYVlvwWwXc2vU,555
@@ -620,7 +621,7 @@ portal/views/teach.py,sha256=nzlyTcgq9ImAjnqrF3esqi212qBLH5Ww1LKE2gSjoRY,210
620
621
  portal/views/aimmo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
621
622
  portal/views/aimmo/dashboard.py,sha256=YMOzonNE87OEP5lThY4BrF0rNyvIYpDInh_sM-n38AM,4046
622
623
  portal/views/cron/__init__.py,sha256=5rxXyhJmLOExRdrYZ1VJttTsyRIPRybzdftbUDwFByI,20
623
- portal/views/cron/user.py,sha256=gtZW-cGpgb0Rvpjwv_ge5Piw2ClolTgC6BDSpVD0EzE,8025
624
+ portal/views/cron/user.py,sha256=N4slzEXqzp557LLPlwA6sD3HVzDu74NBf128uvtwKnM,6044
624
625
  portal/views/login/__init__.py,sha256=xSCtyFPSI87BRUybBgqa86ekFEolX5gUDbBSfBUMTyI,399
625
626
  portal/views/login/independent_student.py,sha256=3dFULhwMAlX4VDrJl-Znril6a9M5xKBSHO1eWvujfS0,2662
626
627
  portal/views/login/student.py,sha256=4bQLLhB-KHvvjim07rYWXaLCZzHjUVofl0wrRkb1s0w,5224
@@ -635,8 +636,8 @@ portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
635
636
  portal/views/two_factor/core.py,sha256=O_wcBeFqdPYSGNGv-pT_vbs5-Dj1Z-Jfkd6f9-E5yZI,760
636
637
  portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
637
638
  portal/views/two_factor/profile.py,sha256=tkl_ludo8arMtd5LKNmohM66vpC_YQiP-0nspTSJiJ4,383
638
- codeforlife_portal-6.41.5.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
639
- codeforlife_portal-6.41.5.dist-info/METADATA,sha256=7Z9vG1kRYxFAzbXra0yCUKroW1Y1zSmcc9fVWU2LSj8,1169
640
- codeforlife_portal-6.41.5.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
641
- codeforlife_portal-6.41.5.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
642
- codeforlife_portal-6.41.5.dist-info/RECORD,,
639
+ codeforlife_portal-6.41.7.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
640
+ codeforlife_portal-6.41.7.dist-info/METADATA,sha256=UdjZlM-_Rgv-73hXgK-hpwyFIlzXdzse5bVsrEWNqso,1169
641
+ codeforlife_portal-6.41.7.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
642
+ codeforlife_portal-6.41.7.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
643
+ codeforlife_portal-6.41.7.dist-info/RECORD,,
@@ -1,5 +1,7 @@
1
1
  """Django settings for example_project project."""
2
+
2
3
  import os
4
+
3
5
  from selenium import webdriver
4
6
 
5
7
  DEBUG = True
@@ -33,6 +35,8 @@ if os.environ.get("SELENIUM_HEADLESS", None):
33
35
  ROOT_URLCONF = "example_project.urls"
34
36
  SECRET_KEY = "bad_test_secret"
35
37
 
38
+ DOTDIGITAL_AUTH = "dummy_dotdigital_auth"
39
+
36
40
  DOTMAILER_CREATE_CONTACT_URL = "https://test-create-contact/"
37
41
  DOTMAILER_DELETE_USER_BY_ID_URL = "https://test-delete-contact/"
38
42
  DOTMAILER_MAIN_ADDRESS_BOOK_URL = "https://test-main-address-book/"
@@ -169,7 +173,7 @@ TEMPLATES = [
169
173
  "common.context_processors.cookie_management_enabled",
170
174
  "portal.context_processors.process_newsletter_form",
171
175
  ]
172
- }
176
+ },
173
177
  }
174
178
  ]
175
179
 
portal/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "6.41.5"
1
+ __version__ = "6.41.7"
@@ -1,4 +1,4 @@
1
- <script type="text/plain" class="optanon-category-CL002">
1
+ <script type="text/plain" class="optanon-category-C0002">
2
2
  var _mfq = _mfq || [];
3
3
  (function() {
4
4
  var mf = document.createElement("script");
@@ -666,6 +666,11 @@
666
666
  also information about the device you use (like the device type (desktop/tablet/phone), location
667
667
  (city/country) and language). Mouseflow does not collect any information on pages where it is not installed
668
668
  and does not track or collect information outside your web browser.</p>
669
+ <p><strong>YouTube</strong>. While embedded YouTube videos on our website do not use cookies, your browser's
670
+ Local Storage may be used by the video platform provider to enhance your viewing experience and collect
671
+ analytics data. This data enables the platform to track your interaction with the video content and may
672
+ gather information about your device and browsing behavior. The use of Local Storage by embedded video
673
+ providers is subject to their respective policies.</p>
669
674
 
670
675
  <h6>Managing and disabling cookies</h6>
671
676
 
@@ -2,11 +2,16 @@ from __future__ import absolute_import
2
2
 
3
3
  import datetime
4
4
  import time
5
+ from unittest.mock import ANY, Mock, patch
5
6
 
7
+ from common.mail import campaign_ids
6
8
  from common.models import JoinReleaseStudent
7
9
  from common.tests.utils import email as email_utils
8
10
  from common.tests.utils.classes import create_class_directly
9
- from common.tests.utils.organisation import create_organisation_directly, join_teacher_to_organisation
11
+ from common.tests.utils.organisation import (
12
+ create_organisation_directly,
13
+ join_teacher_to_organisation,
14
+ )
10
15
  from common.tests.utils.student import (
11
16
  create_independent_student,
12
17
  create_independent_student_directly,
@@ -137,7 +142,8 @@ class TestIndependentStudent(TestCase):
137
142
  # Assert response isn't a redirect (submit failure)
138
143
  assert response.status_code == 200
139
144
 
140
- def test_signup_under_13_sends_parent_email(self):
145
+ @patch("common.helpers.emails.send_dotdigital_email")
146
+ def test_signup_under_13_sends_parent_email(self, mock_send_dotdigital_email: Mock):
141
147
  c = Client()
142
148
 
143
149
  response = c.post(
@@ -156,8 +162,9 @@ class TestIndependentStudent(TestCase):
156
162
  )
157
163
 
158
164
  assert response.status_code == 302
159
- assert len(mail.outbox) == 1
160
- assert mail.outbox[0].subject == "Code for Life account request"
165
+ mock_send_dotdigital_email.assert_called_once_with(
166
+ campaign_ids["verify_new_user_via_parent"], ANY, personalization_values=ANY
167
+ )
161
168
 
162
169
 
163
170
  # Class for Selenium tests. We plan to replace these and turn them into Cypress tests
@@ -256,16 +263,20 @@ class TestIndependentStudentFrontend(BaseTest):
256
263
  page = page.independent_student_login(username, password)
257
264
  assert self.is_dashboard(page)
258
265
 
259
- def test_login_not_verified(self):
266
+ @patch("common.helpers.emails.send_dotdigital_email")
267
+ def test_login_not_verified(self, mock_send_dotdigital_email):
260
268
  username, password, _ = create_independent_student_directly(preverified=False)
261
269
  self.selenium.get(self.live_server_url)
262
270
  page = HomePage(self.selenium)
263
271
  page = page.go_to_independent_student_login_page()
264
272
  page = page.independent_student_login_failure(username, password)
265
273
 
274
+ errors = page.has_login_failed("independent_student_login_form", INVALID_LOGIN_MESSAGE)
266
275
  assert page.has_login_failed("independent_student_login_form", INVALID_LOGIN_MESSAGE)
267
276
 
268
- verify_email(page)
277
+ verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
278
+
279
+ verify_email(page, verification_url)
269
280
 
270
281
  assert is_email_verified_message_showing(self.selenium)
271
282
 
@@ -340,7 +351,8 @@ class TestIndependentStudentFrontend(BaseTest):
340
351
  "student_account_form", "Names may only contain letters, numbers, dashes, underscores, and spaces."
341
352
  )
342
353
 
343
- def test_change_email(self):
354
+ @patch("common.helpers.emails.send_dotdigital_email")
355
+ def test_change_email(self, mock_send_dotdigital_email):
344
356
  homepage = self.go_to_homepage()
345
357
 
346
358
  _, _, _, student_email, password = create_independent_student(homepage)
@@ -354,9 +366,9 @@ class TestIndependentStudentFrontend(BaseTest):
354
366
  assert is_student_details_updated_message_showing(self.selenium)
355
367
  assert is_email_updated_message_showing(self.selenium)
356
368
 
357
- subject = str(mail.outbox[0].subject)
358
- assert subject == "Email address update"
359
- mail.outbox = []
369
+ mock_send_dotdigital_email.assert_called_with(
370
+ campaign_ids["email_change_notification"], ANY, personalization_values=ANY
371
+ )
360
372
 
361
373
  # Try changing email to an existing teacher's email
362
374
  teacher_email, _ = signup_teacher_directly()
@@ -372,9 +384,9 @@ class TestIndependentStudentFrontend(BaseTest):
372
384
  assert is_student_details_updated_message_showing(self.selenium)
373
385
  assert is_email_updated_message_showing(self.selenium)
374
386
 
375
- subject = str(mail.outbox[0].subject)
376
- assert subject == "Email address update"
377
- mail.outbox = []
387
+ mock_send_dotdigital_email.assert_called_with(
388
+ campaign_ids["email_change_notification"], ANY, personalization_values=ANY
389
+ )
378
390
 
379
391
  page = (
380
392
  self.go_to_homepage()
@@ -402,11 +414,12 @@ class TestIndependentStudentFrontend(BaseTest):
402
414
 
403
415
  page = page.logout()
404
416
 
405
- subject = str(mail.outbox[0].subject)
406
- assert subject == "Email address update"
417
+ mock_send_dotdigital_email.assert_called_with(
418
+ campaign_ids["email_change_verification"], ANY, personalization_values=ANY
419
+ )
420
+ verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
407
421
 
408
- page = email_utils.follow_change_email_link_to_independent_dashboard(page, mail.outbox[1])
409
- mail.outbox = []
422
+ page = email_utils.follow_change_email_link_to_independent_dashboard(page, verification_url)
410
423
 
411
424
  page = page.independent_student_login(new_email, password)
412
425
 
@@ -2,10 +2,12 @@ from __future__ import absolute_import
2
2
 
3
3
  import re
4
4
  from datetime import datetime, timedelta
5
+ from unittest.mock import ANY, Mock, patch
5
6
 
6
7
  import pytest
7
8
  import pytz
8
- from common.models import Teacher, Student, DailyActivity
9
+ from common.mail import campaign_ids
10
+ from common.models import DailyActivity, Student, Teacher
9
11
  from common.tests.utils.classes import create_class_directly
10
12
  from common.tests.utils.organisation import create_organisation_directly
11
13
  from common.tests.utils.student import (
@@ -13,11 +15,10 @@ from common.tests.utils.student import (
13
15
  create_school_student_directly,
14
16
  generate_independent_student_details,
15
17
  )
16
- from common.tests.utils.teacher import signup_teacher_directly, generate_details
18
+ from common.tests.utils.teacher import generate_details, signup_teacher_directly
17
19
  from django.core import mail
18
20
  from django.test import Client, TestCase
19
- from django.urls import reverse
20
- from django.urls import reverse_lazy
21
+ from django.urls import reverse, reverse_lazy
21
22
 
22
23
  from portal.helpers.ratelimit import get_ratelimit_count_for_user
23
24
  from portal.views.login import has_user_lockout_expired
@@ -432,8 +433,9 @@ class TestRatelimit(TestCase):
432
433
  assert current_daily_activity.school_student_lockout_resets == 2
433
434
 
434
435
 
436
+ @patch("common.helpers.emails.send_dotdigital_email")
435
437
  @pytest.mark.django_db
436
- def test_teacher_already_registered_email(client):
438
+ def test_teacher_already_registered_email(mock_send_dotdigital_email: Mock, client):
437
439
  first_name, last_name, email, password = generate_details()
438
440
  register_url = reverse("register")
439
441
  data = {
@@ -448,19 +450,20 @@ def test_teacher_already_registered_email(client):
448
450
 
449
451
  # Register the teacher first time, there should be a registration email
450
452
  client.post(register_url, data)
451
- assert len(mail.outbox) == 1
453
+ mock_send_dotdigital_email.assert_called_once_with(campaign_ids["verify_new_user"], ANY, personalization_values=ANY)
452
454
 
453
455
  # Register with the same email again, there should also be an already registered email
454
456
  client.post(register_url, data)
455
- assert len(mail.outbox) == 2
457
+ assert len(mail.outbox) == 1
456
458
 
457
459
  # Register with the same email one more time, there shouldn't be any new emails
458
460
  client.post(register_url, data)
459
- assert len(mail.outbox) == 2
461
+ assert len(mail.outbox) == 1
460
462
 
461
463
 
464
+ @patch("common.helpers.emails.send_dotdigital_email")
462
465
  @pytest.mark.django_db
463
- def test_independent_student_already_registered_email(client):
466
+ def test_independent_student_already_registered_email(mock_send_dotdigital_email: Mock, client):
464
467
  name, username, email_address, password = generate_independent_student_details()
465
468
  register_url = reverse("register")
466
469
  data = {
@@ -477,12 +480,12 @@ def test_independent_student_already_registered_email(client):
477
480
 
478
481
  # Register the independent student first time, there should be a registration email
479
482
  client.post(register_url, data)
480
- assert len(mail.outbox) == 1
483
+ mock_send_dotdigital_email.assert_called_once_with(campaign_ids["verify_new_user"], ANY, personalization_values=ANY)
481
484
 
482
485
  # Register with the same email again, there should also be an already registered email
483
486
  client.post(register_url, data)
484
- assert len(mail.outbox) == 2
487
+ assert len(mail.outbox) == 1
485
488
 
486
489
  # Reset mock and register with the same email one more time, there shouldn't be any new emails
487
490
  client.post(register_url, data)
488
- assert len(mail.outbox) == 2
491
+ assert len(mail.outbox) == 1
@@ -1,17 +1,24 @@
1
1
  from __future__ import absolute_import
2
2
 
3
- import re
4
3
  import time
5
4
  from datetime import timedelta
5
+ from unittest.mock import ANY, Mock, patch
6
6
  from uuid import uuid4
7
7
 
8
8
  import jwt
9
9
  from aimmo.models import Game
10
+ from common.mail import campaign_ids
10
11
  from common.models import Class, Student, Teacher
11
12
  from common.tests.utils import email as email_utils
12
13
  from common.tests.utils.classes import create_class_directly
13
- from common.tests.utils.organisation import create_organisation_directly, join_teacher_to_organisation
14
- from common.tests.utils.student import create_independent_student_directly, create_school_student_directly
14
+ from common.tests.utils.organisation import (
15
+ create_organisation_directly,
16
+ join_teacher_to_organisation,
17
+ )
18
+ from common.tests.utils.student import (
19
+ create_independent_student_directly,
20
+ create_school_student_directly,
21
+ )
15
22
  from common.tests.utils.teacher import (
16
23
  signup_duplicate_teacher_fail,
17
24
  signup_teacher,
@@ -357,7 +364,8 @@ class TestTeacher(TestCase):
357
364
  # Assert response isn't a redirect (submit failure)
358
365
  assert response.status_code == 200
359
366
 
360
- def test_signup_email_verification(self):
367
+ @patch("common.helpers.emails.send_dotdigital_email")
368
+ def test_signup_email_verification(self, mock_send_dotdigital_email: Mock):
361
369
  c = Client()
362
370
 
363
371
  response = c.post(
@@ -374,7 +382,9 @@ class TestTeacher(TestCase):
374
382
  )
375
383
 
376
384
  assert response.status_code == 302
377
- assert len(mail.outbox) == 1
385
+ mock_send_dotdigital_email.assert_called_once_with(
386
+ campaign_ids["verify_new_user"], ANY, personalization_values=ANY
387
+ )
378
388
 
379
389
  # Try verification URL with a fake token
380
390
  fake_token = jwt.encode(
@@ -393,9 +403,8 @@ class TestTeacher(TestCase):
393
403
  # Assert response isn't a redirect (get failure)
394
404
  assert bad_verification_response.status_code == 200
395
405
 
396
- # Get verification link from email
397
- message = str(mail.outbox[0].body)
398
- verification_url = re.search("http.+/", message).group(0)
406
+ # Get verification link from function call
407
+ verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
399
408
 
400
409
  # Verify the email properly
401
410
  verification_response = c.get(verification_url)
@@ -472,7 +481,8 @@ class TestTeacherFrontend(BaseTest):
472
481
  page = page.login(email, password)
473
482
  assert self.is_dashboard_page(page)
474
483
 
475
- def test_login_not_verified(self):
484
+ @patch("common.helpers.emails.send_dotdigital_email")
485
+ def test_login_not_verified(self, mock_send_dotdigital_email):
476
486
  email, password = signup_teacher_directly(preverified=False)
477
487
  create_organisation_directly(email)
478
488
  _, _, access_code = create_class_directly(email)
@@ -484,7 +494,9 @@ class TestTeacherFrontend(BaseTest):
484
494
 
485
495
  assert page.has_login_failed("form-login-teacher", INVALID_LOGIN_MESSAGE)
486
496
 
487
- verify_email(page)
497
+ verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
498
+
499
+ verify_email(page, verification_url)
488
500
 
489
501
  assert is_email_verified_message_showing(self.selenium)
490
502
 
@@ -537,7 +549,8 @@ class TestTeacherFrontend(BaseTest):
537
549
 
538
550
  assert page.check_account_details({"first_name": "Florian", "last_name": "Aucomte"})
539
551
 
540
- def test_change_email(self):
552
+ @patch("common.helpers.emails.send_dotdigital_email")
553
+ def test_change_email(self, mock_send_dotdigital_email):
541
554
  email, password = signup_teacher_directly()
542
555
  create_organisation_directly(email)
543
556
  _, _, access_code = create_class_directly(email)
@@ -553,9 +566,9 @@ class TestTeacherFrontend(BaseTest):
553
566
  assert self.is_email_verification_page(page)
554
567
  assert is_email_updated_message_showing(self.selenium)
555
568
 
556
- subject = str(mail.outbox[0].subject)
557
- assert subject == "Email address update"
558
- mail.outbox = []
569
+ mock_send_dotdigital_email.assert_called_with(
570
+ campaign_ids["email_change_notification"], ANY, personalization_values=ANY
571
+ )
559
572
 
560
573
  # Try changing email to an existing indy student's email, should fail
561
574
  indy_email, _, _ = create_independent_student_directly()
@@ -566,9 +579,9 @@ class TestTeacherFrontend(BaseTest):
566
579
  assert self.is_email_verification_page(page)
567
580
  assert is_email_updated_message_showing(self.selenium)
568
581
 
569
- subject = str(mail.outbox[0].subject)
570
- assert subject == "Email address update"
571
- mail.outbox = []
582
+ mock_send_dotdigital_email.assert_called_with(
583
+ campaign_ids["email_change_notification"], ANY, personalization_values=ANY
584
+ )
572
585
 
573
586
  page = self.go_to_homepage()
574
587
  page = page.go_to_teacher_login_page().login(email, password).open_account_tab()
@@ -586,11 +599,12 @@ class TestTeacherFrontend(BaseTest):
586
599
 
587
600
  page = page.logout()
588
601
 
589
- subject = str(mail.outbox[0].subject)
590
- assert subject == "Email address update"
602
+ mock_send_dotdigital_email.assert_called_with(
603
+ campaign_ids["email_change_verification"], ANY, personalization_values=ANY
604
+ )
605
+ verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
591
606
 
592
- page = email_utils.follow_change_email_link_to_dashboard(page, mail.outbox[1])
593
- mail.outbox = []
607
+ page = email_utils.follow_change_email_link_to_dashboard(page, verification_url)
594
608
 
595
609
  page = page.login(new_email, password).open_account_tab()
596
610
 
@@ -1,11 +1,15 @@
1
1
  from __future__ import absolute_import
2
2
 
3
3
  import json
4
+ from unittest.mock import Mock, patch
4
5
 
5
6
  import pytest
6
7
  from common.models import JoinReleaseStudent
7
8
  from common.tests.utils.classes import create_class_directly
8
- from common.tests.utils.organisation import create_organisation_directly, join_teacher_to_organisation
9
+ from common.tests.utils.organisation import (
10
+ create_organisation_directly,
11
+ join_teacher_to_organisation,
12
+ )
9
13
  from common.tests.utils.student import (
10
14
  create_many_school_students,
11
15
  create_school_student,
@@ -528,7 +532,8 @@ class TestTeacherStudentFrontend(BaseTest):
528
532
  page = page.logout().go_to_teacher_login_page().login(email_2, password_2).open_classes_tab().go_to_class_page()
529
533
  assert page.student_exists(student_name_1)
530
534
 
531
- def test_dismiss(self):
535
+ @patch("common.helpers.emails.send_dotdigital_email")
536
+ def test_dismiss(self, mock_send_dotdigital_email: Mock):
532
537
  email, password = signup_teacher_directly()
533
538
  create_organisation_directly(email)
534
539
  _, _, access_code = create_class_directly(email)
@@ -557,7 +562,10 @@ class TestTeacherStudentFrontend(BaseTest):
557
562
  assert len(logs) == 1
558
563
  assert logs[0].action_type == JoinReleaseStudent.RELEASE
559
564
 
560
- def test_multiple_dismiss(self):
565
+ mock_send_dotdigital_email.assert_called()
566
+
567
+ @patch("common.helpers.emails.send_dotdigital_email")
568
+ def test_multiple_dismiss(self, mock_send_dotdigital_email: Mock):
561
569
  email, password = signup_teacher_directly()
562
570
  create_organisation_directly(email)
563
571
  _, _, access_code = create_class_directly(email)
@@ -597,3 +605,5 @@ class TestTeacherStudentFrontend(BaseTest):
597
605
 
598
606
  # student should still exist
599
607
  assert page.student_exists(student_name_2)
608
+
609
+ mock_send_dotdigital_email.assert_called()