codeforlife-portal 5.33.5__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 (391) 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.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/RECORD +339 -241
  92. {codeforlife_portal-5.33.5.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.33.5.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 +4 -2
  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 +8 -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 -91
  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 +96 -9
  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 +235 -166
  319. portal/views/admin.py +0 -332
  320. portal/views/api.py +82 -48
  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/core.py +22 -19
  338. portal/views/two_factor/profile.py +2 -2
  339. codeforlife_portal-5.33.5.dist-info/LICENSE.md +0 -577
  340. codeforlife_portal-5.33.5.dist-info/METADATA +0 -38
  341. deploy/permissions.py +0 -2
  342. example_project/manage.py +0 -10
  343. portal/autoconfig.py +0 -141
  344. portal/csp_config.py +0 -60
  345. portal/forms/add_game.py +0 -33
  346. portal/helpers/location.py +0 -121
  347. portal/static/portal/img/kurono_hero.jpg +0 -0
  348. portal/static/portal/img/kurono_landing_hero.png +0 -0
  349. portal/static/portal/img/kurono_logo.svg +0 -1
  350. portal/static/portal/img/kurono_logo_grey_background.svg +0 -1
  351. portal/static/portal/img/kurono_logo_mark.svg +0 -1
  352. portal/static/portal/img/kurono_resources_hero.jpg +0 -0
  353. portal/static/portal/img/kurono_story.png +0 -0
  354. portal/static/portal/img/ocado-swirl.svg +0 -22
  355. portal/static/portal/img/thumbnail_educate_kurono.png +0 -0
  356. portal/static/portal/img/thumbnail_educate_resources_and_progress_tracking.png +0 -0
  357. portal/static/portal/img/thumbnail_kurono_resources.png +0 -0
  358. portal/static/portal/img/thumbnail_play_kurono.png +0 -0
  359. portal/static/portal/img/x_close_video.png +0 -0
  360. portal/static/portal/js/aimmoGame.js +0 -106
  361. portal/static/portal/js/deleteWorkspaces.js +0 -14
  362. portal/static/portal/js/fuzzySchoolLookup.js +0 -46
  363. portal/static/portal/js/lib/jquery-3.5.1.min.js +0 -2
  364. portal/static/portal/js/lib/jquery-ui-1.12.1.min.js +0 -13
  365. portal/static/portal/sass/partials/_videos.scss +0 -10
  366. portal/static/portal/video/aimmo_play_now_background_video.mp4 +0 -0
  367. portal/strings/student_aimmo_dashboard.py +0 -6
  368. portal/templates/portal/admin/aggregated_data.html +0 -35
  369. portal/templates/portal/admin/map.html +0 -70
  370. portal/templates/portal/mouseflow.html +0 -9
  371. portal/templates/portal/partials/aimmo_games_table.html +0 -83
  372. portal/templates/portal/partials/register_over_required_age_tickbox.html +0 -9
  373. portal/templates/portal/play/independent_student_dashboard.html +0 -64
  374. portal/templates/portal/play/student_aimmo_dashboard.html +0 -63
  375. portal/templates/portal/privacy_policy.html +0 -483
  376. portal/templates/portal/reset_password_email.html +0 -9
  377. portal/templates/portal/teach/invite.html +0 -25
  378. portal/templates/portal/teach/teacher_aimmo_dashboard.html +0 -95
  379. portal/templates/portal/teach/teacher_resources.html +0 -68
  380. portal/templatetags/character_list_tags.py +0 -16
  381. portal/tests/pageObjects/portal/kurono_teacher_dashboard_page.py +0 -49
  382. portal/tests/pageObjects/portal/student_password_reset_form_page.py +0 -23
  383. portal/tests/pageObjects/portal/teach/onboarding_revoke_request_page.py +0 -20
  384. portal/tests/pageObjects/portal/teacher_password_reset_form_page.py +0 -23
  385. portal/tests/test_aimmo_dashboards.py +0 -172
  386. portal/tests/test_location.py +0 -217
  387. portal/tests/utils/aimmo_games.py +0 -30
  388. portal/views/aimmo/dashboard.py +0 -119
  389. portal/views/privacy_policy.py +0 -9
  390. portal/views/teacher/teacher_resources.py +0 -42
  391. {portal/views/aimmo → cfl_common}/__init__.py +0 -0
portal/views/home.py CHANGED
@@ -1,44 +1,39 @@
1
- from common import email_messages
1
+ import math
2
+
2
3
  from common.helpers.emails import (
3
4
  send_verification_email,
4
- send_email,
5
- NOTIFICATION_EMAIL,
6
- add_to_dotmailer,
7
5
  )
8
- from common.models import Teacher, Student
6
+ from common.mail import campaign_ids, send_dotdigital_email
7
+ from common.models import DynamicElement, Student, Teacher, TotalActivity
9
8
  from common.permissions import logged_in_as_student, logged_in_as_teacher
10
9
  from common.utils import _using_two_factor
11
10
  from django.contrib import messages as messages
12
11
  from django.contrib.auth import logout
13
12
  from django.contrib.auth.models import User
14
- from django.http import HttpResponseRedirect
13
+ from django.core.exceptions import ObjectDoesNotExist
14
+ from django.db.models import F
15
+ from django.http import HttpResponse, HttpResponseRedirect
15
16
  from django.shortcuts import render
16
17
  from django.urls import reverse, reverse_lazy
18
+ from django.utils import timezone
19
+ from django.utils.html import format_html
17
20
  from django.views.decorators.cache import cache_control
18
21
 
19
22
  from deploy import captcha
20
23
  from portal.forms.play import IndependentStudentSignupForm
21
24
  from portal.forms.teach import TeacherSignupForm
22
25
  from portal.helpers.captcha import remove_captcha_from_forms
26
+ from portal.helpers.ratelimit import (
27
+ RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP,
28
+ RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_RATE,
29
+ is_ratelimited,
30
+ )
31
+ from portal.strings.coding_club import CODING_CLUB_BANNER
23
32
  from portal.strings.home_learning import HOME_LEARNING_BANNER
24
-
25
-
26
- def teach_email_labeller(request):
27
- if request.method == "POST" and "teacher_login" in request.POST:
28
- return request.POST["login-teacher_email"]
29
-
30
- return ""
31
-
32
-
33
- def play_name_labeller(request):
34
- if request.method == "POST":
35
- if "school_login" in request.POST:
36
- return request.POST["login-name"] + ":" + request.POST["login-access_code"]
37
-
38
- if "independent_student_login" in request.POST:
39
- return request.POST["independent_student-username"]
40
-
41
- return ""
33
+ from portal.strings.ten_year_map import (
34
+ TEN_YEAR_MAP_BANNER,
35
+ TEN_YEAR_MAP_HEADLINE,
36
+ )
42
37
 
43
38
 
44
39
  def register_view(request):
@@ -113,13 +108,26 @@ def process_signup_form(request, data):
113
108
  email = data["teacher_email"]
114
109
 
115
110
  if email and User.objects.filter(email=email).exists():
116
- email_message = email_messages.userAlreadyRegisteredEmail(request, email)
117
- send_email(
118
- NOTIFICATION_EMAIL,
119
- [email],
120
- email_message["subject"],
121
- email_message["message"],
111
+
112
+ is_email_ratelimited = is_ratelimited(
113
+ request=request,
114
+ group=RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP,
115
+ key=lambda *_: email,
116
+ rate=RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_RATE,
117
+ increment=True,
122
118
  )
119
+
120
+ if not is_email_ratelimited:
121
+ send_dotdigital_email(
122
+ campaign_ids["user_already_registered"],
123
+ [email],
124
+ personalization_values={
125
+ "EMAIL": email,
126
+ "LOGIN_URL": request.build_absolute_uri(
127
+ reverse("teacher_login")
128
+ ),
129
+ },
130
+ )
123
131
  else:
124
132
  teacher = Teacher.objects.factory(
125
133
  first_name=data["teacher_first_name"],
@@ -128,11 +136,11 @@ def process_signup_form(request, data):
128
136
  password=data["teacher_password"],
129
137
  )
130
138
 
131
- if _newsletter_ticked(data):
132
- user = teacher.user.user
133
- add_to_dotmailer(user.first_name, user.last_name, user.email)
139
+ send_verification_email(request, teacher.user.user, data)
134
140
 
135
- send_verification_email(request, teacher.user.user)
141
+ TotalActivity.objects.update(
142
+ teacher_registrations=F("teacher_registrations") + 1
143
+ )
136
144
 
137
145
  return render(
138
146
  request,
@@ -144,37 +152,28 @@ def process_signup_form(request, data):
144
152
 
145
153
  def process_independent_student_signup_form(request, data):
146
154
  email = data["email"]
147
- username = data["username"]
148
-
149
- independent_students = Student.objects.filter(class_field=None)
150
155
 
151
156
  if email and User.objects.filter(email=email).exists():
152
- email_message = email_messages.userAlreadyRegisteredEmail(
153
- request, email, is_independent_student=True
154
- )
155
- send_email(
156
- NOTIFICATION_EMAIL,
157
- [email],
158
- email_message["subject"],
159
- email_message["message"],
160
- )
161
- return render(
162
- request,
163
- "portal/email_verification_needed.html",
164
- {"usertype": "INDEP_STUDENT"},
165
- status=302,
157
+ is_email_ratelimited = is_ratelimited(
158
+ request=request,
159
+ group=RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP,
160
+ key=lambda *_: email,
161
+ rate=RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_RATE,
162
+ increment=True,
166
163
  )
167
164
 
168
- if username and independent_students.filter(new_user__username=username).exists():
169
- email_message = email_messages.indepStudentUsernameAlreadyExistsEmail(
170
- request, username
171
- )
172
- send_email(
173
- NOTIFICATION_EMAIL,
174
- [email],
175
- email_message["subject"],
176
- email_message["message"],
177
- )
165
+ if not is_email_ratelimited:
166
+ send_dotdigital_email(
167
+ campaign_ids["user_already_registered"],
168
+ [email],
169
+ personalization_values={
170
+ "EMAIL": email,
171
+ "LOGIN_URL": request.build_absolute_uri(
172
+ reverse("independent_student_login")
173
+ ),
174
+ },
175
+ )
176
+
178
177
  return render(
179
178
  request,
180
179
  "portal/email_verification_needed.html",
@@ -183,17 +182,18 @@ def process_independent_student_signup_form(request, data):
183
182
  )
184
183
 
185
184
  student = Student.objects.independentStudentFactory(
186
- username=data["username"],
187
- name=data["name"],
188
- email=data["email"],
189
- password=data["password"],
185
+ name=data["name"], email=data["email"], password=data["password"]
190
186
  )
191
187
 
192
- if _newsletter_ticked(data):
193
- user = student.new_user
194
- add_to_dotmailer(user.first_name, user.last_name, user.email)
188
+ dob = data["date_of_birth"]
189
+ age_in_days = timezone.now().date() - dob
190
+ age = math.floor(age_in_days.days / 365.25)
195
191
 
196
- send_verification_email(request, student.new_user)
192
+ send_verification_email(request, student.new_user, data, age=age)
193
+
194
+ TotalActivity.objects.update(
195
+ independent_registrations=F("independent_registrations") + 1
196
+ )
197
197
 
198
198
  return render(
199
199
  request,
@@ -204,44 +204,50 @@ def process_independent_student_signup_form(request, data):
204
204
 
205
205
 
206
206
  def is_developer(request):
207
- return hasattr(request.user, "userprofile") and request.user.userprofile.developer
207
+ return (
208
+ hasattr(request.user, "userprofile")
209
+ and request.user.userprofile.developer
210
+ )
208
211
 
209
212
 
210
213
  def redirect_teacher_to_correct_page(request, teacher):
211
214
  if teacher.has_school():
212
- classes = teacher.class_teacher.all()
213
- if classes:
214
- classes_count = classes.count()
215
- if classes_count > 1 or classes[0].has_students():
216
- link = reverse("two_factor:profile")
217
- if not _using_two_factor(request.user):
218
- messages.info(
219
- request,
220
- (
221
- "You are not currently set up with two-factor authentication. "
222
- + "Use your phone or tablet to enhance your account’s security.</br>"
223
- + "Click <a href='"
224
- + link
225
- + "'>here</a> to find out more and "
226
- + "set it up or go to your account page at any time."
227
- ),
228
- extra_tags="safe",
229
- )
230
- return reverse_lazy("dashboard")
231
- else:
232
- return reverse_lazy(
233
- "onboarding-class",
234
- kwargs={"access_code": classes[0].access_code},
235
- )
236
-
237
- else:
238
- return reverse_lazy("onboarding-classes")
215
+ link = reverse("two_factor:profile")
216
+ if not _using_two_factor(request.user):
217
+ messages.info(
218
+ request,
219
+ (
220
+ "You are not currently set up with two-factor authentication. "
221
+ + "Use your phone or tablet to enhance your account’s security.</br>"
222
+ + "Click <a href='"
223
+ + link
224
+ + "'>here</a> to find out more and "
225
+ + "set it up or go to your account page at any time."
226
+ ),
227
+ extra_tags="safe",
228
+ )
229
+ return reverse_lazy("dashboard")
239
230
  else:
240
231
  return reverse_lazy("onboarding-organisation")
241
232
 
242
233
 
243
234
  @cache_control(private=True)
244
235
  def home(request):
236
+ # Putting this in a try catch because it causes some weird issue in the
237
+ # tests where the first Selenium test passes, but any following test
238
+ # fails because it cannot find the Maintenance banner instance.
239
+ try:
240
+ maintenance_banner = DynamicElement.objects.get(
241
+ name="Maintenance banner"
242
+ )
243
+
244
+ if maintenance_banner.active:
245
+ messages.info(
246
+ request, format_html(maintenance_banner.text), extra_tags="safe"
247
+ )
248
+ except ObjectDoesNotExist:
249
+ pass
250
+
245
251
  """
246
252
  This view is where we can add any messages to be shown upon loading the home page.
247
253
  Following this format:
@@ -255,9 +261,32 @@ def home(request):
255
261
  return render(request, "portal/home.html")
256
262
 
257
263
 
264
+ def coding_club(request):
265
+ return render(
266
+ request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER}
267
+ )
268
+
269
+
258
270
  def home_learning(request):
259
271
  return render(
260
272
  request,
261
273
  "portal/home_learning.html",
262
274
  {"HOME_LEARNING_BANNER": HOME_LEARNING_BANNER},
263
275
  )
276
+
277
+
278
+ def ten_year_map_page(request):
279
+ messages.info(
280
+ request, "This page is currently under construction.", extra_tags="safe"
281
+ )
282
+ return render(
283
+ request,
284
+ "portal/ten_year_map.html",
285
+ {"BANNER": TEN_YEAR_MAP_BANNER, "HEADLINE": TEN_YEAR_MAP_HEADLINE},
286
+ )
287
+
288
+
289
+ def reset_screentime_warning(request):
290
+ if request.user.is_authenticated:
291
+ request.session["last_screentime_warning"] = timezone.now().timestamp()
292
+ return HttpResponse(status=204) # No content
portal/views/legal.py ADDED
@@ -0,0 +1,13 @@
1
+ from django.shortcuts import render
2
+
3
+
4
+ def privacy_notice(request):
5
+ return render(
6
+ request,
7
+ "portal/privacy_notice.html",
8
+ {"last_updated": "19th March 2025", "last_updated_children": "19th March 2025"},
9
+ )
10
+
11
+
12
+ def terms(request):
13
+ return render(request, "portal/terms.html", {"last_updated": "11th July 2022"})
@@ -36,9 +36,9 @@ class IndependentStudentLoginView(LoginView):
36
36
  account, this redirects the user to the locked out page. However, if the lockout
37
37
  time is more than 24 hours before this is executed, the account is unlocked.
38
38
  """
39
- username = request.POST.get("username")
40
- if Student.objects.filter(new_user__username=username).exists():
41
- student = Student.objects.get(new_user__username=username)
39
+ email = request.POST.get("username")
40
+ if Student.objects.filter(new_user__email=email).exists():
41
+ student = Student.objects.get(new_user__email=email)
42
42
 
43
43
  if student.blocked_time is not None:
44
44
  if has_user_lockout_expired(student):
@@ -60,8 +60,8 @@ class IndependentStudentLoginView(LoginView):
60
60
  clear_ratelimit_cache_for_user(form.cleaned_data["username"])
61
61
 
62
62
  # Log the login time
63
- username = self.request.POST.get("username")
64
- user = User.objects.get(username=username)
63
+ email = self.request.POST.get("username")
64
+ user = User.objects.get(email=email)
65
65
  session = UserSession(user=user)
66
66
  session.save()
67
67
 
@@ -3,14 +3,14 @@ from django.contrib import messages
3
3
  from django.contrib.auth import authenticate, login
4
4
  from django.contrib.auth.views import LoginView, FormView
5
5
  from django.http import HttpResponseRedirect
6
+ from django.shortcuts import render
6
7
  from django.urls import reverse_lazy
7
8
  from django.utils.html import escape
8
9
 
9
10
  from portal.forms.play import StudentLoginForm, StudentClassCodeForm
10
-
11
- import logging
12
-
13
- LOGGER = logging.getLogger(__name__)
11
+ from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
12
+ from portal.helpers.request_handlers import get_access_code_from_request
13
+ from portal.views.login import has_user_lockout_expired
14
14
 
15
15
 
16
16
  class StudentClassCodeView(FormView):
@@ -47,7 +47,8 @@ class StudentLoginView(LoginView):
47
47
  class_name = self.kwargs["access_code"].upper()
48
48
  messages.info(
49
49
  request,
50
- f"<strong>You are logged in to class: " f"{escape(class_name)}</strong>",
50
+ f"<strong>You are logged in to class: "
51
+ f"{escape(class_name)}</strong>",
51
52
  extra_tags="safe message--student",
52
53
  )
53
54
 
@@ -71,12 +72,7 @@ class StudentLoginView(LoginView):
71
72
  students = Student.objects.filter(
72
73
  new_user__first_name__iexact=name, class_field=klass
73
74
  )
74
- try:
75
- student = students[0]
76
- except IndexError:
77
- msg = f"Student {name} in class {class_code} is not found!"
78
- LOGGER.error(msg)
79
- raise Exception(msg)
75
+ student = students[0]
80
76
 
81
77
  # Log the login time, class, and login type
82
78
  session = UserSession(
@@ -84,16 +80,53 @@ class StudentLoginView(LoginView):
84
80
  )
85
81
  session.save()
86
82
 
83
+ student.user.is_verified = True
84
+ student.user.save()
85
+
87
86
  def form_valid(self, form):
88
87
  """Security check complete. Log the user in."""
88
+ # Reset ratelimit cache upon successful login
89
+ clear_ratelimit_cache_for_user(form.cleaned_data["username"])
89
90
 
90
- login_type = self.kwargs["login_type"]
91
- if not login_type:
92
- login_type = "classlink" # default if not specified
91
+ login_type = self.kwargs.get(
92
+ "login_type", "classlink"
93
+ ) # default to "classlink" if not specified
93
94
 
94
95
  self._add_login_data(form, login_type)
95
96
  return super(StudentLoginView, self).form_valid(form)
96
97
 
98
+ def post(self, request, *args, **kwargs):
99
+ """
100
+ If the first name and access code found under the url inputted in the
101
+ form corresponds to that of a blocked account, this redirects the user
102
+ to the locked out page. However, if the lockout time is more than 24
103
+ hours before this is executed, the account is unlocked.
104
+ """
105
+ username = request.POST.get("username")
106
+
107
+ # get access code from the current url
108
+ access_code = get_access_code_from_request(request)
109
+ if Student.objects.filter(
110
+ new_user__first_name=username, class_field__access_code=access_code
111
+ ).exists():
112
+ student = Student.objects.get(
113
+ new_user__first_name=username,
114
+ class_field__access_code=access_code,
115
+ )
116
+
117
+ if student.blocked_time is not None:
118
+ if has_user_lockout_expired(student):
119
+ student.blocked_time = None
120
+ student.save()
121
+ else:
122
+ return render(
123
+ self.request,
124
+ "portal/locked_out_school_student.html",
125
+ {"is_teacher": False},
126
+ )
127
+
128
+ return super(StudentLoginView, self).post(request, *args, **kwargs)
129
+
97
130
 
98
131
  def student_direct_login(request, user_id, login_id):
99
132
  """Direct login for student with unique url without username and password"""
@@ -108,5 +141,9 @@ def student_direct_login(request, user_id, login_id):
108
141
  session.save()
109
142
 
110
143
  login(request, user)
144
+
145
+ student.user.is_verified = True
146
+ student.user.save()
147
+
111
148
  return HttpResponseRedirect(reverse_lazy("student_details"))
112
149
  return HttpResponseRedirect(reverse_lazy("home"))
@@ -1,7 +1,5 @@
1
1
  from common.models import Teacher
2
- from common.permissions import logged_in_as_teacher
3
- from django.shortcuts import redirect, render
4
- from django.urls import reverse_lazy
2
+ from django.shortcuts import render
5
3
  from two_factor.views import LoginView
6
4
  from two_factor.forms import AuthenticationTokenForm, BackupTokenForm
7
5
 
@@ -23,9 +21,7 @@ class TeacherLoginView(LoginView):
23
21
 
24
22
  def get_success_url(self):
25
23
  url = self.get_redirect_url()
26
- return url or redirect_teacher_to_correct_page(
27
- self.request, self.request.user.userprofile.teacher
28
- )
24
+ return url or redirect_teacher_to_correct_page(self.request, self.request.user.userprofile.teacher)
29
25
 
30
26
  def post(self, request, *args, **kwargs):
31
27
  """