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/tests/test_api.py CHANGED
@@ -3,12 +3,17 @@ from __future__ import absolute_import
3
3
  from unittest.mock import patch
4
4
 
5
5
  import pytest
6
- from common.helpers.emails import generate_token
7
- from common.models import Student, Teacher
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
8
14
  from common.tests.utils.user import create_user_directly, get_superuser
9
15
  from django.contrib.auth.models import User
10
16
  from django.urls import reverse
11
- from django.utils import timezone
12
17
  from hamcrest import *
13
18
  from hamcrest.core.base_matcher import BaseMatcher
14
19
  from rest_framework import status
@@ -18,7 +23,8 @@ from rest_framework.test import APIClient, APITestCase
18
23
  class APITests(APITestCase):
19
24
  def test_valid_date_registered(self):
20
25
  url = reverse(
21
- "registered-users", kwargs={"year": "2016", "month": "04", "day": "01"}
26
+ "registered-users",
27
+ kwargs={"year": "2016", "month": "04", "day": "01"},
22
28
  )
23
29
  superuser = get_superuser()
24
30
  self.client.force_authenticate(user=superuser)
@@ -28,7 +34,8 @@ class APITests(APITestCase):
28
34
 
29
35
  def test_invalid_date_registered(self):
30
36
  url = reverse(
31
- "registered-users", kwargs={"year": "2016", "month": "05", "day": "35"}
37
+ "registered-users",
38
+ kwargs={"year": "2016", "month": "05", "day": "35"},
32
39
  )
33
40
  superuser = get_superuser()
34
41
  self.client.force_authenticate(user=superuser)
@@ -37,7 +44,8 @@ class APITests(APITestCase):
37
44
 
38
45
  def test_valid_date_lastconnectedsince(self):
39
46
  url = reverse(
40
- "last-connected-since", kwargs={"year": "2016", "month": "04", "day": "01"}
47
+ "last-connected-since",
48
+ kwargs={"year": "2016", "month": "04", "day": "01"},
41
49
  )
42
50
  superuser = get_superuser()
43
51
  self.client.force_authenticate(user=superuser)
@@ -47,7 +55,8 @@ class APITests(APITestCase):
47
55
 
48
56
  def test_invalid_date_lastconnectedsince(self):
49
57
  url = reverse(
50
- "last-connected-since", kwargs={"year": "2016", "month": "05", "day": "35"}
58
+ "last-connected-since",
59
+ kwargs={"year": "2016", "month": "05", "day": "35"},
51
60
  )
52
61
  superuser = get_superuser()
53
62
  self.client.force_authenticate(user=superuser)
@@ -70,10 +79,12 @@ class APITests(APITestCase):
70
79
  client.force_authenticate(user=superuser)
71
80
  url = reverse("inactive_users")
72
81
  response = client.get(url)
73
- self.assertEqual(len(response.data), 1)
82
+ assert len(response.data) == 1
74
83
 
75
84
  @patch("portal.views.api.IS_CLOUD_SCHEDULER_FUNCTION", return_value=True)
76
- 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
+ ):
77
88
  client = APIClient()
78
89
  create_user_directly(active=False)
79
90
  create_user_directly(active=True)
@@ -89,10 +100,12 @@ class APITests(APITestCase):
89
100
  create_user_directly(active=True)
90
101
  url = reverse("inactive_users")
91
102
  response = client.get(url)
92
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
103
+ assert response.status_code == status.HTTP_403_FORBIDDEN
93
104
 
94
105
  @patch("portal.views.api.IS_CLOUD_SCHEDULER_FUNCTION", return_value=True)
95
- 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
+ ):
96
109
  client = APIClient()
97
110
  create_user_directly(active=False)
98
111
  create_user_directly(active=False)
@@ -100,14 +113,25 @@ class APITests(APITestCase):
100
113
  response = client.get(url)
101
114
  users = response.data
102
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
+
103
123
  response = client.delete(url)
104
124
  assert mock_is_cloud_scheduler_function.called
105
125
  assert response.status_code == status.HTTP_204_NO_CONTENT
126
+
106
127
  for user in users:
107
128
  with pytest.raises(User.DoesNotExist):
108
129
  User.objects.get(username=user["username"])
130
+
109
131
  deleted_users = list(User.objects.filter(is_active=False))
110
- 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
+
111
135
  for user in deleted_users:
112
136
  assert user.first_name == "Deleted"
