codeforlife-portal 5.32.2__py2.py3-none-any.whl → 8.9.9__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.
Files changed (393) hide show
  1. cfl_common/common/__init__.py +1 -0
  2. cfl_common/common/app_settings.py +66 -0
  3. cfl_common/common/apps.py +6 -0
  4. cfl_common/common/context_processors.py +9 -0
  5. cfl_common/common/csp_config.py +85 -0
  6. cfl_common/common/helpers/__init__.py +0 -0
  7. cfl_common/common/helpers/data_migration_loader.py +42 -0
  8. cfl_common/common/helpers/emails.py +393 -0
  9. cfl_common/common/helpers/generators.py +52 -0
  10. cfl_common/common/helpers/organisation.py +10 -0
  11. cfl_common/common/mail.py +201 -0
  12. cfl_common/common/migrations/0001_initial.py +240 -0
  13. cfl_common/common/migrations/0002_emailverification.py +55 -0
  14. cfl_common/common/migrations/0003_aimmocharacter.py +31 -0
  15. cfl_common/common/migrations/0004_add_aimmocharacters.py +17 -0
  16. cfl_common/common/migrations/0005_add_worksheets.py +8 -0
  17. cfl_common/common/migrations/0006_update_aimmo_character_image_path.py +17 -0
  18. cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +8 -0
  19. cfl_common/common/migrations/0008_unlock_worksheet_3.py +11 -0
  20. cfl_common/common/migrations/0009_add_blocked_time_to_teacher_and_student.py +24 -0
  21. cfl_common/common/migrations/0010_remove_teacher_title.py +18 -0
  22. cfl_common/common/migrations/0011_student_login_id.py +18 -0
  23. cfl_common/common/migrations/0012_usersession.py +39 -0
  24. cfl_common/common/migrations/0013_class_school.py +42 -0
  25. cfl_common/common/migrations/0014_login_type.py +29 -0
  26. cfl_common/common/migrations/0015_dailyactivity.py +31 -0
  27. cfl_common/common/migrations/0016_joinreleasestudent.py +42 -0
  28. cfl_common/common/migrations/0017_copy_email_to_username.py +18 -0
  29. cfl_common/common/migrations/0018_update_aimmo_character_image_path.py +15 -0
  30. cfl_common/common/migrations/0019_aimmocharacter_alt.py +16 -0
  31. cfl_common/common/migrations/0020_class_is_active_and_null_access_code.py +23 -0
  32. cfl_common/common/migrations/0021_school_is_active.py +28 -0
  33. cfl_common/common/migrations/0022_school_cleanup.py +29 -0
  34. cfl_common/common/migrations/0023_userprofile_aimmo_badges.py +22 -0
  35. cfl_common/common/migrations/0024_teacher_invited_by.py +25 -0
  36. cfl_common/common/migrations/0025_schoolteacherinvitation.py +47 -0
  37. cfl_common/common/migrations/0026_teacher_remove_join_request.py +22 -0
  38. cfl_common/common/migrations/0027_class_created_by.py +25 -0
  39. cfl_common/common/migrations/0028_coding_club_downloads.py +23 -0
  40. cfl_common/common/migrations/0029_dynamicelement.py +22 -0
  41. cfl_common/common/migrations/0030_add_maintenance_banner.py +25 -0
  42. cfl_common/common/migrations/0031_improve_admin_panel.py +56 -0
  43. cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py +18 -0
  44. cfl_common/common/migrations/0033_password_reset_tracking_fields.py +23 -0
  45. cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +18 -0
  46. cfl_common/common/migrations/0035_rename_lockout_fields.py +27 -0
  47. cfl_common/common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +17 -0
  48. cfl_common/common/migrations/0037_migrate_email_verification.py +21 -0
  49. cfl_common/common/migrations/0038_delete_emailverification.py +16 -0
  50. cfl_common/common/migrations/0039_copy_email_to_username.py +18 -0
  51. cfl_common/common/migrations/0040_school_county.py +18 -0
  52. cfl_common/common/migrations/0041_populate_gb_counties.py +27 -0
  53. cfl_common/common/migrations/0042_totalactivity.py +25 -0
  54. cfl_common/common/migrations/0043_add_total_activity.py +30 -0
  55. cfl_common/common/migrations/0044_update_activity_models.py +33 -0
  56. cfl_common/common/migrations/0045_otp.py +23 -0
  57. cfl_common/common/migrations/0046_alter_school_country.py +19 -0
  58. cfl_common/common/migrations/0047_delete_school_postcode.py +16 -0
  59. cfl_common/common/migrations/0048_unique_school_names.py +42 -0
  60. cfl_common/common/migrations/0049_anonymise_orphan_users.py +29 -0
  61. cfl_common/common/migrations/0050_anonymise_orphan_schools.py +30 -0
  62. cfl_common/common/migrations/0051_verify_returning_users.py +26 -0
  63. cfl_common/common/migrations/0052_add_cse_fields.py +68 -0
  64. cfl_common/common/migrations/0053_clean_class_data.py +24 -0
  65. cfl_common/common/migrations/0054_delete_aimmo_models.py +20 -0
  66. cfl_common/common/migrations/0055_alter_schoolteacherinvitation_token.py +18 -0
  67. cfl_common/common/migrations/0056_set_non_school_teachers_as_non_admins.py +25 -0
  68. cfl_common/common/migrations/0057_teacher_teacher__is_admin.py +19 -0
  69. cfl_common/common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
  70. cfl_common/common/migrations/__init__.py +0 -0
  71. cfl_common/common/models.py +557 -0
  72. cfl_common/common/permissions.py +84 -0
  73. cfl_common/common/tests/__init__.py +0 -0
  74. cfl_common/common/tests/test_migration_anonymise_orphan_schools.py +30 -0
  75. cfl_common/common/tests/test_migration_anonymise_orphan_users.py +30 -0
  76. cfl_common/common/tests/test_migration_blocked_time.py +15 -0
  77. cfl_common/common/tests/test_migration_remove_teacher_title.py +13 -0
  78. cfl_common/common/tests/test_migration_unique_school_names.py +33 -0
  79. cfl_common/common/tests/test_migration_verify_returning_users.py +59 -0
  80. cfl_common/common/tests/test_models.py +87 -0
  81. cfl_common/common/tests/utils/__init__.py +0 -0
  82. cfl_common/common/tests/utils/classes.py +38 -0
  83. cfl_common/common/tests/utils/email.py +67 -0
  84. cfl_common/common/tests/utils/organisation.py +41 -0
  85. cfl_common/common/tests/utils/student.py +123 -0
  86. cfl_common/common/tests/utils/teacher.py +73 -0
  87. cfl_common/common/tests/utils/user.py +27 -0
  88. cfl_common/common/utils.py +56 -0
  89. cfl_common/setup.py +61 -0
  90. codeforlife_portal-8.9.9.dist-info/METADATA +226 -0
  91. {codeforlife_portal-5.32.2.dist-info → codeforlife_portal-8.9.9.dist-info}/RECORD +341 -238
  92. {codeforlife_portal-5.32.2.dist-info → codeforlife_portal-8.9.9.dist-info}/WHEEL +1 -1
  93. codeforlife_portal-8.9.9.dist-info/licenses/LICENSE.md +3 -0
  94. {codeforlife_portal-5.32.2.dist-info → codeforlife_portal-8.9.9.dist-info}/top_level.txt +1 -0
  95. deploy/middleware/maintenance.py +25 -0
  96. deploy/middleware/screentime_warning.py +29 -0
  97. deploy/middleware/security.py +5 -6
  98. deploy/middleware/session_timeout.py +31 -0
  99. deploy/middleware/tmp_basic_auth.py +41 -0
  100. example_project/portal_test_settings.py +239 -0
  101. example_project/settings.py +156 -17
  102. example_project/urls.py +5 -6
  103. portal/__init__.py +1 -1
  104. portal/admin.py +142 -29
  105. portal/app_settings.py +10 -7
  106. portal/forms/dotmailer.py +6 -4
  107. portal/forms/invite_teacher.py +19 -10
  108. portal/forms/organisation.py +137 -68
  109. portal/forms/play.py +53 -98
  110. portal/forms/registration.py +70 -164
  111. portal/forms/teach.py +147 -121
  112. portal/handlers.py +1 -2
  113. portal/helpers/decorators.py +30 -10
  114. portal/helpers/password.py +86 -47
  115. portal/helpers/ratelimit.py +32 -15
  116. portal/helpers/regexes.py +5 -0
  117. portal/helpers/request_handlers.py +10 -0
  118. portal/migrations/0044_auto_20150430_0959.py +6 -2
  119. portal/mixins/__init__.py +1 -0
  120. portal/mixins/cron_mixin.py +12 -0
  121. portal/permissions/__init__.py +1 -0
  122. portal/permissions/is_cron_request_from_google.py +14 -0
  123. portal/static/portal/img/10_years_anniversary.png +0 -0
  124. portal/static/portal/img/RR_logo_grass_background.png +0 -0
  125. portal/static/portal/img/coding_club_hero.jpg +0 -0
  126. portal/static/portal/img/coding_club_python_pack.png +0 -0
  127. portal/static/portal/img/facebook.png +0 -0
  128. portal/static/portal/img/gitbook.png +0 -0
  129. portal/static/portal/img/howe_dell_1.png +0 -0
  130. portal/static/portal/img/howe_dell_2.png +0 -0
  131. portal/static/portal/img/howe_dell_3.png +0 -0
  132. portal/static/portal/img/logo_cfl.png +0 -0
  133. portal/static/portal/img/logo_cfl_powered.svg +35 -0
  134. portal/static/portal/img/logo_cfl_reminder_cards.jpg +0 -0
  135. portal/static/portal/img/logo_ocado_group.png +0 -0
  136. portal/static/portal/img/logo_python_den.svg +21 -0
  137. portal/static/portal/img/long_europe_map.png +0 -0
  138. portal/static/portal/img/python_den.png +0 -0
  139. portal/static/portal/img/python_den_banner.svg +26 -0
  140. portal/static/portal/img/rapid_router_landing_hero.png +0 -0
  141. portal/static/portal/img/rr_advanced.png +0 -0
  142. portal/static/portal/img/ten_year_map_pin.svg +1 -0
  143. portal/static/portal/img/thumbnail_educate_rapid_router.png +0 -0
  144. portal/static/portal/img/thumbnail_educate_resources.png +0 -0
  145. portal/static/portal/img/thumbnail_play_rapid_router.png +0 -0
  146. portal/static/portal/img/thumbnail_python_den.png +0 -0
  147. portal/static/portal/img/twitter.png +0 -0
  148. portal/static/portal/js/carouselCards.js +25 -0
  149. portal/static/portal/js/common.js +96 -1
  150. portal/static/portal/js/independentLogin.js +16 -0
  151. portal/static/portal/js/independentRegistration.js +86 -0
  152. portal/static/portal/js/levelControl.js +77 -0
  153. portal/static/portal/js/lib/jquery.min.js +2 -0
  154. portal/static/portal/js/organisation_manage.js +142 -14
  155. portal/static/portal/js/passwordStrength.js +154 -64
  156. portal/static/portal/js/resetPassword.js +23 -0
  157. portal/static/portal/js/riveted.min.js +238 -239
  158. portal/static/portal/js/school.js +13 -0
  159. portal/static/portal/js/studentLogin.js +16 -0
  160. portal/static/portal/js/teacherEditStudent.js +23 -0
  161. portal/static/portal/js/teacherLogin.js +16 -0
  162. portal/static/portal/js/tenYearMap.js +14 -0
  163. portal/static/portal/sass/colorbox.scss +0 -1
  164. portal/static/portal/sass/modules/_colours.scss +1 -0
  165. portal/static/portal/sass/modules/_levels.scss +1 -1
  166. portal/static/portal/sass/modules/_mixins.scss +21 -0
  167. portal/static/portal/sass/partials/_banners.scss +4 -177
  168. portal/static/portal/sass/partials/_buttons.scss +12 -15
  169. portal/static/portal/sass/partials/_carousel.scss +129 -0
  170. portal/static/portal/sass/partials/_footer.scss +21 -22
  171. portal/static/portal/sass/partials/_forms.scss +60 -5
  172. portal/static/portal/sass/partials/_grids.scss +34 -61
  173. portal/static/portal/sass/partials/_header.scss +28 -20
  174. portal/static/portal/sass/partials/_images.scss +292 -39
  175. portal/static/portal/sass/partials/_popup.scss +18 -15
  176. portal/static/portal/sass/partials/_tables.scss +12 -20
  177. portal/static/portal/sass/partials/_text.scss +6 -10
  178. portal/static/portal/sass/styles.scss +0 -1
  179. portal/static/portal/video/code for life .pdf +0 -0
  180. portal/strings/about.py +5 -0
  181. portal/strings/coding_club.py +9 -0
  182. portal/strings/play.py +6 -5
  183. portal/strings/teach.py +1 -1
  184. portal/strings/teacher_resources.py +2 -8
  185. portal/strings/ten_year_map.py +13 -0
  186. portal/templates/403.html +2 -2
  187. portal/templates/404.html +1 -1
  188. portal/templates/500.html +2 -2
  189. portal/templates/{captcha → django_recaptcha}/includes/js_v2_invisible.html +3 -3
  190. portal/templates/{captcha → django_recaptcha}/widget_v2_invisible.html +2 -2
  191. portal/templates/email.html +4 -2
  192. portal/templates/maintenance.html +34 -0
  193. portal/templates/portal/about.html +94 -62
  194. portal/templates/portal/base.html +176 -152
  195. portal/templates/portal/coding_club.html +100 -0
  196. portal/templates/portal/contribute.html +56 -52
  197. portal/templates/portal/email_invitation_sent.html +1 -1
  198. portal/templates/portal/email_style_template.html +374 -0
  199. portal/templates/portal/email_verification_failed.html +1 -1
  200. portal/templates/portal/email_verification_needed.html +9 -9
  201. portal/templates/portal/form_shapes.html +20 -8
  202. portal/templates/portal/getinvolved.html +6 -6
  203. portal/templates/portal/home.html +35 -10
  204. portal/templates/portal/home_learning.html +19 -19
  205. portal/templates/portal/locked_out.html +0 -1
  206. portal/templates/portal/locked_out_school_student.html +16 -0
  207. portal/templates/portal/login/independent_student.html +31 -15
  208. portal/templates/portal/login/student.html +10 -7
  209. portal/templates/portal/login/student_class_code.html +7 -4
  210. portal/templates/portal/login/teacher.html +34 -17
  211. portal/templates/portal/partials/banner.html +18 -4
  212. portal/templates/portal/partials/benefits.html +1 -1
  213. portal/templates/portal/partials/card_list.html +34 -24
  214. portal/templates/portal/partials/character_list.html +5 -5
  215. portal/templates/portal/partials/cookie_list.html +161 -0
  216. portal/templates/portal/partials/delete_popup.html +18 -0
  217. portal/templates/portal/partials/footer.html +57 -26
  218. portal/templates/portal/partials/header.html +118 -117
  219. portal/templates/portal/partials/hero_card.html +4 -3
  220. portal/templates/portal/partials/info_popup.html +3 -3
  221. portal/templates/portal/partials/invite_admin_teacher.html +23 -0
  222. portal/templates/portal/partials/popup.html +7 -2
  223. portal/templates/portal/partials/register_newsletter_tickbox.html +2 -5
  224. portal/templates/portal/partials/screentime_popup.html +14 -0
  225. portal/templates/portal/partials/service_unavailable_popup.html +17 -0
  226. portal/templates/portal/partials/session_popup.html +19 -0
  227. portal/templates/portal/play/student_dashboard.html +42 -29
  228. portal/templates/portal/play/student_edit_account.html +64 -9
  229. portal/templates/portal/play.html +61 -41
  230. portal/templates/portal/privacy_notice.html +697 -0
  231. portal/templates/portal/register.html +122 -92
  232. portal/templates/portal/reset_password.html +20 -40
  233. portal/templates/portal/reset_password_confirm.html +9 -4
  234. portal/templates/portal/reset_password_email_sent.html +15 -13
  235. portal/templates/portal/teach/base_registering.html +1 -1
  236. portal/templates/portal/teach/class.html +4 -6
  237. portal/templates/portal/teach/dashboard.html +212 -117
  238. portal/templates/portal/teach/invited.html +90 -0
  239. portal/templates/portal/teach/onboarding_classes.html +5 -3
  240. portal/templates/portal/teach/onboarding_print.html +1 -1
  241. portal/templates/portal/teach/onboarding_school.html +26 -139
  242. portal/templates/portal/teach/onboarding_students.html +1 -1
  243. portal/templates/portal/teach/teacher_dismiss_students.html +73 -55
  244. portal/templates/portal/teach/teacher_edit_class.html +168 -11
  245. portal/templates/portal/teach/teacher_edit_student.html +12 -5
  246. portal/templates/portal/teach/teacher_move_all_classes.html +25 -38
  247. portal/templates/portal/teach/teacher_move_students_to_class.html +1 -1
  248. portal/templates/portal/teach.html +61 -42
  249. portal/templates/portal/ten_year_map.html +147 -0
  250. portal/templates/portal/terms.html +191 -42
  251. portal/templates/two_factor/core/login.html +71 -59
  252. portal/templates/two_factor/core/setup.html +58 -49
  253. portal/templates/two_factor/profile/disable.html +1 -1
  254. portal/templates/two_factor/profile/profile.html +35 -17
  255. portal/templatetags/app_tags.py +59 -84
  256. portal/templatetags/card_list_tags.py +0 -4
  257. portal/tests/base_test.py +14 -3
  258. portal/tests/conftest.py +0 -15
  259. portal/tests/migrations/test_migration_make_portaladmin_teacher.py +2 -6
  260. portal/tests/migrations/test_migration_preview_users.py +3 -9
  261. portal/tests/migrations/test_migration_remove_guardian.py +1 -3
  262. portal/tests/migrations/test_migration_use_common_models.py +2 -6
  263. portal/tests/migrations/test_migration_verify_portaladmin.py +1 -3
  264. portal/tests/pageObjects/portal/admin/admin_base_page.py +0 -21
  265. portal/tests/pageObjects/portal/base_page.py +16 -26
  266. portal/tests/pageObjects/portal/email_verification_needed_page.py +3 -2
  267. portal/tests/pageObjects/portal/game_page.py +12 -19
  268. portal/tests/pageObjects/portal/home_page.py +13 -15
  269. portal/tests/pageObjects/portal/independent_login_page.py +13 -17
  270. portal/tests/pageObjects/portal/password_reset_form_page.py +20 -4
  271. portal/tests/pageObjects/portal/password_reset_page.py +25 -0
  272. portal/tests/pageObjects/portal/play/account_page.py +18 -27
  273. portal/tests/pageObjects/portal/play/dashboard_page.py +4 -4
  274. portal/tests/pageObjects/portal/play/join_school_or_club_page.py +8 -10
  275. portal/tests/pageObjects/portal/play/play_base_page.py +5 -3
  276. portal/tests/pageObjects/portal/signup_page.py +28 -59
  277. portal/tests/pageObjects/portal/student_login_class_code.py +6 -9
  278. portal/tests/pageObjects/portal/student_login_page.py +6 -8
  279. portal/tests/pageObjects/portal/teach/add_independent_student_to_class_page.py +3 -3
  280. portal/tests/pageObjects/portal/teach/added_independent_student_to_class_page.py +3 -1
  281. portal/tests/pageObjects/portal/teach/class_page.py +36 -13
  282. portal/tests/pageObjects/portal/teach/dashboard_page.py +43 -84
  283. portal/tests/pageObjects/portal/teach/dismiss_students_page.py +7 -5
  284. portal/tests/pageObjects/portal/teach/edit_student_page.py +10 -8
  285. portal/tests/pageObjects/portal/teach/move_class_page.py +5 -10
  286. portal/tests/pageObjects/portal/teach/move_classes_page.py +4 -2
  287. portal/tests/pageObjects/portal/teach/move_students_disambiguate_page.py +4 -2
  288. portal/tests/pageObjects/portal/teach/move_students_page.py +6 -13
  289. portal/tests/pageObjects/portal/teach/onboarding_classes_page.py +5 -3
  290. portal/tests/pageObjects/portal/teach/onboarding_organisation_page.py +11 -49
  291. portal/tests/pageObjects/portal/teach/onboarding_student_list_page.py +7 -12
  292. portal/tests/pageObjects/portal/teach/onboarding_students_page.py +4 -27
  293. portal/tests/pageObjects/portal/teach/teach_base_page.py +6 -4
  294. portal/tests/pageObjects/portal/teacher_login_page.py +10 -16
  295. portal/tests/selenium_test_case.py +3 -43
  296. portal/tests/snapshots/snap_test_partials.py +11 -165
  297. portal/tests/test_2FA.py +15 -33
  298. portal/tests/test_admin.py +15 -97
  299. portal/tests/test_api.py +212 -73
  300. portal/tests/test_captcha_forms.py +2 -2
  301. portal/tests/test_class.py +374 -24
  302. portal/tests/test_emails.py +83 -20
  303. portal/tests/{test_newsletter_footer.py → test_global_forms.py} +5 -5
  304. portal/tests/test_helper_methods.py +30 -0
  305. portal/tests/test_independent_student.py +255 -144
  306. portal/tests/test_invite_teacher.py +318 -10
  307. portal/tests/test_middleware.py +138 -15
  308. portal/tests/test_organisation.py +78 -262
  309. portal/tests/test_partials.py +0 -88
  310. portal/tests/test_ratelimit.py +218 -36
  311. portal/tests/test_school_student.py +35 -40
  312. portal/tests/test_security.py +12 -31
  313. portal/tests/test_teacher.py +425 -325
  314. portal/tests/test_teacher_student.py +103 -91
  315. portal/tests/test_views.py +900 -76
  316. portal/tests/utils/classes.py +2 -2
  317. portal/tests/utils/messages.py +13 -28
  318. portal/urls.py +237 -164
  319. portal/views/admin.py +0 -332
  320. portal/views/api.py +82 -68
  321. portal/views/cron/__init__.py +1 -0
  322. portal/views/cron/user.py +322 -0
  323. portal/views/dotmailer.py +9 -1
  324. portal/views/email.py +33 -77
  325. portal/views/google_analytics.py +28 -0
  326. portal/views/home.py +126 -97
  327. portal/views/legal.py +13 -0
  328. portal/views/login/independent_student.py +5 -5
  329. portal/views/login/student.py +51 -14
  330. portal/views/login/teacher.py +2 -6
  331. portal/views/organisation.py +20 -189
  332. portal/views/registration.py +97 -17
  333. portal/views/student/edit_account_details.py +99 -72
  334. portal/views/student/play.py +81 -62
  335. portal/views/teacher/dashboard.py +421 -149
  336. portal/views/teacher/teach.py +226 -177
  337. portal/views/two_factor/__init__.py +0 -0
  338. portal/views/two_factor/core.py +28 -0
  339. portal/views/two_factor/form.py +7 -0
  340. portal/views/two_factor/profile.py +11 -0
  341. codeforlife_portal-5.32.2.dist-info/LICENSE.md +0 -577
  342. codeforlife_portal-5.32.2.dist-info/METADATA +0 -38
  343. deploy/permissions.py +0 -2
  344. example_project/manage.py +0 -10
  345. portal/autoconfig.py +0 -140
  346. portal/csp_config.py +0 -60
  347. portal/forms/add_game.py +0 -33
  348. portal/helpers/location.py +0 -121
  349. portal/static/portal/img/kurono_hero.jpg +0 -0
  350. portal/static/portal/img/kurono_landing_hero.png +0 -0
  351. portal/static/portal/img/kurono_logo.svg +0 -1
  352. portal/static/portal/img/kurono_logo_grey_background.svg +0 -1
  353. portal/static/portal/img/kurono_logo_mark.svg +0 -1
  354. portal/static/portal/img/kurono_resources_hero.jpg +0 -0
  355. portal/static/portal/img/kurono_story.png +0 -0
  356. portal/static/portal/img/ocado-swirl.svg +0 -22
  357. portal/static/portal/img/thumbnail_educate_kurono.png +0 -0
  358. portal/static/portal/img/thumbnail_educate_resources_and_progress_tracking.png +0 -0
  359. portal/static/portal/img/thumbnail_kurono_resources.png +0 -0
  360. portal/static/portal/img/thumbnail_play_kurono.png +0 -0
  361. portal/static/portal/img/x_close_video.png +0 -0
  362. portal/static/portal/js/aimmoGame.js +0 -106
  363. portal/static/portal/js/deleteWorkspaces.js +0 -14
  364. portal/static/portal/js/fuzzySchoolLookup.js +0 -46
  365. portal/static/portal/js/lib/jquery-3.5.1.min.js +0 -2
  366. portal/static/portal/js/lib/jquery-ui-1.12.1.min.js +0 -13
  367. portal/static/portal/sass/partials/_videos.scss +0 -10
  368. portal/static/portal/video/aimmo_play_now_background_video.mp4 +0 -0
  369. portal/strings/student_aimmo_dashboard.py +0 -6
  370. portal/templates/portal/admin/aggregated_data.html +0 -35
  371. portal/templates/portal/admin/map.html +0 -70
  372. portal/templates/portal/mouseflow.html +0 -9
  373. portal/templates/portal/partials/aimmo_games_table.html +0 -83
  374. portal/templates/portal/partials/register_over_required_age_tickbox.html +0 -9
  375. portal/templates/portal/play/independent_student_dashboard.html +0 -64
  376. portal/templates/portal/play/student_aimmo_dashboard.html +0 -63
  377. portal/templates/portal/privacy_policy.html +0 -483
  378. portal/templates/portal/reset_password_email.html +0 -9
  379. portal/templates/portal/teach/invite.html +0 -25
  380. portal/templates/portal/teach/teacher_aimmo_dashboard.html +0 -95
  381. portal/templates/portal/teach/teacher_resources.html +0 -68
  382. portal/templatetags/character_list_tags.py +0 -16
  383. portal/tests/pageObjects/portal/kurono_teacher_dashboard_page.py +0 -49
  384. portal/tests/pageObjects/portal/student_password_reset_form_page.py +0 -23
  385. portal/tests/pageObjects/portal/teach/onboarding_revoke_request_page.py +0 -20
  386. portal/tests/pageObjects/portal/teacher_password_reset_form_page.py +0 -23
  387. portal/tests/test_aimmo_dashboards.py +0 -172
  388. portal/tests/test_location.py +0 -217
  389. portal/tests/utils/aimmo_games.py +0 -30
  390. portal/views/aimmo/dashboard.py +0 -119
  391. portal/views/privacy_policy.py +0 -9
  392. portal/views/teacher/teacher_resources.py +0 -42
  393. {portal/views/aimmo → cfl_common}/__init__.py +0 -0
