codeforlife-portal 5.32.2__py2.py3-none-any.whl → 8.9.9__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. cfl_common/common/__init__.py +1 -0
  2. cfl_common/common/app_settings.py +66 -0
  3. cfl_common/common/apps.py +6 -0
  4. cfl_common/common/context_processors.py +9 -0
  5. cfl_common/common/csp_config.py +85 -0
  6. cfl_common/common/helpers/__init__.py +0 -0
  7. cfl_common/common/helpers/data_migration_loader.py +42 -0
  8. cfl_common/common/helpers/emails.py +393 -0
  9. cfl_common/common/helpers/generators.py +52 -0
  10. cfl_common/common/helpers/organisation.py +10 -0
  11. cfl_common/common/mail.py +201 -0
  12. cfl_common/common/migrations/0001_initial.py +240 -0
  13. cfl_common/common/migrations/0002_emailverification.py +55 -0
  14. cfl_common/common/migrations/0003_aimmocharacter.py +31 -0
  15. cfl_common/common/migrations/0004_add_aimmocharacters.py +17 -0
  16. cfl_common/common/migrations/0005_add_worksheets.py +8 -0
  17. cfl_common/common/migrations/0006_update_aimmo_character_image_path.py +17 -0
  18. cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +8 -0
  19. cfl_common/common/migrations/0008_unlock_worksheet_3.py +11 -0
  20. cfl_common/common/migrations/0009_add_blocked_time_to_teacher_and_student.py +24 -0
  21. cfl_common/common/migrations/0010_remove_teacher_title.py +18 -0
  22. cfl_common/common/migrations/0011_student_login_id.py +18 -0
  23. cfl_common/common/migrations/0012_usersession.py +39 -0
  24. cfl_common/common/migrations/0013_class_school.py +42 -0
  25. cfl_common/common/migrations/0014_login_type.py +29 -0
  26. cfl_common/common/migrations/0015_dailyactivity.py +31 -0
  27. cfl_common/common/migrations/0016_joinreleasestudent.py +42 -0
  28. cfl_common/common/migrations/0017_copy_email_to_username.py +18 -0
  29. cfl_common/common/migrations/0018_update_aimmo_character_image_path.py +15 -0
  30. cfl_common/common/migrations/0019_aimmocharacter_alt.py +16 -0
  31. cfl_common/common/migrations/0020_class_is_active_and_null_access_code.py +23 -0
  32. cfl_common/common/migrations/0021_school_is_active.py +28 -0
  33. cfl_common/common/migrations/0022_school_cleanup.py +29 -0
  34. cfl_common/common/migrations/0023_userprofile_aimmo_badges.py +22 -0
  35. cfl_common/common/migrations/0024_teacher_invited_by.py +25 -0
  36. cfl_common/common/migrations/0025_schoolteacherinvitation.py +47 -0
  37. cfl_common/common/migrations/0026_teacher_remove_join_request.py +22 -0
  38. cfl_common/common/migrations/0027_class_created_by.py +25 -0
  39. cfl_common/common/migrations/0028_coding_club_downloads.py +23 -0
  40. cfl_common/common/migrations/0029_dynamicelement.py +22 -0
  41. cfl_common/common/migrations/0030_add_maintenance_banner.py +25 -0
  42. cfl_common/common/migrations/0031_improve_admin_panel.py +56 -0
  43. cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py +18 -0
  44. cfl_common/common/migrations/0033_password_reset_tracking_fields.py +23 -0
  45. cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +18 -0
  46. cfl_common/common/migrations/0035_rename_lockout_fields.py +27 -0
  47. cfl_common/common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +17 -0
  48. cfl_common/common/migrations/0037_migrate_email_verification.py +21 -0
  49. cfl_common/common/migrations/0038_delete_emailverification.py +16 -0
  50. cfl_common/common/migrations/0039_copy_email_to_username.py +18 -0
  51. cfl_common/common/migrations/0040_school_county.py +18 -0
  52. cfl_common/common/migrations/0041_populate_gb_counties.py +27 -0
  53. cfl_common/common/migrations/0042_totalactivity.py +25 -0
  54. cfl_common/common/migrations/0043_add_total_activity.py +30 -0
  55. cfl_common/common/migrations/0044_update_activity_models.py +33 -0
  56. cfl_common/common/migrations/0045_otp.py +23 -0
  57. cfl_common/common/migrations/0046_alter_school_country.py +19 -0
  58. cfl_common/common/migrations/0047_delete_school_postcode.py +16 -0
  59. cfl_common/common/migrations/0048_unique_school_names.py +42 -0
  60. cfl_common/common/migrations/0049_anonymise_orphan_users.py +29 -0
  61. cfl_common/common/migrations/0050_anonymise_orphan_schools.py +30 -0
  62. cfl_common/common/migrations/0051_verify_returning_users.py +26 -0
  63. cfl_common/common/migrations/0052_add_cse_fields.py +68 -0
  64. cfl_common/common/migrations/0053_clean_class_data.py +24 -0
  65. cfl_common/common/migrations/0054_delete_aimmo_models.py +20 -0
  66. cfl_common/common/migrations/0055_alter_schoolteacherinvitation_token.py +18 -0
  67. cfl_common/common/migrations/0056_set_non_school_teachers_as_non_admins.py +25 -0
  68. cfl_common/common/migrations/0057_teacher_teacher__is_admin.py +19 -0
  69. cfl_common/common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
  70. cfl_common/common/migrations/__init__.py +0 -0
  71. cfl_common/common/models.py +557 -0
  72. cfl_common/common/permissions.py +84 -0
  73. cfl_common/common/tests/__init__.py +0 -0
  74. cfl_common/common/tests/test_migration_anonymise_orphan_schools.py +30 -0
  75. cfl_common/common/tests/test_migration_anonymise_orphan_users.py +30 -0
  76. cfl_common/common/tests/test_migration_blocked_time.py +15 -0
  77. cfl_common/common/tests/test_migration_remove_teacher_title.py +13 -0
  78. cfl_common/common/tests/test_migration_unique_school_names.py +33 -0
  79. cfl_common/common/tests/test_migration_verify_returning_users.py +59 -0
  80. cfl_common/common/tests/test_models.py +87 -0
  81. cfl_common/common/tests/utils/__init__.py +0 -0
  82. cfl_common/common/tests/utils/classes.py +38 -0
  83. cfl_common/common/tests/utils/email.py +67 -0
  84. cfl_common/common/tests/utils/organisation.py +41 -0
  85. cfl_common/common/tests/utils/student.py +123 -0
  86. cfl_common/common/tests/utils/teacher.py +73 -0
  87. cfl_common/common/tests/utils/user.py +27 -0
  88. cfl_common/common/utils.py +56 -0
  89. cfl_common/setup.py +61 -0
  90. codeforlife_portal-8.9.9.dist-info/METADATA +226 -0
  91. {codeforlife_portal-5.32.2.dist-info → codeforlife_portal-8.9.9.dist-info}/RECORD +341 -238
  92. {codeforlife_portal-5.32.2.dist-info → codeforlife_portal-8.9.9.dist-info}/WHEEL +1 -1
  93. codeforlife_portal-8.9.9.dist-info/licenses/LICENSE.md +3 -0
  94. {codeforlife_portal-5.32.2.dist-info → codeforlife_portal-8.9.9.dist-info}/top_level.txt +1 -0
  95. deploy/middleware/maintenance.py +25 -0
  96. deploy/middleware/screentime_warning.py +29 -0
  97. deploy/middleware/security.py +5 -6
  98. deploy/middleware/session_timeout.py +31 -0
  99. deploy/middleware/tmp_basic_auth.py +41 -0
  100. example_project/portal_test_settings.py +239 -0
  101. example_project/settings.py +156 -17
  102. example_project/urls.py +5 -6
  103. portal/__init__.py +1 -1
  104. portal/admin.py +142 -29
  105. portal/app_settings.py +10 -7
  106. portal/forms/dotmailer.py +6 -4
  107. portal/forms/invite_teacher.py +19 -10
  108. portal/forms/organisation.py +137 -68
  109. portal/forms/play.py +53 -98
  110. portal/forms/registration.py +70 -164
  111. portal/forms/teach.py +147 -121
  112. portal/handlers.py +1 -2
  113. portal/helpers/decorators.py +30 -10
  114. portal/helpers/password.py +86 -47
  115. portal/helpers/ratelimit.py +32 -15
  116. portal/helpers/regexes.py +5 -0
  117. portal/helpers/request_handlers.py +10 -0
  118. portal/migrations/0044_auto_20150430_0959.py +6 -2
  119. portal/mixins/__init__.py +1 -0
  120. portal/mixins/cron_mixin.py +12 -0
  121. portal/permissions/__init__.py +1 -0
  122. portal/permissions/is_cron_request_from_google.py +14 -0
  123. portal/static/portal/img/10_years_anniversary.png +0 -0
  124. portal/static/portal/img/RR_logo_grass_background.png +0 -0
  125. portal/static/portal/img/coding_club_hero.jpg +0 -0
  126. portal/static/portal/img/coding_club_python_pack.png +0 -0
  127. portal/static/portal/img/facebook.png +0 -0
  128. portal/static/portal/img/gitbook.png +0 -0
  129. portal/static/portal/img/howe_dell_1.png +0 -0
  130. portal/static/portal/img/howe_dell_2.png +0 -0
  131. portal/static/portal/img/howe_dell_3.png +0 -0
  132. portal/static/portal/img/logo_cfl.png +0 -0
  133. portal/static/portal/img/logo_cfl_powered.svg +35 -0
  134. portal/static/portal/img/logo_cfl_reminder_cards.jpg +0 -0
  135. portal/static/portal/img/logo_ocado_group.png +0 -0
  136. portal/static/portal/img/logo_python_den.svg +21 -0
  137. portal/static/portal/img/long_europe_map.png +0 -0
  138. portal/static/portal/img/python_den.png +0 -0
  139. portal/static/portal/img/python_den_banner.svg +26 -0
  140. portal/static/portal/img/rapid_router_landing_hero.png +0 -0
  141. portal/static/portal/img/rr_advanced.png +0 -0
  142. portal/static/portal/img/ten_year_map_pin.svg +1 -0
  143. portal/static/portal/img/thumbnail_educate_rapid_router.png +0 -0
  144. portal/static/portal/img/thumbnail_educate_resources.png +0 -0
  145. portal/static/portal/img/thumbnail_play_rapid_router.png +0 -0
  146. portal/static/portal/img/thumbnail_python_den.png +0 -0
  147. portal/static/portal/img/twitter.png +0 -0
  148. portal/static/portal/js/carouselCards.js +25 -0
  149. portal/static/portal/js/common.js +96 -1
  150. portal/static/portal/js/independentLogin.js +16 -0
  151. portal/static/portal/js/independentRegistration.js +86 -0
  152. portal/static/portal/js/levelControl.js +77 -0
  153. portal/static/portal/js/lib/jquery.min.js +2 -0
  154. portal/static/portal/js/organisation_manage.js +142 -14
  155. portal/static/portal/js/passwordStrength.js +154 -64
  156. portal/static/portal/js/resetPassword.js +23 -0
  157. portal/static/portal/js/riveted.min.js +238 -239
  158. portal/static/portal/js/school.js +13 -0
  159. portal/static/portal/js/studentLogin.js +16 -0
  160. portal/static/portal/js/teacherEditStudent.js +23 -0
  161. portal/static/portal/js/teacherLogin.js +16 -0
  162. portal/static/portal/js/tenYearMap.js +14 -0
  163. portal/static/portal/sass/colorbox.scss +0 -1
  164. portal/static/portal/sass/modules/_colours.scss +1 -0
  165. portal/static/portal/sass/modules/_levels.scss +1 -1
  166. portal/static/portal/sass/modules/_mixins.scss +21 -0
  167. portal/static/portal/sass/partials/_banners.scss +4 -177
  168. portal/static/portal/sass/partials/_buttons.scss +12 -15
  169. portal/static/portal/sass/partials/_carousel.scss +129 -0
  170. portal/static/portal/sass/partials/_footer.scss +21 -22
  171. portal/static/portal/sass/partials/_forms.scss +60 -5
  172. portal/static/portal/sass/partials/_grids.scss +34 -61
  173. portal/static/portal/sass/partials/_header.scss +28 -20
  174. portal/static/portal/sass/partials/_images.scss +292 -39
  175. portal/static/portal/sass/partials/_popup.scss +18 -15
  176. portal/static/portal/sass/partials/_tables.scss +12 -20
  177. portal/static/portal/sass/partials/_text.scss +6 -10
  178. portal/static/portal/sass/styles.scss +0 -1
  179. portal/static/portal/video/code for life .pdf +0 -0
  180. portal/strings/about.py +5 -0
  181. portal/strings/coding_club.py +9 -0
  182. portal/strings/play.py +6 -5
  183. portal/strings/teach.py +1 -1
  184. portal/strings/teacher_resources.py +2 -8
  185. portal/strings/ten_year_map.py +13 -0
  186. portal/templates/403.html +2 -2
  187. portal/templates/404.html +1 -1
  188. portal/templates/500.html +2 -2
  189. portal/templates/{captcha → django_recaptcha}/includes/js_v2_invisible.html +3 -3
  190. portal/templates/{captcha → django_recaptcha}/widget_v2_invisible.html +2 -2
  191. portal/templates/email.html +4 -2
  192. portal/templates/maintenance.html +34 -0
  193. portal/templates/portal/about.html +94 -62
  194. portal/templates/portal/base.html +176 -152
  195. portal/templates/portal/coding_club.html +100 -0
  196. portal/templates/portal/contribute.html +56 -52
  197. portal/templates/portal/email_invitation_sent.html +1 -1
  198. portal/templates/portal/email_style_template.html +374 -0
  199. portal/templates/portal/email_verification_failed.html +1 -1
  200. portal/templates/portal/email_verification_needed.html +9 -9
  201. portal/templates/portal/form_shapes.html +20 -8
  202. portal/templates/portal/getinvolved.html +6 -6
  203. portal/templates/portal/home.html +35 -10
  204. portal/templates/portal/home_learning.html +19 -19
  205. portal/templates/portal/locked_out.html +0 -1
  206. portal/templates/portal/locked_out_school_student.html +16 -0
  207. portal/templates/portal/login/independent_student.html +31 -15
  208. portal/templates/portal/login/student.html +10 -7
  209. portal/templates/portal/login/student_class_code.html +7 -4
  210. portal/templates/portal/login/teacher.html +34 -17
  211. portal/templates/portal/partials/banner.html +18 -4
  212. portal/templates/portal/partials/benefits.html +1 -1
  213. portal/templates/portal/partials/card_list.html +34 -24
  214. portal/templates/portal/partials/character_list.html +5 -5
  215. portal/templates/portal/partials/cookie_list.html +161 -0
  216. portal/templates/portal/partials/delete_popup.html +18 -0
  217. portal/templates/portal/partials/footer.html +57 -26
  218. portal/templates/portal/partials/header.html +118 -117
  219. portal/templates/portal/partials/hero_card.html +4 -3
  220. portal/templates/portal/partials/info_popup.html +3 -3
  221. portal/templates/portal/partials/invite_admin_teacher.html +23 -0
  222. portal/templates/portal/partials/popup.html +7 -2
  223. portal/templates/portal/partials/register_newsletter_tickbox.html +2 -5
  224. portal/templates/portal/partials/screentime_popup.html +14 -0
  225. portal/templates/portal/partials/service_unavailable_popup.html +17 -0
  226. portal/templates/portal/partials/session_popup.html +19 -0
  227. portal/templates/portal/play/student_dashboard.html +42 -29
  228. portal/templates/portal/play/student_edit_account.html +64 -9
  229. portal/templates/portal/play.html +61 -41
  230. portal/templates/portal/privacy_notice.html +697 -0
  231. portal/templates/portal/register.html +122 -92
  232. portal/templates/portal/reset_password.html +20 -40
  233. portal/templates/portal/reset_password_confirm.html +9 -4
  234. portal/templates/portal/reset_password_email_sent.html +15 -13
  235. portal/templates/portal/teach/base_registering.html +1 -1
  236. portal/templates/portal/teach/class.html +4 -6
  237. portal/templates/portal/teach/dashboard.html +212 -117
  238. portal/templates/portal/teach/invited.html +90 -0
  239. portal/templates/portal/teach/onboarding_classes.html +5 -3
  240. portal/templates/portal/teach/onboarding_print.html +1 -1
  241. portal/templates/portal/teach/onboarding_school.html +26 -139
  242. portal/templates/portal/teach/onboarding_students.html +1 -1
  243. portal/templates/portal/teach/teacher_dismiss_students.html +73 -55
  244. portal/templates/portal/teach/teacher_edit_class.html +168 -11
  245. portal/templates/portal/teach/teacher_edit_student.html +12 -5
  246. portal/templates/portal/teach/teacher_move_all_classes.html +25 -38
  247. portal/templates/portal/teach/teacher_move_students_to_class.html +1 -1
  248. portal/templates/portal/teach.html +61 -42
  249. portal/templates/portal/ten_year_map.html +147 -0
  250. portal/templates/portal/terms.html +191 -42
  251. portal/templates/two_factor/core/login.html +71 -59
  252. portal/templates/two_factor/core/setup.html +58 -49
  253. portal/templates/two_factor/profile/disable.html +1 -1
  254. portal/templates/two_factor/profile/profile.html +35 -17
  255. portal/templatetags/app_tags.py +59 -84
  256. portal/templatetags/card_list_tags.py +0 -4
  257. portal/tests/base_test.py +14 -3
  258. portal/tests/conftest.py +0 -15
  259. portal/tests/migrations/test_migration_make_portaladmin_teacher.py +2 -6
  260. portal/tests/migrations/test_migration_preview_users.py +3 -9
  261. portal/tests/migrations/test_migration_remove_guardian.py +1 -3
  262. portal/tests/migrations/test_migration_use_common_models.py +2 -6
  263. portal/tests/migrations/test_migration_verify_portaladmin.py +1 -3
  264. portal/tests/pageObjects/portal/admin/admin_base_page.py +0 -21
  265. portal/tests/pageObjects/portal/base_page.py +16 -26
  266. portal/tests/pageObjects/portal/email_verification_needed_page.py +3 -2
  267. portal/tests/pageObjects/portal/game_page.py +12 -19
  268. portal/tests/pageObjects/portal/home_page.py +13 -15
  269. portal/tests/pageObjects/portal/independent_login_page.py +13 -17
  270. portal/tests/pageObjects/portal/password_reset_form_page.py +20 -4
  271. portal/tests/pageObjects/portal/password_reset_page.py +25 -0
  272. portal/tests/pageObjects/portal/play/account_page.py +18 -27
  273. portal/tests/pageObjects/portal/play/dashboard_page.py +4 -4
  274. portal/tests/pageObjects/portal/play/join_school_or_club_page.py +8 -10
  275. portal/tests/pageObjects/portal/play/play_base_page.py +5 -3
  276. portal/tests/pageObjects/portal/signup_page.py +28 -59
  277. portal/tests/pageObjects/portal/student_login_class_code.py +6 -9
  278. portal/tests/pageObjects/portal/student_login_page.py +6 -8
  279. portal/tests/pageObjects/portal/teach/add_independent_student_to_class_page.py +3 -3
  280. portal/tests/pageObjects/portal/teach/added_independent_student_to_class_page.py +3 -1
  281. portal/tests/pageObjects/portal/teach/class_page.py +36 -13
  282. portal/tests/pageObjects/portal/teach/dashboard_page.py +43 -84
  283. portal/tests/pageObjects/portal/teach/dismiss_students_page.py +7 -5
  284. portal/tests/pageObjects/portal/teach/edit_student_page.py +10 -8
  285. portal/tests/pageObjects/portal/teach/move_class_page.py +5 -10
  286. portal/tests/pageObjects/portal/teach/move_classes_page.py +4 -2
  287. portal/tests/pageObjects/portal/teach/move_students_disambiguate_page.py +4 -2
  288. portal/tests/pageObjects/portal/teach/move_students_page.py +6 -13
  289. portal/tests/pageObjects/portal/teach/onboarding_classes_page.py +5 -3
  290. portal/tests/pageObjects/portal/teach/onboarding_organisation_page.py +11 -49
  291. portal/tests/pageObjects/portal/teach/onboarding_student_list_page.py +7 -12
  292. portal/tests/pageObjects/portal/teach/onboarding_students_page.py +4 -27
  293. portal/tests/pageObjects/portal/teach/teach_base_page.py +6 -4
  294. portal/tests/pageObjects/portal/teacher_login_page.py +10 -16
  295. portal/tests/selenium_test_case.py +3 -43
  296. portal/tests/snapshots/snap_test_partials.py +11 -165
  297. portal/tests/test_2FA.py +15 -33
  298. portal/tests/test_admin.py +15 -97
  299. portal/tests/test_api.py +212 -73
  300. portal/tests/test_captcha_forms.py +2 -2
  301. portal/tests/test_class.py +374 -24
  302. portal/tests/test_emails.py +83 -20
  303. portal/tests/{test_newsletter_footer.py → test_global_forms.py} +5 -5
  304. portal/tests/test_helper_methods.py +30 -0
  305. portal/tests/test_independent_student.py +255 -144
  306. portal/tests/test_invite_teacher.py +318 -10
  307. portal/tests/test_middleware.py +138 -15
  308. portal/tests/test_organisation.py +78 -262
  309. portal/tests/test_partials.py +0 -88
  310. portal/tests/test_ratelimit.py +218 -36
  311. portal/tests/test_school_student.py +35 -40
  312. portal/tests/test_security.py +12 -31
  313. portal/tests/test_teacher.py +425 -325
  314. portal/tests/test_teacher_student.py +103 -91
  315. portal/tests/test_views.py +900 -76
  316. portal/tests/utils/classes.py +2 -2
  317. portal/tests/utils/messages.py +13 -28
  318. portal/urls.py +237 -164
  319. portal/views/admin.py +0 -332
  320. portal/views/api.py +82 -68
  321. portal/views/cron/__init__.py +1 -0
  322. portal/views/cron/user.py +322 -0
  323. portal/views/dotmailer.py +9 -1
  324. portal/views/email.py +33 -77
  325. portal/views/google_analytics.py +28 -0
  326. portal/views/home.py +126 -97
  327. portal/views/legal.py +13 -0
  328. portal/views/login/independent_student.py +5 -5
  329. portal/views/login/student.py +51 -14
  330. portal/views/login/teacher.py +2 -6
  331. portal/views/organisation.py +20 -189
  332. portal/views/registration.py +97 -17
  333. portal/views/student/edit_account_details.py +99 -72
  334. portal/views/student/play.py +81 -62
  335. portal/views/teacher/dashboard.py +421 -149
  336. portal/views/teacher/teach.py +226 -177
  337. portal/views/two_factor/__init__.py +0 -0
  338. portal/views/two_factor/core.py +28 -0
  339. portal/views/two_factor/form.py +7 -0
  340. portal/views/two_factor/profile.py +11 -0
  341. codeforlife_portal-5.32.2.dist-info/LICENSE.md +0 -577
  342. codeforlife_portal-5.32.2.dist-info/METADATA +0 -38
  343. deploy/permissions.py +0 -2
  344. example_project/manage.py +0 -10
  345. portal/autoconfig.py +0 -140
  346. portal/csp_config.py +0 -60
  347. portal/forms/add_game.py +0 -33
  348. portal/helpers/location.py +0 -121
  349. portal/static/portal/img/kurono_hero.jpg +0 -0
  350. portal/static/portal/img/kurono_landing_hero.png +0 -0
  351. portal/static/portal/img/kurono_logo.svg +0 -1
  352. portal/static/portal/img/kurono_logo_grey_background.svg +0 -1
  353. portal/static/portal/img/kurono_logo_mark.svg +0 -1
  354. portal/static/portal/img/kurono_resources_hero.jpg +0 -0
  355. portal/static/portal/img/kurono_story.png +0 -0
  356. portal/static/portal/img/ocado-swirl.svg +0 -22
  357. portal/static/portal/img/thumbnail_educate_kurono.png +0 -0
  358. portal/static/portal/img/thumbnail_educate_resources_and_progress_tracking.png +0 -0
  359. portal/static/portal/img/thumbnail_kurono_resources.png +0 -0
  360. portal/static/portal/img/thumbnail_play_kurono.png +0 -0
  361. portal/static/portal/img/x_close_video.png +0 -0
  362. portal/static/portal/js/aimmoGame.js +0 -106
  363. portal/static/portal/js/deleteWorkspaces.js +0 -14
  364. portal/static/portal/js/fuzzySchoolLookup.js +0 -46
  365. portal/static/portal/js/lib/jquery-3.5.1.min.js +0 -2
  366. portal/static/portal/js/lib/jquery-ui-1.12.1.min.js +0 -13
  367. portal/static/portal/sass/partials/_videos.scss +0 -10
  368. portal/static/portal/video/aimmo_play_now_background_video.mp4 +0 -0
  369. portal/strings/student_aimmo_dashboard.py +0 -6
  370. portal/templates/portal/admin/aggregated_data.html +0 -35
  371. portal/templates/portal/admin/map.html +0 -70
  372. portal/templates/portal/mouseflow.html +0 -9
  373. portal/templates/portal/partials/aimmo_games_table.html +0 -83
  374. portal/templates/portal/partials/register_over_required_age_tickbox.html +0 -9
  375. portal/templates/portal/play/independent_student_dashboard.html +0 -64
  376. portal/templates/portal/play/student_aimmo_dashboard.html +0 -63
  377. portal/templates/portal/privacy_policy.html +0 -483
  378. portal/templates/portal/reset_password_email.html +0 -9
  379. portal/templates/portal/teach/invite.html +0 -25
  380. portal/templates/portal/teach/teacher_aimmo_dashboard.html +0 -95
  381. portal/templates/portal/teach/teacher_resources.html +0 -68
  382. portal/templatetags/character_list_tags.py +0 -16
  383. portal/tests/pageObjects/portal/kurono_teacher_dashboard_page.py +0 -49
  384. portal/tests/pageObjects/portal/student_password_reset_form_page.py +0 -23
  385. portal/tests/pageObjects/portal/teach/onboarding_revoke_request_page.py +0 -20
  386. portal/tests/pageObjects/portal/teacher_password_reset_form_page.py +0 -23
  387. portal/tests/test_aimmo_dashboards.py +0 -172
  388. portal/tests/test_location.py +0 -217
  389. portal/tests/utils/aimmo_games.py +0 -30
  390. portal/views/aimmo/dashboard.py +0 -119
  391. portal/views/privacy_policy.py +0 -9
  392. portal/views/teacher/teacher_resources.py +0 -42
  393. {portal/views/aimmo → cfl_common}/__init__.py +0 -0
