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
@@ -1,220 +1,51 @@
1
- import json
2
-
3
- from common.models import School, Teacher, Class
1
+ import common.permissions as permissions
2
+ from common.models import Class, School, Teacher
4
3
  from django.contrib import messages as messages
5
4
  from django.contrib.auth.decorators import login_required, user_passes_test
5
+ from django.http import Http404, HttpResponseRedirect
6
+ from django.shortcuts import get_object_or_404, render
6
7
  from django.urls import reverse_lazy
7
- from django.db.models import Q
8
- from django.http import Http404, HttpResponse, HttpResponseRedirect
9
- from django.shortcuts import render, get_object_or_404
10
- from rest_framework.authentication import SessionAuthentication
11
- from rest_framework.views import APIView
12
-
13
- import common.permissions as permissions
14
- from common import email_messages
15
- from portal.forms.organisation import OrganisationJoinForm, OrganisationForm
16
- from common.helpers.emails import send_email, NOTIFICATION_EMAIL
17
-
18
-
19
- class OrganisationFuzzyLookup(APIView):
20
- authentication_classes = (SessionAuthentication,)
21
- permission_classes = (permissions.LoggedInAsTeacher,)
22
-
23
- def get(self, request):
24
- fuzzy_name = request.GET.get("fuzzy_name", None)
25
- school_data = []
26
-
27
- # The idea here is to return all schools that satisfy that each
28
- # part of the fuzzy_name (separated by spaces) occurs in either
29
- # school.name or school.postcode.
30
- # So it is an intersection of unions.
31
-
32
- if fuzzy_name and len(fuzzy_name) > 2:
33
- schools = School.objects.all()
34
- for part in fuzzy_name.split():
35
- schools = schools.filter(
36
- Q(name__icontains=part) | Q(postcode__icontains=part)
37
- )
38
-
39
- self._search_schools(schools, school_data)
40
8
 
41
- return HttpResponse(json.dumps(school_data), content_type="application/json")
42
-
43
- def _search_schools(self, schools, school_data):
44
- for school in schools:
45
- admins = Teacher.objects.filter(school=school, is_admin=True)
46
- admin = admins.first()
47
- if admin:
48
- email = admin.new_user.email
49
- admin_domain = "*********" + email[email.find("@") :]
50
- school_data.append(
51
- {
52
- "id": school.id,
53
- "name": school.name,
54
- "postcode": school.postcode,
55
- "admin_domain": admin_domain,
56
- }
57
- )
9
+ from portal.forms.organisation import OrganisationForm
58
10
 
59
11
 
60
12
  @login_required(login_url=reverse_lazy("teacher_login"))
61
- @user_passes_test(
62
- permissions.logged_in_as_teacher, login_url=reverse_lazy("teacher_login")
63
- )
13
+ @user_passes_test(permissions.logged_in_as_teacher, login_url=reverse_lazy("teacher_login"))
64
14
  def organisation_create(request):
65
-
66
15
  teacher = request.user.new_teacher
67
16
 
68
17
  create_form = OrganisationForm(user=request.user)
69
- join_form = OrganisationJoinForm()
70
18
 
71
19
  if request.method == "POST":
72
- if "create_organisation" in request.POST:
73
- create_form = OrganisationForm(request.POST, user=request.user)
74
- if create_form.is_valid():
75
- data = create_form.cleaned_data
76
- name = data.get("name", "")
77
- postcode = data.get("postcode", "").upper()
78
- country = data.get("country", "")
79
-
80
- error, town, lat, lng = (
81
- "",
82
- "0",
83
- "0",
84
- "0",
85
- ) # lookup_coord(postcode, country)
20
+ create_form = OrganisationForm(request.POST, user=request.user)
21
+ if create_form.is_valid():
22
+ data = create_form.cleaned_data
23
+ name = data.get("name", "")
24
+ country = data.get("country")
86
25
 
87
- school = School.objects.create(
88
- name=name,
89
- postcode=postcode,
90
- town=town,
91
- latitude=lat,
92
- longitude=lng,
93
- country=country,
94
- )
26
+ school = School.objects.create(name=name, country=country)
95
27
 
96
- teacher.school = school
97
- teacher.is_admin = True
98
- teacher.save()
28
+ teacher.school = school
29
+ teacher.is_admin = True
30
+ teacher.save()
99
31
 
