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/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,82 +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 DuplicateIndyStudentsView(generics.ListAPIView):
169
+ class RemoveFakeAccounts(generics.ListAPIView):
118
170
  """
119
- This API endpoint deletes the 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
- indystudents = self.get_queryset()
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
+ ]
131
185
 
132
- # dictionary of duplicate emails and list of students
133
- studentdict = {}
134
- for student in indystudents:
135
- email = student.new_user.email
136
- assert email != ""
186
+ return HttpResponse(status=204)
137
187
 
138
- if not studentdict.get(email):
139
- studentdict[email] = []
140
- studentdict[email].append(student)
141
188
 
142
- def _get_last_joined(students):
143
- # get the student with the latest date_joined
144
- last_joined = None
145
- for student in students:
146
- if not last_joined:
147
- last_joined = student
148
- else:
149
- if student.new_user.date_joined > last_joined.new_user.date_joined:
150
- last_joined = student
151
- return last_joined
152
-
153
- def _get_last_login(students):
154
- # get the student with the latest last_login
155
- last_login = None
156
- for student in students:
157
- if not last_login:
158
- last_login = student
159
- else:
160
- if student.new_user.last_login > last_login.new_user.last_login:
161
- last_login = student
162
- return last_login
163
-
164
- def _anonymise_others(students, kept_student):
165
- for student in students:
166
- if student != kept_student:
167
- _anonymise(student.new_user)
189
+ class AnonymiseOrphanSchoolsView(generics.ListAPIView):
190
+ authentication_classes = (SessionAuthentication,)
191
+ serializer_class = InactiveUserSerializer
192
+ permission_classes = (IsAdminOrGoogleAppEngine,)
168
193
 
169
- for email, students in studentdict.items():
170
- if len(students) <= 1:
171
- continue # no duplicate
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)
172
200
 
173
- logged_in_students = []
174
- # collect accounts who have last_login
175
- for student in students:
176
- if student.new_user.last_login:
177
- logged_in_students.append(student)
178
-
179
- # if there's no login at all, keep the one with the most recent date_joined
180
- if len(logged_in_students) == 0:
181
- last_joined = _get_last_joined(students)
182
- _anonymise_others(students, last_joined)
183
- elif len(logged_in_students) == 1:
184
- # if there's one with login, keep that one, anonymise the rest
185
- _anonymise_others(students, logged_in_students[0])
186
- else:
187
- # if there's more than one with login, keep the most recent login, anonymise the rest
188
- last_login = _get_last_login(logged_in_students)
189
- _anonymise_others(students, last_login)
201
+ # Anonymise schools without any teachers
202
+ for school in School.objects.filter(teacher_school__isnull=True):
203
+ school.anonymise()
190
204
 
191
205
  return Response(status=status.HTTP_204_NO_CONTENT)
@@ -0,0 +1 @@
1
+ from .user import *