portal/admin.py CHANGED
@@ -1,41 +1,75 @@
1
+ import csv
2
+
1
3
  from common.models import (
2
4
  Class,
3
- EmailVerification,
5
+ DailyActivity,
6
+ DynamicElement,
4
7
  School,
8
+ SchoolTeacherInvitation,
5
9
  Student,
6
10
  Teacher,
11
+ TotalActivity,
7
12
  UserProfile,
8
13
  )
9
14
  from django.contrib import admin
10
- from django.contrib.auth.admin import UserAdmin
15
+ from django.contrib.auth.admin import UserAdmin as _UserAdmin
11
16
  from django.contrib.auth.models import User
17
+ from django.http import HttpResponse
18
+ from import_export.admin import ExportActionMixin
12
19
 
13
- from portal.forms.admin import AdminUserCreationForm, AdminChangeUserPasswordForm
20
+ from portal.forms.admin import (
21
+ AdminChangeUserPasswordForm,
22
+ AdminUserCreationForm,
23
+ )
24
+ from portal.views.api import anonymise
14
25
 
15
26
 
16
- class ClassAdmin(admin.ModelAdmin):
27
+ class ClassAdmin(admin.ModelAdmin, ExportActionMixin):
17
28
  search_fields = [
18
29
  "name",
19
30
  "teacher__new_user__first_name",
20
31
  "teacher__new_user__last_name",
21
32
  "teacher__school__name",
22
33
  ]
