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/tests/test_api.py CHANGED
@@ -3,11 +3,17 @@ from __future__ import absolute_import
3
3
  from unittest.mock import patch
4
4
 
5
5
  import pytest
6
- from common.models import Student
6
+ from common.models import Class, School, Student, Teacher
7
+ from common.tests.utils.classes import create_class_directly
8
+ from common.tests.utils.organisation import (
9
+ create_organisation_directly,
10
+ join_teacher_to_organisation,
11
+ )
12
+ from common.tests.utils.student import create_school_student_directly
13
+ from common.tests.utils.teacher import signup_teacher_directly
7
14
  from common.tests.utils.user import create_user_directly, get_superuser
8
15
  from django.contrib.auth.models import User
9
16
  from django.urls import reverse
10
- from django.utils import timezone
11
17
  from hamcrest import *
12
18
  from hamcrest.core.base_matcher import BaseMatcher
13
19
  from rest_framework import status
@@ -17,7 +23,8 @@ from rest_framework.test import APIClient, APITestCase
17
23
  class APITests(APITestCase):
18
24
  def test_valid_date_registered(self):
19
25
  url = reverse(
20
- "registered-users", kwargs={"year": "2016", "month": "04", "day": "01"}
26
+ "registered-users",
27
+ kwargs={"year": "2016", "month": "04", "day": "01"},
21
28
  )
22
29
  superuser = get_superuser()
23
30
  self.client.force_authenticate(user=superuser)
@@ -27,7 +34,8 @@ class APITests(APITestCase):
27
34
 
28
35
  def test_invalid_date_registered(self):
29
36
  url = reverse(
30
- "registered-users", kwargs={"year": "2016", "month": "05", "day": "35"}
37
+ "registered-users",
38
+ kwargs={"year": "2016", "month": "05", "day": "35"},
31
39
  )
32
40
  superuser = get_superuser()
33
41
  self.client.force_authenticate(user=superuser)
@@ -36,7 +44,8 @@ class APITests(APITestCase):
36
44
 
37
45
  def test_valid_date_lastconnectedsince(self):
38
46
  url = reverse(
39
- "last-connected-since", kwargs={"year": "2016", "month": "04", "day": "01"}
47
+ "last-connected-since",
48
+ kwargs={"year": "2016", "month": "04", "day": "01"},
40
49
  )
41
50
  superuser = get_superuser()
42
51
  self.client.force_authenticate(user=superuser)
@@ -46,7 +55,8 @@ class APITests(APITestCase):
46
55
 
47
56
  def test_invalid_date_lastconnectedsince(self):
48
57
  url = reverse(
49
- "last-connected-since", kwargs={"year": "2016", "month": "05", "day": "35"}
58
+ "last-connected-since",
59
+ kwargs={"year": "2016", "month": "05", "day": "35"},
50
60
  )
51
61
  superuser = get_superuser()
52
62
  self.client.force_authenticate(user=superuser)
@@ -69,10 +79,12 @@ class APITests(APITestCase):
69
79
  client.force_authenticate(user=superuser)
70
80
  url = reverse("inactive_users")
71
81
  response = client.get(url)
72
- self.assertEqual(len(response.data), 1)
82
+ assert len(response.data) == 1
73
83
 
74
84
  @patch("portal.views.api.IS_CLOUD_SCHEDULER_FUNCTION", return_value=True)
75
- def test_get_inactive_users_if_appengine(self, mock_is_cloud_scheduler_function):
85
+ def test_get_inactive_users_if_appengine(
86
+ self, mock_is_cloud_scheduler_function
87
+ ):
76
88
  client = APIClient()
77
89
  create_user_directly(active=False)
78
90
  create_user_directly(active=True)
@@ -88,10 +100,12 @@ class APITests(APITestCase):
88
100
  create_user_directly(active=True)
89
101
  url = reverse("inactive_users")
90
102
  response = client.get(url)
91
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
103
+ assert response.status_code == status.HTTP_403_FORBIDDEN
92
104
 
93
105
  @patch("portal.views.api.IS_CLOUD_SCHEDULER_FUNCTION", return_value=True)
94
- def test_delete_inactive_users_if_appengine(self, mock_is_cloud_scheduler_function):
106
+ def test_delete_inactive_users_if_appengine(
107
+ self, mock_is_cloud_scheduler_function
108
+ ):
95
109
  client = APIClient()
96
110
  create_user_directly(active=False)