@@ -1,6 +1,9 @@
1
1
  from __future__ import absolute_import
2
2
 
3
- from common.models import Class, Teacher
3
+ import time
4
+ from datetime import datetime, timedelta
5
+
6
+ from common.models import Class, DailyActivity, Teacher
4
7
  from common.tests.utils.classes import create_class_directly
5
8
  from common.tests.utils.organisation import (
6
9
  create_organisation_directly,
@@ -10,9 +13,12 @@ from common.tests.utils.student import create_school_student_directly
10
13
  from common.tests.utils.teacher import signup_teacher_directly
11
14
  from django.test import Client, TestCase
12
15
  from django.urls import reverse
16
+ from game.models import Level
13
17
 
14
- from portal.tests.utils.classes import create_class
15
18
  from .base_test import BaseTest
19
+ from .pageObjects.portal.home_page import HomePage
20
+ from .pageObjects.portal.teach.class_page import TeachClassPage
21
+ from .utils.classes import create_class
16
22
  from .utils.messages import is_class_created_message_showing
17
23
 
18
24
 
@@ -21,7 +27,7 @@ class TestClass(TestCase):
21
27
  email1, password1 = signup_teacher_directly()
22
28
  email2, password2 = signup_teacher_directly()
23
29
  create_organisation_directly(email1)
24
- _, _, access_code = create_class_directly(email1)
30
+ klass, klass_name, access_code = create_class_directly(email1)
25
31
  _, _, student = create_school_student_directly(access_code)
26
32
 
27
33
  c = Client()
@@ -52,6 +58,7 @@ class TestClass(TestCase):
52
58
 
53
59
  assert response.status_code == 302
54
60
  assert len(teacher_classes) == 1
61
+ assert teacher.has_class()
55
62
 
56
63
  # Delete the student, and try again, check the class is deleted successfully
57
64
  student.delete()
@@ -62,6 +69,13 @@ class TestClass(TestCase):
62
69
 
63
70
  assert response.status_code == 302
64
71
  assert len(teacher_classes) == 0
72
+ assert not teacher.has_class()
73
+
74
+ # Check class is anonymised
75
+ new_klass = Class._base_manager.get(pk=klass.id)
76
+ assert new_klass.name != klass_name
77
+ assert new_klass.access_code == ""
78
+ assert not new_klass.is_active
65
79
 
66
80
  def test_edit_class(self):
67
81
  email1, password1 = signup_teacher_directly()
@@ -111,11 +125,321 @@ class TestClass(TestCase):
111
125
  assert klass.classmates_data_viewable
112
126
  assert klass.always_accept_requests
113
127
 
128
+ def test_level_control(self):
129
+ email, password = signup_teacher_directly()
130
+ create_organisation_directly(email)
131
+ klass1, _, access_code1 = create_class_directly(email)
132
+ klass2, _, access_code2 = create_class_directly(email)
133
+
134
+ c = Client()
135
+ old_date = datetime.now() - timedelta(days=1)
136
+ old_daily_activity = DailyActivity(date=old_date)
137
+ old_daily_activity.save()
138
+
139
+ url = reverse("teacher_edit_class", kwargs={"access_code": access_code1})
140
+ # POST request data for locking only the first level
141
+ data = {
142
+ "episode1": [
143
+ "level:2",
144
+ "level:3",
145
+ "level:4",
146
+ "level:5",
147
+ "level:6",
148
+ "level:7",
149
+ "level:8",
150
+ "level:9",
151
+ "level:10",
152
+ "level:11",
153
+ "level:12",
154
+ ],
155
+ "episode2": [
156
+ "level:13",
157
+ "level:14",
158
+ "level:15",
159
+ "level:16",
160
+ "level:17",
161
+ "level:18",
162
+ ],
163
+ "episode3": [
164
+ "level:19",
165
+ "level:20",
166
+ "level:21",
167
+ "level:22",
168
+ "level:23",
169
+ "level:24",
170
+ "level:25",
171
+ "level:26",
172
+ "level:27",
173
+ "level:28",
174
+ ],
175
+ "episode4": [
176
+ "level:29",
177
+ "level:30",
178
+ "level:31",
179
+ "level:32",
180
+ ],
181
+ "episode5": [
182
+ "level:33",
183
+ "level:34",
184
+ "level:35",
185
+ "level:36",
186
+ "level:37",
187
+ "level:38",
188
+ "level:39",
189
+ "level:40",
190
+ "level:41",
191
+ "level:42",
192
+ "level:43",
193
+ ],
194
+ "episode6": [
195
+ "level:44",
196
+ "level:45",
197
+ "level:46",
198
+ "level:47",
199
+ "level:48",
200
+ "level:49",
201
+ "level:50",
202
+ ],
203
+ "episode7": [
204
+ "level:53",
205
+ "level:78",
206
+ "level:79",
207
+ "level:80",
208
+ "level:81",
209
+ "level:82",
210
+ "level:83",
211
+ "level:84",
212
+ "level:54",
213
+ "level:55",
214
+ ],
215
+ "episode8": [
216
+ # "level:85",
217
+ # "level:52",
218
+ # "level:60",
219
+ # "level:86",
220
+ # "level:62",
221
+ # "level:87",
222
+ # "level:61",
223
+ ],
224
+ "episode9": [
225
+ "level:56",
226
+ "level:57",
227
+ "level:58",
228
+ "level:59",
229
+ "level:88",
230
+ "level:91",
231
+ "level:90",
232
+ "level:89",
233
+ "level:110",
234
+ "level:111",
235
+ "level:112",
236
+ "level:92",
237
+ ],
238
+ "episode10": [
239
+ "level:93",
240
+ "level:63",
241
+ "level:64",
242
+ "level:65",
243
+ "level:94",
244
+ "level:66",
245
+ "level:67",
246
+ "level:68",
247
+ "level:95",
248
+ "level:69",
249
+ "level:96",
250
+ "level:97",
251
+ ],
252
+ "episode11": [
253
+ "level:98",
254
+ "level:70",
255
+ "level:71",
256
+ "level:73",
257
+ "level:72",
258
+ "level:99",
259
+ "level:74",
260
+ "level:75",
261
+ "level:100",
262
+ "level:101",
263
+ "level:102",
264
+ "level:103",
265
+ "level:104",
266
+ "level:105",
267
+ "level:106",
268
+ "level:107",
269
+ "level:108",
270
+ "level:109",
271
+ ],
272
+ "level_control_submit": "",
273
+ }
274
+
275
+ c.login(username=email, password=password)
276
+
277
+ response = c.post(url, data)
278
+
279
+ assert response.status_code == 302
280
+
281
+ level1 = Level.objects.get(name=1)
282
+
283
+ assert klass1 in level1.locked_for_class.all()
284
+ assert klass2 not in level1.locked_for_class.all()
285
+ messages = list(response.wsgi_request._messages)
286
+ assert len(messages) == 1
287
+ assert str(messages[0]) == "Your level preferences have been saved."
288
+
289
+ # test the old analytic stays the same and the new one is incremented
290
+ assert DailyActivity.objects.get(date=old_date).level_control_submits == 0
291
+ assert DailyActivity.objects.get(date=datetime.now()).level_control_submits == 1
292
+
293
+ # Resubmitting to unlock level 1
294
+ data = {
295
+ "episode1": [
296
+ "level:1",
297
+ "level:2",
298
+ "level:3",
299
+ "level:4",
300
+ "level:5",
301
+ "level:6",
302
+ "level:7",
303
+ "level:8",
304
+ "level:9",
305
+ "level:10",
306
+ "level:11",
307
+ "level:12",
308
+ ],
309
+ "episode2": [
310
+ "level:13",
311
+ "level:14",
312
+ "level:15",
313
+ "level:16",
314
+ "level:17",
315
+ "level:18",
316
+ ],
317
+ "episode3": [
318
+ "level:19",
319
+ "level:20",
320
+ "level:21",
321
+ "level:22",
322
+ "level:23",
323
+ "level:24",
324
+ "level:25",
325
+ "level:26",
326
+ "level:27",
327
+ "level:28",
328
+ ],
329
+ "episode4": [
330
+ "level:29",
331
+ "level:30",
332
+ "level:31",
333
+ "level:32",
334
+ ],
335
+ "episode5": [
336
+ "level:33",
337
+ "level:34",
338
+ "level:35",
339
+ "level:36",
340
+ "level:37",
341
+ "level:38",
342
+ "level:39",
343
+ "level:40",
344
+ "level:41",
345
+ "level:42",
346
+ "level:43",
347
+ ],
348
+ "episode6": [
349
+ "level:44",
350
+ "level:45",
351
+ "level:46",
352
+ "level:47",
353
+ "level:48",
354
+ "level:49",
355
+ "level:50",
356
+ ],
357
+ "episode7": [
358
+ "level:53",
359
+ "level:78",
360
+ "level:79",
361
+ "level:80",
362
+ "level:81",
363
+ "level:82",
364
+ "level:83",
365
+ "level:84",
366
+ "level:54",
367
+ "level:55",
368
+ ],
369
+ "episode8": [
370
+ # "level:85",
371
+ # "level:52",
372
+ # "level:60",
373
+ # "level:86",
374
+ # "level:62",
375
+ # "level:87",
376
+ # "level:61",
377
+ ],
378
+ "episode9": [
379
+ "level:56",
380
+ "level:57",
381
+ "level:58",
382
+ "level:59",
383
+ "level:88",
384
+ "level:91",
385
+ "level:90",
386
+ "level:89",
387
+ "level:110",
388
+ "level:111",
389
+ "level:112",
390
+ "level:92",
391
+ ],
392
+ "episode10": [
393
+ "level:93",
394
+ "level:63",
395
+ "level:64",
396
+ "level:65",
397
+ "level:94",
398
+ "level:66",
399
+ "level:67",
400
+ "level:68",
401
+ "level:95",
402
+ "level:69",
403
+ "level:96",
404
+ "level:97",
405
+ ],
406
+ "episode11": [
407
+ "level:98",
408
+ "level:70",
409
+ "level:71",
410
+ "level:73",
411
+ "level:72",
412
+ "level:99",
413
+ "level:74",
414
+ "level:75",
415
+ "level:100",
416
+ "level:101",
417
+ "level:102",
418
+ "level:103",
419
+ "level:104",
420
+ "level:105",
421
+ "level:106",
422
+ "level:107",
423
+ "level:108",
424
+ "level:109",
425
+ ],
426
+ "level_control_submit": "",
427
+ }
428
+
429
+ response = c.post(url, data)
430
+
431
+ assert response.status_code == 302
432
+
433
+ level1 = Level.objects.get(name=1)
434
+
435
+ assert klass1 not in level1.locked_for_class.all()
436
+ assert klass2 not in level1.locked_for_class.all()
437
+
114
438
  def test_transfer_class(self):
115
439
  email1, password1 = signup_teacher_directly()
116
440
  email2, password2 = signup_teacher_directly()
117
- school_name, postcode = create_organisation_directly(email1)
118
- join_teacher_to_organisation(email2, school_name, postcode)
441
+ school = create_organisation_directly(email1)
442
+ join_teacher_to_organisation(email2, school.name)
119
443
  klass1, _, access_code1 = create_class_directly(email1)
120
444
  klass2, _, access_code2 = create_class_directly(email2)
121
445
  _, _, student1 = create_school_student_directly(access_code1)
@@ -158,29 +482,43 @@ class TestClassFrontend(BaseTest):
158
482
  def test_create(self):
159
483
  email, password = signup_teacher_directly()
160
484
  create_organisation_directly(email)
161
- page = (
162
- self.go_to_homepage()
163
- .go_to_teacher_login_page()
164
- .login_no_class(email, password)
165
- )
485
+ page = self.go_to_homepage().go_to_teacher_login_page().login_no_class(email, password).open_classes_tab()
166
486
 
167
487
  assert page.does_not_have_classes()
168
488
 
169
489
  page, class_name = create_class(page)
170
490
  assert is_class_created_message_showing(self.selenium, class_name)
171
491
 
492
+ def test_create_class_as_admin_for_another_teacher(self):
493
+ email1, password1 = signup_teacher_directly()
494
+ email2, password2 = signup_teacher_directly()
495
+ teacher2 = Teacher.objects.get(new_user__email=email2)
496
+ school = create_organisation_directly(email1)
497
+ join_teacher_to_organisation(email2, school.name)
498
+
499
+ # Check teacher 2 doesn't have any classes
500
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email2, password2).open_classes_tab()
501
+ assert page.does_not_have_classes()
502
+ page.logout()
503
+
504
+ # Log in as the first teacher and create a class for the second one
505
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email1, password1).open_classes_tab()
506
+ page, class_name = create_class(page, teacher_id=teacher2.id)
507
+ page = TeachClassPage(page.browser)
508
+ assert is_class_created_message_showing(self.selenium, class_name)
509
+ page.logout()
510
+
511
+ # Check teacher 2 now has the class
512
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email2, password2).open_classes_tab()
513
+ assert page.has_classes()
514
+
172
515
  def test_create_dashboard(self):