23
- list_display = ["__str__", "teacher", "teacher_school"]
24
- raw_id_fields = ["teacher"]
34
+ list_display = [
35
+ "__str__",
36
+ "teacher",
37
+ "teacher_school",
38
+ "number_of_students",
39
+ "id",
40
+ ]
41
+ autocomplete_fields = ["teacher"]
25
42
 
26
43
  def teacher_school(self, obj):
27
44
  return obj.teacher.school
28
45
 
46
+ def number_of_students(self, obj):
47
+ return len(obj.students.all())
48
+
49
+
50
+ class SchoolAdmin(admin.ModelAdmin, ExportActionMixin):
51
+ search_fields = ["name", "country", "county"]
52
+ list_filter = ["county", "country"]
53
+ list_display = [
54
+ "__str__",
55
+ "country",
56
+ "county",
57
+ "number_of_teachers",
58
+ "number_of_classes",
59
+ ]
60
+
61
+ def number_of_teachers(self, obj):
62
+ return len(obj.teacher_school.all())
29
63
 
30
- class SchoolAdmin(admin.ModelAdmin):
31
- search_fields = ["name", "country", "postcode", "town"]
32
- list_filter = ["postcode", "country"]
64
+ def number_of_classes(self, obj):
65
+ return len(obj.classes()) if obj.classes() else 0
33
66
 