97
111
  create_user_directly(active=False)
@@ -99,14 +113,25 @@ class APITests(APITestCase):
99
113
  response = client.get(url)
100
114
  users = response.data
101
115
  assert len(users) == 2
116
+
117
+ # NOTE: Migration 0049 causes user 34 (created via migration 0001) to
118
+ # be marked as inactive. Slightly tweaked this test so it still
119
+ # passes but takes into account this new anonymisation.
120
+ old_deleted_users = list(User.objects.filter(is_active=False))
121
+ assert len(old_deleted_users) == 1
122
+
102
123
  response = client.delete(url)
103
124
  assert mock_is_cloud_scheduler_function.called
104
125
  assert response.status_code == status.HTTP_204_NO_CONTENT
126
+
105
127
  for user in users:
106
128
  with pytest.raises(User.DoesNotExist):
107
129
  User.objects.get(username=user["username"])
130
+
108
131
  deleted_users = list(User.objects.filter(is_active=False))
109
- assert len(deleted_users) == 2
132
+ new_deleted_users_count = len(deleted_users) - len(old_deleted_users)
133
+ assert new_deleted_users_count == 2
134
+
110
135
  for user in deleted_users:
111
136
  assert user.first_name == "Deleted"
112
137
  assert user.last_name == "User"
@@ -116,74 +141,186 @@ class APITests(APITestCase):
116
141
  response = client.get(url)
117
142
  assert len(response.data) == 0
118
143
 
119
- def _create_indy_directly(self, username, email):
120
- """Create an indy in the database."""
121
- student = Student.objects.independentStudentFactory(
122
- username=username,
123
- name=username,
124
- email=email,
125
- password="Password1",
126
- )
127
- return student
128
-
129
144
  @patch("portal.views.api.IS_CLOUD_SCHEDULER_FUNCTION", return_value=True)
130
- def test_get_duplicate_indies(self, mock_is_cloud_scheduler_function):
145
+ def test_orphan_schools_and_classes_are_anonymised(
146
+ self, mock_is_cloud_scheduler_function
147
+ ):
131
148
  client = APIClient()
132
- url = reverse("indy_cleanup")
133
-
134
- # 1) if users never log in, the one with the latest date_joined is kept
135
- SAME_EMAIL = "same@email.com"
136
- student1 = self._create_indy_directly("student one", SAME_EMAIL)
137
- student1.new_user.date_joined = timezone.now() - timezone.timedelta(days=10)
138
- student1.new_user.save()
139
-
140
- student2 = self._create_indy_directly("student two", SAME_EMAIL)
141
- student2.new_user.date_joined = timezone.now() - timezone.timedelta(days=20)
142
- student2.new_user.save()
143
-
144
- students = Student.objects.filter(new_user__email=SAME_EMAIL)
145
- assert len(students) == 2
149
+ # Create a school with an active teacher
150
+ school1_teacher1_email, _ = signup_teacher_directly()
151
+ school1 = create_organisation_directly(school1_teacher1_email)
152
+ klass11, _, access_code11 = create_class_directly(
153
+ school1_teacher1_email
154
+ )
155
+ _, _, student11 = create_school_student_directly(access_code11)
156
+
157
+ # Create a school with one active non-admin teacher and one inactive admin teacher
158
+ school2_teacher1_email, _ = signup_teacher_directly()
159
+ school2_teacher2_email, _ = signup_teacher_directly()
160
+ school2 = create_organisation_directly(school2_teacher1_email)
161
+ join_teacher_to_organisation(
162
+ school2_teacher2_email, school2.name, is_admin=True
163
+ )
164
+ klass21, _, access_code21 = create_class_directly(
165
+ school2_teacher1_email
166
+ )
167
+ _, _, student21 = create_school_student_directly(access_code21)
168
+ klass22, _, access_code22 = create_class_directly(
169
+ school2_teacher2_email
170
+ )
171
+ _, _, student22 = create_school_student_directly(access_code22)
172
+ school2_teacher1 = Teacher.objects.get(
173
+ new_user__email=school2_teacher1_email
174
+ )
175
+ school2_teacher1.is_admin = False
176
+ school2_teacher1.save()
177
+ school2_teacher2 = Teacher.objects.get(
178
+ new_user__email=school2_teacher2_email
179
+ )
180
+ school2_teacher2.new_user.is_active = False
181
+ school2_teacher2.new_user.save()
182
+
183
+ # Create a school with 2 inactive teachers
184
+ school3_teacher1_email, _ = signup_teacher_directly()
185
+ school3_teacher2_email, _ = signup_teacher_directly()
186
+ school3 = create_organisation_directly(school3_teacher1_email)
187
+ join_teacher_to_organisation(school3_teacher2_email, school3.name)
188
+ klass31, _, access_code31 = create_class_directly(
189
+ school3_teacher1_email
190
+ )
191
+ _, _, student31 = create_school_student_directly(access_code31)
192
+ klass32, _, access_code32 = create_class_directly(
193
+ school3_teacher2_email
194
+ )
195
+ _, _, student32 = create_school_student_directly(access_code32)
196
+ school3_teacher1 = Teacher.objects.get(
197
+ new_user__email=school3_teacher1_email
198
+ )
199
+ school3_teacher1.new_user.is_active = False
200
+ school3_teacher1.new_user.save()
201
+ school3_teacher2 = Teacher.objects.get(
202
+ new_user__email=school3_teacher2_email
203
+ )
204
+ school3_teacher2.new_user.is_active = False
205
+ school3_teacher2.new_user.save()
206
+
207
+ # Create a school with no active teachers
208
+ school4_teacher1_email, _ = signup_teacher_directly()
209
+ school4 = create_organisation_directly(school4_teacher1_email)
210
+ school4_teacher1 = Teacher.objects.get(
211
+ new_user__email=school4_teacher1_email
212
+ )
213
+ school4_teacher1.new_user.is_active = False
214
+ school4_teacher1.new_user.save()
215
+
216
+ # Create a school with no teachers
217
+ school5_teacher1_email, _ = signup_teacher_directly()
218
+ school5 = create_organisation_directly(school5_teacher1_email)
219
+ school5_teacher1 = Teacher.objects.get(
220
+ new_user__email=school5_teacher1_email
221
+ )
222
+ school5_teacher1.delete()
146
223
 