113
137
  assert user.last_name == "User"
@@ -117,91 +141,186 @@ class APITests(APITestCase):
117
141
  response = client.get(url)
118
142
  assert len(response.data) == 0
119
143
 
120
- def _create_indy_directly(self, email):
121
- """Create an indy in the database."""
122
- student = Student.objects.independentStudentFactory(
123
- username="indiana",
124
- name="some student",
125
- email=email,
126
- password="Password1",
127
- )
128
- return student
129
-
130
- def _create_teacher_directly(self, email):
131
- first_name = "some"
132
- last_name = "teacher"
133
- password = "Password1!"
134
- teacher = Teacher.objects.factory(first_name, last_name, email, password)
135
- generate_token(teacher.new_user, preverified=True)
136
- teacher.user.save()
137
- return teacher
138
-
139
144
  @patch("portal.views.api.IS_CLOUD_SCHEDULER_FUNCTION", return_value=True)
140
- def test_cleanup_duplicate_teacher_indy(self, mock_is_cloud_scheduler_function):
145
+ def test_orphan_schools_and_classes_are_anonymised(
146
+ self, mock_is_cloud_scheduler_function
147
+ ):
141
148
  client = APIClient()
142
- url = reverse("teacher_indy_cleanup")
143
-
144
- # 1) if users never log in, the one with the latest date_joined is kept
145
- SAME_EMAIL = "same@email.com"
146
- student = self._create_indy_directly(SAME_EMAIL)
147
- student.new_user.date_joined = timezone.now() - timezone.timedelta(days=10)
148
- student.new_user.save()
149
-
150
- teacher = self._create_teacher_directly(SAME_EMAIL)
151
- teacher.new_user.date_joined = timezone.now() - timezone.timedelta(days=20)
152
- teacher.new_user.save()
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()
153
223
 
154
- response = client.delete(url)
155
- 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)
156
227
  assert response.status_code == status.HTTP_204_NO_CONTENT
157
228
 
158
- user = User.objects.get(email=SAME_EMAIL)
159
- assert user == student.new_user
160
- user = User.objects.get(id=teacher.new_user.id)
161
- assert user.email != SAME_EMAIL # teacher anonymised
162
-
163
- # now teacher created later
164
- teacher = self._create_teacher_directly(SAME_EMAIL)
165
- teacher.new_user.date_joined = timezone.now() - timezone.timedelta(days=5)
166
- teacher.new_user.save()
167
-
168
- response = client.delete(url)
169
-
170
- user = User.objects.get(email=SAME_EMAIL)
171
- assert user == teacher.new_user
172
- user = User.objects.get(id=student.new_user.id)
173
- assert user.email != SAME_EMAIL # student anonymised
174
-
175
- # 2) if there's one with login, keep that one, anonymise the other
176
- teacher.new_user.date_joined = timezone.now() - timezone.timedelta(days=20)
177
- teacher.new_user.last_login = timezone.now() - timezone.timedelta(days=19)
178
- teacher.new_user.save()
179
-
180
- student = self._create_indy_directly(SAME_EMAIL)
181
- student.new_user.last_login = None
182
- student.new_user.save()
183
-
184
- response = client.delete(url)
185
-
186
- user = User.objects.get(email=SAME_EMAIL)
187
- assert user == teacher.new_user
188
- user = User.objects.get(id=student.new_user.id)
189
- assert user.email != SAME_EMAIL # student anonymised
190
-
191
- # now try the student to log in and not teacher
192
- teacher.new_user.last_login = None
193
- teacher.new_user.save()
194
-
195
- student = self._create_indy_directly(SAME_EMAIL)
196
- student.new_user.last_login = timezone.now() - timezone.timedelta(days=9)
197
- student.new_user.save()
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
+ )
198
315
 
199
- 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
200
319
 
201
- user = User.objects.get(email=SAME_EMAIL)
202
- assert user == student.new_user
203
- user = User.objects.get(id=teacher.new_user.id)
204
- assert user.email != SAME_EMAIL # teacher anonymised
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
205
324
 
206
325
 
207
326
  def has_status_code(status_code):
@@ -216,7 +335,9 @@ class HasStatusCode(BaseMatcher):
216
335
  return response.status_code == self.status_code
217
336
 
218
337
  def describe_to(self, description):
219
- 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
+ )
220
341
 
221
342
  def describe_mismatch(self, response, mismatch_description):
222
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