34
67
 
35
- class StudentAdmin(admin.ModelAdmin):
68
+ class StudentAdmin(admin.ModelAdmin, ExportActionMixin):
36
69
  search_fields = [
37
70
  "new_user__first_name",
38
71
  "new_user__last_name",
72
+ "new_user__username",
39
73
  "class_field__name",
40
74
  "class_field__teacher__new_user__first_name",
41
75
  "class_field__teacher__new_user__last_name",
@@ -46,9 +80,10 @@ class StudentAdmin(admin.ModelAdmin):
46
80
  "class_field",
47
81
  "class_field_teacher",
48
82
  "class_field_school",
83
+ "new_user",
49
84
  ]
50
- readonly_fields = ["user", "new_user"]
51
- raw_id_fields = ["class_field", "pending_class_request"]
85
+ readonly_fields = ["user", "new_user", "login_id"]
86
+ autocomplete_fields = ["class_field", "pending_class_request"]
52
87
 
53
88
  def class_field_teacher(self, obj):
54
89
  if obj.class_field:
@@ -63,39 +98,114 @@ class StudentAdmin(admin.ModelAdmin):
63
98
  return "Independent"
64
99
 
65
100
 
66
- class TeacherAdmin(admin.ModelAdmin):
67
- search_fields = ["new_user__first_name", "new_user__last_name", "school__name"]
68
- list_display = ["__str__", "school"]
101
+ class TeacherAdmin(admin.ModelAdmin, ExportActionMixin):
102
+ search_fields = [
103
+ "new_user__first_name",
104
+ "new_user__last_name",
105
+ "school__name",
106
+ "new_user__username",
107
+ ]
108
+ list_display = [
109
+ "__str__",
110
+ "school",
111
+ "new_user",
112
+ "number_of_classes",
113
+ "is_admin",
114
+ ]
69
115
  readonly_fields = ["user", "new_user"]