100
- messages.success(
101
- request,
102
- "The school or club '"
103
- + teacher.school.name
104
- + "' has been successfully added.",
105
- )
32
+ messages.success(request, "The school or club '" + teacher.school.name + "' has been successfully added.")
106
33
 
107
- return HttpResponseRedirect(reverse_lazy("onboarding-classes"))
108
-
109
- elif "join_organisation" in request.POST:
110
- process_join_form(
111
- request, teacher, OrganisationJoinForm, OrganisationJoinForm
112
- )
34
+ return HttpResponseRedirect(reverse_lazy("onboarding-classes"))
113
35
 
114
- else:
115
- return process_revoke_request(request, teacher)
116
-
117
- res = render(
118
- request,
119
- "portal/teach/onboarding_school.html",
120
- {"create_form": create_form, "join_form": join_form, "teacher": teacher},
121
- )
36
+ res = render(request, "portal/teach/onboarding_school.html", {"create_form": create_form, "teacher": teacher})
122
37
 
123
38
  return res
124
39
 
125
40
 
126
- def compute_input_join_form(
127
- OrganisationJoinFormWithCaptcha, OrganisationJoinForm, using_captcha
128
- ):
129
- InputOrganisationJoinForm = (
130
- OrganisationJoinFormWithCaptcha if using_captcha else OrganisationJoinForm
131
- )
132
- return InputOrganisationJoinForm
133
-
134
-
135
- def compute_output_join_form(
136
- OrganisationJoinFormWithCaptcha, OrganisationJoinForm, should_use_captcha
137
- ):
138
- OutputOrganisationJoinForm = (
139
- OrganisationJoinFormWithCaptcha if should_use_captcha else OrganisationJoinForm
140
- )
141
- return OutputOrganisationJoinForm
142
-
143
-
144
- def send_pending_requests_emails(school, email_message):
145
- for admin in Teacher.objects.filter(school=school, is_admin=True):
146
- send_email(
147
- NOTIFICATION_EMAIL,
148
- [admin.new_user.email],
149
- email_message["subject"],
150
- email_message["message"],
151
- )
152
-
153
-
154
- def process_join_form(
155
- request, teacher, InputOrganisationJoinForm, OutputOrganisationJoinForm
156
- ):
157
- join_form = InputOrganisationJoinForm(request.POST)
158
- if join_form.is_valid():
159
- school = get_object_or_404(School, id=join_form.cleaned_data["chosen_org"])
160
-
161
- teacher.pending_join_request = school
162
- teacher.save()
163
-
164
- email_message = email_messages.joinRequestPendingEmail(
165
- request, teacher.new_user.email
166
- )
167
-
168
- send_pending_requests_emails(school, email_message)
169
-
170
- email_message = email_messages.joinRequestSentEmail(request, school.name)
171
- send_email(
172
- NOTIFICATION_EMAIL,
173
- [teacher.new_user.email],
174
- email_message["subject"],
175
- email_message["message"],
176
- )
177
-
178
- messages.success(
179
- request,
180
- "Your request to join a school or club has been sent.",
181
- )
182
-
183
- return render(
184
- request,
185
- "portal/teach/onboarding_school.html",
186
- {"school": school, "teacher": teacher},
187
- )
188
-
189
- else:
190
- join_form = OutputOrganisationJoinForm(request.POST)
191
-
192
-
193
- def process_revoke_request(request, teacher):
194
- if "revoke_join_request" in request.POST:
195
- teacher.pending_join_request = None
196
- teacher.save()
197
-
198
- messages.success(
199
- request,
200
- "Your request to join the school or club has been revoked successfully.",
201
- )
202
-
203
- return HttpResponseRedirect(reverse_lazy("onboarding-organisation"))
204
-
205
-
206
41
  @login_required(login_url=reverse_lazy("teacher_login"))
207
- @user_passes_test(
208
- permissions.logged_in_as_teacher, login_url=reverse_lazy("teacher_login")
209
- )
42
+ @user_passes_test(permissions.logged_in_as_teacher, login_url=reverse_lazy("teacher_login"))
210
43
  def organisation_manage(request):
211
44
  return organisation_create(request)
