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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. cfl_common/common/__init__.py +1 -0
  2. cfl_common/common/app_settings.py +66 -0
  3. cfl_common/common/apps.py +6 -0
  4. cfl_common/common/context_processors.py +9 -0
  5. cfl_common/common/csp_config.py +85 -0
  6. cfl_common/common/helpers/__init__.py +0 -0
  7. cfl_common/common/helpers/data_migration_loader.py +42 -0
  8. cfl_common/common/helpers/emails.py +393 -0
  9. cfl_common/common/helpers/generators.py +52 -0
  10. cfl_common/common/helpers/organisation.py +10 -0
  11. cfl_common/common/mail.py +201 -0
  12. cfl_common/common/migrations/0001_initial.py +240 -0
  13. cfl_common/common/migrations/0002_emailverification.py +55 -0
  14. cfl_common/common/migrations/0003_aimmocharacter.py +31 -0
  15. cfl_common/common/migrations/0004_add_aimmocharacters.py +17 -0
  16. cfl_common/common/migrations/0005_add_worksheets.py +8 -0
  17. cfl_common/common/migrations/0006_update_aimmo_character_image_path.py +17 -0
  18. cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +8 -0
  19. cfl_common/common/migrations/0008_unlock_worksheet_3.py +11 -0
  20. cfl_common/common/migrations/0009_add_blocked_time_to_teacher_and_student.py +24 -0
  21. cfl_common/common/migrations/0010_remove_teacher_title.py +18 -0
  22. cfl_common/common/migrations/0011_student_login_id.py +18 -0
  23. cfl_common/common/migrations/0012_usersession.py +39 -0
  24. cfl_common/common/migrations/0013_class_school.py +42 -0
  25. cfl_common/common/migrations/0014_login_type.py +29 -0
  26. cfl_common/common/migrations/0015_dailyactivity.py +31 -0
  27. cfl_common/common/migrations/0016_joinreleasestudent.py +42 -0
  28. cfl_common/common/migrations/0017_copy_email_to_username.py +18 -0
  29. cfl_common/common/migrations/0018_update_aimmo_character_image_path.py +15 -0
  30. cfl_common/common/migrations/0019_aimmocharacter_alt.py +16 -0
  31. cfl_common/common/migrations/0020_class_is_active_and_null_access_code.py +23 -0
  32. cfl_common/common/migrations/0021_school_is_active.py +28 -0
  33. cfl_common/common/migrations/0022_school_cleanup.py +29 -0
  34. cfl_common/common/migrations/0023_userprofile_aimmo_badges.py +22 -0
  35. cfl_common/common/migrations/0024_teacher_invited_by.py +25 -0
  36. cfl_common/common/migrations/0025_schoolteacherinvitation.py +47 -0
  37. cfl_common/common/migrations/0026_teacher_remove_join_request.py +22 -0
  38. cfl_common/common/migrations/0027_class_created_by.py +25 -0
  39. cfl_common/common/migrations/0028_coding_club_downloads.py +23 -0
  40. cfl_common/common/migrations/0029_dynamicelement.py +22 -0
  41. cfl_common/common/migrations/0030_add_maintenance_banner.py +25 -0
  42. cfl_common/common/migrations/0031_improve_admin_panel.py +56 -0
  43. cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py +18 -0
  44. cfl_common/common/migrations/0033_password_reset_tracking_fields.py +23 -0
  45. cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +18 -0
  46. cfl_common/common/migrations/0035_rename_lockout_fields.py +27 -0
  47. cfl_common/common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +17 -0
  48. cfl_common/common/migrations/0037_migrate_email_verification.py +21 -0
  49. cfl_common/common/migrations/0038_delete_emailverification.py +16 -0
  50. cfl_common/common/migrations/0039_copy_email_to_username.py +18 -0
  51. cfl_common/common/migrations/0040_school_county.py +18 -0
  52. cfl_common/common/migrations/0041_populate_gb_counties.py +27 -0
  53. cfl_common/common/migrations/0042_totalactivity.py +25 -0
  54. cfl_common/common/migrations/0043_add_total_activity.py +30 -0
  55. cfl_common/common/migrations/0044_update_activity_models.py +33 -0
  56. cfl_common/common/migrations/0045_otp.py +23 -0
  57. cfl_common/common/migrations/0046_alter_school_country.py +19 -0
  58. cfl_common/common/migrations/0047_delete_school_postcode.py +16 -0
  59. cfl_common/common/migrations/0048_unique_school_names.py +42 -0
  60. cfl_common/common/migrations/0049_anonymise_orphan_users.py +29 -0
  61. cfl_common/common/migrations/0050_anonymise_orphan_schools.py +30 -0
  62. cfl_common/common/migrations/0051_verify_returning_users.py +26 -0
  63. cfl_common/common/migrations/0052_add_cse_fields.py +68 -0
  64. cfl_common/common/migrations/0053_clean_class_data.py +24 -0
  65. cfl_common/common/migrations/0054_delete_aimmo_models.py +20 -0
  66. cfl_common/common/migrations/0055_alter_schoolteacherinvitation_token.py +18 -0
  67. cfl_common/common/migrations/0056_set_non_school_teachers_as_non_admins.py +25 -0
  68. cfl_common/common/migrations/0057_teacher_teacher__is_admin.py +19 -0
  69. cfl_common/common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
  70. cfl_common/common/migrations/__init__.py +0 -0
  71. cfl_common/common/models.py +557 -0
  72. cfl_common/common/permissions.py +84 -0
  73. cfl_common/common/tests/__init__.py +0 -0
  74. cfl_common/common/tests/test_migration_anonymise_orphan_schools.py +30 -0
  75. cfl_common/common/tests/test_migration_anonymise_orphan_users.py +30 -0
  76. cfl_common/common/tests/test_migration_blocked_time.py +15 -0
  77. cfl_common/common/tests/test_migration_remove_teacher_title.py +13 -0
  78. cfl_common/common/tests/test_migration_unique_school_names.py +33 -0
  79. cfl_common/common/tests/test_migration_verify_returning_users.py +59 -0
  80. cfl_common/common/tests/test_models.py +87 -0
  81. cfl_common/common/tests/utils/__init__.py +0 -0
  82. cfl_common/common/tests/utils/classes.py +38 -0
  83. cfl_common/common/tests/utils/email.py +67 -0
  84. cfl_common/common/tests/utils/organisation.py +41 -0
  85. cfl_common/common/tests/utils/student.py +123 -0
  86. cfl_common/common/tests/utils/teacher.py +73 -0
  87. cfl_common/common/tests/utils/user.py +27 -0
  88. cfl_common/common/utils.py +56 -0
  89. cfl_common/setup.py +61 -0
  90. codeforlife_portal-8.9.9.dist-info/METADATA +226 -0
  91. {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/RECORD +339 -241
  92. {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/WHEEL +1 -1
  93. codeforlife_portal-8.9.9.dist-info/licenses/LICENSE.md +3 -0
  94. {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/top_level.txt +1 -0
  95. deploy/middleware/maintenance.py +25 -0
  96. deploy/middleware/screentime_warning.py +29 -0
  97. deploy/middleware/security.py +5 -6
  98. deploy/middleware/session_timeout.py +4 -2
  99. deploy/middleware/tmp_basic_auth.py +41 -0
  100. example_project/portal_test_settings.py +239 -0
  101. example_project/settings.py +156 -17
  102. example_project/urls.py +5 -6
  103. portal/__init__.py +1 -1
  104. portal/admin.py +142 -29
  105. portal/app_settings.py +8 -7
  106. portal/forms/dotmailer.py +6 -4
  107. portal/forms/invite_teacher.py +19 -10
  108. portal/forms/organisation.py +137 -68
  109. portal/forms/play.py +53 -98
  110. portal/forms/registration.py +70 -164
  111. portal/forms/teach.py +147 -121
  112. portal/handlers.py +1 -2
  113. portal/helpers/decorators.py +30 -10
  114. portal/helpers/password.py +86 -47
  115. portal/helpers/ratelimit.py +32 -15
  116. portal/helpers/regexes.py +5 -0
  117. portal/helpers/request_handlers.py +10 -0
  118. portal/migrations/0044_auto_20150430_0959.py +6 -2
  119. portal/mixins/__init__.py +1 -0
  120. portal/mixins/cron_mixin.py +12 -0
  121. portal/permissions/__init__.py +1 -0
  122. portal/permissions/is_cron_request_from_google.py +14 -0
  123. portal/static/portal/img/10_years_anniversary.png +0 -0
  124. portal/static/portal/img/RR_logo_grass_background.png +0 -0
  125. portal/static/portal/img/coding_club_hero.jpg +0 -0
  126. portal/static/portal/img/coding_club_python_pack.png +0 -0
  127. portal/static/portal/img/facebook.png +0 -0
  128. portal/static/portal/img/gitbook.png +0 -0
  129. portal/static/portal/img/howe_dell_1.png +0 -0
  130. portal/static/portal/img/howe_dell_2.png +0 -0
  131. portal/static/portal/img/howe_dell_3.png +0 -0
  132. portal/static/portal/img/logo_cfl.png +0 -0
  133. portal/static/portal/img/logo_cfl_powered.svg +35 -0
  134. portal/static/portal/img/logo_cfl_reminder_cards.jpg +0 -0
  135. portal/static/portal/img/logo_ocado_group.png +0 -0
  136. portal/static/portal/img/logo_python_den.svg +21 -0
  137. portal/static/portal/img/long_europe_map.png +0 -0
  138. portal/static/portal/img/python_den.png +0 -0
  139. portal/static/portal/img/python_den_banner.svg +26 -0
  140. portal/static/portal/img/rapid_router_landing_hero.png +0 -0
  141. portal/static/portal/img/rr_advanced.png +0 -0
  142. portal/static/portal/img/ten_year_map_pin.svg +1 -0
  143. portal/static/portal/img/thumbnail_educate_rapid_router.png +0 -0
  144. portal/static/portal/img/thumbnail_educate_resources.png +0 -0
  145. portal/static/portal/img/thumbnail_play_rapid_router.png +0 -0
  146. portal/static/portal/img/thumbnail_python_den.png +0 -0
  147. portal/static/portal/img/twitter.png +0 -0
  148. portal/static/portal/js/carouselCards.js +25 -0
  149. portal/static/portal/js/common.js +96 -1
  150. portal/static/portal/js/independentLogin.js +16 -0
  151. portal/static/portal/js/independentRegistration.js +86 -0
  152. portal/static/portal/js/levelControl.js +77 -0
  153. portal/static/portal/js/lib/jquery.min.js +2 -0
  154. portal/static/portal/js/organisation_manage.js +142 -14
  155. portal/static/portal/js/passwordStrength.js +154 -64
  156. portal/static/portal/js/resetPassword.js +23 -0
  157. portal/static/portal/js/riveted.min.js +238 -239
  158. portal/static/portal/js/school.js +13 -0
  159. portal/static/portal/js/studentLogin.js +16 -0
  160. portal/static/portal/js/teacherEditStudent.js +23 -0
  161. portal/static/portal/js/teacherLogin.js +16 -0
  162. portal/static/portal/js/tenYearMap.js +14 -0
  163. portal/static/portal/sass/colorbox.scss +0 -1
  164. portal/static/portal/sass/modules/_colours.scss +1 -0
  165. portal/static/portal/sass/modules/_levels.scss +1 -1
  166. portal/static/portal/sass/modules/_mixins.scss +21 -0
  167. portal/static/portal/sass/partials/_banners.scss +4 -177
  168. portal/static/portal/sass/partials/_buttons.scss +12 -15
  169. portal/static/portal/sass/partials/_carousel.scss +129 -0
  170. portal/static/portal/sass/partials/_footer.scss +21 -22
  171. portal/static/portal/sass/partials/_forms.scss +60 -5
  172. portal/static/portal/sass/partials/_grids.scss +34 -61
  173. portal/static/portal/sass/partials/_header.scss +28 -20
  174. portal/static/portal/sass/partials/_images.scss +292 -39
  175. portal/static/portal/sass/partials/_popup.scss +18 -15
  176. portal/static/portal/sass/partials/_tables.scss +12 -20
  177. portal/static/portal/sass/partials/_text.scss +6 -10
  178. portal/static/portal/sass/styles.scss +0 -1
  179. portal/static/portal/video/code for life .pdf +0 -0
  180. portal/strings/about.py +5 -0
  181. portal/strings/coding_club.py +9 -0
  182. portal/strings/play.py +6 -5
  183. portal/strings/teach.py +1 -1
  184. portal/strings/teacher_resources.py +2 -8
  185. portal/strings/ten_year_map.py +13 -0
  186. portal/templates/403.html +2 -2
  187. portal/templates/404.html +1 -1
  188. portal/templates/500.html +2 -2
  189. portal/templates/{captcha → django_recaptcha}/includes/js_v2_invisible.html +3 -3
  190. portal/templates/{captcha → django_recaptcha}/widget_v2_invisible.html +2 -2
  191. portal/templates/email.html +4 -2
  192. portal/templates/maintenance.html +34 -0
  193. portal/templates/portal/about.html +94 -62
  194. portal/templates/portal/base.html +176 -152
  195. portal/templates/portal/coding_club.html +100 -0
  196. portal/templates/portal/contribute.html +56 -52
  197. portal/templates/portal/email_invitation_sent.html +1 -1
  198. portal/templates/portal/email_style_template.html +374 -0
  199. portal/templates/portal/email_verification_failed.html +1 -1
  200. portal/templates/portal/email_verification_needed.html +9 -9
  201. portal/templates/portal/form_shapes.html +20 -8
  202. portal/templates/portal/getinvolved.html +6 -6
  203. portal/templates/portal/home.html +35 -10
  204. portal/templates/portal/home_learning.html +19 -19
  205. portal/templates/portal/locked_out.html +0 -1
  206. portal/templates/portal/locked_out_school_student.html +16 -0
  207. portal/templates/portal/login/independent_student.html +31 -15
  208. portal/templates/portal/login/student.html +10 -7
  209. portal/templates/portal/login/student_class_code.html +7 -4
  210. portal/templates/portal/login/teacher.html +34 -17
  211. portal/templates/portal/partials/banner.html +18 -4
  212. portal/templates/portal/partials/benefits.html +1 -1
  213. portal/templates/portal/partials/card_list.html +34 -24
  214. portal/templates/portal/partials/character_list.html +5 -5
  215. portal/templates/portal/partials/cookie_list.html +161 -0
  216. portal/templates/portal/partials/delete_popup.html +18 -0
  217. portal/templates/portal/partials/footer.html +57 -26
  218. portal/templates/portal/partials/header.html +118 -117
  219. portal/templates/portal/partials/hero_card.html +4 -3
  220. portal/templates/portal/partials/info_popup.html +3 -3
  221. portal/templates/portal/partials/invite_admin_teacher.html +23 -0
  222. portal/templates/portal/partials/popup.html +7 -2
  223. portal/templates/portal/partials/register_newsletter_tickbox.html +2 -5
  224. portal/templates/portal/partials/screentime_popup.html +14 -0
  225. portal/templates/portal/partials/service_unavailable_popup.html +17 -0
  226. portal/templates/portal/partials/session_popup.html +19 -0
  227. portal/templates/portal/play/student_dashboard.html +42 -29
  228. portal/templates/portal/play/student_edit_account.html +64 -9
  229. portal/templates/portal/play.html +61 -41
  230. portal/templates/portal/privacy_notice.html +697 -0
  231. portal/templates/portal/register.html +122 -92
  232. portal/templates/portal/reset_password.html +20 -40
  233. portal/templates/portal/reset_password_confirm.html +9 -4
  234. portal/templates/portal/reset_password_email_sent.html +15 -13
  235. portal/templates/portal/teach/base_registering.html +1 -1
  236. portal/templates/portal/teach/class.html +4 -6
  237. portal/templates/portal/teach/dashboard.html +212 -117
  238. portal/templates/portal/teach/invited.html +90 -0
  239. portal/templates/portal/teach/onboarding_classes.html +5 -3
  240. portal/templates/portal/teach/onboarding_print.html +1 -1
  241. portal/templates/portal/teach/onboarding_school.html +26 -139
  242. portal/templates/portal/teach/onboarding_students.html +1 -1
  243. portal/templates/portal/teach/teacher_dismiss_students.html +73 -55
  244. portal/templates/portal/teach/teacher_edit_class.html +168 -11
  245. portal/templates/portal/teach/teacher_edit_student.html +12 -5
  246. portal/templates/portal/teach/teacher_move_all_classes.html +25 -38
  247. portal/templates/portal/teach/teacher_move_students_to_class.html +1 -1
  248. portal/templates/portal/teach.html +61 -42
  249. portal/templates/portal/ten_year_map.html +147 -0
  250. portal/templates/portal/terms.html +191 -42
  251. portal/templates/two_factor/core/login.html +71 -59
  252. portal/templates/two_factor/core/setup.html +58 -49
  253. portal/templates/two_factor/profile/disable.html +1 -1
  254. portal/templates/two_factor/profile/profile.html +35 -17
  255. portal/templatetags/app_tags.py +59 -84
  256. portal/templatetags/card_list_tags.py +0 -4
  257. portal/tests/base_test.py +14 -3
  258. portal/tests/conftest.py +0 -15
  259. portal/tests/migrations/test_migration_make_portaladmin_teacher.py +2 -6
  260. portal/tests/migrations/test_migration_preview_users.py +3 -9
  261. portal/tests/migrations/test_migration_remove_guardian.py +1 -3
  262. portal/tests/migrations/test_migration_use_common_models.py +2 -6
  263. portal/tests/migrations/test_migration_verify_portaladmin.py +1 -3
  264. portal/tests/pageObjects/portal/admin/admin_base_page.py +0 -21
  265. portal/tests/pageObjects/portal/base_page.py +16 -26
  266. portal/tests/pageObjects/portal/email_verification_needed_page.py +3 -2
  267. portal/tests/pageObjects/portal/game_page.py +12 -19
  268. portal/tests/pageObjects/portal/home_page.py +13 -15
  269. portal/tests/pageObjects/portal/independent_login_page.py +13 -17
  270. portal/tests/pageObjects/portal/password_reset_form_page.py +20 -4
  271. portal/tests/pageObjects/portal/password_reset_page.py +25 -0
  272. portal/tests/pageObjects/portal/play/account_page.py +18 -27
  273. portal/tests/pageObjects/portal/play/dashboard_page.py +4 -4
  274. portal/tests/pageObjects/portal/play/join_school_or_club_page.py +8 -10
  275. portal/tests/pageObjects/portal/play/play_base_page.py +5 -3
  276. portal/tests/pageObjects/portal/signup_page.py +28 -59
  277. portal/tests/pageObjects/portal/student_login_class_code.py +6 -9
  278. portal/tests/pageObjects/portal/student_login_page.py +6 -8
  279. portal/tests/pageObjects/portal/teach/add_independent_student_to_class_page.py +3 -3
  280. portal/tests/pageObjects/portal/teach/added_independent_student_to_class_page.py +3 -1
  281. portal/tests/pageObjects/portal/teach/class_page.py +36 -13
  282. portal/tests/pageObjects/portal/teach/dashboard_page.py +43 -84
  283. portal/tests/pageObjects/portal/teach/dismiss_students_page.py +7 -5
  284. portal/tests/pageObjects/portal/teach/edit_student_page.py +10 -8
  285. portal/tests/pageObjects/portal/teach/move_class_page.py +5 -10
  286. portal/tests/pageObjects/portal/teach/move_classes_page.py +4 -2
  287. portal/tests/pageObjects/portal/teach/move_students_disambiguate_page.py +4 -2
  288. portal/tests/pageObjects/portal/teach/move_students_page.py +6 -13
  289. portal/tests/pageObjects/portal/teach/onboarding_classes_page.py +5 -3
  290. portal/tests/pageObjects/portal/teach/onboarding_organisation_page.py +11 -49
  291. portal/tests/pageObjects/portal/teach/onboarding_student_list_page.py +7 -12
  292. portal/tests/pageObjects/portal/teach/onboarding_students_page.py +4 -27
  293. portal/tests/pageObjects/portal/teach/teach_base_page.py +6 -4
  294. portal/tests/pageObjects/portal/teacher_login_page.py +10 -16
  295. portal/tests/selenium_test_case.py +3 -43
  296. portal/tests/snapshots/snap_test_partials.py +11 -165
  297. portal/tests/test_2FA.py +15 -33
  298. portal/tests/test_admin.py +15 -97
  299. portal/tests/test_api.py +212 -91
  300. portal/tests/test_captcha_forms.py +2 -2
  301. portal/tests/test_class.py +374 -24
  302. portal/tests/test_emails.py +83 -20
  303. portal/tests/{test_newsletter_footer.py → test_global_forms.py} +5 -5
  304. portal/tests/test_helper_methods.py +30 -0
  305. portal/tests/test_independent_student.py +255 -144
  306. portal/tests/test_invite_teacher.py +318 -10
  307. portal/tests/test_middleware.py +96 -9
  308. portal/tests/test_organisation.py +78 -262
  309. portal/tests/test_partials.py +0 -88
  310. portal/tests/test_ratelimit.py +218 -36
  311. portal/tests/test_school_student.py +35 -40
  312. portal/tests/test_security.py +12 -31
  313. portal/tests/test_teacher.py +425 -325
  314. portal/tests/test_teacher_student.py +103 -91
  315. portal/tests/test_views.py +900 -76
  316. portal/tests/utils/classes.py +2 -2
  317. portal/tests/utils/messages.py +13 -28
  318. portal/urls.py +235 -166
  319. portal/views/admin.py +0 -332
  320. portal/views/api.py +82 -48
  321. portal/views/cron/__init__.py +1 -0
  322. portal/views/cron/user.py +322 -0
  323. portal/views/dotmailer.py +9 -1
  324. portal/views/email.py +33 -77
  325. portal/views/google_analytics.py +28 -0
  326. portal/views/home.py +126 -97
  327. portal/views/legal.py +13 -0
  328. portal/views/login/independent_student.py +5 -5
  329. portal/views/login/student.py +51 -14
  330. portal/views/login/teacher.py +2 -6
  331. portal/views/organisation.py +20 -189
  332. portal/views/registration.py +97 -17
  333. portal/views/student/edit_account_details.py +99 -72
  334. portal/views/student/play.py +81 -62
  335. portal/views/teacher/dashboard.py +421 -149
  336. portal/views/teacher/teach.py +226 -177
  337. portal/views/two_factor/core.py +22 -19
  338. portal/views/two_factor/profile.py +2 -2
  339. codeforlife_portal-5.33.5.dist-info/LICENSE.md +0 -577
  340. codeforlife_portal-5.33.5.dist-info/METADATA +0 -38
  341. deploy/permissions.py +0 -2
  342. example_project/manage.py +0 -10
  343. portal/autoconfig.py +0 -141
  344. portal/csp_config.py +0 -60
  345. portal/forms/add_game.py +0 -33
  346. portal/helpers/location.py +0 -121
  347. portal/static/portal/img/kurono_hero.jpg +0 -0
  348. portal/static/portal/img/kurono_landing_hero.png +0 -0
  349. portal/static/portal/img/kurono_logo.svg +0 -1
  350. portal/static/portal/img/kurono_logo_grey_background.svg +0 -1
  351. portal/static/portal/img/kurono_logo_mark.svg +0 -1
  352. portal/static/portal/img/kurono_resources_hero.jpg +0 -0
  353. portal/static/portal/img/kurono_story.png +0 -0
  354. portal/static/portal/img/ocado-swirl.svg +0 -22
  355. portal/static/portal/img/thumbnail_educate_kurono.png +0 -0
  356. portal/static/portal/img/thumbnail_educate_resources_and_progress_tracking.png +0 -0
  357. portal/static/portal/img/thumbnail_kurono_resources.png +0 -0
  358. portal/static/portal/img/thumbnail_play_kurono.png +0 -0
  359. portal/static/portal/img/x_close_video.png +0 -0
  360. portal/static/portal/js/aimmoGame.js +0 -106
  361. portal/static/portal/js/deleteWorkspaces.js +0 -14
  362. portal/static/portal/js/fuzzySchoolLookup.js +0 -46
  363. portal/static/portal/js/lib/jquery-3.5.1.min.js +0 -2
  364. portal/static/portal/js/lib/jquery-ui-1.12.1.min.js +0 -13
  365. portal/static/portal/sass/partials/_videos.scss +0 -10
  366. portal/static/portal/video/aimmo_play_now_background_video.mp4 +0 -0
  367. portal/strings/student_aimmo_dashboard.py +0 -6
  368. portal/templates/portal/admin/aggregated_data.html +0 -35
  369. portal/templates/portal/admin/map.html +0 -70
  370. portal/templates/portal/mouseflow.html +0 -9
  371. portal/templates/portal/partials/aimmo_games_table.html +0 -83
  372. portal/templates/portal/partials/register_over_required_age_tickbox.html +0 -9
  373. portal/templates/portal/play/independent_student_dashboard.html +0 -64
  374. portal/templates/portal/play/student_aimmo_dashboard.html +0 -63
  375. portal/templates/portal/privacy_policy.html +0 -483
  376. portal/templates/portal/reset_password_email.html +0 -9
  377. portal/templates/portal/teach/invite.html +0 -25
  378. portal/templates/portal/teach/teacher_aimmo_dashboard.html +0 -95
  379. portal/templates/portal/teach/teacher_resources.html +0 -68
  380. portal/templatetags/character_list_tags.py +0 -16
  381. portal/tests/pageObjects/portal/kurono_teacher_dashboard_page.py +0 -49
  382. portal/tests/pageObjects/portal/student_password_reset_form_page.py +0 -23
  383. portal/tests/pageObjects/portal/teach/onboarding_revoke_request_page.py +0 -20
  384. portal/tests/pageObjects/portal/teacher_password_reset_form_page.py +0 -23
  385. portal/tests/test_aimmo_dashboards.py +0 -172
  386. portal/tests/test_location.py +0 -217
  387. portal/tests/utils/aimmo_games.py +0 -30
  388. portal/views/aimmo/dashboard.py +0 -119
  389. portal/views/privacy_policy.py +0 -9
  390. portal/views/teacher/teacher_resources.py +0 -42
  391. {portal/views/aimmo → cfl_common}/__init__.py +0 -0
portal/views/admin.py CHANGED
@@ -1,25 +1,13 @@
1
- from builtins import str
2
- from datetime import timedelta
3
- from time import sleep
4
-
5
- from common.models import Teacher, School, Class, Student
6
1
  from django.contrib import messages as messages
7
2
  from django.contrib.auth import logout
8
- from django.contrib.auth.decorators import permission_required, login_required
9
- from django.contrib.auth.models import User
10
3
  from django.contrib.auth.views import (
11
4
  PasswordChangeView,
12
5
  PasswordChangeDoneView,
13
6
  )
14
- from django.db.models import Avg, Count, Q
15
7
  from django.http import HttpResponseRedirect
16
- from django.shortcuts import render
17
- from django.utils import timezone
18
- from django_otp import device_classes
19
8
  from rest_framework.reverse import reverse_lazy
20
9
 
21
10
  from portal.forms.admin import AdminChangeOwnPasswordForm
22
- from portal.helpers.location import lookup_coord
23
11
 
24
12
  block_limit = 5
25
13
 
@@ -40,323 +28,3 @@ class AdminChangePasswordDoneView(PasswordChangeDoneView):
40
28
  "Password updated successfully. Please login using your new password.",
41
29
  )
42
30
  return HttpResponseRedirect(reverse_lazy("teacher_login"))
43
-
44
-
45
- @login_required(login_url=reverse_lazy("teacher_login"))
46
- @permission_required("common.view_aggregated_data", raise_exception=True)
47
- def aggregated_data(request):
48
-
49
- tables = []
50
-
51
- table_head = ["Data description", "Value", "More info"]
52
- table_data = []
53
-
54
- """
55
- Overall statistics
56
- """
57
-
58
- teacher_count = Teacher.objects.count()
59
- student_count = Student.objects.count()
60
- new_profiles_count = User.objects.filter(
61
- date_joined__gte=timezone.now() - timedelta(days=7)
62
- ).count()
63
-
64
- table_data.append(
65
- [
66
- "Number of users",
67
- teacher_count + student_count,
68
- "Number of teachers + Number of students",
69
- ]
70
- )
71
-
72
- table_data.append(
73
- [
74
- "Number of new users (past week)",
75
- new_profiles_count,
76
- "Number of user profiles",
77
- ]
78
- )
79
-
80
- tables.append(
81
- {
82
- "title": "Overall Statistics",
83
- "description": "CFL site overall statistics",
84
- "header": table_head,
85
- "data": table_data,
86
- }
87
- )
88
-
89
- """
90
- School statistics
91
- """
92
- table_data = []
93
- table_data.append(["Number of schools signed up", School.objects.count(), ""])
94
- num_of_teachers_per_school = School.objects.annotate(
95
- num_teachers=Count("teacher_school")
96
- )
97
- stats_teachers_per_school = num_of_teachers_per_school.aggregate(
98
- Avg("num_teachers")
99
- )
100
-
101
- table_data.append(
102
- [
103
- "Average number of teachers per school",
104
- stats_teachers_per_school["num_teachers__avg"],
105
- "",
106
- ]
107
- )
108
-
109
- tables.append(
110
- {
111
- "title": "Schools or Clubs",
112
- "description": "",
113
- "header": table_head,
114
- "data": table_data,
115
- }
116
- )
117
-
118
- """
119
- Teacher statistics
120
- """
121
- table_data = []
122
- table_data.append(["Number of teachers signed up", teacher_count, ""])
123
-
124
- table_data.append(
125
- [
126
- "Number of teachers not in a school",
127
- Teacher.objects.filter(school=None).count(),
128
- "",
129
- ]
130
- )
131
-
132
- table_data.append(
133
- [
134
- "Number of teachers with request pending to join a school",
135
- Teacher.objects.exclude(pending_join_request=None).count(),
136
- "",
137
- ]
138
- )
139
-
140
- table_data.append(
141
- [
142
- "Number of teachers with unverified email address",
143
- Teacher.objects.exclude(
144
- new_user__email_verifications__verified=True
145
- ).count(),
146
- "",
147
- ]
148
- )
149
-
150
- otp_model_names = [model._meta.model_name for model in device_classes()]
151
- otp_query = Q()
152
- for model_name in otp_model_names:
153
- otp_query = otp_query | Q(**{"new_user__%s__name" % model_name: "default"})
154
- two_factor_teachers = Teacher.objects.filter(otp_query).distinct().count()
155
- table_data.append(["Number of teachers setup with 2FA", two_factor_teachers, ""])
156
- num_of_classes_per_teacher = Teacher.objects.annotate(
157
- num_classes=Count("class_teacher")
158
- )
159
- stats_classes_per_teacher = num_of_classes_per_teacher.aggregate(Avg("num_classes"))
160
- num_of_classes_per_active_teacher = num_of_classes_per_teacher.exclude(school=None)
161
- stats_classes_per_active_teacher = num_of_classes_per_active_teacher.aggregate(
162
- Avg("num_classes")
163
- )
164
-
165
- table_data.append(
166
- [
167
- "Average number of classes per teacher",
168
- stats_classes_per_teacher["num_classes__avg"],
169
- "",
170
- ]
171
- )
172
-
173
- table_data.append(
174
- [
175
- "Average number of classes per active teacher",
176
- stats_classes_per_active_teacher["num_classes__avg"],
177
- "Excludes teachers without a school",
178
- ]
179
- )
180
-
181
- table_data.append(
182
- [
183
- "Number of of teachers with no classes",
184
- num_of_classes_per_teacher.filter(num_classes=0).count(),
185
- "",
186
- ]
187
- )
188
-
189
- table_data.append(
190
- [
191
- "Number of of active teachers with no classes",
192
- num_of_classes_per_active_teacher.filter(num_classes=0).count(),
193
- "Excludes teachers without a school",
194
- ]
195
- )
196
-
197
- tables.append(
198
- {
199
- "title": "Teachers",
200
- "description": "",
201
- "header": table_head,
202
- "data": table_data,
203
- }
204
- )
205
-
206
- """
207
- Class statistics
208
- """
209
- table_data = []
210
- table_data.append(["Number of classes", Class.objects.count(), ""])
211
-
212
- num_students_per_class = Class.objects.annotate(num_students=Count("students"))
213
- stats_students_per_class = num_students_per_class.aggregate(Avg("num_students"))
214
- stats_students_per_active_class = num_students_per_class.exclude(
215
- num_students=0
216
- ).aggregate(Avg("num_students"))
217
-
218
- table_data.append(
219
- [
220
- "Average number of students per class",
221
- stats_students_per_class["num_students__avg"],
222
- "",
223
- ]
224
- )
225
-
226
- table_data.append(
227
- [
228
- "Average number of students per active class",
229
- stats_students_per_active_class["num_students__avg"],
230
- "Excludes classes which are empty",
231
- ]
232
- )
233
-
234
- tables.append(
235
- {
236
- "title": "Classes",
237
- "description": "",
238
- "header": table_head,
239
- "data": table_data,
240
- }
241
- )
242
-
243
- """
244
- Student statistics
245
- """
246
- table_data = []
247
- table_data.append(["Number of students", student_count, ""])
248
-
249
- independent_students = Student.objects.filter(class_field=None)
250
-
251
- table_data.append(
252
- ["Number of independent students", independent_students.count(), ""]
253
- )
254
-
255
- table_data.append(
256
- [
257
- "Number of independent students with unverified email address",
258
- Student.objects.exclude(
259
- new_user__email_verifications__verified=True
260
- ).count(),
261
- "",
262
- ]
263
- )
264
-
265
- table_data.append(
266
- [
267
- "Number of school students",
268
- Student.objects.exclude(class_field=None).count(),
269
- "",
270
- ]
271
- )
272
-
273
- tables.append(
274
- {
275
- "title": "Students",
276
- "description": "",
277
- "header": table_head,
278
- "data": table_data,
279
- }
280
- )
281
-
282
- """
283
- Rapid Router Student Progress statistics
284
- """
285
- table_data = []
286
-
287
- students_with_attempts = Student.objects.annotate(
288
- num_attempts=Count("attempts")
289
- ).exclude(num_attempts=0)
290
- table_data.append(
291
- ["Number of students who have started RR", students_with_attempts.count(), ""]
292
- )
293
-
294
- school_students_with_attempts = students_with_attempts.exclude(class_field=None)
295
- table_data.append(
296
- [
297
- "Number of school students who have started RR",
298
- school_students_with_attempts.count(),
299
- "",
300
- ]
301
- )
302
-
303
- independent_students_with_attempts = students_with_attempts.filter(class_field=None)
304
- table_data.append(
305
- [
306
- "Number of independent students who have started RR",
307
- independent_students_with_attempts.count(),
308
- "",
309
- ]
310
- )
311
-
312
- tables.append(
313
- {
314
- "title": "Rapid Router Student Progress",
315
- "description": "",
316
- "header": table_head,
317
- "data": table_data,
318
- }
319
- )
320
-
321
- return render(request, "portal/admin/aggregated_data.html", {"tables": tables})
322
-
323
-
324
- def fill_in_missing_school_locations(request):
325
- schools = School.objects.filter(latitude="0", longitude="0")
326
-
327
- requests = 0
328
- failures = []
329
- town0 = 0
330
-
331
- for school in schools:
332
- requests += 1
333
- sleep(0.2) # so we execute a bit less than 5/sec
334
-
335
- (
336
- error,
337
- school.country,
338
- school.town,
339
- school.latitude,
340
- school.longitude,
341
- ) = lookup_coord(school.postcode, school.country.code)
342
-
343
- if error is None:
344
- school.save()
345
-
346
- if error is not None:
347
- failures += [(school.id, school.postcode, error)]
348
-
349
- if school.town == "0":
350
- town0 += 1
351
-
352
- messages.info(request, "Made %d requests" % requests)
353
- messages.info(request, "There were %d errors: %s" % (len(failures), str(failures)))
354
- messages.info(request, "%d school have no town" % town0)
355
-
356
-
357
- @login_required(login_url=reverse_lazy("teacher_login"))
358
- @permission_required("common.view_map_data", raise_exception=True)
359
- def schools_map(request):
360
- fill_in_missing_school_locations(request)
361
-
362
- return render(request, "portal/admin/map.html", {"schools": School.objects.all()})
portal/views/api.py CHANGED
@@ -1,18 +1,19 @@
1
1
  import datetime
2
2
  import uuid
3
3
 
4
- from common.models import Student, Teacher
4
+ from common.models import Class, School, Student, Teacher, UserProfile
5
5
  from django.contrib.auth.decorators import login_required
6
6
  from django.contrib.auth.models import User
7
7
  from django.http import HttpRequest, HttpResponse
8
8
  from django.utils import timezone
9
- from portal.app_settings import IS_CLOUD_SCHEDULER_FUNCTION
10
9
  from rest_framework import generics, permissions, serializers, status
11
10
  from rest_framework.authentication import SessionAuthentication
12
11
  from rest_framework.decorators import api_view
13
12
  from rest_framework.response import Response
14
13
  from rest_framework.reverse import reverse_lazy
15
14
 
15
+ from portal.app_settings import IS_CLOUD_SCHEDULER_FUNCTION
16
+
16
17
  THREE_YEARS_IN_DAYS = 1095
17
18
 
18
19
 
@@ -21,7 +22,9 @@ THREE_YEARS_IN_DAYS = 1095
21
22
  def registered_users(request, year, month, day):
22
23
  try:
23
24
  nbr_reg = User.objects.filter(
24
- date_joined__startswith=datetime.date(int(year), int(month), int(day))
25
+ date_joined__startswith=datetime.date(
26
+ int(year), int(month), int(day)
27
+ )
25
28
  ).count()
26
29
  return Response(nbr_reg)
27
30
  except ValueError:
@@ -70,11 +73,14 @@ class IsAdminOrGoogleAppEngine(permissions.IsAdminUser):
70
73
  """Checks whether the request is from a Google App Engine cron job."""
71
74
 
72
75
  def has_permission(self, request: HttpRequest, view):
73
- is_admin = super(IsAdminOrGoogleAppEngine, self).has_permission(request, view)
76
+ is_admin = super(IsAdminOrGoogleAppEngine, self).has_permission(
77
+ request, view
78
+ )
74
79
  return IS_CLOUD_SCHEDULER_FUNCTION(request) or is_admin
75
80
 
76
81
 
77
- def _anonymise(user):
82
+ def __anonymise_user(user):
83
+ # the actual user anonymisation
78
84
  user.username = uuid.uuid4().hex
79
85
  user.first_name = "Deleted"
80
86
  user.last_name = "User"
@@ -83,6 +89,52 @@ def _anonymise(user):
83
89
  user.save()
84
90
 
85
91
 
92
+ def anonymise(user):
93
+ """Anonymise user. If admin teacher, pass the admin role to another teacher (if exists).
94
+ If the only teacher, anonymise the school.
95
+ """
96
+ is_admin = False
97
+ teacher = None
98
+ # Find the teacher even if they're anonymised
99
+ teacher_set = Teacher._base_manager.filter(new_user=user)
100
+ if teacher_set:
101
+ is_admin = teacher_set[0].is_admin
102
+ school = teacher_set[0].school
103
+ teacher = teacher_set[0]
104
+
105
+ __anonymise_user(user)
106
+
107
+ # if teacher, anonymise classes and students
108
+ if teacher:
109
+ classes = Class.objects.filter(teacher=teacher)
110
+ for klass in classes:
111
+ students = Student.objects.filter(class_field=klass)
112
+ for student in students:
113
+ __anonymise_user(student.new_user)
114
+ klass.anonymise()
115
+
116
+ # if user is admin and the school does not have another admin, appoint another teacher as admin
117
+ if is_admin:
118
+ teachers = Teacher.objects.filter(school=school).order_by(
119
+ "new_user__last_name", "new_user__first_name"
120
+ )
121
+ if not teachers:
122
+ # no other teacher, anonymise the school
123
+ school.anonymise()
124
+ return
125
+
126
+ admin_exists = False
127
+ for teacher in teachers:
128
+ if teacher.is_admin:
129
+ admin_exists = True
130
+ break
131
+
132
+ # if no admin, appoint the first teacher as admin
133
+ if not admin_exists:
134
+ teachers[0].is_admin = True
135
+ teachers[0].save()
136
+
137
+
86
138
  class InactiveUsersView(generics.ListAPIView):
87
139
  """
88
140
  This API view endpoint allows us to see our inactive users.
@@ -110,62 +162,44 @@ class InactiveUsersView(generics.ListAPIView):
110
162
  """Delete all personal data from inactive users and mark them as inactive."""
111
163
  inactive_users = self.get_queryset()
112
164
  for user in inactive_users:
113
- _anonymise(user)
165
+ anonymise(user)
114
166
  return Response(status=status.HTTP_204_NO_CONTENT)
115
167
 
116
168
 
117
- class DuplicateIndyTeacherView(generics.ListAPIView):
169
+ class RemoveFakeAccounts(generics.ListAPIView):
118
170
  """
119
- This API endpoint deletes teacher or independent student accounts with duplicate emails.
171
+ This API endpoint will delete suspicious accounts that have the same first and last name and who are not verified
120
172
  """
121
173
 
122
- queryset = Student.objects.filter(
123
- class_field__isnull=True, new_user__is_active=True
124
- ).select_related("new_user")
125
-
126
174
  authentication_classes = (SessionAuthentication,)
175
+ serializer_class = InactiveUserSerializer
127
176
  permission_classes = (IsAdminOrGoogleAppEngine,)
128
177
 
129
- def delete(self, request, *args, **kwargs):
130
- def _tidyup(usrone, usrtwo):
131
- # if there's no login at all, keep the one with the most recent date_joined
132
- if not usrone.last_login and not usrtwo.last_login:
133
- if usrone.date_joined > usrtwo.date_joined:
134
- _anonymise(usrtwo)
135
- elif usrone.date_joined < usrtwo.date_joined:
136
- _anonymise(usrone)
137
- # else: should not happen, but if it does, leave them
138
- # if there's one with login, keep that one
139
- elif usrone.last_login and not usrtwo.last_login:
140
- _anonymise(usrtwo)
141
- elif not usrone.last_login and usrtwo.last_login:
142
- _anonymise(usrone)
143
- # else: both have logged in, we don't want to automatically choose for teacher+indy duplicates
144
-
145
- def _tidyup_students(students):
146
- for student in students:
147
- email = student.new_user.email
148
- assert email != ""
178
+ def get(self, request):
179
+ userprofiles = UserProfile.objects.filter(is_verified=False)
180
+ [
181
+ userprofile.user.delete()
182
+ for userprofile in userprofiles
183
+ if userprofile.user.first_name == userprofile.user.last_name
184
+ ]
149
185
 
150
- teachers = Teacher.objects.filter(
151
- new_user__is_active=True, new_user__email=email
152
- ).select_related("new_user")
186
+ return HttpResponse(status=204)
153
187
 
154
- if not teachers.exists():
155
- continue # no duplicate
156
188
 
157
- # else there's a duplicate
158
- assert len(teachers) == 1
159
- _tidyup(student.new_user, teachers[0].new_user)
189
+ class AnonymiseOrphanSchoolsView(generics.ListAPIView):
190
+ authentication_classes = (SessionAuthentication,)
191
+ serializer_class = InactiveUserSerializer
192
+ permission_classes = (IsAdminOrGoogleAppEngine,)
160
193
 
161
- # do it in batches
162
- offset = 0
163
- LIMIT = 1000
194
+ def get(self, request: HttpRequest, start_id):
195
+ # Re-anonymise all inactive teachers so their schools (if necessary) and classes/students are anonymised
196
+ for teacher in Teacher._base_manager.filter(
197
+ pk__gte=start_id, new_user__is_active=False
198
+ ):
199
+ anonymise(teacher.new_user)
164
200
 
165
- indystudents = self.get_queryset()[offset : (offset + LIMIT)]
166
- while indystudents.exists():
167
- _tidyup_students(indystudents)
168
- offset += LIMIT
169
- indystudents = self.get_queryset()[offset : (offset + LIMIT)]
201
+ # Anonymise schools without any teachers
202
+ for school in School.objects.filter(teacher_school__isnull=True):
203
+ school.anonymise()
170
204
 
171
205
  return Response(status=status.HTTP_204_NO_CONTENT)
@@ -0,0 +1 @@
1
+ from .user import *