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/forms/play.py CHANGED
@@ -1,8 +1,6 @@
1
1
  import re
2
- from datetime import timedelta
2
+ from datetime import timedelta, date
3
3
 
4
- from captcha.fields import ReCaptchaField
5
- from captcha.widgets import ReCaptchaV2Invisible
6
4
  from common.helpers.emails import send_verification_email
7
5
  from common.models import Class, Student, stripStudentName
8
6
  from common.permissions import logged_in_as_independent_student
@@ -10,21 +8,17 @@ from django import forms
10
8
  from django.contrib.auth import authenticate
11
9
  from django.contrib.auth.forms import AuthenticationForm
12
10
  from django.utils import timezone
11
+ from django_recaptcha.fields import ReCaptchaField
12
+ from django_recaptcha.widgets import ReCaptchaV2Invisible
13
13
 
14
14
  from portal.forms.error_messages import INVALID_LOGIN_MESSAGE
15
15
  from portal.helpers.password import PasswordStrength, form_clean_password
16
16
  from portal.helpers.regexes import ACCESS_CODE_PATTERN
17
- from portal.templatetags.app_tags import is_verified
18
17
 
19
18
 
20
19
  class StudentClassCodeForm(forms.Form):
21
20
  access_code = forms.CharField(
22
- widget=forms.TextInput(
23
- attrs={
24
- "autocomplete": "off",
25
- "placeholder": "Class code",
26
- }
27
- ),
21
+ widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Class code"}),
28
22
  help_text="Enter your class code",
29
23
  )
30
24
 
@@ -33,30 +27,18 @@ class StudentClassCodeForm(forms.Form):
33
27
 
34
28
  if access_code:
35
29
  if re.fullmatch(ACCESS_CODE_PATTERN, access_code.upper()) is None:
36
- raise forms.ValidationError(
37
- "Uh oh! You didn't input a valid class code."
38
- )
30
+ raise forms.ValidationError("Uh oh! You didn't input a valid class code.")
39
31
 
40
32
  return self.cleaned_data
41
33
 
42
34
 
43
35
  class StudentLoginForm(AuthenticationForm):
44
36
  username = forms.CharField(
45
- widget=forms.TextInput(
46
- attrs={
47
- "autocomplete": "off",
48
- "placeholder": "Username",
49
- }
50
- ),
37
+ widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Username"}),
51
38
  help_text="Enter your username",
52
39
  )
53
40
  password = forms.CharField(
54
- widget=forms.PasswordInput(
55
- attrs={
56
- "autocomplete": "off",
57
- "placeholder": "Password",
58
- }
59
- ),
41
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
60
42
  help_text="Enter your password",
61
43
  )
62
44
 
@@ -69,7 +51,6 @@ class StudentLoginForm(AuthenticationForm):
69
51
  password = self.cleaned_data.get("password", None)
70
52
 
71
53
  if name and self.access_code and password:
72
-
73
54
  student, user = self.check_for_errors(name, self.access_code, password)
74
55
 
75
56
  self.student = student
@@ -84,16 +65,12 @@ class StudentLoginForm(AuthenticationForm):
84
65
 
85
66
  name = stripStudentName(name)
86
67
 
87
- students = Student.objects.filter(
88
- new_user__first_name__iexact=name, class_field=klass
89
- )
68
+ students = Student.objects.filter(new_user__first_name__iexact=name, class_field=klass)
90
69
  if len(students) != 1:
91
70
  raise forms.ValidationError("Invalid name, class access code or password")
92
71
 
93
72
  student = students[0]
94
- user = authenticate(
95
- username=student.new_user.username, password=password.lower()
96
- )
73
+ user = authenticate(username=student.new_user.username, password=password.lower())
97
74
 
98
75
  # Try the case sensitive password too, for previous accounts that don't have the lowercase one stored
99
76
  if user is None:
@@ -110,16 +87,16 @@ class StudentLoginForm(AuthenticationForm):
110
87
  class StudentEditAccountForm(forms.Form):
111
88
  password = forms.CharField(
112
89
  required=True,
113
- widget=forms.PasswordInput(attrs={"placeholder": "New password"}),
90
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "New password"}),
114
91
  help_text="Enter new password",