147
- response = client.delete(url)
148
- assert mock_is_cloud_scheduler_function.called
224
+ # Call the API
225
+ url = reverse("anonymise_orphan_schools", kwargs={"start_id": 1})
226
+ response = client.get(url)
149
227
  assert response.status_code == status.HTTP_204_NO_CONTENT
150
228
 
151
- students = Student.objects.filter(new_user__email=SAME_EMAIL)
152
- assert len(students) == 1
153
-
154
- # 2) if there's one with login, keep that one, anonymise the rest
155
- student2 = self._create_indy_directly("student two", SAME_EMAIL)
156
- student2.new_user.date_joined = timezone.now() - timezone.timedelta(days=20)
157
- student2.new_user.last_login = timezone.now() - timezone.timedelta(days=19)
158
- student2.new_user.save()
159
-
160
- students = Student.objects.filter(new_user__email=SAME_EMAIL)
161
- assert len(students) == 2
162
-
163
- response = client.delete(url)
164
-
165
- students = Student.objects.filter(new_user__email=SAME_EMAIL)
166
- assert len(students) == 1
167
- assert students[0] == student2 # student 2 should be kept
168
-
169
- # 3) if there's more than one with login, keep the most recent login, anonymise the rest
170
- student1 = self._create_indy_directly("student one", SAME_EMAIL)
171
- student1.new_user.date_joined = timezone.now() - timezone.timedelta(days=10)
172
- student1.new_user.last_login = timezone.now() - timezone.timedelta(days=9)
173
- student1.new_user.save()
174
-
175
- student3 = self._create_indy_directly("student three", SAME_EMAIL)
176
- student3.new_user.date_joined = timezone.now() - timezone.timedelta(days=5)
177
- student3.new_user.save()
178
-
179
- students = Student.objects.filter(new_user__email=SAME_EMAIL)
180
- assert len(students) == 3
229
+ # Check the first school/class/student still exist
230
+ assert School.objects.filter(name=school1.name).exists()
231
+ assert Class.objects.filter(pk=klass11.pk).exists()
232
+ assert Student.objects.filter(pk=student11.pk).exists()
233
+
234
+ # Check the second school exists and its first class/student, but the second ones are anonymised
235
+ assert School.objects.filter(name=school2.name).exists()
236
+ assert Class.objects.filter(pk=klass21.pk).exists()
237
+ assert not Class.objects.filter(pk=klass22.pk).exists()
238
+ assert Student.objects.filter(pk=student21.pk).exists()
239
+ assert not Student.objects.get(pk=student22.pk).new_user.is_active
240
+ # Also check the first teacher is now an admin
241
+ assert Teacher.objects.get(
242
+ new_user__email=school2_teacher1_email
243
+ ).is_admin
244
+
245
+ # Check the third school is anonymised together with its classes and students
246
+ assert not School.objects.filter(name=school3.name).exists()
247
+ assert not Class.objects.filter(pk=klass31.pk).exists()
248
+ assert not Class.objects.filter(pk=klass32.pk).exists()
249
+ assert not Student.objects.get(pk=student31.pk).new_user.is_active
250
+ assert not Student.objects.get(pk=student32.pk).new_user.is_active
251
+
252
+ # Check that the fourth school is anonymised
253
+ assert not School.objects.filter(name=school4.name).exists()
254
+
255
+ # Check that the fifth school is anonymised
256
+ assert not School.objects.filter(name=school5.name).exists()
257
+
258
+ def test_remove_fake_accounts(self):
259
+ client = APIClient()
260
+ initial_users_length = len(User.objects.all())
261
+ admin_username = "codeforlife-portal@ocado.com"
262
+ admin_password = "abc123"
263
+
264
+ # First two accounts should be deleted
265
+ # Third account should be omitted because first and last name is different
266
+ # Fourth account should be omitted because it is verified
267
+ random_accounts = [
268
+ {
269
+ "username": "hiya",
270
+ "first_name": "name",
271
+ "last_name": "name",
272
+ "email": "eml@email.email",
273
+ "password": '!QAZXSW"3edc',
274
+ "verified": False,
275
+ },
276
+ {
277
+ "username": "goodbye",
278
+ "first_name": "hello",
279
+ "last_name": "hello",
280
+ "email": "el@email.email",
281
+ "password": '!QAZXSW"3edc',
282
+ "verified": False,
283
+ },
284
+ {
285
+ "username": "different",
286
+ "first_name": "nope",
287
+ "last_name": "maybe",
288
+ "email": "eail@email.email",
289
+ "password": '!QAZXSW"3edc',
290
+ "verified": False,
291
+ },
292
+ {
293
+ "username": "same",
294
+ "first_name": "lastname",
295
+ "last_name": "lastname",
296
+ "email": "eail@email.email",
297
+ "password": '!QAZXSW"3edc',
298
+ "verified": True,
299
+ },
300
+ ]
301
+
302
+ for random_account in random_accounts:
303
+ signup_teacher_directly(
304
+ preverified=random_account["verified"],
305
+ username=random_account["username"],
306
+ email=random_account["email"],
307
+ first_name=random_account["first_name"],
308
+ last_name=random_account["last_name"],
309
+ )
310
+
311
+ assert (
312
+ len(User.objects.all())
313
+ == len(random_accounts) + initial_users_length
314
+ )
181
315
 
