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/teach.py CHANGED
@@ -1,62 +1,41 @@
1
+ import itertools
1
2
  import re
2
3
  from builtins import map, range, str
3
4
 
4
- from captcha.fields import ReCaptchaField
5
- from captcha.widgets import ReCaptchaV2Invisible
6
5
  from common.helpers.emails import send_verification_email
7
- from common.models import Student, stripStudentName, UserSession, Teacher
6
+ from common.models import Student, Teacher, UserSession, stripStudentName
8
7
  from django import forms
9
8
  from django.contrib.auth import authenticate
10
9
  from django.contrib.auth.forms import AuthenticationForm
11
10
  from django.contrib.auth.models import User
11
+ from django_recaptcha.fields import ReCaptchaField
12
+ from django_recaptcha.widgets import ReCaptchaV2Invisible
13
+ from game.models import Episode, Worksheet
12
14
 
13
15
  from portal.forms.error_messages import INVALID_LOGIN_MESSAGE
14
16
  from portal.helpers.password import PasswordStrength, form_clean_password
15
17
  from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
16
- from portal.templatetags.app_tags import is_verified
17
18
 
18
19
 
19
- class TeacherSignupForm(forms.Form):
20
+ class InvitedTeacherForm(forms.Form):
21
+ prefix = "teacher_signup"
20
22
 
21
- teacher_first_name = forms.CharField(
22
- help_text="Enter your first name",
23
- max_length=100,
24
- widget=forms.TextInput(
25
- attrs={"autocomplete": "off", "placeholder": "First name"}
26
- ),
27
- )
28
- teacher_last_name = forms.CharField(
29
- help_text="Enter your last name",
30
- max_length=100,
31
- widget=forms.TextInput(
32
- attrs={"autocomplete": "off", "placeholder": "Last name"}
33
- ),
34
- )
35
- teacher_email = forms.EmailField(
36
- help_text="Enter your email address",
37
- widget=forms.EmailInput(
38
- attrs={"autocomplete": "off", "placeholder": "Email address"}
39
- ),
40
- )
41
-
42
- newsletter_ticked = forms.BooleanField(
43
- widget=forms.CheckboxInput(), initial=False, required=False
44
- )
23
+ def __init__(self, *args, **kwargs):
24
+ super().__init__(*args, **kwargs)
25
+ for field_name, field in self.fields.items():
26
+ field.widget.attrs["id"] = f"id_teacher_signup-{field_name}"
45
27
 
46
28
  teacher_password = forms.CharField(
47
29
  help_text="Enter a password",
48
- widget=forms.PasswordInput(
49
- attrs={"autocomplete": "off", "placeholder": "Password"}
50
- ),
30
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
51
31
  )
52
32
  teacher_confirm_password = forms.CharField(
53
33
  help_text="Repeat password",
54
- widget=forms.PasswordInput(
55
- attrs={"autocomplete": "off", "placeholder": "Repeat password"}
56
- ),
34
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Repeat password"}),
57
35
  )
58
36
 
59
- captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
37
+ consent_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=True)
38
+ newsletter_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=False)
60
39
 
61
40
  def clean_teacher_password(self):
62
41
  return form_clean_password(self, "teacher_password", PasswordStrength.TEACHER)
@@ -73,8 +52,26 @@ class TeacherSignupForm(forms.Form):
73
52
  return self.cleaned_data
74
53
 
75
54
 
76
- class TeacherEditAccountForm(forms.Form):
55
+ class TeacherSignupForm(InvitedTeacherForm):
56
+ teacher_first_name = forms.CharField(
57
+ help_text="Enter your first name",
58
+ max_length=100,
59
+ widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "First name"}),
60
+ )
61
+ teacher_last_name = forms.CharField(
62
+ help_text="Enter your last name",
63
+ max_length=100,
64
+ widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Last name"}),
65
+ )
66
+ teacher_email = forms.EmailField(
67
+ help_text="Enter your email address",
68
+ widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
69
+ )
77
70
 
71
+ captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
72
+
73
+
74
+ class TeacherEditAccountForm(forms.Form):
78
75
  first_name = forms.CharField(
79
76
  max_length=100,
80
77
  widget=forms.TextInput(attrs={"placeholder": "First name", "class": "fName"}),
@@ -97,9 +94,7 @@ class TeacherEditAccountForm(forms.Form):
97
94
  )
98
95
  confirm_password = forms.CharField(
99
96
  required=False,
100
- widget=forms.PasswordInput(
101
- attrs={"placeholder": "Confirm new password (optional)"}
102
- ),
97
+ widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password (optional)"}),
103
98
  help_text="Confirm new password (optional)",
104
99
  )