212
45
 
213
46
 
214
47
  @login_required(login_url=reverse_lazy("teacher_login"))
215
- @user_passes_test(
216
- permissions.logged_in_as_teacher, login_url=reverse_lazy("teacher_login")
217
- )
48
+ @user_passes_test(permissions.logged_in_as_teacher, login_url=reverse_lazy("teacher_login"))
218
49
  def organisation_leave(request):
219
50
  teacher = request.user.new_teacher
220
51
 
@@ -1,30 +1,46 @@
1
- from common.helpers.emails import PASSWORD_RESET_EMAIL
2
- from common.models import Teacher, Student
3
- from common.permissions import not_logged_in, not_fully_logged_in
1
+ import ast
2
+ import re
3
+ from datetime import datetime
4
+
5
+ from common.helpers.emails import (
6
+ NOTIFICATION_EMAIL,
7
+ PASSWORD_RESET_EMAIL,
8
+ delete_contact,
9
+ send_email,
10
+ )
11
+ from common.mail import campaign_ids, send_dotdigital_email
12
+ from common.models import DailyActivity, Student, Teacher
13
+ from common.permissions import not_fully_logged_in, not_logged_in
14
+ from django.contrib import messages as messages
4
15
  from django.contrib.auth import get_user_model
5
- from django.contrib.auth.decorators import user_passes_test
16
+ from django.contrib.auth.decorators import login_required, user_passes_test
6
17
  from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
18
+ from django.contrib.auth.models import User
7
19
  from django.contrib.auth.tokens import default_token_generator
20
+ from django.http import HttpResponseRedirect
8
21
  from django.shortcuts import render
9
22
  from django.template.response import TemplateResponse
10
23
  from django.urls import reverse_lazy
11
- from django.utils.encoding import force_text
24
+ from django.utils.encoding import force_bytes
12
25
  from django.utils.http import urlsafe_base64_decode
13
- from django.utils.translation import ugettext as _
26
+ from django.utils.translation import gettext as _
14
27
  from django.views.decorators.cache import never_cache
15
28
  from django.views.decorators.csrf import csrf_protect
16
29
  from django.views.decorators.debug import sensitive_post_parameters
30
+ from django.views.decorators.http import require_POST
17
31
 
18
32
  from deploy import captcha
19
33
  from portal import app_settings
20
34
  from portal.forms.registration import (
21
- TeacherPasswordResetForm,
22
- TeacherPasswordResetSetPasswordForm,
23
35
  StudentPasswordResetForm,
24
36
  StudentPasswordResetSetPasswordForm,
37
+ TeacherPasswordResetForm,
38
+ TeacherPasswordResetSetPasswordForm,
25
39
  )
26
40
  from portal.helpers.captcha import remove_captcha_from_form
27
41
  from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
42
+ from portal.views.api import anonymise
43
+ from portal.views.login import has_user_lockout_expired
28
44
 
29
45
 
30
46
  @user_passes_test(not_logged_in, login_url=reverse_lazy("home"))
@@ -39,6 +55,48 @@ def student_password_reset(request):
39
55
  )
40
56
 
41
57
 
58
+ def blocked_and_not_expired(user: Student or Teacher):
59
+ return user.blocked_time and not has_user_lockout_expired(user)
60
+
61
+
62
+ def school_student_reset_password_tracker(request, activity_today):
63
+ if "transfer_students" in request.POST:
64
+ student_list = ast.literal_eval(request.POST.get("transfer_students", []))
65
+ for student_id in student_list:
66
+ current_student = Student.objects.get(id=student_id)
67
+ if blocked_and_not_expired(current_student):
68
+ activity_today.school_student_lockout_resets += 1
69
+ elif "set_password" in request.POST:
70
+ student_id = re.search("/(\d+)/", request.path).group(1)
71
+ current_student = Student.objects.get(id=student_id)
72
+ if blocked_and_not_expired(current_student):
73
+ activity_today.school_student_lockout_resets += 1
74
+ activity_today.save()
75
+
76
+
77
+ def teacher_or_indy_reset_password_tracker(request, activity_today, email):
78
+ get_user = Teacher.objects.filter(new_user__email=email) or Student.objects.filter(new_user__email=email)
79
+ if get_user.exists():
80
+ user = get_user[0]
81
+ if blocked_and_not_expired(user):
82
+ if "teacher" in request.path:
83
+ activity_today.teacher_lockout_resets += 1
84
+ elif "student" in request.path:
85
+ activity_today.indy_lockout_resets += 1
86
+ activity_today.save()
87
+
88
+
89
+ def handle_reset_password_tracking(request, user_type, access_code=None, student_id=None):
90
+ activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
91
+ # school student has 2 different ways of resetting password
92
+ # hence the function is extended
93
+ # check for indy student or teacher account
94
+ if user_type == "SCHOOL_STUDENT":
95
+ school_student_reset_password_tracker(request, activity_today)
96
+ elif "email" in request.POST:
97
+ teacher_or_indy_reset_password_tracker(request, activity_today, request.POST.get("email", ""))
98
+
99
+
42
100
  @user_passes_test(not_fully_logged_in, login_url=reverse_lazy("teacher_login"))