115
92
  )
116
93
  confirm_password = forms.CharField(
117
94
  required=True,
118
- widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password"}),
95
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm new password"}),
119
96
  help_text="Confirm new password",
120
97
  )
121
98
  current_password = forms.CharField(
122
- widget=forms.PasswordInput(attrs={"placeholder": "Current password"}),
99
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Current password"}),
123
100
  help_text="Enter your current password",
124
101
  )
125
102
 
@@ -138,28 +115,28 @@ class IndependentStudentEditAccountForm(forms.Form):
138
115
  name = forms.CharField(
139
116
  max_length=100,
140
117
  required=False,
141
- widget=forms.TextInput(attrs={"placeholder": "Name"}),
118
+ widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Name"}),
142
119
  help_text="Enter your name",
143
120
  )
144
121
  email = forms.EmailField(
145
122
  required=False,
146
- widget=forms.EmailInput(attrs={"placeholder": "New email address (optional)"}),
123
+ widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "New email address (optional)"}),
147
124
  help_text="Enter new email address (optional)",
148
125
  )
149
126
  password = forms.CharField(
150
127
  required=False,
151
- widget=forms.PasswordInput(attrs={"placeholder": "New password (optional)"}),
128
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "New password (optional)"}),
152
129
  help_text="Enter new password (optional)",
153
130
  )
154
131
  confirm_password = forms.CharField(
155
132
  label="Confirm new password",
156
133
  required=False,
157
- widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password"}),
134
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm new password"}),
158
135
  help_text="Confirm new password",
159
136
  )
160
137
  current_password = forms.CharField(
161
138
  label="Current password",
162
- widget=forms.PasswordInput(attrs={"placeholder": "Current password"}),
139
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Current password"}),
163
140
  help_text="Enter your current password",
164
141
  )
165
142
 
@@ -174,9 +151,7 @@ class IndependentStudentEditAccountForm(forms.Form):
174
151
  raise forms.ValidationError("This field is required")
175
152
 
176
153
  if re.match(re.compile("^[\w ]+$"), name) is None:
177
- raise forms.ValidationError(
178
- "Names may only contain letters, numbers, dashes, underscores, and spaces."
179
- )
154
+ raise forms.ValidationError("Names may only contain letters, numbers, dashes, underscores, and spaces.")
180
155
 
181
156
  return name
182
157
 
@@ -215,45 +190,36 @@ def are_password_and_confirm_password_different(password, confirm_password):
215
190
 
216
191
 
217
192
  class IndependentStudentSignupForm(forms.Form):