173
516
  email, password = signup_teacher_directly()
174
517
  create_organisation_directly(email)
175
518
  klass, name, access_code = create_class_directly(email)
176
519
  create_school_student_directly(access_code)
177
520
 
178
- page = (
179
- self.go_to_homepage()
180
- .go_to_teacher_login_page()
181
- .login(email, password)
182
- .open_classes_tab()
183
- )
521
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email, password).open_classes_tab()
184
522
 
185
523
  page, class_name = create_class(page)
186
524
 
@@ -189,20 +527,32 @@ class TestClassFrontend(BaseTest):
189
527
  def test_create_dashboard_non_admin(self):
190
528
  email_1, password_1 = signup_teacher_directly()
191
529
  email_2, password_2 = signup_teacher_directly()
192
- name, postcode = create_organisation_directly(email_1)
530
+ school = create_organisation_directly(email_1)
193
531
  klass_1, class_name_1, access_code_1 = create_class_directly(email_1)
194
532
  create_school_student_directly(access_code_1)
195
- join_teacher_to_organisation(email_2, name, postcode)
533
+ join_teacher_to_organisation(email_2, school.name)
196
534
  klass_2, class_name_2, access_code_2 = create_class_directly(email_2)
197
535
  create_school_student_directly(access_code_2)
198
536
 