105
100
  current_password = forms.CharField(
@@ -135,13 +130,12 @@ class TeacherEditAccountForm(forms.Form):
135
130
 
136
131
  class TeacherLoginForm(AuthenticationForm):
137
132
  username = forms.EmailField(
138
- label="Email address",
139
- widget=forms.EmailInput(
140
- attrs={"autocomplete": "off", "placeholder": "my.email@address.com"}
141
- ),
133
+ widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
134
+ help_text="Enter your email address",
142
135
  )
143
136
  password = forms.CharField(
144
- label="Password", widget=forms.PasswordInput(attrs={"autocomplete": "off"})
137
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
138
+ help_text="Enter your password",
145
139
  )
146
140
 
147
141
  def clean(self):
@@ -149,7 +143,6 @@ class TeacherLoginForm(AuthenticationForm):
149
143
  password = self.cleaned_data.get("password", None)
150
144
 
151
145
  if email and password:
152
-
153
146
  # Check it's a teacher and not a student using the same email address
154
147
  user = None
155
148
 
@@ -175,9 +168,7 @@ class TeacherLoginForm(AuthenticationForm):
175
168
  users = User.objects.filter(email=email)
176
169
 
177
170
  for result in users:
178
- if hasattr(result, "userprofile") and hasattr(
179
- result.userprofile, "teacher"
180
- ):
171
+ if hasattr(result, "userprofile") and hasattr(result.userprofile, "teacher"):
181
172
  user = result
182
173
  break
183
174
 
@@ -190,8 +181,8 @@ class TeacherLoginForm(AuthenticationForm):
190
181
  if user is None:
191
182
  self.show_invalid_login_message()
192
183
 
193
- if not is_verified(user):
194
- send_verification_email(self.request, user)
184
+ if not user.userprofile.is_verified:
185
+ send_verification_email(self.request, user, self.data)
195
186
  self.show_invalid_login_message()
196
187
 
197
188
  if not user.is_active:
@@ -206,6 +197,7 @@ class ClassCreationForm(forms.Form):
206
197
  widget=forms.TextInput(attrs={"placeholder": "Class name"}),
207
198
  help_text="Enter a class name",
208
199
  )
200
+ teacher = forms.ChoiceField(help_text="Select teacher", required=False)
209
201
  classmate_progress = forms.BooleanField(
210
202
  label="Allow students to see their classmates' progress?",
211
203
  widget=forms.CheckboxInput(),
@@ -213,6 +205,45 @@ class ClassCreationForm(forms.Form):
213
205
  required=False,
214
206
  )
215
207
 
208
+ def __init__(self, *args, teacher=None, **kwargs):
209
+ super().__init__(*args, **kwargs)
210
+
211
+ if teacher is not None:
212
+ # Place current teacher at the top
213
+ teacher_choices = [
214
+ (
215
+ teacher.id,
216
+ f"{teacher.new_user.first_name} {teacher.new_user.last_name}",
217
+ )
218
+ ]
219
+
220
+ # Get coworkers and add them to the choices if the teacher is an admin
221
+ if teacher.is_admin:
222
+ coworkers = (
223
+ Teacher.objects.filter(school=teacher.school)
224
+ .exclude(id=teacher.id)
225
+ .order_by("new_user__last_name", "new_user__first_name")
226
+ )
227
+ for coworker in coworkers:
228
+ teacher_choices.append(
229
+ (
230
+ coworker.id,
231
+ f"{coworker.new_user.first_name} {coworker.new_user.last_name}",
232
+ )
233
+ )
234
+
235
+ self.fields["teacher"].choices = teacher_choices
236
+
237
+ def clean(self):
238
+ name = self.cleaned_data.get("class_name", "")
239
+
240
+ if re.match(re.compile("^[\w -]+$"), name) is None:
241
+ raise forms.ValidationError(
242
+ "Class name may only contain letters, numbers, dashes, underscores, and spaces."
243
+ )
244
+
245
+ return self.cleaned_data
246
+
216
247
 
217
248
  class ClassEditForm(forms.Form):
218
249
  # select dropdown choices for potentially limiting time in which external students may join
@@ -229,9 +260,7 @@ class ClassEditForm(forms.Form):
229
260
  [
230
261
  (
231
262
  str(hours),
232
- "Allow external requests to this class for the next "
233
- + str(hours)
234
- + " hours",
263
+ "Allow external requests to this class for the next " + str(hours) + " hours",
235
264
  )
236
265
  for hours in range(4, 28, 4)
237
266
  ]