43
101
  def teacher_password_reset(request):
44
102
  usertype = "TEACHER"
@@ -66,6 +124,7 @@ def password_reset(
66
124
  html_email_template_name=None,
67
125
  ):
68
126
  if request.method == "POST":
127
+ handle_reset_password_tracking(request, usertype)
69
128
  form = password_reset_form(request.POST)
70
129
  if not captcha.CAPTCHA_ENABLED:
71
130
  remove_captcha_from_form(form)
@@ -81,9 +140,7 @@ def password_reset(
81
140
  }
82
141
  form.save(**opts)
83
142
 
84
- return render(
85
- request, "portal/reset_password_email_sent.html", {"usertype": usertype}
86
- )
143
+ return render(request, "portal/reset_password_email_sent.html", {"usertype": usertype})
87
144
  else:
88
145
  form = password_reset_form()
89
146
 
@@ -145,7 +202,7 @@ def password_reset_confirm(
145
202
  check_uidb64(uidb64, token)
146
203
 
147
204
  try:
148
- uid = force_text(urlsafe_base64_decode(uidb64))
205
+ uid = force_bytes(urlsafe_base64_decode(uidb64))
149
206
  user = UserModel._default_manager.get(pk=uid)
150
207
  except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
151
208
  user = None
@@ -163,9 +220,7 @@ def password_reset_confirm(
163
220
 
164
221
  _check_and_unblock_user(user.username, usertype)
165
222
 
166
- return render(
167
- request, "portal/reset_password_done.html", {"usertype": usertype}
168
- )
223
+ return render(request, "portal/reset_password_done.html", {"usertype": usertype})
169
224
  else:
170
225
  form = set_password_form(user)
171
226
  else:
@@ -180,11 +235,13 @@ def password_reset_confirm(
180
235
  return TemplateResponse(request, template_name, context)
181
236
 
182
237
 
183
- def _check_and_unblock_user(username, usertype):
238
+ def _check_and_unblock_user(username, usertype, access_code=None):
184
239
  if usertype == "TEACHER":
185
240
  user = Teacher.objects.get(new_user__username=username)
186
- else:
241
+ elif usertype == "INDEP_STUDENT":
187
242
  user = Student.objects.get(new_user__username=username)
243
+ elif usertype == "SCHOOL_STUDENT":
244
+ user = Student.objects.get(new_user__first_name=username, class_field__access_code=access_code)
188
245
 
189
246
  if user.blocked_time is not None:
190
247
  user.blocked_time = None
@@ -230,3 +287,26 @@ def password_reset_check_and_confirm(request, uidb64=None, token=None):
230
287
  token=token,
231
288
  extra_context={"usertype": usertype},
232
289
  )
290
+
291
+
292
+ @require_POST
293
+ @login_required(login_url=reverse_lazy("teacher_login"))
294
+ def delete_account(request):
295
+ user = request.user
296
+ password = request.POST.get("password")
297
+
298
+ if not user.check_password(password):
299
+ messages.error(request, "Your account was not deleted due to incorrect password.")
300
+ return HttpResponseRedirect(reverse_lazy("dashboard"))
301
+
302
+ email = user.email
303
+ anonymise(user)
304
+
305
+ # remove from dotmailer
306
+ if bool(request.POST.get("unsubscribe_newsletter")):
307
+ delete_contact(email)
308
+
309
+ # send confirmation email
310
+ send_dotdigital_email(campaign_ids["delete_account"], [email])
311
+
312
+ return HttpResponseRedirect(reverse_lazy("home"))
@@ -1,4 +1,9 @@
1
- from common.helpers.emails import update_email
1
+ from common.helpers.emails import (
2
+ NOTIFICATION_EMAIL,
3
+ delete_contact,
4
+ send_email,
5
+ update_indy_email,
6
+ )
2
7
  from common.models import Student
3
8
  from common.permissions import logged_in_as_student
4
9
  from django.contrib import messages as messages
@@ -6,12 +11,15 @@ from django.contrib.auth import logout
6
11
  from django.contrib.auth.decorators import login_required, user_passes_test
7
12
  from django.contrib.auth.mixins import LoginRequiredMixin
8
13
  from django.http import HttpResponseRedirect
14
+ from django.shortcuts import render
9
15
  from django.urls import reverse_lazy
10
16
  from django.views.generic.edit import FormView
11
17
 
12
- from portal.forms.play import StudentEditAccountForm, IndependentStudentEditAccountForm
18
+ from portal.forms.play import IndependentStudentEditAccountForm, StudentEditAccountForm
19
+ from portal.forms.registration import DeleteAccountForm
13
20
  from portal.helpers.password import check_update_password
14
21
  from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
22
+ from portal.views.api import anonymise
15
23
 
16
24
 
17
25
  def _get_form(self, form_class):
@@ -68,9 +76,7 @@ class SchoolStudentEditAccountView(LoginRequiredMixin, FormView):
68
76
  # check not default value for CharField
69
77
  changing_password = check_update_password(form, student.new_user, request, data)
70
78
 
71
- messages.success(
72
- request, "Your account details have been changed successfully."
73
- )
79
+ messages.success(request, "Your account details have been changed successfully.")
74
80
 
75
81
  if changing_password:
76
82
  logout(request)
@@ -84,86 +90,107 @@ class SchoolStudentEditAccountView(LoginRequiredMixin, FormView):
84
90
  return _get_form(self, form_class)
85
91
 
86
92
 
87
- class IndependentStudentEditAccountView(LoginRequiredMixin, FormView):
93
+ def independentStudentEditAccountView(request):
88
94
  """
89
95
  A FormView for editing an independent student's account details. This forms enables
90
96
  an independent student to change their name, their email and / or their password.
91
- """
97
+ Additionally it also deletes the independent students account with its second form
92
98
 
93
- login_url = reverse_lazy("independent_student_login")
94
- form_class = IndependentStudentEditAccountForm
99
+ Django class based views makes it very difficult to have multiple forms in one view,
100
+ hence using the functional based view
101
+ """
102
+ user = request.user
103
+ change_email_password_form = IndependentStudentEditAccountForm(user, initial={"name": user.first_name})
104
+ delete_account_form = DeleteAccountForm(user)
95
105
  template_name = "../templates/portal/play/student_edit_account.html"
96
- model = Student
97
- initial = {"name": "Could not find name"}
98
- changing_email = False
99
- changing_password = False
100
-
101
- def get_form_kwargs(self):
102
- kwargs = super(IndependentStudentEditAccountView, self).get_form_kwargs()
103
- kwargs["initial"]["name"] = "{}{}".format(
104
- self.request.user.first_name, self.request.user.last_name
105
- )
106
- return kwargs
107
-
108
- def get_form(self, form_class=None):
109
- return _get_form(self, form_class)
110
-
111
- def get_success_url(self):
112
- if self.changing_email:
113
- return reverse_lazy("email_verification")
114
- elif self.changing_password:
115
- return reverse_lazy("independent_student_login")
116
- else:
117
- return reverse_lazy("independent_student_details")
118
-
119
- def form_valid(self, form):
120
- return _process_form(
121
- self,
122
- self.process_independent_student_edit_account_form,
123
- form,
124
- IndependentStudentEditAccountView,
125
- )
106
+ delete_account_confirm = False
126
107
 
127
- def process_independent_student_edit_account_form(self, form, student, request):
128
- data = form.cleaned_data
129
-
130
- # check not default value for CharField
131
- self.changing_password = check_update_password(
132
- form, student.new_user, request, data
133
- )
108
+ def process_change_email_password_form(request, student, old_anchor):
109
+ change_email_password_form = IndependentStudentEditAccountForm(request.user, request.POST)
110
+ changing_email = False
111
+ changing_password = False
112
+ new_email = ""
113
+ if change_email_password_form.is_valid():
114
+ data = change_email_password_form.cleaned_data
115
+ # check not default value for CharField
116
+ changing_password = check_update_password(change_email_password_form, student, request, data)
134
117
 
135
- # allow individual students to update more
136
- self.changing_email, new_email = update_email(student, request, data)
118
+ changing_email, new_email = update_indy_email(student, request, data)
119
+ changing_first_name = False if request.POST["name"] == student.first_name else True
120
+ student.first_name = request.POST["name"]
137
121
 
138
- self.update_name(student, data)
122
+ student.save()
139
123
 
140
- # Reset ratelimit cache after successful account details update
141
- clear_ratelimit_cache_for_user(student.new_user.username)
124
+ anchor = ""
142
125
 
143
- messages.success(
144
- request, "Your account details have been changed successfully."
145
- )
126
+ # Reset ratelimit cache after successful account details update
127
+ clear_ratelimit_cache_for_user(student.username)
146
128
 
147
- if self.changing_email:
148
- logout(request)
149
- messages.success(
150
- request,
151
- "Your email will be changed once you have verified it, until then "
152
- "you can still log in with your old email.",
153
- )
129
+ messages.success(request, "Your account details have been successfully changed.")
130
+ else:
131
+ anchor = old_anchor
154
132
 
155
- if self.changing_password:
156
- logout(request)
157
- messages.success(
158
- request,
159
- "Please login using your new password.",
160
- )
133
+ return changing_email, new_email, changing_password, changing_first_name, anchor
161
134
 
162
- def update_name(self, student, data):
163
- student.new_user.first_name = data["name"]
164
- # save all tables
165
- student.save()
166
- student.new_user.save()
135
+ if request.method == "POST":
136
+ if "delete_account" in request.POST:
137
+ delete_account_form = DeleteAccountForm(user, request.POST)
138
+ if not delete_account_form.is_valid():
139
+ messages.warning(request, "Your account was not deleted due to incorrect password.")
140
+ else:
141
+ delete_account_confirm = True
142
+ else:
143
+ change_email_password_form = IndependentStudentEditAccountForm(request.user, request.POST)
144
+ if not change_email_password_form.is_valid():
145
+ messages.warning(request, "Your account was not updated do to incorrect details")
146
+ return render(
147
+ request,
148
+ template_name,
149
+ {
150
+ "form": change_email_password_form,
151
+ "delete_account_form": delete_account_form,
152
+ },
153
+ )
154
+ else:
155
+ (
156
+ changing_email,
157
+ new_email,
158
+ changing_password,
159
+ changing_first_name,
160
+ anchor,
161
+ ) = process_change_email_password_form(request, request.user, "")
162
+ if changing_email:
163
+ logout(request)
164
+ messages.success(request, "Your account details have been changed successfully.")
165
+ messages.success(
166
+ request,
167
+ "Your email will be changed once you have verified it, until then "
168
+ "you can still log in with your old email.",
169
+ )
170
+ return render(
171
+ request,
172
+ "portal/email_verification_needed.html",
173
+ {"usertype": "INDEP_STUDENT"},
174
+ )
175
+ if changing_password:
176
+ logout(request)
177
+ messages.success(request, "Your account details have been changed successfully.")
178
+ messages.success(request, "Please login using your new password.")
179
+ return HttpResponseRedirect(reverse_lazy("independent_student_login"))
180
+
181
+ if changing_first_name:
182
+ messages.success(request, "Your account details have been changed successfully.")
183
+ return HttpResponseRedirect(reverse_lazy("independent_student_details"))
184
+
185
+ return render(
186
+ request,
187
+ template_name,
188
+ {
189
+ "form": change_email_password_form,
190
+ "delete_account_form": delete_account_form,
191
+ "delete_account_confirm": delete_account_confirm,
192
+ },
193
+ )
167
194
 
168
195
 
169
196
  @login_required(login_url=reverse_lazy("home"))