199
- page = (
200
- self.go_to_homepage()
201
- .go_to_teacher_login_page()
202
- .login(email_2, password_2)
203
- .open_classes_tab()
204
- )
537
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email_2, password_2).open_classes_tab()
205
538
 
206
539
  page, class_name_3 = create_class(page)
207
540
 
208
541
  assert is_class_created_message_showing(self.selenium, class_name_3)
542
+
543
+ def test_create_invalid_name(self):
544
+ email, password = signup_teacher_directly()
545
+ create_organisation_directly(email)
546
+
547
+ class_name = "Class!"
548
+
549
+ self.selenium.get(self.live_server_url)
550
+ page = HomePage(self.selenium).go_to_teacher_login_page().login_no_class(email, password).open_classes_tab()
551
+
552
+ page = page.create_class(class_name, False)
553
+
554
+ time.sleep(1)
555
+
556
+ assert page.was_form_invalid(
557
+ "form-create-class", "Class name may only contain letters, numbers, dashes, underscores, and spaces."
558
+ )
@@ -2,23 +2,29 @@ import datetime
2
2
 
3
3
  import pytest
4
4
  from common.helpers.emails import (
5
+ add_consent_record_to_dotmailer_user,
6
+ add_contact_to_address_book,
5
7
  add_to_dotmailer,
6
8
  create_contact,
7
- add_contact_to_address_book,
8
- add_consent_record_to_dotmailer_user,
9
+ delete_contact,
9
10
  send_dotmailer_consent_confirmation_email_to_user,
11
+ DotmailerUserType,
10
12
  )
