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
@@ -1,5 +1,7 @@
1
1
  from __future__ import absolute_import
2
2
 
3
+ from selenium.webdriver.common.by import By
4
+
3
5
  from . import class_page, dashboard_page
4
6
  from .teach_base_page import TeachBasePage
5
7
 
@@ -11,25 +13,18 @@ class OnboardingStudentListPage(TeachBasePage):
11
13
  assert self.on_correct_page("onboarding_student_list_page")
12
14
 
13
15
  def student_exists(self, name):
14
- return name in self.browser.find_element_by_id("student_table").text
16
+ return name in self.browser.find_element(By.ID, "student_table").text
15
17
 
16
18
  def is_student_password(self, password):
17
- return (
18
- password
19
- in self.browser.find_element_by_xpath(
20
- "//table[@id='student_table']/tbody/tr[4]/td[2]"
21
- ).text
22
- )
19
+ return password in self.browser.find_element(By.XPATH, "//table[@id='student_table']/tbody/tr[4]/td[2]").text
23
20
 
24
21
  def go_back_to_class(self):
25
- self.browser.find_element_by_id("back_to_class_button").click()
22
+ self.browser.find_element(By.ID, "back_to_class_button").click()
26
23
  return class_page.TeachClassPage(self.browser)
27
24
 
28
25
  def get_first_login_url(self):
29
- return self.browser.find_element_by_xpath(
30
- "//table[@id='student_table']/tbody/tr[4]/td[4]/div/div[1]"
31
- ).text
26
+ return self.browser.find_element(By.XPATH, "//table[@id='student_table']/tbody/tr[4]/td[4]/div/div[1]").text
32
27
 
33
28
  def complete_setup(self):
34
- self.browser.find_element_by_id("complete_setup_button").click()
29
+ self.browser.find_element(By.ID, "complete_setup_button").click()
35
30
  return dashboard_page.TeachDashboardPage(self.browser)
@@ -1,5 +1,7 @@
1
1
  from __future__ import absolute_import
2
2
 
3
+ from selenium.webdriver.common.by import By
4
+
3
5
  from . import onboarding_student_list_page
4
6
  from .teach_base_page import TeachBasePage
5
7
 
@@ -26,33 +28,8 @@ class OnboardingStudentsPage(TeachBasePage):
26
28
  return self
27
29
 
28
30
  def _click_create_students(self):
29
- self.browser.find_element_by_name("new_students").click()
30
-
31
- def adding_students_failed(self):
32
- if not self.element_exists_by_css(".errorlist"):
33
- return False
34
-
35
- error_list = self.browser.find_element_by_id(
36
- "form-create-students"
37
- ).find_element_by_class_name("errorlist")
38
-
39
- if error_list.text:
40
- return True
41
- else:
42
- return False
43
-
44
- def duplicate_students(self, name):
45
- if not self.element_exists_by_css(".errorlist"):
46
- return False
47
-
48
- errors = (
49
- self.browser.find_element_by_id("form-create-students")
50
- .find_element_by_class_name("errorlist")
51
- .text
52
- )
53
- error = "You cannot add more than one student called '{0}'".format(name)
54
- return error in errors
31
+ self.browser.find_element(By.NAME, "new_students").click()
55
32
 
56
33
  def type_student_name(self, name):
57
- self.browser.find_element_by_id("id_names").send_keys(name + "\n")
34
+ self.browser.find_element(By.ID, "id_names").send_keys(name + "\n")
58
35
  return self
@@ -1,5 +1,7 @@
1
1
  from pathlib import Path
2
2
 
3
+ from selenium.webdriver.common.by import By
4
+
3
5
  from portal.tests.pageObjects.portal.base_page import BasePage
4
6
 
5
7
 
@@ -8,9 +10,9 @@ class TeachBasePage(BasePage):
8
10
  super(TeachBasePage, self).__init__(browser)
9
11
 
10
12
  def logout(self):
11
- self.browser.find_element_by_id("logout_menu").click()
13
+ self.browser.find_element(By.ID, "logout_menu").click()
12
14
 
13
- self.browser.find_element_by_id("logout_button").click()
15
+ self.browser.find_element(By.ID, "logout_button").click()
14
16
  from portal.tests.pageObjects.portal.home_page import HomePage
15
17
 
16
18
  return HomePage(self.browser)
@@ -27,11 +29,11 @@ class TeachBasePage(BasePage):
27
29
  })