70
- raw_id_fields = ["school", "pending_join_request"]
116
+ autocomplete_fields = ["school", "invited_by"]
71
117
 
118
+ def number_of_classes(self, obj):
119
+ return len(obj.class_teacher.all())
72
120
 
73
- class UserProfileAdmin(admin.ModelAdmin):
121
+
122
+ class UserProfileAdmin(admin.ModelAdmin, ExportActionMixin):
74
123
  search_fields = [
124
+ "id",
75
125
  "user__first_name",
76
126
  "user__last_name",
77
127
  "user__username",
78
128
  "user__date_joined",
79
129
  ]
80
130
  list_filter = ["user__date_joined"]
81
- list_display = ["user", "__str__", "joined_recently"]
131
+ list_display = ["user", "__str__", "is_verified", "type", "id"]
82
132
  readonly_fields = ["user"]
83
133
 
134
+ def type(self, obj):
135
+ if hasattr(obj, "student"):
136
+ if obj.student.class_field is None:
137
+ return "INDEPENDENT"
138
+ return "STUDENT"
139
+ return "TEACHER"
140
+
84
141
 
85
- class EmailVerificationAdmin(admin.ModelAdmin):
142
+ class SchoolTeacherInvitationAdmin(admin.ModelAdmin, ExportActionMixin):
86
143
  search_fields = [
87
- "user__first_name",
88
- "user__last_name",
89
- "user__username",
90
- "user__date_joined",
144
+ "from_teacher__new_user__first_name",
145
+ "from_teacher__new_user__last_name",
146
+ "from_teacher__new_user__email",
147
+ "school__name",
148
+ "invited_teacher_first_name",
149
+ "invited_teacher_last_name",
150
+ "invited_teacher_email",
151
+ "expiry",
152
+ "creation_time",
91
153
  ]