@@ -240,15 +269,16 @@ class ClassEditForm(forms.Form):
240
269
  [
241
270
  (
242
271
  str(days * 24),
243
- "Allow external requests to this class for the next "
244
- + str(days)
245
- + " days",
272
+ "Allow external requests to this class for the next " + str(days) + " days",
246
273
  )
247
274
  for days in range(2, 5)
248
275
  ]
249
276
  )
250
277
  join_choices.append(
251
- ("1000", "Always allow external requests to this class (not recommended)")
278
+ (
279
+ "1000",
280
+ "Always allow external requests to this class (not recommended)",
281
+ )
252
282
  )
253
283
  name = forms.CharField(
254
284
  widget=forms.TextInput(attrs={"placeholder": "Enter class name"}),
@@ -268,6 +298,41 @@ class ClassEditForm(forms.Form):
268
298
  widget=forms.Select(),
269
299
  )
270
300
 
301
+ def clean(self):
302
+ name = self.cleaned_data.get("name", "")
303
+
304
+ if re.match(re.compile("^[\w -]+$"), name) is None:
305
+ raise forms.ValidationError(
306
+ "Class name may only contain letters, numbers, dashes, underscores, and spaces."
307
+ )
308
+
309
+ return self.cleaned_data
310
+
311
+
312
+ class ClassLevelControlForm(forms.Form):
313
+ def __init__(self, *args, **kwargs):
314
+ super(ClassLevelControlForm, self).__init__(*args, **kwargs)
315
+
316
+ episodes = Episode.objects.filter(pk__in=range(1, 25))
317
+
318
+ for episode in episodes:
319
+ choices = []
320
+ for level in episode.levels:
321
+ try:
322
+ choices.append((f"worksheet:{level.after_worksheet.id}", episode.name))
323
+ except Worksheet.DoesNotExist:
324
+ pass
325
+ choices.append((f"level:{level.id}", level.name))
326
+
327
+ for worksheet in episode.worksheets.filter(before_level__isnull=True):
328
+ choices.append((f"worksheet:{worksheet.id}", episode.name))
329
+
330
+ self.fields[f"episode{episode.id}"] = forms.MultipleChoiceField(
331
+ choices=itertools.chain(choices),
332
+ widget=forms.CheckboxSelectMultiple(),
333
+ required=False,
334
+ )
335
+
271
336
 
272
337
  class ClassMoveForm(forms.Form):