28
30
  """
29
31
  )
30
- self.browser.find_element_by_xpath("/html/body/input").send_keys(
32
+ self.browser.find_element(By.XPATH, "/html/body/input").send_keys(
31
33
  str((Path(__file__).parents[3] / "data" / filename).resolve())
32
34
  )
33
35
 
34
36
  return self
35
37
 
36
38
  def get_students_input_value(self):
37
- return self.browser.find_element_by_id("id_names").get_attribute("value")
39
+ return self.browser.find_element(By.ID, "id_names").get_attribute("value")
@@ -1,10 +1,8 @@
1
- import time
1
+ from selenium.webdriver.common.by import By
2
2
 
3
3
  from .base_page import BasePage
4
+ from .password_reset_page import PasswordResetPage
4
5
  from .teach import dashboard_page as teach_dashboard_page
5
- from .teach import onboarding_classes_page
6
- from .teach import onboarding_students_page
7
- from .teacher_password_reset_form_page import TeacherPasswordResetFormPage
8
6
 
9
7
 
10
8
  class TeacherLoginPage(BasePage):
@@ -18,9 +16,9 @@ class TeacherLoginPage(BasePage):
18
16
  return teach_dashboard_page.TeachDashboardPage(self.browser)
19
17
 
20
18
  def _login(self, email, password):
21
- self.browser.find_element_by_id("id_auth-username").send_keys(email)
22
- self.browser.find_element_by_id("id_auth-password").send_keys(password)
23
- self.browser.find_element_by_name("login_view").click()
19
+ self.browser.find_element(By.ID, "id_auth-username").send_keys(email)
20
+ self.browser.find_element(By.ID, "id_auth-password").send_keys(password)
21
+ self.browser.find_element(By.NAME, "login_view").click()
24
22
 
25
23
  def login_no_school(self, email, password):
26
24
  self._login(email, password)
@@ -31,25 +29,21 @@ class TeacherLoginPage(BasePage):
31
29
  def login_no_class(self, email, password):
32
30
  self._login(email, password)
33
31
 
34
- return onboarding_classes_page.OnboardingClassesPage(self.browser)
32
+ return teach_dashboard_page.TeachDashboardPage(self.browser)
35
33
 
36
34
  def login_no_students(self, email, password):
37
35
  self._login(email, password)
38
36
 
39
- return onboarding_students_page.OnboardingStudentsPage(self.browser)
37
+ return teach_dashboard_page.TeachDashboardPage(self.browser)
40
38
 
41
39
  def login_failure(self, email, password):
42
40
  self._login(email, password)
43
41
  return self
44
42
 
45
43
  def has_login_failed(self, form_id, error):
46
- errors = (
47
- self.browser.find_element_by_id(form_id)
48
- .find_element_by_class_name("errorlist")
49
- .text
50
- )
44
+ errors = self.browser.find_element(By.ID, form_id).find_element(By.CLASS_NAME, "errorlist").text
51
45
  return error in errors
52
46
 
53
47
  def go_to_teacher_forgotten_password_page(self):
54
- self.browser.find_element_by_id("teacher_forgotten_password_button").click()
55
- return TeacherPasswordResetFormPage(self.browser)
48
+ self.browser.find_element(By.ID, "teacher_forgotten_password_button").click()
49
+ return PasswordResetPage(self.browser)
@@ -1,52 +1,12 @@
1
1
  """
2
- This SeleniumTestCase is copied over from django-selenium-clean==0.2.1
2
+ Patch SeleniumTestCase from django-selenium-clean package.
3
3
 
4
4
  Instead of inheriting from StaticLiveServerTestCase, we inherit from LiveServerTestCase.
5
5
  This solves a bug introduced when upgrading to Django 1.11,
6
6
  see more information here: https://github.com/jazzband/django-pipeline/issues/593