92
- readonly_fields = ["user", "token"]
154
+ readonly_fields = ["token", "school", "from_teacher"]
155
+
156
+
157
+ class DynamicElementAdmin(admin.ModelAdmin, ExportActionMixin):
158
+ def has_delete_permission(self, request, obj=None):
159
+ return False
160
+
161
+
162
+ class DailyActivityAdmin(admin.ModelAdmin, ExportActionMixin):
163
+ search_fields = ["date"]
164
+ list_display = ["__str__", "date"]
165
+
166
+
167
+ class TotalActivityAdmin(admin.ModelAdmin, ExportActionMixin):
168
+ def has_add_permission(self, request):
169
+ return False
170
+
171
+ def has_delete_permission(self, request, obj=None):
172
+ return False
173
+
174
+
175
+ _UserAdmin.list_display += ("date_joined", "id")
176
+ _UserAdmin.list_filter += ("date_joined",)
177
+
178
+
179
+ class UserAdmin(_UserAdmin):
180
+ actions = ["anonymise_user", "export_as_csv"]
181
+ add_form = AdminUserCreationForm
182
+ change_password_form = AdminChangeUserPasswordForm
183
+
184
+ def anonymise_user(self, request, queryset):
185
+ for user in queryset:
186
+ anonymise(user)
187
+
188
+ anonymise_user.short_description = "Anonymise selected users"
189
+
190
+ def export_as_csv(self, request, queryset):
191
+ meta = self.model._meta
192
+ field_names = [
193
+ field.name for field in meta.fields if field.name != "password"
194
+ ]
195
+
196
+ response = HttpResponse(content_type="text/csv")
197
+ response["Content-Disposition"] = "attachment; filename={}.csv".format(
198
+ meta
199
+ )
200
+ writer = csv.writer(response)
201
+
202
+ writer.writerow(field_names)
203
+ for obj in queryset:
204
+ writer.writerow([getattr(obj, field) for field in field_names])
93
205
 
206
+ return response
94
207
 
95
- UserAdmin.list_display += ("date_joined",)
96
- UserAdmin.list_filter += ("date_joined",)
97
- UserAdmin.add_form = AdminUserCreationForm
98
- UserAdmin.change_password_form = AdminChangeUserPasswordForm
208
+ export_as_csv.short_description = "Export selected users data as CSV"
99
209
 
100
210
 
101
211
  admin.site.register(Class, ClassAdmin)
@@ -105,4 +215,7 @@ admin.site.register(School, SchoolAdmin)
105
215
  admin.site.unregister(User)
106
216
  admin.site.register(User, UserAdmin)
107
217
  admin.site.register(UserProfile, UserProfileAdmin)
108
- admin.site.register(EmailVerification, EmailVerificationAdmin)
218
+ admin.site.register(SchoolTeacherInvitation, SchoolTeacherInvitationAdmin)
219
+ admin.site.register(DynamicElement, DynamicElementAdmin)
220
+ admin.site.register(DailyActivity, DailyActivityAdmin)
221
+ admin.site.register(TotalActivity, TotalActivityAdmin)
portal/app_settings.py CHANGED
@@ -1,9 +1,6 @@
1
1
  from django.conf import settings
2
2
 
3
- CONTACT_FORM_EMAILS = getattr(
4
- settings, "PORTAL_CONTACT_FORM_EMAIL", ("codeforlife@ocado.com",)
5
- )
6
-
3
+ CONTACT_FORM_EMAILS = getattr(settings, "PORTAL_CONTACT_FORM_EMAIL", ("codeforlife@ocado.com",))
7
4
 
8
5
  # Private key for Recaptcha
9
6
  RECAPTCHA_PRIVATE_KEY = getattr(settings, "RECAPTCHA_PRIVATE_KEY", None)
@@ -14,6 +11,12 @@ RECAPTCHA_PUBLIC_KEY = getattr(settings, "RECAPTCHA_PUBLIC_KEY", None)
14
11
  DEBUG = getattr(settings, "DEBUG", False)
15
12
 
16
13
  # The permission function for checking if the request is coming from a cron job
17
- IS_CLOUD_SCHEDULER_FUNCTION = getattr(
18
- settings, "IS_CLOUD_SCHEDULER_FUNCTION", lambda _: False
19
- )
14
+ IS_CLOUD_SCHEDULER_FUNCTION = getattr(settings, "IS_CLOUD_SCHEDULER_FUNCTION", lambda _: False)
15
+
16
+ # Half an hour
17
+ SESSION_EXPIRY_TIME = 60 * 30
18
+
19
+ # One hour
20
+ SCREENTIME_WARNING_EXPIRY_TIME = 60 * 60
21
+
22
+ TMP_AUTH_TOKEN = getattr(settings, "TMP_AUTH_TOKEN", "token")
portal/forms/dotmailer.py CHANGED
@@ -3,7 +3,7 @@ from django import forms
3
3
 
4
4
  class NewsletterForm(forms.Form):