218
- name = forms.CharField(
219
- max_length=100,
220
- help_text="Enter full name",
221
- widget=forms.TextInput(
222
- attrs={"autocomplete": "off", "placeholder": "Full name"}
193
+ date_of_birth = forms.DateField(
194
+ help_text="Please enter your date of birth (we do not store this information).",
195
+ widget=forms.SelectDateWidget(
196
+ years=range(date.today().year, date.today().year - 100, -1), empty_label=("Year", "Month", "Day")
223
197
  ),
198
+ required=False,
224
199
  )
225
200
 
226
- username = forms.CharField(
201
+ name = forms.CharField(
227
202
  max_length=100,
228
- help_text="Enter a username",
229
- widget=forms.TextInput(
230
- attrs={"autocomplete": "off", "placeholder": "Username"}
231
- ),
203
+ help_text="Enter full name",
204
+ widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Full name"}),
232
205
  )
233
206
 
234
207
  email = forms.EmailField(
235
208
  help_text="Enter your email address",
236
- widget=forms.EmailInput(
237
- attrs={"autocomplete": "off", "placeholder": "Email address"}
238
- ),
209
+ widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
239
210
  )
240
211
 
241
- newsletter_ticked = forms.BooleanField(initial=False, required=False)
242
-
243
- is_over_required_age = forms.BooleanField(initial=False, required=True)
212
+ consent_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=True)
213
+ newsletter_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=False)
244
214
 
245
215
  password = forms.CharField(
246
216
  help_text="Enter a password",
247
- widget=forms.PasswordInput(
248
- attrs={"autocomplete": "off", "placeholder": "Password"}
249
- ),
217
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
250
218
  )
251
219
 
252
220
  confirm_password = forms.CharField(
253
221
  help_text="Repeat password",
254
- widget=forms.PasswordInput(
255
- attrs={"autocomplete": "off", "placeholder": "Repeat password"}
256
- ),
222
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Repeat password"}),
257
223
  )
258
224
 
259
225
  captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
@@ -261,22 +227,10 @@ class IndependentStudentSignupForm(forms.Form):
261
227
  def clean_name(self):
262
228
  name = self.cleaned_data.get("name", None)
263
229
  if re.match(re.compile("^[\w ]+$"), name) is None:
264
- raise forms.ValidationError(
265
- "Names may only contain letters, numbers, dashes, underscores, and spaces."
266
- )
230
+ raise forms.ValidationError("Names may only contain letters, numbers, dashes, underscores, and spaces.")
267
231
 
268
232
  return name
269
233
 
270
- def clean_username(self):
271
- username = self.cleaned_data.get("username", None)
272
-
273
- if re.match(re.compile("[\w]+"), username) is None:
274
- raise forms.ValidationError(
275
- "Usernames may only contain letters, numbers, dashes, and underscores."
276
- )
277
-
278
- return username
279
-
280
234
  def clean_password(self):
281
235
  return form_clean_password(self, "password", PasswordStrength.INDEPENDENT)
282
236
 
@@ -291,8 +245,14 @@ class IndependentStudentSignupForm(forms.Form):
291
245
 
292
246
 
293
247
  class IndependentStudentLoginForm(AuthenticationForm):
294
- username = forms.CharField(label="Username", widget=forms.TextInput())
295
- password = forms.CharField(label="Password", widget=forms.PasswordInput())
248
+ username = forms.EmailField(
249
+ widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
250
+ help_text="Enter your email address",
251
+ )
252
+ password = forms.CharField(
253
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
254
+ help_text="Enter your password",
255
+ )
296
256
 
297
257
  def clean(self):
298
258
  super().clean()
@@ -301,8 +261,8 @@ class IndependentStudentLoginForm(AuthenticationForm):
301
261
  if not logged_in_as_independent_student(user):
302
262
  self.show_invalid_login_message()
303
263
 
304
- if not is_verified(user):
305
- send_verification_email(self.request, user)
264
+ if not user.userprofile.is_verified:
265
+ send_verification_email(self.request, user, self.data)
306
266
  self.show_invalid_login_message()
307
267
 
308
268
  def get_invalid_login_error(self):
@@ -313,28 +273,23 @@ class IndependentStudentLoginForm(AuthenticationForm):
313
273
 
314
274
 
315
275
  class StudentJoinOrganisationForm(forms.Form):
316
- access_code = forms.CharField(
317
- label="Class Access Code",
318
- widget=forms.TextInput(attrs={"placeholder": "AB123"}),
319
- )
276
+ access_code = forms.CharField(label="Class Access Code", widget=forms.TextInput(attrs={"placeholder": "AB123"}))
320
277
 
321
278
  def clean(self):
322
279
  access_code = self.cleaned_data.get("access_code", None)
280
+ join_error_text = "The class code you entered either does not exist or is not currently accepting join requests. Please double check that you have entered the correct class code and contact the teacher of the class to ensure their class is currently accepting join requests."
323
281
 
324
282
  if access_code:
325
283
  classes = Class.objects.filter(access_code=access_code)
326
284
  if len(classes) != 1:
327
- raise forms.ValidationError(
328
- "Cannot find the school or club and/or class"
329
- )
285
+ raise forms.ValidationError(join_error_text)
286
+
330
287
  self.klass = classes[0]
331
- if not self.klass.always_accept_requests:
332
- if self.klass.accept_requests_until is None:
333
- raise forms.ValidationError(
334
- "Cannot find the school or club and/or class"
335
- )
336
- elif (self.klass.accept_requests_until - timezone.now()) < timedelta():
337
- raise forms.ValidationError(
338
- "Cannot find the school or club and/or class"
339
- )
288
+
289
+ if not self.klass.always_accept_requests and (
290
+ self.klass.accept_requests_until is None
291
+ or self.klass.accept_requests_until - timezone.now()
292
+ < timedelta()
293
+ ):
294
+ raise forms.ValidationError(join_error_text)
340
295
  return self.cleaned_data
@@ -1,16 +1,15 @@
1
- from captcha.fields import ReCaptchaField
2
- from captcha.widgets import ReCaptchaV2Invisible
1
+ from common.mail import campaign_ids, send_dotdigital_email
3
2
  from common.models import Student, Teacher
4
3
  from django import forms
5
4
  from django.contrib.auth import forms as django_auth_forms
6
5
  from django.contrib.auth import get_user_model
7
- from django.contrib.auth.models import User
8
6
  from django.contrib.auth.tokens import default_token_generator
9
7
  from django.contrib.sites.shortcuts import get_current_site
10
- from django.core.mail import EmailMultiAlternatives
11
- from django.template import loader
8
+ from django.urls import reverse_lazy
12
9
  from django.utils.encoding import force_bytes
13
10
  from django.utils.http import urlsafe_base64_encode
11
+ from django_recaptcha.fields import ReCaptchaField
12
+ from django_recaptcha.widgets import ReCaptchaV2Invisible
14
13
 
15
14
  from portal.helpers.password import PasswordStrength, form_clean_password
16
15
 
@@ -20,8 +19,10 @@ class TeacherPasswordResetSetPasswordForm(django_auth_forms.SetPasswordForm):
20
19
  super(TeacherPasswordResetSetPasswordForm, self).__init__(user, *args, **kwargs)
21
20
  self.fields["new_password1"].help_text = "Enter your new password"
22
21
  self.fields["new_password1"].widget.attrs["placeholder"] = "New password"
22
+ self.fields["new_password1"].widget.attrs["autocomplete"] = "off"
23
23
  self.fields["new_password2"].help_text = "Confirm your new password"
24
24
  self.fields["new_password2"].widget.attrs["placeholder"] = "Confirm password"
25
+ self.fields["new_password2"].widget.attrs["autocomplete"] = "off"
25
26
 
26
27
  def clean_new_password1(self):
27
28
  return form_clean_password(self, "new_password1", PasswordStrength.TEACHER)
@@ -32,69 +33,33 @@ class StudentPasswordResetSetPasswordForm(django_auth_forms.SetPasswordForm):
32
33
  super(StudentPasswordResetSetPasswordForm, self).__init__(user, *args, **kwargs)
33
34
  self.fields["new_password1"].help_text = "Enter your new password"
34
35
  self.fields["new_password1"].widget.attrs["placeholder"] = "New password"
36
+ self.fields["new_password1"].widget.attrs["autocomplete"] = "off"
35
37
  self.fields["new_password2"].help_text = "Confirm your new password"
36
38
  self.fields["new_password2"].widget.attrs["placeholder"] = "Confirm password"
39
+ self.fields["new_password2"].widget.attrs["autocomplete"] = "off"
37
40
 
38
41
  def clean_new_password1(self):
39
42
  return form_clean_password(self, "new_password1", PasswordStrength.INDEPENDENT)
40
43
 
41
44
 
42
- class TeacherPasswordResetForm(forms.Form):
45
+ class PasswordResetForm(forms.Form):
43
46
  email = forms.EmailField(
44
- widget=forms.EmailInput(
45
- attrs={"autocomplete": "off", "placeholder": "Email address"}
46
- ),
47
+ widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
47
48
  help_text="Enter your email address",
48
49
  )
49
50
 
50
51
  captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
51
52
 
52
- def clean_email(self):
53
- email = self.cleaned_data.get("email", None)
54
- self.username = ""
55
- # Check such an email exists
56
- if User.objects.filter(email=email).exists():
57
- teacher = Teacher.objects.filter(new_user__email=email)
58
- # Check such an email is associated with a teacher
59
- if teacher.exists():
60
- self.username = teacher[0].new_user.username
61
-
62
- return email
63
-
64
- def send_mail(
65
- self,
66
- subject_template_name,
67
- email_template_name,
68
- context,
69
- from_email,
70
- to_email,
71
- html_email_template_name=None,
72
- ):
73
- """
74
- Sends a django.core.mail.EmailMultiAlternatives to `to_email`.
75
- """
76
- subject = loader.render_to_string(subject_template_name, context)
77
- # Email subject *must not* contain newlines
78
- subject = "".join(subject.splitlines())
79
- body = loader.render_to_string(email_template_name, context)
80
-
81
- email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
82
- if html_email_template_name is not None:
83
- html_email = loader.render_to_string(html_email_template_name, context)
84
- email_message.attach_alternative(html_email, "text/html")
85
-
86
- email_message.send()
87
-
88
53
  def save(
89
54
  self,
90
55
  domain_override=None,
91
- subject_template_name="registration/password_reset_subject.txt",
92
- email_template_name="portal/reset_password_email.html",
56
+ subject_template_name=None,
57
+ email_template_name="portal/reset_password_email.txt",
93
58
  use_https=False,
94
59
  token_generator=default_token_generator,
95
60
  from_email=None,
96
61
  request=None,
97
- html_email_template_name=None,
62
+ html_email_template_name="portal/reset_password_email.html",
98
63
  ):
99
64
  """
100
65
  Generates a one-use only link for resetting password and sends to the
@@ -102,9 +67,7 @@ class TeacherPasswordResetForm(forms.Form):
102
67
  """
103
68
  UserModel = get_user_model()
104
69
  if self.username:
105
- active_users = UserModel._default_manager.filter(
106
- username=self.username, is_active=True
107
- )
70
+ active_users = UserModel._default_manager.filter(username=self.username, is_active=True)
108
71
  for user in active_users:
109
72
  # Make sure that no email is sent to a user that actually has
110
73
  # a password marked as unusable
@@ -112,128 +75,71 @@ class TeacherPasswordResetForm(forms.Form):
112
75
  continue
113
76
  if not domain_override:
114
77
  current_site = get_current_site(request)
115
- site_name = current_site.name
116
78
  domain = current_site.domain
117
79
  else:
118
- site_name = domain = domain_override
119
- context = {
120
- "email": user.email,
121
- "domain": domain,
122
- "site_name": site_name,
123
- "uid": urlsafe_base64_encode(force_bytes(user.pk)),
124
- "user": user,
125
- "token": token_generator.make_token(user),
126
- "protocol": compute_protocol(use_https),
127
- }
128
-
129
- self.send_mail(
130
- subject_template_name,
131
- email_template_name,
132
- context,
133
- from_email,
134
- user.email,
135
- html_email_template_name=html_email_template_name,
80
+ domain = domain_override
81
+
82
+ reset_password_uri = reverse_lazy(
83
+ "password_reset_check_and_confirm",
84
+ kwargs={
85
+ "uidb64": urlsafe_base64_encode(force_bytes(user.pk)),
86
+ "token": token_generator.make_token(user),
87
+ },
136
88
  )
89
+ protocol = self._compute_protocol(use_https)
90
+ reset_password_url = f"{protocol}://{domain}{reset_password_uri}"
137
91
 
92
+ send_dotdigital_email(
93
+ campaign_ids["reset_password"],
94
+ [user.email],
95
+ personalization_values={"RESET_PASSWORD_LINK": reset_password_url},
96
+ )
138
97
 
139
- class StudentPasswordResetForm(forms.Form):
140
- username = forms.CharField(
141
- widget=forms.TextInput(
142
- attrs={"autocomplete": "off", "placeholder": "Username"}
143
- ),
144
- help_text="Enter your username",
145
- )
98
+ def _compute_protocol(self, use_https):
99
+ return "https" if use_https else "http"
146
100
 
147
- captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
148
101
 
149
- def clean_username(self):
150
- username = self.cleaned_data.get("username", None)
151
- user_filter = User.objects.filter(username=username)
152
- # Check such a username exists and such a username is not in use by a student part of a class/school
153
- if (
154
- not user_filter.exists()
155
- or not Student.objects.filter(
156
- class_field=None, new_user__username=username
157
- ).exists()
158
- ):
159
- username = ""
160
- return username
161
-
162
- def send_mail(
163
- self,
164
- subject_template_name,
165
- email_template_name,
166
- context,
167
- from_email,
168
- to_email,
169
- html_email_template_name=None,
170
- ):
171
- """
172
- Sends a django.core.mail.EmailMultiAlternatives to `to_email`.
173
- """
174
- subject = loader.render_to_string(subject_template_name, context)
175
- # Email subject *must not* contain newlines
176
- subject = "".join(subject.splitlines())
177
- body = loader.render_to_string(email_template_name, context)
102
+ class TeacherPasswordResetForm(PasswordResetForm):
103
+ def clean_email(self):
104
+ email = self.cleaned_data.get("email", None)
105
+ self.username = ""
106
+ teacher = Teacher.objects.filter(new_user__email=email)
107
+ # Check such an email exists
108
+ if teacher.exists():
109
+ self.username = teacher[0].new_user.username
110
+ return email
178
111
 
179
- email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
180
- if html_email_template_name is not None:
181
- html_email = loader.render_to_string(html_email_template_name, context)
182
- email_message.attach_alternative(html_email, "text/html")
183
112
 
184
- email_message.send()
113
+ class StudentPasswordResetForm(PasswordResetForm):
114
+ def clean_email(self):
115
+ email = self.cleaned_data.get("email", None)
116
+ self.username = ""
117
+ student = Student.objects.filter(new_user__email=email)
118
+ # Check such an email exists
119
+ if student.exists():
120
+ self.username = student[0].new_user.username
121
+ return email
185
122
 
186
- def save(
187
- self,
188
- domain_override=None,
189
- subject_template_name="registration/password_reset_subject.txt",
190
- email_template_name="portal/reset_password_email.html",
191
- use_https=False,
192
- token_generator=default_token_generator,
193
- from_email=None,
194
- request=None,
195
- html_email_template_name=None,
196
- ):
197
- """
198
- Generates a one-use only link for resetting password and sends to the
199
- user.
200
- """
201
- UserModel = get_user_model()
202
- username = self.cleaned_data["username"]
203
- if username:
204
- active_users = UserModel._default_manager.filter(
205
- username=username, is_active=True
206
- )
207
- for user in active_users:
208
- # Make sure that no email is sent to a user that actually has
209
- # a password marked as unusable
210
- if not user.has_usable_password():
211
- continue
212
- if not domain_override:
213
- current_site = get_current_site(request)
214
- site_name = current_site.name
215
- domain = current_site.domain
216
- else:
217
- site_name = domain = domain_override
218
- context = {
219
- "email": user.email,
220
- "domain": domain,
221
- "site_name": site_name,
222
- "uid": urlsafe_base64_encode(force_bytes(user.pk)),
223
- "user": user,
224
- "token": token_generator.make_token(user),
225
- "protocol": compute_protocol(use_https),
226
- }
227
-
228
- self.send_mail(
229
- subject_template_name,
230
- email_template_name,
231
- context,
232
- from_email,
233
- user.email,
234
- html_email_template_name=html_email_template_name,
235
- )
236
123
 
124
+ class DeleteAccountForm(forms.Form):
125
+ delete_password = forms.CharField(
126
+ required=True,
127
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm password"}),
128
+ help_text="Confirm password",
129
+ )
130
+
131
+ unsubscribe_newsletter = forms.BooleanField(
132
+ label="Please remove me from the newsletter and marketing emails too.",
133
+ widget=forms.CheckboxInput(),
134
+ initial=True,
135
+ required=False,
136
+ )
137
+
138
+ def __init__(self, user, *args, **kwargs):
139
+ self.user = user
140
+ super(DeleteAccountForm, self).__init__(*args, **kwargs)
237
141
 
238
- def compute_protocol(use_https):
239
- return "https" if use_https else "http"
142
+ def clean(self):
143
+ delete_password = self.cleaned_data.get("delete_password", None)
144
+ if not self.user.check_password(delete_password):
145
+ raise forms.ValidationError("Incorrect password")