7
7
  """
8
8
 
9
- from django.conf import settings
10
- from django.contrib.sites.models import Site
11
9
  from django.contrib.staticfiles.testing import LiveServerTestCase
12
- from django_selenium_clean import SeleniumWrapper, PageElement
10
+ from django_selenium_clean import SeleniumTestCase
13
11
 
14
-
15
- class SeleniumTestCase(LiveServerTestCase):
16
- @classmethod
17
- def setUpClass(cls):
18
- super(SeleniumTestCase, cls).setUpClass()
19
- cls.selenium = SeleniumWrapper()
20
- PageElement.selenium = cls.selenium
21
-
22
- # Normally we would just do something like
23
- # selenium.live_server_url = self.live_server_url
24
- # However, there is no "self" at this time, so we
25
- # essentially duplicate the code from the definition of
26
- # the LiveServerTestCase.live_server_url property.
27
- cls.selenium.live_server_url = "http://%s:%s" % (
28
- cls.server_thread.host,
29
- cls.server_thread.port,
30
- )
31
-
32
- @classmethod
33
- def tearDownClass(cls):
34
- cls.selenium.quit()
35
- PageElement.selenium = None
36
- super(SeleniumTestCase, cls).tearDownClass()
37
-
38
- def __call__(self, result=None):
39
- self._set_site_to_local_domain()
40
- if hasattr(self, "selenium"):
41
- for width in getattr(settings, "SELENIUM_WIDTHS", [1624]):
42
- self.selenium.set_window_size(width, 1024)
43
- return super(SeleniumTestCase, self).__call__(result)
44
-
45
- def _set_site_to_local_domain(self):
46
- """
47
- Sets the Site Django object to the local domain (locally, localhost:8000).
48
- Needed to generate valid registration and password reset links in tests.
49
- """
50
- current_site = Site.objects.get_current()
51
- current_site.domain = f"{self.server_thread.host}:{self.server_thread.port}"
52
- current_site.save()
12
+ SeleniumTestCase.__bases__ = (LiveServerTestCase,)
@@ -7,9 +7,7 @@ from snapshottest import Snapshot
7
7
 
8
8
  snapshots = Snapshot()
9
9
 
10
- snapshots[
11
- "test_banner 1"
12
- ] = """<div class="banner banner--teacher">
10
+ snapshots['test_banner 1'] = '''<div class="banner banner--teacher">
13
11
  <div class="container">
14
12
  <div class="row">
15
13
  <div class="col-sm-12 d-flex">
@@ -21,12 +19,16 @@ snapshots[
21
19
 
22
20
  <p>Test text</p>
23
21
 
24
-
22
+ <div class="button-group">
23
+
24
+
25
+ </div>
25
26
  </div>
26
27
  <div>
27
28
  <div class="banner--picture">
28
29
  <div class="banner--picture__inside1">
29
30
  <div class="banner--picture__inside2 test--image--class"
31
+
30
32
  >
31
33
  </div>
32
34
  </div>
@@ -36,11 +38,9 @@ snapshots[
36
38
  </div>
37
39
  </div>
38
40
  </div>
39
- """
41
+ '''
40
42
 