5
5
  email = forms.EmailField(
6
- label="Sign up to our newsletter",
6
+ label="Sign up to receive updates about Code for Life games and teaching resources.",
7
7
  label_suffix="",
8
8
  widget=forms.EmailInput(
9
9
  attrs={
@@ -14,15 +14,17 @@ class NewsletterForm(forms.Form):
14
14
  help_text="Enter email address above",
15
15
  )
16
16
 
17
+ age_verification = forms.BooleanField(
18
+ widget=forms.CheckboxInput(), initial=False, required=True
19
+ )
20
+
17
21
 
18
22
  class ConsentForm(forms.Form):
19
23
  email = forms.EmailField(
20
24
  label="Email",
21
25
  label_suffix="",
22
26
  widget=forms.EmailInput(
23
- attrs={
24
- "placeholder": "your.name@yourdomain.com",
25
- }
27
+ attrs={"placeholder": "your.name@yourdomain.com"}
26
28
  ),
27
29
  )
28
30
 
@@ -3,15 +3,24 @@ from django import forms
3
3
 
4
4
  class InviteTeacherForm(forms.Form):
5
5
 
6
- email_regex = "(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
6
+ teacher_first_name = forms.CharField(
7
+ help_text="Enter first name of teacher",
8
+ max_length=100,
9
+ widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "First name of teacher"}),
10
+ )
11
+ teacher_last_name = forms.CharField(
12
+ help_text="Enter last name of teacher",
13
+ max_length=100,
14
+ widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Last name of teacher"}),
15
+ )
16
+ teacher_email = forms.EmailField(
17
+ help_text="Enter email address",
18
+ widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
19
+ )
7
20
 