273
338
  new_teacher = forms.ChoiceField(
@@ -306,22 +371,14 @@ class TeacherEditStudentForm(forms.Form):
306
371
  name = stripStudentName(self.cleaned_data.get("name", ""))
307
372
 
308
373
  if name == "":
309
- raise forms.ValidationError(
310
- "'" + self.cleaned_data.get("name", "") + "' is not a valid name"
311
- )
374
+ raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
312
375
 
313
376
  if re.match(re.compile("^[\w -]+$"), name) is None:
314
- raise forms.ValidationError(
315
- "Names may only contain letters, numbers, dashes, underscores, and spaces."
316
- )
377
+ raise forms.ValidationError("Names may only contain letters, numbers, dashes, underscores, and spaces.")
317
378
 
318
- students = Student.objects.filter(
319
- class_field=self.klass, new_user__first_name__iexact=name
320
- )
379
+ students = Student.objects.filter(class_field=self.klass, new_user__first_name__iexact=name)
321
380
  if students.exists() and students[0] != self.student:
322
- raise forms.ValidationError(
323
- "There is already a student called '" + name + "' in this class"
324
- )
381
+ raise forms.ValidationError("There is already a student called '" + name + "' in this class")
325
382
 
326
383
  return name
327
384
 
@@ -330,12 +387,12 @@ class TeacherSetStudentPass(forms.Form):
330
387
  password = forms.CharField(
331
388
  label="New password",
332
389
  help_text="Enter new password",
333
- widget=forms.PasswordInput(attrs={"placeholder": "Enter new password"}),
390
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Enter new password"}),
334
391
  )
335
392
  confirm_password = forms.CharField(
336
393
  label="Confirm new password",
337
394
  help_text="Confirm new password",
338
- widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password"}),
395
+ widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm new password"}),
339
396
  )
340
397
 
341
398
  def clean_password(self):
@@ -379,14 +436,9 @@ def validateStudentNames(klass, names):
379
436
 
380
437
  def find_clashes(names, students, clashes_found, validationErrors):
381
438
  for name in names:
382
- if (
383
- students.filter(new_user__first_name__iexact=name).exists()
384
- and name not in clashes_found
385
- ):
439
+ if students.filter(new_user__first_name__iexact=name).exists() and name not in clashes_found:
386
440
  validationErrors.append(
387
- forms.ValidationError(
388
- "There is already a student called '" + name + "' in this class"
389
- )
441
+ forms.ValidationError("There is already a student called '" + name + "' in this class")
390
442
  )
391
443
  clashes_found.append(name)
392
444
 
@@ -396,9 +448,7 @@ def find_duplicates(names, lower_names, validationErrors):
396
448
  for duplicate in [name for name in names if lower_names.count(name.lower()) > 1]:
397
449
  if duplicate not in duplicates_found:
398
450
  validationErrors.append(
399
- forms.ValidationError(
400
- "You cannot add more than one student called '" + duplicate + "'"
401
- )
451
+ forms.ValidationError("You cannot add more than one student called '" + duplicate + "'")
402
452
  )
403
453
  duplicates_found.append(duplicate)
404
454
 
@@ -417,9 +467,7 @@ def find_illegal_characters(names, validationErrors):
417
467
 
418
468
  def check_passwords(password, confirm_password):
419
469
  if password is not None and password != confirm_password:
420
- raise forms.ValidationError(
421
- "The password and the confirmation password do not match"
422
- )
470
+ raise forms.ValidationError("The password and the confirmation password do not match")
423
471
 
424
472
 
425
473
  class TeacherMoveStudentsDestinationForm(forms.Form):
@@ -467,18 +515,14 @@ class TeacherMoveStudentDisambiguationForm(forms.Form):
467
515
  def clean_name(self):
468
516
  name = stripStudentName(self.cleaned_data.get("name", ""))
469
517
  if name == "":
470
- raise forms.ValidationError(
471
- "'" + self.cleaned_data.get("name", "") + "' is not a valid name"
472
- )
518
+ raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
473
519
  return name
474
520
 
475
521
 
476
522
  class BaseTeacherMoveStudentsDisambiguationFormSet(forms.BaseFormSet):
477
523
  def __init__(self, destination, *args, **kwargs):
478
524
  self.destination = destination
479
- super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__(
480
- *args, **kwargs
481
- )
525
+ super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__(*args, **kwargs)
482
526
 
483
527
  def clean(self):
484
528
  if any(self.errors):
@@ -496,48 +540,35 @@ class BaseTeacherMoveStudentsDisambiguationFormSet(forms.BaseFormSet):
496
540
 
497
541
  class TeacherDismissStudentsForm(forms.Form):
498
542
  orig_name = forms.CharField(
499
- label="Original Name",
500
543
  help_text="Original student name",
501
544
  widget=forms.TextInput(
502
545
  attrs={
503
546
  "readonly": "readonly",
504
547
  "placeholder": "Original Name",
505
- "style": "margin: 0",
548
+ "class": "m-0",
506
549
  }
507
550
  ),
508
551
  )
509
552
  name = forms.CharField(
510
- label="New Name",
511
553
  help_text="New student name",
512
- widget=forms.TextInput(
513
- attrs={"placeholder": "Enter new student name", "style": "margin : 0px"}
514
- ),
554
+ widget=forms.TextInput(attrs={"placeholder": "Enter new student name", "class": "m-0"}),
515
555
  )
516
556
  email = forms.EmailField(
517
557
  label="Email",
518
- help_text="Current email address",
519
- widget=forms.EmailInput(
520
- attrs={"placeholder": "Enter email address", "style": "margin : 0px"}
521
- ),
558
+ help_text="New email address",
559
+ widget=forms.EmailInput(attrs={"placeholder": "Enter email address", "class": "m-0"}),
522
560
  )
523
561
  confirm_email = forms.EmailField(
524
562
  label="Confirm Email",
525
- help_text="New email address",
526
- widget=forms.EmailInput(
527
- attrs={"placeholder": "Confirm email address", "style": "margin : 0px"}
528
- ),
563
+ help_text="Confirm email address",
564
+ widget=forms.EmailInput(attrs={"placeholder": "Confirm email address", "class": "m-0"}),
529
565
  )
530
566
 
531
567
  def clean_name(self):
532
568
  name = stripStudentName(self.cleaned_data.get("name", ""))
533
569
 
534
570
  if name == "":
535
- raise forms.ValidationError(
536
- "'" + self.cleaned_data.get("name", "") + "' is not a valid name"
537
- )
538
-
539
- if User.objects.filter(username=name).exists():
540
- raise forms.ValidationError("That username is already in use")
571
+ raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
541
572
 
542
573
  return name
543
574
 
@@ -596,7 +627,8 @@ class StudentCreationForm(forms.Form):
596
627
 
597
628
  class TeacherAddExternalStudentForm(forms.Form):
598
629
  name = forms.CharField(
599
- label="Student name", widget=forms.TextInput(attrs={"placeholder": "Name"})
630
+ label="Student name",
631
+ widget=forms.TextInput(attrs={"placeholder": "Name"}),
600
632
  )
601
633
 
602
634
  def __init__(self, klass, *args, **kwargs):
@@ -607,15 +639,9 @@ class TeacherAddExternalStudentForm(forms.Form):
607
639
  name = stripStudentName(self.cleaned_data.get("name", ""))
608
640
 
609
641
  if name == "":
610
- raise forms.ValidationError(
611
- "'" + self.cleaned_data.get("name", "") + "' is not a valid name"
612
- )
642
+ raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
613
643
 
614
- if Student.objects.filter(
615
- class_field=self.klass, new_user__first_name__iexact=name
616
- ).exists():
617
- raise forms.ValidationError(
618
- "There is already a student called '" + name + "' in this class"
619
- )
644
+ if Student.objects.filter(class_field=self.klass, new_user__first_name__iexact=name).exists():
645
+ raise forms.ValidationError("There is already a student called '" + name + "' in this class")
620
646
 
621
647
  return name
portal/handlers.py CHANGED
@@ -1,10 +1,9 @@
1
+ from common.utils import two_factor_cache_key
1
2
  from django.core.cache import cache
2
3
  from django.db.models.signals import post_save, pre_delete
3
4
  from django.dispatch import receiver
4
5
  from django_otp.models import Device
5
6
 
6
- from common.utils import two_factor_cache_key
7
-
8
7
 
9
8
  @receiver([post_save, pre_delete])
10
9
  def clear_two_factor_cache(sender, **kwargs):
@@ -1,15 +1,16 @@
1
1
  from __future__ import absolute_import
2
2
 
3
3
  import datetime
4
- import pytz
5
4
  from functools import wraps
6
5
 
6
+ import pytz
7
7
  from common.models import Teacher, Student
8
8
  from django.contrib.auth import logout
9
9
  from django.shortcuts import render
10
10
  from ratelimit import ALL, UNSAFE
11
11
 
12
12
  from portal.helpers.ratelimit import is_ratelimited
13
+ from portal.helpers.request_handlers import get_access_code_from_request
13
14
  from portal.templatetags.app_tags import is_logged_in
14
15
 
15
16
  __all__ = ["ratelimit"]
@@ -39,14 +40,15 @@ def ratelimit(
39
40
  increment=True,
40
41
  )
41
42
  request.limited = ratelimited or old_limited
42
-
43
43
  if ratelimited and block:
44
44
  if is_teacher:
45
45
  model = Teacher
46
46
  else:
47
47
  model = Student
48
48
 
49
- user = None
49
+ user_to_lockout = None
50
+
51
+ lockout_template = "portal/locked_out.html"
50
52
 
51
53
  if request.user.is_anonymous:
52
54
  data = request.POST
@@ -55,21 +57,39 @@ def ratelimit(
55
57
  else:
56
58
  username = data.get("username")
57
59
 
58
- if model.objects.filter(new_user__username=username).exists():
59
- user = model.objects.get(new_user__username=username)
60
+ access_code = get_access_code_from_request(request)
61
+ model_finder = model.objects.get
62
+ # look for school student if access code not found
63
+ # (AttributeError) if student not found (IndexError) move
64
+ # on to another try block similar logic followed afterwards
65
+ if access_code:
66
+ user_to_lockout = model_finder(
67
+ new_user__first_name=username,
68
+ class_field__access_code=access_code, # extract the found text from regex
69
+ )
70
+ lockout_template = (
71
+ "portal/locked_out_school_student.html"
72
+ )
73
+ # look for indy student or teacher
74
+ else:
75
+ user_to_lockout = model_finder(
76
+ new_user__username=username
77
+ )
60
78
  else:
61
- user = model.objects.get(new_user=request.user)
79
+ user_to_lockout = model.objects.get(new_user=request.user)
62
80
 
63
- if user:
64
- user.blocked_time = datetime.datetime.now(tz=pytz.utc)
65
- user.save()
81
+ if user_to_lockout:
82
+ user_to_lockout.blocked_time = datetime.datetime.now(
83
+ tz=pytz.utc
84
+ )
85
+ user_to_lockout.save()
66
86
 
67
87
  if is_logged_in(request.user):
68
88
  logout(request)
69
89
 
70
90
  return render(
71
91
  request,
72
- "portal/locked_out.html",
92
+ lockout_template,
73
93
  {"is_teacher": is_teacher},
74
94
  )
75
95