11
- from django.core import mail
13
+ from common.mail import address_book_ids
12
14
  from django.test import Client
13
15
  from django.urls import reverse
14
- from test_settings import (
16
+
17
+ from example_project.portal_test_settings import (
15
18
  DOTMAILER_USER,
16
19
  DOTMAILER_PASSWORD,
17
20
  DOTMAILER_SEND_CAMPAIGN_URL,
18
21
  DOTMAILER_PUT_CONSENT_DATA_URL,
19
22
  DOTMAILER_THANKS_FOR_STAYING_CAMPAIGN_ID,
20
23
  DOTMAILER_CREATE_CONTACT_URL,
21
- DOTMAILER_ADDRESS_BOOK_URL,
24
+ DOTMAILER_DELETE_USER_BY_ID_URL,
25
+ DOTMAILER_TEACHER_ADDRESS_BOOK_URL,
26
+ DOTMAILER_STUDENT_ADDRESS_BOOK_URL,
27
+ DOTMAILER_NO_ACCOUNT_ADDRESS_BOOK_URL,
22
28
  )
23
29
 
24
30
  FAKE_TIME = datetime.datetime(2020, 12, 25, 17, 5, 55)
@@ -34,26 +40,25 @@ def patch_datetime_now(monkeypatch):
34
40
  monkeypatch.setattr(datetime, "datetime", mydatetime)
35
41
 
36
42
 
37
- @pytest.mark.django_db
38
- def test_send_new_users_numbers_email():
39
- client = Client()
40
- response = client.get(reverse("send_new_users_report"))
41
- assert response.status_code == 200
42
- assert len(mail.outbox) == 1
43
-
44
-
45
43
  def test_newsletter_calls_correct_requests(mocker, monkeypatch):
46
44
  mocked_create_contact = mocker.patch("common.helpers.emails.create_contact")
47
45
  mocked_add_to_address_book = mocker.patch(
48
46
  "common.helpers.emails.add_contact_to_address_book"
49
47
  )
50
48
 
51
- add_to_dotmailer("Ray", "Charles", "ray.charles@example.com")
49
+ add_to_dotmailer(
50
+ "Ray",
51
+ "Charles",
52
+ "ray.charles@example.com",
53
+ address_book_ids["newsletter"],
54
+ DotmailerUserType.TEACHER,
55
+ )
52
56
 
53
57
  mocked_create_contact.assert_called_once()
54
58
  mocked_add_to_address_book.assert_called_once()
55
59
 
56
60
 
61
+ @pytest.mark.django_db
57
62
  def test_newsletter_get_not_allowed():
58
63
  c = Client()
59
64
 
@@ -62,7 +67,21 @@ def test_newsletter_get_not_allowed():
62
67
  assert response.status_code == 405
63
68
 
64
69
 
65
- def test_newsletter_sends_correct_request_data(mocker, monkeypatch, patch_datetime_now):
70
+ def test_delete_account(mocker):
71
+ mocked_delete = mocker.patch("common.helpers.emails.delete")
72
+ mocker.patch("common.helpers.emails.get_dotmailer_user_by_email")
73
+
74
+ delete_contact("example@mail.com")
75
+
76
+ mocked_delete.assert_called_once_with(
77
+ DOTMAILER_DELETE_USER_BY_ID_URL,
78
+ auth=(DOTMAILER_USER, DOTMAILER_PASSWORD),
79
+ )
80
+
81
+
82
+ def test_newsletter_sends_correct_request_data(
83
+ mocker, monkeypatch, patch_datetime_now
84
+ ):
66
85
  mocked_post = mocker.patch("common.helpers.emails.post")
67
86
 
68
87
  expected_body1 = {
@@ -79,7 +98,7 @@ def test_newsletter_sends_correct_request_data(mocker, monkeypatch, patch_dateti
79
98
  "consentFields": [
80
99
  {
81
100
  "fields": [
82
- {"key": "DATETIMECONSENTED", "value": FAKE_TIME.__str__()},
101
+ {"key": "DATETIMECONSENTED", "value": FAKE_TIME.__str__()}
83
102
  ]
84
103
  }
85
104
  ],
@@ -105,10 +124,54 @@ def test_newsletter_sends_correct_request_data(mocker, monkeypatch, patch_dateti
105
124
  json=expected_body1,
106
125
  )
107
126
 
108
- add_contact_to_address_book("Ray", "Charles", "ray.charles@example.com")
127
+ add_contact_to_address_book(
128
+ "Ray",
129
+ "Charles",
130
+ "ray.charles@example.com",
131
+ address_book_ids["newsletter"],
132
+ DotmailerUserType.TEACHER,
133
+ )
109
134
 
110
- mocked_post.assert_called_with(
111
- DOTMAILER_ADDRESS_BOOK_URL,
135
+ assert mocked_post.call_count == 3
136
+
137
+ mocked_post.assert_any_call(
138
+ DOTMAILER_TEACHER_ADDRESS_BOOK_URL,
139
+ auth=(DOTMAILER_USER, DOTMAILER_PASSWORD),
140
+ json=expected_body2,
141
+ )
142
+
143
+ mocked_post.reset_mock()
144
+
145
+ add_contact_to_address_book(
146
+ "Ray",
147
+ "Charles",
148
+ "ray.charles@example.com",
149
+ address_book_ids["newsletter"],
150
+ DotmailerUserType.STUDENT,
151
+ )
152
+
153
+ assert mocked_post.call_count == 2
154
+
155
+ mocked_post.assert_any_call(
156
+ DOTMAILER_STUDENT_ADDRESS_BOOK_URL,
157
+ auth=(DOTMAILER_USER, DOTMAILER_PASSWORD),
158
+ json=expected_body2,
159
+ )
160
+
161
+ mocked_post.reset_mock()
162
+
163
+ add_contact_to_address_book(
164
+ "Ray",
165
+ "Charles",
166
+ "ray.charles@example.com",
167
+ address_book_ids["newsletter"],
168
+ DotmailerUserType.NO_ACCOUNT,
169
+ )
170
+
171
+ assert mocked_post.call_count == 2
172
+
173
+ mocked_post.assert_any_call(
174
+ DOTMAILER_NO_ACCOUNT_ADDRESS_BOOK_URL,
112
175
  auth=(DOTMAILER_USER, DOTMAILER_PASSWORD),
113
176
  json=expected_body2,
114
177
  )
@@ -147,7 +210,7 @@ def test_consent_calls_send_correct_request_data(
147
210
  "consentFields": [
148
211
  {
149
212
  "fields": [
150
- {"key": "DATETIMECONSENTED", "value": FAKE_TIME.__str__()},
213
+ {"key": "DATETIMECONSENTED", "value": FAKE_TIME.__str__()}
151
214
  ]
152
215
  }
153
216
  ],
@@ -2,19 +2,19 @@ from django.urls import reverse
2
2
  from django.test import TestCase, Client
3
3
 
4
4
 
5
- class TestNewsletterFooter(TestCase):
5
+ class TestGlobalForms(TestCase):
6
6
  def test_newsletter_signup_successful(self):
7
7
  url = reverse("process_newsletter_form")
8
8
  client = Client()
9
- data = {"email": "valid_email@example.com"}
9
+ data = {"email": "valid_email@example.com", "age_verification": "on"}
10
10
  response = client.post(url, data)
11
11
  messages = list(response.wsgi_request._messages)
12
- self.assertEquals(1, len([m for m in messages if m.tags == "success"]))
12
+ assert len([m for m in messages if m.tags == "success"]) == 1
13
13
 
14
14
  def test_newsletter_signup_fail(self):
15
15
  url = reverse("process_newsletter_form")
16
16
  client = Client()
17
- data = {"email": "invalid_email"}
17
+ data = {"email": "invalid_email", "age_verification": "on"}
18
18
  response = client.post(url, data)
19
19
  messages = list(response.wsgi_request._messages)
20
- self.assertEquals(1, len([m for m in messages if "error" in m.tags]))
20
+ assert len([m for m in messages if "error" in m.tags]) == 1
@@ -0,0 +1,30 @@
1
+ import hashlib
2
+ from unittest.mock import patch, Mock
3
+
4
+ from portal.helpers.password import is_password_pwned
5
+
6
+
7
+ class TestClass:
8
+ def test_is_password_pwned(self):
9
+ weak_password = "Password123$"
10
+ strong_password = "£EDCVFR$%TGBnhy667ujm"
11
+ assert is_password_pwned(weak_password)
12
+ assert not is_password_pwned(strong_password)
13
+
14
+ @patch("requests.get")
15
+ def test_is_password_pwned__status_code_not_200(self, mock_get):
16
+ # Arrange
17
+ password = "password123"
18
+ sha1_hash = hashlib.sha1(password.encode()).hexdigest()
19
+
20
+ mock_response = Mock()
21
+ mock_response.status_code = 500
22
+
23
+ mock_get.return_value = mock_response
24
+
25
+ # Act
26
+ result = is_password_pwned(password)
27
+
28
+ # Assert
29
+ mock_get.assert_called_once_with(f"https://api.pwnedpasswords.com/range/{sha1_hash[:5]}")
30
+ assert not result