8
- email = forms.EmailField(
9
- widget=forms.TextInput(
10
- attrs={
11
- "placeholder": "recipient.name@domain.com",
12
- "id": "newsletter_email_field",
13
- "pattern": email_regex,
14
- "type": "email",
15
- }
16
- )
21
+ make_admin_ticked = forms.BooleanField(
22
+ label="Make an administrator of the school",
23
+ widget=forms.CheckboxInput(),
24
+ initial=False,
25
+ required=False,
17
26
  )
@@ -1,107 +1,176 @@
1
+ import re
1
2
  from builtins import object
2
3
 
3
4
  from common.models import School
4
5
  from django import forms
5
6
  from django.core.exceptions import ObjectDoesNotExist
6
- from django.core.validators import EmailValidator
7
7
  from django_countries.widgets import CountrySelectWidget
8
8
 
9
9
 
10
10
  class OrganisationForm(forms.ModelForm):
11
- class Meta(object):
11
+ county = forms.ChoiceField(
12
+ choices=[
13
+ [None, "(select county)"],
14
+ ["Aberdeen City", "Aberdeen City"],
15
+ ["Aberdeenshire", "Aberdeenshire"],
16
+ ["Angus", "Angus"],
17
+ ["Argyll and Bute", "Argyll and Bute"],
18
+ ["Bedfordshire", "Bedfordshire"],
19
+ ["Belfast", "Belfast"],
20
+ ["Belfast Greater", "Belfast Greater"],
21
+ ["Berkshire", "Berkshire"],
22
+ ["Blaenau Gwent", "Blaenau Gwent"],
23
+ ["Bridgend", "Bridgend"],
24
+ ["Buckinghamshire", "Buckinghamshire"],
25
+ ["Caerphilly", "Caerphilly"],
26
+ ["Cambridgeshire", "Cambridgeshire"],
27
+ ["Cardiff", "Cardiff"],
28
+ ["Carmarthenshire", "Carmarthenshire"],
29
+ ["Ceredigion", "Ceredigion"],
30
+ ["Channel Islands", "Channel Islands"],
31
+ ["Cheshire", "Cheshire"],
32
+ ["City of Edinburgh", "City of Edinburgh"],
33
+ ["Clackmannanshire", "Clackmannanshire"],
34
+ ["Conwy", "Conwy"],
35
+ ["Cornwall", "Cornwall"],
36
+ ["County Antrim", "County Antrim"],
37
+ ["County Armagh", "County Armagh"],
38
+ ["County Down", "County Down"],
39
+ ["County Fermanagh", "County Fermanagh"],
40
+ ["County Londonderry", "County Londonderry"],
41
+ ["County Tyrone", "County Tyrone"],
42
+ ["County of Bristol", "County of Bristol"],
43
+ ["Cumbria", "Cumbria"],
44
+ ["Denbighshire", "Denbighshire"],
45
+ ["Derbyshire", "Derbyshire"],
46
+ ["Devon", "Devon"],
47
+ ["Dorset", "Dorset"],
48
+ ["Dumfries and Galloway", "Dumfries and Galloway"],
49
+ ["Dunbartonshire", "Dunbartonshire"],
50
+ ["Dundee City", "Dundee City"],
51
+ ["Durham", "Durham"],
52
+ ["East Ayrshire", "East Ayrshire"],
53
+ ["East Dunbartonshire", "East Dunbartonshire"],
54
+ ["East Lothian", "East Lothian"],
55
+ ["East Renfrewshire", "East Renfrewshire"],
56
+ ["East Riding of Yorkshire", "East Riding of Yorkshire"],
57
+ ["East Sussex", "East Sussex"],
58
+ ["Essex", "Essex"],
59
+ ["Falkirk", "Falkirk"],
60
+ ["Fife", "Fife"],
61
+ ["Flintshire", "Flintshire"],
62
+ ["Glasgow City", "Glasgow City"],
63
+ ["Gloucestershire", "Gloucestershire"],
64
+ ["Greater London", "Greater London"],
65
+ ["Greater Manchester", "Greater Manchester"],
66
+ ["Guernsey Channel Islands", "Guernsey Channel Islands"],
67
+ ["Gwynedd", "Gwynedd"],
68
+ ["Hampshire", "Hampshire"],
69
+ ["Hereford and Worcester", "Hereford and Worcester"],
70
+ ["Herefordshire", "Herefordshire"],
71
+ ["Hertfordshire", "Hertfordshire"],
72
+ ["Highland", "Highland"],
73
+ ["Inverclyde", "Inverclyde"],
74
+ ["Inverness", "Inverness"],
75
+ ["Isle of Anglesey", "Isle of Anglesey"],
76
+ ["Isle of Barra", "Isle of Barra"],
77
+ ["Isle of Man", "Isle of Man"],
78
+ ["Isle of Wight", "Isle of Wight"],
79
+ ["Jersey Channel Islands", "Jersey Channel Islands"],
80
+ ["Kent", "Kent"],
81
+ ["Lancashire", "Lancashire"],
82
+ ["Leicestershire", "Leicestershire"],
83
+ ["Lincolnshire", "Lincolnshire"],
84
+ ["Merseyside", "Merseyside"],
85
+ ["Merthyr Tydfil", "Merthyr Tydfil"],
86
+ ["Midlothian", "Midlothian"],
87
+ ["Monmouthshire", "Monmouthshire"],
88
+ ["Moray", "Moray"],
89
+ ["Neath Port Talbot", "Neath Port Talbot"],
90
+ ["Newport", "Newport"],
91
+ ["Norfolk", "Norfolk"],
92
+ ["North Ayrshire", "North Ayrshire"],
93
+ ["North Lanarkshire", "North Lanarkshire"],
94
+ ["North Yorkshire", "North Yorkshire"],
95
+ ["Northamptonshire", "Northamptonshire"],
96
+ ["Northumberland", "Northumberland"],
97
+ ["Nottinghamshire", "Nottinghamshire"],
98
+ ["Orkney", "Orkney"],
99
+ ["Orkney Islands", "Orkney Islands"],
100
+ ["Oxfordshire", "Oxfordshire"],
101
+ ["Pembrokeshire", "Pembrokeshire"],
102
+ ["Perth and Kinross", "Perth and Kinross"],
103
+ ["Powys", "Powys"],
104
+ ["Renfrewshire", "Renfrewshire"],
105
+ ["Rhondda Cynon Taff", "Rhondda Cynon Taff"],
106
+ ["Rutland", "Rutland"],
107
+ ["Scottish Borders", "Scottish Borders"],
108
+ ["Shetland Islands", "Shetland Islands"],
109
+ ["Shropshire", "Shropshire"],
110
+ ["Somerset", "Somerset"],
111
+ ["South Ayrshire", "South Ayrshire"],
112
+ ["South Lanarkshire", "South Lanarkshire"],
113
+ ["South Yorkshire", "South Yorkshire"],
114
+ ["Staffordshire", "Staffordshire"],
115
+ ["Stirling", "Stirling"],
116
+ ["Suffolk", "Suffolk"],
117
+ ["Surrey", "Surrey"],
118
+ ["Swansea", "Swansea"],
119
+ ["Torfaen", "Torfaen"],
120
+ ["Tyne and Wear", "Tyne and Wear"],
121
+ ["Vale of Glamorgan", "Vale of Glamorgan"],
122
+ ["Warwickshire", "Warwickshire"],
123
+ ["West Dunbart", "West Dunbart"],
124
+ ["West Lothian", "West Lothian"],
125
+ ["West Midlands", "West Midlands"],
126
+ ["West Sussex", "West Sussex"],
127
+ ["West Yorkshire", "West Yorkshire"],
128
+ ["Western Isles", "Western Isles"],
129
+ ["Wiltshire", "Wiltshire"],
130
+ ["Worcestershire", "Worcestershire"],
131
+ ["Wrexham", "Wrexham"],
132
+ ],
133
+ required=False,
134
+ help_text="County (optional)",
135
+ )
12
136
 
137
+ class Meta(object):
13
138
  model = School
14
- fields = ["name", "postcode", "country"]
139
+ fields = ["name", "country", "county"]
15
140
  widgets = {
16
- "name": forms.TextInput(
17
- attrs={
18
- "autocomplete": "off",
19
- "placeholder": "Name of school or club",
20
- },
21
- ),
22
- "postcode": forms.TextInput(
23
- attrs={"autocomplete": "off", "placeholder": "Postcode / Zipcode"}
24
- ),
141
+ "name": forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Name of school or club"}),
25
142
  "country": CountrySelectWidget(layout="{widget}"),
26
143
  }
27
144
  help_texts = {
28
145
  "name": "Name of school or club",
29
- "postcode": "Postcode / Zipcode",
30
- "country": "Country",
146
+ "country": "Country (optional)",
31
147
  }
32
148
 
33
149
  def __init__(self, *args, **kwargs):
34
150
  self.user = kwargs.pop("user", None)
35
151
  self.current_school = kwargs.pop("current_school", None)
36
152
  super(OrganisationForm, self).__init__(*args, **kwargs)
37
- self.fields["postcode"].strip = False
38
153
 
39
154
  def clean(self):
40
155
  name = self.cleaned_data.get("name", None)
41
- postcode = self.cleaned_data.get("postcode", None)
42
156
 
43
- if name and postcode:
157
+ if name:
44
158
  try:
45
- school = School.objects.get(name=name, postcode=postcode)
159
+ school = School.objects.get(name=name)
46
160
  except ObjectDoesNotExist:
47
161
  return self.cleaned_data
48
162
 
49
163
  if not self.current_school or self.current_school.id != school.id:
50
- raise forms.ValidationError(
51
- "There is already a school or club registered with that name and postcode"
52
- )
164
+ raise forms.ValidationError("There is already a school or club registered with that name")
53
165
 
54
166
  return self.cleaned_data
55
167
 
56
168
  def clean_name(self):
57
169
  name = self.cleaned_data.get("name", None)
58
- validator = EmailValidator()
59
170
 
60
- if name:
61
- try:
62
- validator(name)
63
- is_email = True
64
- except forms.ValidationError:
65
- is_email = False
66
-
67
- if is_email:
68
- raise forms.ValidationError(
69
- "Please make sure your organisation name is valid"
70
- )
171
+ if re.match(re.compile("^[\w\.' ]+$"), name) is None:
172
+ raise forms.ValidationError(
173
+ "School names cannot contain special characters except full stops and apostrophes."
174
+ )
71
175
 
72
176
  return name
73
-
74
- def clean_postcode(self):
75
- postcode = self.cleaned_data.get("postcode", None)
76
-
77
- if postcode:
78
- if (
79
- len(postcode.replace(" ", "")) > 10
80
- or len(postcode.replace(" ", "")) == 0
81
- ):
82
- raise forms.ValidationError("Please enter a valid postcode or ZIP code")
83
-
84
- return postcode
85
-
86
-
87
- class OrganisationJoinForm(forms.Form):
88
- fuzzy_name = forms.CharField(
89
- widget=forms.TextInput(
90
- attrs={"placeholder": "School or club by name or postcode"}
91
- ),
92
- help_text="Enter school or club by name or postcode",
93
- )
94
-
95
- # Note: the reason this is a CharField rather than a ChoiceField is to avoid having to
96
- # provide choices which was problematic given that the options are dynamically generated.
97
- chosen_org = forms.CharField(
98
- widget=forms.Select(), help_text="Select school or club"
99
- )
100
-
101
- def clean_chosen_org(self):
102
- chosen_org = self.cleaned_data.get("chosen_org", None)
103
-
104
- if chosen_org and not School.objects.filter(id=int(chosen_org)).exists():
105
- raise forms.ValidationError("That school or club was not recognised")
106
-
107
- return chosen_org