41
- snapshots[
42
- "test_benefits 1"
43
- ] = """
43
+ snapshots['test_benefits 1'] = '''
44
44
 
45
45
  <div class="grid-benefits col-sm-8 col-center">
46
46
 
@@ -80,164 +80,10 @@ snapshots[
80
80
  </div>
81
81
 
82
82
  </div>
83
- """
84
-
85
- snapshots[
86
- "test_card_list 1"
87
- ] = """
88
-
83
+ '''
89
84
 
90
- <div class="grid grid-worksheets grid__fit">
91
-
92
- <div class="card">
93
- <div class="card__images">
94
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/worksheets/future2.jpg">
95
-
96
- <img class="card__thumbnail" src="https://storage.googleapis.com/codeforlife-assets/images/worksheets/lock.png">
97
-
98
- </div>
99
- <div class="card__text">
100
- <h5 class="card__title">Test card 1</h5>
101
-
102
- <p>Test description 1</p>
103
-
104
- </div>
105
- </div>
106
-
107
- <div class="card">
108
- <div class="card__images">
109
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/worksheets/ancient.jpg">
110
-
111
- <h3 class="card__thumbnail">Coming Soon</h3>
112
-
113
- </div>
114
- <div class="card__text">
115
- <h5 class="card__title">Test card 2</h5>
116
-
117
- <p>Test description 2</p>
118
-
119
- </div>
120
- </div>
121
-
122
- <div class="card">
123
- <div class="card__images">
124
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/worksheets/modern_day.jpg">
125
-
126
- <h3 class="card__thumbnail">Coming Soon</h3>
127
-
128
- </div>
129
- <div class="card__text">
130
- <h5 class="card__title">Test card 3</h5>
131
-
132
- <p>Test description 3</p>
133
-
134
- </div>
135
- </div>
136
-
137
- <div class="card">
138
- <div class="card__images">
139
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/worksheets/prehistory.jpg">
140
-
141
- <h3 class="card__thumbnail">Coming Soon</h3>
142
-
143
- </div>
144
- <div class="card__text">
145
- <h5 class="card__title">Test card 4</h5>
146
-
147
- <p>Test description 4</p>
148
-
149
- </div>
150
- </div>
151
-
152
- <div class="card">
153
- <div class="card__images">
154
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/worksheets/broken_future.jpg">
155
-
156
- <h3 class="card__thumbnail">Coming Soon</h3>
157
-
158
- </div>
159
- <div class="card__text">
160
- <h5 class="card__title">Test card 5</h5>
161
-
162
- <p>Test description 5</p>
163
-
164
- </div>
165
- </div>
166
-
167
- <div class="card">
168
- <div class="card__images">
169
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/worksheets/kurono_logo.svg">
170
-
171
- </div>
172
- <div class="card__text">
173
- <h5 class="card__title">Test card 6</h5>
174
-
175
- <div class="button-group button-group__icon">
176
- <a target="_blank" href="home" class="button button--primary button-right-arrow">
177
- Test button
178
- </a>
179
- </div>
180
-
181
- </div>
182
- </div>
183
-
184
- </div>
185
- """
186
-
187
- snapshots[
188
- "test_character_list 1"
189
- ] = """
190
-
191
-
192
- <div class="grid grid-characters grid__fit">
193
-
194
- <div class="card">
195
- <h5 class="card__title">Xian</h5>
196
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/aimmo_characters/Xian.png">
197
- <p class="card__text">Fun, active, will dance to just about anything that produces a beat. Has great memory, always a joke at hand, might try to introduce memes in Ancient Greece. Scored gold in a track race once and will take any opportunity to bring that up.</p>
198
- </div>
199
-
200
- <div class="card">
201
- <h5 class="card__title">Jools</h5>
202
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/aimmo_characters/Jools.png">
203
- <p class="card__text">A quick-witted kid who wasn’t expecting to embark in a time-warping journey but can’t say no to a challenge. Someone has to keep the rest of the group in check, after all!</p>
204
- </div>
205
-
206
- <div class="card">
207
- <h5 class="card__title">Zayed</h5>
208
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/aimmo_characters/Zayed.png">
209
- <p class="card__text">A pretty chill, curious soul that prefers practice to theory. Always ready to jump into an adventure if it looks interesting enough; not so much otherwise. Probably the one who accidentally turned the time machine on in first place.</p>
210
- </div>
211
-
212
- </div>
213
- """
214
-
215
- snapshots[
216
- "test_headline 1"
217
- ] = """<section>
85
+ snapshots['test_headline 1'] = '''<section>
218
86
  <h4>Test title</h4>
219
87
  </section>
220
88
  <p class="container">Test description</p>
221
- """
222
-
223
- snapshots[
224
- "test_hero_card 1"
225
- ] = """
226
-
227
-
228
- <div class="card">
229
- <img class="card__image" src="https://storage.googleapis.com/codeforlife-assets/images/worksheets/future_active.png">
230
- <div class="card__text">
231
- <h4 class="card__title">Test title</h4>
232
- <p>Test description</p>
233
- <div class="button-group button-group__icon">
234
- <a target="_blank" href="https://www.codeforlife.education" class="button button--primary button--icon">
235
- Test button 1<span class="iconify" data-icon="mdi:open-in-new"></span>
236
- </a>
237
- <a href="/kurono/play/1/" class="button button--primary button-right-arrow">
238
- Test button 2
239
- </a>
240
- </div>
241
- </div>
242
- </div>
243
- """
89
+ '''
portal/tests/test_2FA.py CHANGED
@@ -6,10 +6,11 @@ from django.contrib.auth.models import User
6
6
  from django.test import Client, TestCase
7
7
  from django.test.utils import override_settings
8
8
  from django.conf import settings
9
+ from django.urls import reverse
9
10
 
10
11
  from django_otp.util import random_hex
11
12
  from django_otp.oath import totp
12
- from django_otp import DEVICE_ID_SESSION_KEY
13
+ from django_otp import DEVICE_ID_SESSION_KEY, user_has_device
13
14
 
14
15
 
15
16
  class Test2FA(TestCase):
@@ -43,34 +44,19 @@ class Test2FA(TestCase):
43
44
 
44
45
  # In production tolerance value is 1 by default, which means it will accept any of three
45
46
  # tokens: the current one, the previous one, and the next one.
46
- device = user.totpdevice_set.create(
47
- name="default", key=random_hex(), tolerance=0
48
- )
47
+ device = user.totpdevice_set.create(name="default", key=random_hex(), tolerance=0)
49
48
 
50
49
  # For us the authentication is done in 2 steps. First step is 'auth'.
51
50
  response = _post(
52
- {
53
- "auth-username": self.email,
54
- "auth-password": self.password,
55
- "teacher_login_view-current_step": "auth",
56
- }
51
+ {"auth-username": self.email, "auth-password": self.password, "teacher_login_view-current_step": "auth"}
57
52
  )
58
53
  self.assertContains(response, "Token:")
59
54
 
60
55
  # Second step is 'token'. There's a time window when these 2 steps must be done.
61
56
  #
62
57
  # Test 1: test random token
63
- response = _post(
64
- {
65
- "token-otp_token": "123456",
66
- "teacher_login_view-current_step": "token",
67
- },
68
- )
69
- INVALID_ERR = {
70
- "__all__": [
71
- "Invalid token. Please make sure you have entered it correctly."
72
- ]
73
- }
58
+ response = _post({"token-otp_token": "123456", "teacher_login_view-current_step": "token"})
59
+ INVALID_ERR = {"__all__": ["Invalid token. Please make sure you have entered it correctly."]}
74
60
  assert response.context_data["wizard"]["form"].errors == INVALID_ERR
75
61
 
76
62
  # reset throttle to allow us to continue after failure
@@ -79,10 +65,7 @@ class Test2FA(TestCase):
79
65
  # Test 2: test with 1 token before (drift = -1 which would fail with tolerance = 0)
80
66
  # In production, drift of [-1, 0, 1] should work with tolerance = 1
81
67
  response = _post(
82
- {
83
- "token-otp_token": totp(device.bin_key, drift=-1),
84
- "teacher_login_view-current_step": "token",
85
- }
68
+ {"token-otp_token": totp(device.bin_key, drift=-1), "teacher_login_view-current_step": "token"}
86
69
  )
87
70
  assert response.context_data["wizard"]["form"].errors == INVALID_ERR
88
71
 
@@ -90,15 +73,14 @@ class Test2FA(TestCase):
90
73
  device.throttle_reset()
91
74
 
92
75
  # Test 3: test with valid current token (drift = 0)
93
- response = _post(
94
- {
95
- "token-otp_token": totp(device.bin_key, drift=0),
96
- "teacher_login_view-current_step": "token",
97
- }
98
- )
76
+ response = _post({"token-otp_token": totp(device.bin_key, drift=0), "teacher_login_view-current_step": "token"})
99
77
 
100
- self.assertRedirects(
101
- response, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
102
- )
78
+ self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False)
103
79
 
104
80
  assert device.persistent_id == self.client.session.get(DEVICE_ID_SESSION_KEY)
81
+
82
+ # test delete 2FA
83
+ url = reverse("teacher_disable_2FA", kwargs={"pk": user.new_teacher.id})
84
+ self.client.post(url)
85
+
86
+ assert not user_has_device(user)
@@ -1,15 +1,9 @@
1
- from common.tests.utils.classes import create_class_directly
2
- from common.tests.utils.organisation import create_organisation_directly
3
- from common.tests.utils.student import create_school_student_directly
4
- from common.tests.utils.teacher import signup_teacher_directly
5
- from django.contrib.auth.models import User, Permission
1
+ from django.test import Client
6
2
  from django.urls import reverse
7
-
8
3
  from portal.tests.base_test import BaseTest
9
- from portal.tests.pageObjects.portal.admin.admin_data_page import AdminDataPage
10
- from portal.tests.pageObjects.portal.admin.admin_map_page import AdminMapPage
11
4
  from portal.tests.pageObjects.portal.teacher_login_page import TeacherLoginPage
12
5
  from portal.views import admin
6
+ import pytest
13
7
 
14
8
 
15
9
  class TestAdmin(BaseTest):
@@ -24,101 +18,25 @@ class TestAdmin(BaseTest):
24
18
  self.selenium.get(url)
25
19
  return TeacherLoginPage(self.selenium)
26
20
 
27
- def navigate_to_admin_data_not_logged_in(self):
28
- url = self.live_server_url + reverse("aggregated_data")
29
- self.selenium.get(url)
30
- # gets redirected to login page when not logged in
31
- return TeacherLoginPage(self.selenium)
32
-
33
- def navigate_to_admin_map_not_logged_in(self):
34
- url = self.live_server_url + reverse("map")
35
- self.selenium.get(url)
36
- # gets redirected to login page when not logged in
37
- return TeacherLoginPage(self.selenium)
38
-
39
- def navigate_to_admin_data_logged_in(self):
40
- url = self.live_server_url + reverse("aggregated_data")
41
- self.selenium.get(url)
42
- # gets redirected to login page when not logged in
43
- return AdminDataPage(self.selenium, self.live_server_url)
44
-
45
- def navigate_to_admin_map_logged_in(self):
46
- url = self.live_server_url + reverse("map")
47
- self.selenium.get(url)
48
- # gets redirected to login page when not logged in
49
- return AdminMapPage(self.selenium, self.live_server_url)
50
-
51
- def navigate_to_admin_data(self):
52
- url = self.live_server_url + reverse("aggregated_data")
53
- self.selenium.get(url)
54
- # gets redirected to login page when not logged in
55
- return AdminDataPage(self.selenium, self.live_server_url)
56
-
57
- def navigate_to_admin_map(self):
58
- url = self.live_server_url + reverse("map")
59
- self.selenium.get(url)
60
- # gets redirected to login page when not logged in
61
- return AdminMapPage(self.selenium, self.live_server_url)
62
-
63
21
  # Checks all admin pages goes to admin_login when user is not logged in
64
22
  def test_navigate_to_admin_login(self):
65
23
  self.navigate_to_admin_login()
66
24
 
67
- def test_navigate_to_admin_data(self):
68
- self.navigate_to_admin_data_not_logged_in()
69
-
70
- def test_navigate_to_admin_map(self):
71
- self.navigate_to_admin_map_not_logged_in()
72
-
73
- # Check superuser access to each admin pages
74
- def test_superuser_access(self):
75
- email, password = signup_teacher_directly()
76
- create_organisation_directly(email)
77
- _, _, access_code = create_class_directly(email)
78
- create_school_student_directly(access_code)
79
-
80
- user = User.objects.get(username=email)
81
- user.is_superuser = True
82
- user.save()
83
-
84
- self.go_to_homepage().go_to_teacher_login_page().login(email, password)
85
- page = self.navigate_to_admin_data_logged_in()
86
- assert page.is_on_admin_data_page()
87
- page = page.go_to_admin_map_page()
88
- assert page.is_on_admin_map_page()
89
-
90
- # Check user with view_map_data permission can access to /admin/map but not /admin/data
91
- def test_view_map_data_permission_access(self):
92
- email, password = signup_teacher_directly()
93
- create_organisation_directly(email)
94
- _, _, access_code = create_class_directly(email)
95
- create_school_student_directly(access_code)
96
25
 
97
- user = User.objects.get(username=email)
98
- permission = Permission.objects.get(codename="view_map_data")
99
- user.user_permissions.add(permission)
100
- user.save()
26
+ @pytest.mark.django_db
27
+ def test_export_user_data():
28
+ admin_username = "codeforlife-portal@ocado.com"
29
+ admin_password = "abc123"
30
+ expected_data = ["indianajones@codeforlife.com", "Indiana", "Jones", "indianajones@codeforlife.com"]
101
31
 
102
- self.go_to_homepage().go_to_teacher_login_page().login(email, password)
103
- page = self.navigate_to_admin_map_logged_in()
104
- assert page.is_on_admin_map_page()
105
- page = page.go_to_admin_data_page_failure()
106
- assert page.is_on_403_forbidden()
32
+ c = Client()
33
+ c.login(username=admin_username, password=admin_password)
107
34
 
108
- # Check user with view_aggregated_data permission can access to /admin/data but not /admin/map
109
- def test_view_aggregated_data_permission_access(self):
110
- email, password = signup_teacher_directly()
111
- create_organisation_directly(email)
112
- _, _, access_code = create_class_directly(email)
113
- create_school_student_directly(access_code)
35
+ url = reverse("admin:auth_user_changelist")
36
+ data = {"action": "export_as_csv", "select_across": 0, "index": 0, "_selected_action": 11}
114
37
 
115
- user = User.objects.get(username=email)
116
- permission = Permission.objects.get(codename="view_aggregated_data")
117
- user.user_permissions.add(permission)
118
- user.save()
38
+ response = c.post(url, data)
39
+ csv_data = response.getvalue().decode("utf-8").split(",")
119
40
 
120
- self.go_to_homepage().go_to_teacher_login_page().login(email, password)
121
- page = self.navigate_to_admin_data_logged_in()
122
- assert page.is_on_admin_data_page()
123
- page = page.go_to_admin_map_page_failure()
124
- assert page.is_on_403_forbidden()
41
+ assert any(item in expected_data for item in csv_data)
42
+ assert "password" not in csv_data