182
- response = client.delete(url)
316
+ client.login(username=admin_username, password=admin_password)
317
+ response = client.get(reverse("remove_fake_accounts"))
318
+ assert response.status_code == 204
183
319
 
184
- students = Student.objects.filter(new_user__email=SAME_EMAIL)
185
- assert len(students) == 1
186
- assert students[0] == student1 # student 1 should be kept
320
+ # check if after deletion all the users are still there
321
+ assert (
322
+ len(User.objects.all()) == initial_users_length + 2
323
+ ) # mentioned in the fake_accounts description
187
324
 
188
325
 
189
326
  def has_status_code(status_code):
@@ -198,7 +335,9 @@ class HasStatusCode(BaseMatcher):
198
335
  return response.status_code == self.status_code
199
336
 
200
337
  def describe_to(self, description):
201
- description.append_text("has status code ").append_text(self.status_code)
338
+ description.append_text("has status code ").append_text(
339
+ self.status_code
340
+ )
202
341
 
203
342
  def describe_mismatch(self, response, mismatch_description):
204
343
  mismatch_description.append_text("had status code ").append_text(
@@ -1,7 +1,7 @@
1
- from captcha.fields import ReCaptchaField
2
- from captcha.widgets import ReCaptchaV2Invisible
3
1
  from django import forms
4
2
  from django.test import TestCase
3
+ from django_recaptcha.fields import ReCaptchaField
4
+ from django_recaptcha.widgets import ReCaptchaV2Invisible
5
5
 
6
6
  from portal.helpers.captcha import is_captcha_in_form, remove_captcha_from_forms
7
7