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.
- cfl_common/common/__init__.py +1 -0
- cfl_common/common/app_settings.py +66 -0
- cfl_common/common/apps.py +6 -0
- cfl_common/common/context_processors.py +9 -0
- cfl_common/common/csp_config.py +85 -0
- cfl_common/common/helpers/__init__.py +0 -0
- cfl_common/common/helpers/data_migration_loader.py +42 -0
- cfl_common/common/helpers/emails.py +393 -0
- cfl_common/common/helpers/generators.py +52 -0
- cfl_common/common/helpers/organisation.py +10 -0
- cfl_common/common/mail.py +201 -0
- cfl_common/common/migrations/0001_initial.py +240 -0
- cfl_common/common/migrations/0002_emailverification.py +55 -0
- cfl_common/common/migrations/0003_aimmocharacter.py +31 -0
- cfl_common/common/migrations/0004_add_aimmocharacters.py +17 -0
- cfl_common/common/migrations/0005_add_worksheets.py +8 -0
- cfl_common/common/migrations/0006_update_aimmo_character_image_path.py +17 -0
- cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +8 -0
- cfl_common/common/migrations/0008_unlock_worksheet_3.py +11 -0
- cfl_common/common/migrations/0009_add_blocked_time_to_teacher_and_student.py +24 -0
- cfl_common/common/migrations/0010_remove_teacher_title.py +18 -0
- cfl_common/common/migrations/0011_student_login_id.py +18 -0
- cfl_common/common/migrations/0012_usersession.py +39 -0
- cfl_common/common/migrations/0013_class_school.py +42 -0
- cfl_common/common/migrations/0014_login_type.py +29 -0
- cfl_common/common/migrations/0015_dailyactivity.py +31 -0
- cfl_common/common/migrations/0016_joinreleasestudent.py +42 -0
- cfl_common/common/migrations/0017_copy_email_to_username.py +18 -0
- cfl_common/common/migrations/0018_update_aimmo_character_image_path.py +15 -0
- cfl_common/common/migrations/0019_aimmocharacter_alt.py +16 -0
- cfl_common/common/migrations/0020_class_is_active_and_null_access_code.py +23 -0
- cfl_common/common/migrations/0021_school_is_active.py +28 -0
- cfl_common/common/migrations/0022_school_cleanup.py +29 -0
- cfl_common/common/migrations/0023_userprofile_aimmo_badges.py +22 -0
- cfl_common/common/migrations/0024_teacher_invited_by.py +25 -0
- cfl_common/common/migrations/0025_schoolteacherinvitation.py +47 -0
- cfl_common/common/migrations/0026_teacher_remove_join_request.py +22 -0
- cfl_common/common/migrations/0027_class_created_by.py +25 -0
- cfl_common/common/migrations/0028_coding_club_downloads.py +23 -0
- cfl_common/common/migrations/0029_dynamicelement.py +22 -0
- cfl_common/common/migrations/0030_add_maintenance_banner.py +25 -0
- cfl_common/common/migrations/0031_improve_admin_panel.py +56 -0
- cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py +18 -0
- cfl_common/common/migrations/0033_password_reset_tracking_fields.py +23 -0
- cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +18 -0
- cfl_common/common/migrations/0035_rename_lockout_fields.py +27 -0
- cfl_common/common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +17 -0
- cfl_common/common/migrations/0037_migrate_email_verification.py +21 -0
- cfl_common/common/migrations/0038_delete_emailverification.py +16 -0
- cfl_common/common/migrations/0039_copy_email_to_username.py +18 -0
- cfl_common/common/migrations/0040_school_county.py +18 -0
- cfl_common/common/migrations/0041_populate_gb_counties.py +27 -0
- cfl_common/common/migrations/0042_totalactivity.py +25 -0
- cfl_common/common/migrations/0043_add_total_activity.py +30 -0
- cfl_common/common/migrations/0044_update_activity_models.py +33 -0
- cfl_common/common/migrations/0045_otp.py +23 -0
- cfl_common/common/migrations/0046_alter_school_country.py +19 -0
- cfl_common/common/migrations/0047_delete_school_postcode.py +16 -0
- cfl_common/common/migrations/0048_unique_school_names.py +42 -0
- cfl_common/common/migrations/0049_anonymise_orphan_users.py +29 -0
- cfl_common/common/migrations/0050_anonymise_orphan_schools.py +30 -0
- cfl_common/common/migrations/0051_verify_returning_users.py +26 -0
- cfl_common/common/migrations/0052_add_cse_fields.py +68 -0
- cfl_common/common/migrations/0053_clean_class_data.py +24 -0
- cfl_common/common/migrations/0054_delete_aimmo_models.py +20 -0
- cfl_common/common/migrations/0055_alter_schoolteacherinvitation_token.py +18 -0
- cfl_common/common/migrations/0056_set_non_school_teachers_as_non_admins.py +25 -0
- cfl_common/common/migrations/0057_teacher_teacher__is_admin.py +19 -0
- cfl_common/common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
- cfl_common/common/migrations/__init__.py +0 -0
- cfl_common/common/models.py +557 -0
- cfl_common/common/permissions.py +84 -0
- cfl_common/common/tests/__init__.py +0 -0
- cfl_common/common/tests/test_migration_anonymise_orphan_schools.py +30 -0
- cfl_common/common/tests/test_migration_anonymise_orphan_users.py +30 -0
- cfl_common/common/tests/test_migration_blocked_time.py +15 -0
- cfl_common/common/tests/test_migration_remove_teacher_title.py +13 -0
- cfl_common/common/tests/test_migration_unique_school_names.py +33 -0
- cfl_common/common/tests/test_migration_verify_returning_users.py +59 -0
- cfl_common/common/tests/test_models.py +87 -0
- cfl_common/common/tests/utils/__init__.py +0 -0
- cfl_common/common/tests/utils/classes.py +38 -0
- cfl_common/common/tests/utils/email.py +67 -0
- cfl_common/common/tests/utils/organisation.py +41 -0
- cfl_common/common/tests/utils/student.py +123 -0
- cfl_common/common/tests/utils/teacher.py +73 -0
- cfl_common/common/tests/utils/user.py +27 -0
- cfl_common/common/utils.py +56 -0
- cfl_common/setup.py +61 -0
- codeforlife_portal-8.9.9.dist-info/METADATA +226 -0
- {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/RECORD +339 -241
- {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/WHEEL +1 -1
- codeforlife_portal-8.9.9.dist-info/licenses/LICENSE.md +3 -0
- {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/top_level.txt +1 -0
- deploy/middleware/maintenance.py +25 -0
- deploy/middleware/screentime_warning.py +29 -0
- deploy/middleware/security.py +5 -6
- deploy/middleware/session_timeout.py +4 -2
- deploy/middleware/tmp_basic_auth.py +41 -0
- example_project/portal_test_settings.py +239 -0
- example_project/settings.py +156 -17
- example_project/urls.py +5 -6
- portal/__init__.py +1 -1
- portal/admin.py +142 -29
- portal/app_settings.py +8 -7
- portal/forms/dotmailer.py +6 -4
- portal/forms/invite_teacher.py +19 -10
- portal/forms/organisation.py +137 -68
- portal/forms/play.py +53 -98
- portal/forms/registration.py +70 -164
- portal/forms/teach.py +147 -121
- portal/handlers.py +1 -2
- portal/helpers/decorators.py +30 -10
- portal/helpers/password.py +86 -47
- portal/helpers/ratelimit.py +32 -15
- portal/helpers/regexes.py +5 -0
- portal/helpers/request_handlers.py +10 -0
- portal/migrations/0044_auto_20150430_0959.py +6 -2
- portal/mixins/__init__.py +1 -0
- portal/mixins/cron_mixin.py +12 -0
- portal/permissions/__init__.py +1 -0
- portal/permissions/is_cron_request_from_google.py +14 -0
- portal/static/portal/img/10_years_anniversary.png +0 -0
- portal/static/portal/img/RR_logo_grass_background.png +0 -0
- portal/static/portal/img/coding_club_hero.jpg +0 -0
- portal/static/portal/img/coding_club_python_pack.png +0 -0
- portal/static/portal/img/facebook.png +0 -0
- portal/static/portal/img/gitbook.png +0 -0
- portal/static/portal/img/howe_dell_1.png +0 -0
- portal/static/portal/img/howe_dell_2.png +0 -0
- portal/static/portal/img/howe_dell_3.png +0 -0
- portal/static/portal/img/logo_cfl.png +0 -0
- portal/static/portal/img/logo_cfl_powered.svg +35 -0
- portal/static/portal/img/logo_cfl_reminder_cards.jpg +0 -0
- portal/static/portal/img/logo_ocado_group.png +0 -0
- portal/static/portal/img/logo_python_den.svg +21 -0
- portal/static/portal/img/long_europe_map.png +0 -0
- portal/static/portal/img/python_den.png +0 -0
- portal/static/portal/img/python_den_banner.svg +26 -0
- portal/static/portal/img/rapid_router_landing_hero.png +0 -0
- portal/static/portal/img/rr_advanced.png +0 -0
- portal/static/portal/img/ten_year_map_pin.svg +1 -0
- portal/static/portal/img/thumbnail_educate_rapid_router.png +0 -0
- portal/static/portal/img/thumbnail_educate_resources.png +0 -0
- portal/static/portal/img/thumbnail_play_rapid_router.png +0 -0
- portal/static/portal/img/thumbnail_python_den.png +0 -0
- portal/static/portal/img/twitter.png +0 -0
- portal/static/portal/js/carouselCards.js +25 -0
- portal/static/portal/js/common.js +96 -1
- portal/static/portal/js/independentLogin.js +16 -0
- portal/static/portal/js/independentRegistration.js +86 -0
- portal/static/portal/js/levelControl.js +77 -0
- portal/static/portal/js/lib/jquery.min.js +2 -0
- portal/static/portal/js/organisation_manage.js +142 -14
- portal/static/portal/js/passwordStrength.js +154 -64
- portal/static/portal/js/resetPassword.js +23 -0
- portal/static/portal/js/riveted.min.js +238 -239
- portal/static/portal/js/school.js +13 -0
- portal/static/portal/js/studentLogin.js +16 -0
- portal/static/portal/js/teacherEditStudent.js +23 -0
- portal/static/portal/js/teacherLogin.js +16 -0
- portal/static/portal/js/tenYearMap.js +14 -0
- portal/static/portal/sass/colorbox.scss +0 -1
- portal/static/portal/sass/modules/_colours.scss +1 -0
- portal/static/portal/sass/modules/_levels.scss +1 -1
- portal/static/portal/sass/modules/_mixins.scss +21 -0
- portal/static/portal/sass/partials/_banners.scss +4 -177
- portal/static/portal/sass/partials/_buttons.scss +12 -15
- portal/static/portal/sass/partials/_carousel.scss +129 -0
- portal/static/portal/sass/partials/_footer.scss +21 -22
- portal/static/portal/sass/partials/_forms.scss +60 -5
- portal/static/portal/sass/partials/_grids.scss +34 -61
- portal/static/portal/sass/partials/_header.scss +28 -20
- portal/static/portal/sass/partials/_images.scss +292 -39
- portal/static/portal/sass/partials/_popup.scss +18 -15
- portal/static/portal/sass/partials/_tables.scss +12 -20
- portal/static/portal/sass/partials/_text.scss +6 -10
- portal/static/portal/sass/styles.scss +0 -1
- portal/static/portal/video/code for life .pdf +0 -0
- portal/strings/about.py +5 -0
- portal/strings/coding_club.py +9 -0
- portal/strings/play.py +6 -5
- portal/strings/teach.py +1 -1
- portal/strings/teacher_resources.py +2 -8
- portal/strings/ten_year_map.py +13 -0
- portal/templates/403.html +2 -2
- portal/templates/404.html +1 -1
- portal/templates/500.html +2 -2
- portal/templates/{captcha → django_recaptcha}/includes/js_v2_invisible.html +3 -3
- portal/templates/{captcha → django_recaptcha}/widget_v2_invisible.html +2 -2
- portal/templates/email.html +4 -2
- portal/templates/maintenance.html +34 -0
- portal/templates/portal/about.html +94 -62
- portal/templates/portal/base.html +176 -152
- portal/templates/portal/coding_club.html +100 -0
- portal/templates/portal/contribute.html +56 -52
- portal/templates/portal/email_invitation_sent.html +1 -1
- portal/templates/portal/email_style_template.html +374 -0
- portal/templates/portal/email_verification_failed.html +1 -1
- portal/templates/portal/email_verification_needed.html +9 -9
- portal/templates/portal/form_shapes.html +20 -8
- portal/templates/portal/getinvolved.html +6 -6
- portal/templates/portal/home.html +35 -10
- portal/templates/portal/home_learning.html +19 -19
- portal/templates/portal/locked_out.html +0 -1
- portal/templates/portal/locked_out_school_student.html +16 -0
- portal/templates/portal/login/independent_student.html +31 -15
- portal/templates/portal/login/student.html +10 -7
- portal/templates/portal/login/student_class_code.html +7 -4
- portal/templates/portal/login/teacher.html +34 -17
- portal/templates/portal/partials/banner.html +18 -4
- portal/templates/portal/partials/benefits.html +1 -1
- portal/templates/portal/partials/card_list.html +34 -24
- portal/templates/portal/partials/character_list.html +5 -5
- portal/templates/portal/partials/cookie_list.html +161 -0
- portal/templates/portal/partials/delete_popup.html +18 -0
- portal/templates/portal/partials/footer.html +57 -26
- portal/templates/portal/partials/header.html +118 -117
- portal/templates/portal/partials/hero_card.html +4 -3
- portal/templates/portal/partials/info_popup.html +3 -3
- portal/templates/portal/partials/invite_admin_teacher.html +23 -0
- portal/templates/portal/partials/popup.html +7 -2
- portal/templates/portal/partials/register_newsletter_tickbox.html +2 -5
- portal/templates/portal/partials/screentime_popup.html +14 -0
- portal/templates/portal/partials/service_unavailable_popup.html +17 -0
- portal/templates/portal/partials/session_popup.html +19 -0
- portal/templates/portal/play/student_dashboard.html +42 -29
- portal/templates/portal/play/student_edit_account.html +64 -9
- portal/templates/portal/play.html +61 -41
- portal/templates/portal/privacy_notice.html +697 -0
- portal/templates/portal/register.html +122 -92
- portal/templates/portal/reset_password.html +20 -40
- portal/templates/portal/reset_password_confirm.html +9 -4
- portal/templates/portal/reset_password_email_sent.html +15 -13
- portal/templates/portal/teach/base_registering.html +1 -1
- portal/templates/portal/teach/class.html +4 -6
- portal/templates/portal/teach/dashboard.html +212 -117
- portal/templates/portal/teach/invited.html +90 -0
- portal/templates/portal/teach/onboarding_classes.html +5 -3
- portal/templates/portal/teach/onboarding_print.html +1 -1
- portal/templates/portal/teach/onboarding_school.html +26 -139
- portal/templates/portal/teach/onboarding_students.html +1 -1
- portal/templates/portal/teach/teacher_dismiss_students.html +73 -55
- portal/templates/portal/teach/teacher_edit_class.html +168 -11
- portal/templates/portal/teach/teacher_edit_student.html +12 -5
- portal/templates/portal/teach/teacher_move_all_classes.html +25 -38
- portal/templates/portal/teach/teacher_move_students_to_class.html +1 -1
- portal/templates/portal/teach.html +61 -42
- portal/templates/portal/ten_year_map.html +147 -0
- portal/templates/portal/terms.html +191 -42
- portal/templates/two_factor/core/login.html +71 -59
- portal/templates/two_factor/core/setup.html +58 -49
- portal/templates/two_factor/profile/disable.html +1 -1
- portal/templates/two_factor/profile/profile.html +35 -17
- portal/templatetags/app_tags.py +59 -84
- portal/templatetags/card_list_tags.py +0 -4
- portal/tests/base_test.py +14 -3
- portal/tests/conftest.py +0 -15
- portal/tests/migrations/test_migration_make_portaladmin_teacher.py +2 -6
- portal/tests/migrations/test_migration_preview_users.py +3 -9
- portal/tests/migrations/test_migration_remove_guardian.py +1 -3
- portal/tests/migrations/test_migration_use_common_models.py +2 -6
- portal/tests/migrations/test_migration_verify_portaladmin.py +1 -3
- portal/tests/pageObjects/portal/admin/admin_base_page.py +0 -21
- portal/tests/pageObjects/portal/base_page.py +16 -26
- portal/tests/pageObjects/portal/email_verification_needed_page.py +3 -2
- portal/tests/pageObjects/portal/game_page.py +12 -19
- portal/tests/pageObjects/portal/home_page.py +13 -15
- portal/tests/pageObjects/portal/independent_login_page.py +13 -17
- portal/tests/pageObjects/portal/password_reset_form_page.py +20 -4
- portal/tests/pageObjects/portal/password_reset_page.py +25 -0
- portal/tests/pageObjects/portal/play/account_page.py +18 -27
- portal/tests/pageObjects/portal/play/dashboard_page.py +4 -4
- portal/tests/pageObjects/portal/play/join_school_or_club_page.py +8 -10
- portal/tests/pageObjects/portal/play/play_base_page.py +5 -3
- portal/tests/pageObjects/portal/signup_page.py +28 -59
- portal/tests/pageObjects/portal/student_login_class_code.py +6 -9
- portal/tests/pageObjects/portal/student_login_page.py +6 -8
- portal/tests/pageObjects/portal/teach/add_independent_student_to_class_page.py +3 -3
- portal/tests/pageObjects/portal/teach/added_independent_student_to_class_page.py +3 -1
- portal/tests/pageObjects/portal/teach/class_page.py +36 -13
- portal/tests/pageObjects/portal/teach/dashboard_page.py +43 -84
- portal/tests/pageObjects/portal/teach/dismiss_students_page.py +7 -5
- portal/tests/pageObjects/portal/teach/edit_student_page.py +10 -8
- portal/tests/pageObjects/portal/teach/move_class_page.py +5 -10
- portal/tests/pageObjects/portal/teach/move_classes_page.py +4 -2
- portal/tests/pageObjects/portal/teach/move_students_disambiguate_page.py +4 -2
- portal/tests/pageObjects/portal/teach/move_students_page.py +6 -13
- portal/tests/pageObjects/portal/teach/onboarding_classes_page.py +5 -3
- portal/tests/pageObjects/portal/teach/onboarding_organisation_page.py +11 -49
- portal/tests/pageObjects/portal/teach/onboarding_student_list_page.py +7 -12
- portal/tests/pageObjects/portal/teach/onboarding_students_page.py +4 -27
- portal/tests/pageObjects/portal/teach/teach_base_page.py +6 -4
- portal/tests/pageObjects/portal/teacher_login_page.py +10 -16
- portal/tests/selenium_test_case.py +3 -43
- portal/tests/snapshots/snap_test_partials.py +11 -165
- portal/tests/test_2FA.py +15 -33
- portal/tests/test_admin.py +15 -97
- portal/tests/test_api.py +212 -91
- portal/tests/test_captcha_forms.py +2 -2
- portal/tests/test_class.py +374 -24
- portal/tests/test_emails.py +83 -20
- portal/tests/{test_newsletter_footer.py → test_global_forms.py} +5 -5
- portal/tests/test_helper_methods.py +30 -0
- portal/tests/test_independent_student.py +255 -144
- portal/tests/test_invite_teacher.py +318 -10
- portal/tests/test_middleware.py +96 -9
- portal/tests/test_organisation.py +78 -262
- portal/tests/test_partials.py +0 -88
- portal/tests/test_ratelimit.py +218 -36
- portal/tests/test_school_student.py +35 -40
- portal/tests/test_security.py +12 -31
- portal/tests/test_teacher.py +425 -325
- portal/tests/test_teacher_student.py +103 -91
- portal/tests/test_views.py +900 -76
- portal/tests/utils/classes.py +2 -2
- portal/tests/utils/messages.py +13 -28
- portal/urls.py +235 -166
- portal/views/admin.py +0 -332
- portal/views/api.py +82 -48
- portal/views/cron/__init__.py +1 -0
- portal/views/cron/user.py +322 -0
- portal/views/dotmailer.py +9 -1
- portal/views/email.py +33 -77
- portal/views/google_analytics.py +28 -0
- portal/views/home.py +126 -97
- portal/views/legal.py +13 -0
- portal/views/login/independent_student.py +5 -5
- portal/views/login/student.py +51 -14
- portal/views/login/teacher.py +2 -6
- portal/views/organisation.py +20 -189
- portal/views/registration.py +97 -17
- portal/views/student/edit_account_details.py +99 -72
- portal/views/student/play.py +81 -62
- portal/views/teacher/dashboard.py +421 -149
- portal/views/teacher/teach.py +226 -177
- portal/views/two_factor/core.py +22 -19
- portal/views/two_factor/profile.py +2 -2
- codeforlife_portal-5.33.5.dist-info/LICENSE.md +0 -577
- codeforlife_portal-5.33.5.dist-info/METADATA +0 -38
- deploy/permissions.py +0 -2
- example_project/manage.py +0 -10
- portal/autoconfig.py +0 -141
- portal/csp_config.py +0 -60
- portal/forms/add_game.py +0 -33
- portal/helpers/location.py +0 -121
- portal/static/portal/img/kurono_hero.jpg +0 -0
- portal/static/portal/img/kurono_landing_hero.png +0 -0
- portal/static/portal/img/kurono_logo.svg +0 -1
- portal/static/portal/img/kurono_logo_grey_background.svg +0 -1
- portal/static/portal/img/kurono_logo_mark.svg +0 -1
- portal/static/portal/img/kurono_resources_hero.jpg +0 -0
- portal/static/portal/img/kurono_story.png +0 -0
- portal/static/portal/img/ocado-swirl.svg +0 -22
- portal/static/portal/img/thumbnail_educate_kurono.png +0 -0
- portal/static/portal/img/thumbnail_educate_resources_and_progress_tracking.png +0 -0
- portal/static/portal/img/thumbnail_kurono_resources.png +0 -0
- portal/static/portal/img/thumbnail_play_kurono.png +0 -0
- portal/static/portal/img/x_close_video.png +0 -0
- portal/static/portal/js/aimmoGame.js +0 -106
- portal/static/portal/js/deleteWorkspaces.js +0 -14
- portal/static/portal/js/fuzzySchoolLookup.js +0 -46
- portal/static/portal/js/lib/jquery-3.5.1.min.js +0 -2
- portal/static/portal/js/lib/jquery-ui-1.12.1.min.js +0 -13
- portal/static/portal/sass/partials/_videos.scss +0 -10
- portal/static/portal/video/aimmo_play_now_background_video.mp4 +0 -0
- portal/strings/student_aimmo_dashboard.py +0 -6
- portal/templates/portal/admin/aggregated_data.html +0 -35
- portal/templates/portal/admin/map.html +0 -70
- portal/templates/portal/mouseflow.html +0 -9
- portal/templates/portal/partials/aimmo_games_table.html +0 -83
- portal/templates/portal/partials/register_over_required_age_tickbox.html +0 -9
- portal/templates/portal/play/independent_student_dashboard.html +0 -64
- portal/templates/portal/play/student_aimmo_dashboard.html +0 -63
- portal/templates/portal/privacy_policy.html +0 -483
- portal/templates/portal/reset_password_email.html +0 -9
- portal/templates/portal/teach/invite.html +0 -25
- portal/templates/portal/teach/teacher_aimmo_dashboard.html +0 -95
- portal/templates/portal/teach/teacher_resources.html +0 -68
- portal/templatetags/character_list_tags.py +0 -16
- portal/tests/pageObjects/portal/kurono_teacher_dashboard_page.py +0 -49
- portal/tests/pageObjects/portal/student_password_reset_form_page.py +0 -23
- portal/tests/pageObjects/portal/teach/onboarding_revoke_request_page.py +0 -20
- portal/tests/pageObjects/portal/teacher_password_reset_form_page.py +0 -23
- portal/tests/test_aimmo_dashboards.py +0 -172
- portal/tests/test_location.py +0 -217
- portal/tests/utils/aimmo_games.py +0 -30
- portal/views/aimmo/dashboard.py +0 -119
- portal/views/privacy_policy.py +0 -9
- portal/views/teacher/teacher_resources.py +0 -42
- {portal/views/aimmo → cfl_common}/__init__.py +0 -0
|
@@ -1,19 +1,327 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
from time import sleep
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from common.models import SchoolTeacherInvitation, Teacher
|
|
7
|
+
from common.tests.utils.classes import create_class_directly
|
|
8
|
+
from common.tests.utils.organisation import create_organisation_directly, join_teacher_to_organisation
|
|
9
|
+
from common.tests.utils.teacher import signup_teacher_directly
|
|
10
|
+
from django.contrib.messages import get_messages
|
|
11
|
+
from django.core import mail
|
|
12
|
+
from django.test import Client, TestCase
|
|
1
13
|
from django.urls import reverse
|
|
2
|
-
from django.
|
|
14
|
+
from django.utils import timezone
|
|
15
|
+
from selenium.webdriver.common.by import By
|
|
16
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
17
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
|
18
|
+
|
|
19
|
+
from portal.tests.base_test import BaseTest
|
|
20
|
+
|
|
21
|
+
FADE_TIME = 0.9
|
|
22
|
+
WAIT_TIME = 15
|
|
3
23
|
|
|
4
24
|
|
|
5
25
|
class TestInviteTeacher(TestCase):
|
|
6
26
|
def test_invite_teacher_successful(self):
|
|
7
|
-
|
|
27
|
+
email, password = signup_teacher_directly()
|
|
28
|
+
school = create_organisation_directly(email)
|
|
29
|
+
create_class_directly(email)
|
|
30
|
+
teacher = Teacher.objects.get(new_user__email=email)
|
|
31
|
+
|
|
8
32
|
client = Client()
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
client.login(username=email, password=password)
|
|
34
|
+
|
|
35
|
+
invited_teacher_first_name = "Valid"
|
|
36
|
+
invited_teacher_last_name = "Name"
|
|
37
|
+
invited_teacher_email = "valid_email@example.com"
|
|
38
|
+
invited_teacher_password = "$RRFVBGT%^yhnmju7"
|
|
39
|
+
|
|
40
|
+
# Invite another teacher to school and check they got an email
|
|
41
|
+
dashboard_url = reverse("dashboard")
|
|
42
|
+
data = {
|
|
43
|
+
"teacher_first_name": invited_teacher_first_name,
|
|
44
|
+
"teacher_last_name": invited_teacher_last_name,
|
|
45
|
+
"teacher_email": invited_teacher_email,
|
|
46
|
+
"invite_teacher": "",
|
|
47
|
+
}
|
|
48
|
+
assert len(mail.outbox) == 0
|
|
49
|
+
response = client.post(dashboard_url, data)
|
|
50
|
+
assert response.status_code == 200
|
|
51
|
+
messages = list(response.context["messages"])
|
|
52
|
+
assert len(messages) == 1
|
|
53
|
+
assert (
|
|
54
|
+
str(messages[0])
|
|
55
|
+
== f"You have invited {invited_teacher_first_name} {invited_teacher_last_name} to your school."
|
|
56
|
+
)
|
|
57
|
+
assert len(mail.outbox) == 1
|
|
58
|
+
client.logout()
|
|
59
|
+
|
|
60
|
+
# Complete the registration as the invited teacher
|
|
61
|
+
invitation = SchoolTeacherInvitation.objects.get(invited_teacher_email=invited_teacher_email)
|
|
62
|
+
invitation_url = reverse("invited_teacher", kwargs={"token": invitation.token})
|
|
63
|
+
response = client.post(
|
|
64
|
+
invitation_url,
|
|
65
|
+
{
|
|
66
|
+
"teacher_signup-teacher_password": invited_teacher_password,
|
|
67
|
+
"teacher_signup-teacher_confirm_password": invited_teacher_password,
|
|
68
|
+
"teacher_signup-consent_ticked": "on",
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Check the message displays correctly after registration
|
|
73
|
+
messages = [m.message for m in get_messages(response.wsgi_request)]
|
|
74
|
+
assert len(messages) == 1
|
|
75
|
+
assert messages[0] == "Your account has been created successfully, please log in."
|
|
76
|
+
|
|
77
|
+
# Check that the teacher account is created successfully and linked to the school
|
|
78
|
+
invited_teacher = Teacher.objects.get(new_user__email=invited_teacher_email)
|
|
79
|
+
assert invited_teacher.new_user.first_name == invited_teacher_first_name
|
|
80
|
+
assert invited_teacher.new_user.last_name == invited_teacher_last_name
|
|
81
|
+
assert invited_teacher.school == school
|
|
82
|
+
assert invited_teacher.invited_by == teacher
|
|
83
|
+
|
|
84
|
+
# Check that the invitation is now inactive
|
|
85
|
+
with pytest.raises(SchoolTeacherInvitation.DoesNotExist):
|
|
86
|
+
SchoolTeacherInvitation.objects.get(invited_teacher_email=invited_teacher_email)
|
|
87
|
+
old_invitation = SchoolTeacherInvitation._base_manager.get(id=invitation.id)
|
|
88
|
+
assert old_invitation.invited_teacher_first_name != invited_teacher_first_name
|
|
89
|
+
assert old_invitation.invited_teacher_last_name != invited_teacher_last_name
|
|
90
|
+
assert old_invitation.invited_teacher_email != invited_teacher_email
|
|
91
|
+
assert not old_invitation.is_active
|
|
13
92
|
|
|
14
93
|
def test_invite_teacher_fail(self):
|
|
15
|
-
|
|
94
|
+
email, password = signup_teacher_directly()
|
|
95
|
+
school = create_organisation_directly(email)
|
|
96
|
+
create_class_directly(email)
|
|
97
|
+
teacher = Teacher.objects.get(new_user__email=email)
|
|
98
|
+
|
|
99
|
+
client = Client()
|
|
100
|
+
client.login(username=email, password=password)
|
|
101
|
+
|
|
102
|
+
# Try to invite a teacher with an invalid email address
|
|
103
|
+
dashboard_url = reverse("dashboard")
|
|
104
|
+
data = {
|
|
105
|
+
"teacher_first_name": "Valid",
|
|
106
|
+
"teacher_last_name": "Name",
|
|
107
|
+
"teacher_email": "invalid_email",
|
|
108
|
+
"invite_teacher": "",
|
|
109
|
+
}
|
|
110
|
+
response = client.post(dashboard_url, data)
|
|
111
|
+
assert len(response.context["invite_teacher_form"]["teacher_email"].errors) == 1
|
|
112
|
+
assert response.context["invite_teacher_form"]["teacher_email"].errors[0] == "Enter a valid email address."
|
|
113
|
+
client.logout()
|
|
114
|
+
|
|
115
|
+
# Try to access an invitation with an invalid token
|
|
116
|
+
invitation_url = reverse("invited_teacher", kwargs={"token": "1"})
|
|
117
|
+
response = client.get(invitation_url)
|
|
118
|
+
assert response.context["error_message"] == "Uh oh, the Invitation does not exist or it has expired. 😞"
|
|
119
|
+
|
|
120
|
+
# Try to access an expired invitation
|
|
121
|
+
expired_invitation = SchoolTeacherInvitation.objects.create(
|
|
122
|
+
token=uuid4().hex,
|
|
123
|
+
school=school,
|
|
124
|
+
from_teacher=teacher,
|
|
125
|
+
invited_teacher_first_name="Valid",
|
|
126
|
+
invited_teacher_last_name="Name",
|
|
127
|
+
invited_teacher_email="valid@cfl.com",
|
|
128
|
+
expiry=timezone.now() - timedelta(days=1),
|
|
129
|
+
)
|
|
130
|
+
invitation_url = reverse("invited_teacher", kwargs={"token": expired_invitation.token})
|
|
131
|
+
response = client.get(invitation_url)
|
|
132
|
+
assert response.context["error_message"] == "Uh oh, the Invitation does not exist or it has expired. 😞"
|
|
133
|
+
|
|
134
|
+
# Try to access an invitation for an account that already exists
|
|
135
|
+
same_account_invitation = SchoolTeacherInvitation.objects.create(
|
|
136
|
+
token=uuid4().hex,
|
|
137
|
+
school=school,
|
|
138
|
+
from_teacher=teacher,
|
|
139
|
+
invited_teacher_first_name="Valid",
|
|
140
|
+
invited_teacher_last_name="Name",
|
|
141
|
+
invited_teacher_email=email,
|
|
142
|
+
expiry=timezone.now() + timedelta(days=1),
|
|
143
|
+
)
|
|
144
|
+
invitation_url = reverse("invited_teacher", kwargs={"token": same_account_invitation.token})
|
|
145
|
+
response = client.get(invitation_url)
|
|
146
|
+
assert response.context["error_message"] == (
|
|
147
|
+
"It looks like an account is already registered with this email address. You will need to delete the "
|
|
148
|
+
"other account first or change the email associated with it in order to proceed. You will then be able to "
|
|
149
|
+
"access this page."
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def test_invite_permissions(self):
|
|
153
|
+
# Create an admin and a standard teacher in the same school
|
|
154
|
+
admin_email, admin_password = signup_teacher_directly()
|
|
155
|
+
school = create_organisation_directly(admin_email)
|
|
156
|
+
create_class_directly(admin_email)
|
|
157
|
+
|
|
158
|
+
standard_email, standard_password = signup_teacher_directly()
|
|
159
|
+
join_teacher_to_organisation(standard_email, school.name)
|
|
160
|
+
create_class_directly(standard_email)
|
|
161
|
+
|
|
162
|
+
# Log in as standard teacher, try inviting a teacher, no invitation should be created
|
|
163
|
+
client = Client()
|
|
164
|
+
client.login(username=standard_email, password=standard_password)
|
|
165
|
+
|
|
166
|
+
dashboard_url = reverse("dashboard")
|
|
167
|
+
data = {
|
|
168
|
+
"teacher_first_name": "Valid",
|
|
169
|
+
"teacher_last_name": "Name",
|
|
170
|
+
"teacher_email": "new@teacher.com",
|
|
171
|
+
"invite_teacher": "",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
response = client.post(dashboard_url, data)
|
|
175
|
+
|
|
176
|
+
assert response.status_code == 200
|
|
177
|
+
messages = list(response.context["messages"])
|
|
178
|
+
assert len(messages) == 0
|
|
179
|
+
assert len(mail.outbox) == 0
|
|
180
|
+
client.logout()
|
|
181
|
+
|
|
182
|
+
assert not SchoolTeacherInvitation.objects.filter(invited_teacher_email="new@teacher.com").exists()
|
|
183
|
+
|
|
184
|
+
# Log in as admin teacher to invite a teacher
|
|
185
|
+
client.login(username=admin_email, password=admin_password)
|
|
186
|
+
|
|
187
|
+
dashboard_url = reverse("dashboard")
|
|
188
|
+
data = {
|
|
189
|
+
"teacher_first_name": "Valid",
|
|
190
|
+
"teacher_last_name": "Name",
|
|
191
|
+
"teacher_email": "new@teacher.com",
|
|
192
|
+
"invite_teacher": "",
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
client.post(dashboard_url, data)
|
|
196
|
+
client.logout()
|
|
197
|
+
|
|
198
|
+
assert SchoolTeacherInvitation.objects.filter(invited_teacher_email="new@teacher.com").exists()
|
|
199
|
+
invite = SchoolTeacherInvitation.objects.get(invited_teacher_email="new@teacher.com")
|
|
200
|
+
|
|
201
|
+
# Log in as standard teacher, try resending and deleting the invitation, both should fail
|
|
202
|
+
client.login(username=standard_email, password=standard_password)
|
|
203
|
+
|
|
204
|
+
response = client.post(reverse("resend_invite_teacher", kwargs={"token": invite.token}))
|
|
205
|
+
message = list(response.wsgi_request._messages)[0].message
|
|
206
|
+
assert message == "You do not have permission to perform this action or the invite does not exist"
|
|
207
|
+
|
|
208
|
+
response = client.post(reverse("delete_teacher_invite", kwargs={"token": invite.token}))
|
|
209
|
+
message = list(response.wsgi_request._messages)[0].message
|
|
210
|
+
assert message == "You do not have permission to perform this action or the invite does not exist"
|
|
211
|
+
|
|
212
|
+
def test_delete_exception(self):
|
|
213
|
+
email, password = signup_teacher_directly()
|
|
214
|
+
create_organisation_directly(email)
|
|
215
|
+
create_class_directly(email)
|
|
216
|
+
|
|
16
217
|
client = Client()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
218
|
+
client.login(username=email, password=password)
|
|
219
|
+
|
|
220
|
+
response = client.post(reverse("delete_teacher_invite", kwargs={"token": "2345678"}))
|
|
221
|
+
message = list(response.wsgi_request._messages)[0].message
|
|
222
|
+
assert message == "You do not have permission to perform this action or the invite does not exist"
|
|
223
|
+
|
|
224
|
+
response = client.post(reverse("resend_invite_teacher", kwargs={"token": "2345678"}))
|
|
225
|
+
message = list(response.wsgi_request._messages)[0].message
|
|
226
|
+
assert message == "You do not have permission to perform this action or the invite does not exist"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class TestTeacherInviteActions(BaseTest):
|
|
230
|
+
def test_revoke_and_make_admin_invite(self):
|
|
231
|
+
teacher_email, teacher_password = signup_teacher_directly()
|
|
232
|
+
create_organisation_directly(teacher_email)
|
|
233
|
+
class_name = "Test Class"
|
|
234
|
+
klass, _, _ = create_class_directly(teacher_email, class_name)
|
|
235
|
+
|
|
236
|
+
page = self.go_to_homepage()
|
|
237
|
+
page = page.go_to_teacher_login_page().login(teacher_email, teacher_password)
|
|
238
|
+
|
|
239
|
+
# Generate an invite and make admin
|
|
240
|
+
invite_data = {"teacher_first_name": "Adam", "teacher_last_name": "NotAdam", "teacher_email": "adam@adam.not"}
|
|
241
|
+
for key in invite_data.keys():
|
|
242
|
+
field = page.browser.find_element(By.NAME, key)
|
|
243
|
+
field.send_keys(invite_data[key])
|
|
244
|
+
|
|
245
|
+
# check if invite text for a user has been generated
|
|
246
|
+
page.browser.find_element(By.ID, "invite_teacher_button").click()
|
|
247
|
+
banner = page.browser.find_element(By.ID, "messages")
|
|
248
|
+
assert (
|
|
249
|
+
f"You have invited {invite_data['teacher_first_name']} {invite_data['teacher_last_name']} to your school."
|
|
250
|
+
in banner.text
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# check if popup message appears and if the invite is changed to admin
|
|
254
|
+
sleep(1) # this HAS to be there because of the animation :/
|
|
255
|
+
page.browser.find_element(By.ID, "make_admin_button_invite").click()
|
|
256
|
+
sleep(1)
|
|
257
|
+
page.browser.find_element(By.ID, "add_admin_button").click()
|
|
258
|
+
|
|
259
|
+
invite = SchoolTeacherInvitation.objects.filter(invited_teacher_first_name="Adam")[0]
|
|
260
|
+
assert invite.invited_teacher_is_admin
|
|
261
|
+
banner = page.browser.find_element(By.ID, "messages")
|
|
262
|
+
assert "Administrator invite status has been given successfully" in banner.text
|
|
263
|
+
|
|
264
|
+
# revoke admin
|
|
265
|
+
page.browser.find_element(By.ID, "make_non_admin_button_invite").click()
|
|
266
|
+
|
|
267
|
+
banner = page.browser.find_element(By.ID, "messages")
|
|
268
|
+
assert "Administrator invite status has been revoked successfully" in banner.text
|
|
269
|
+
|
|
270
|
+
def test_delete_invite(self):
|
|
271
|
+
teacher_email, teacher_password = signup_teacher_directly()
|
|
272
|
+
create_organisation_directly(teacher_email)
|
|
273
|
+
class_name = "Test Class"
|
|
274
|
+
klass, _, _ = create_class_directly(teacher_email, class_name)
|
|
275
|
+
|
|
276
|
+
page = self.go_to_homepage()
|
|
277
|
+
page = page.go_to_teacher_login_page().login(teacher_email, teacher_password)
|
|
278
|
+
|
|
279
|
+
# Generate an invite
|
|
280
|
+
invite_data = {"teacher_first_name": "Adam", "teacher_last_name": "NotAdam", "teacher_email": "adam@adam.not"}
|
|
281
|
+
for key in invite_data.keys():
|
|
282
|
+
field = page.browser.find_element(By.NAME, key)
|
|
283
|
+
field.send_keys(invite_data[key])
|
|
284
|
+
page.browser.find_element(By.NAME, "invite_teacher_button").click()
|
|
285
|
+
|
|
286
|
+
# check object was created
|
|
287
|
+
invite_queryset = SchoolTeacherInvitation.objects.filter(invited_teacher_first_name="Adam")
|
|
288
|
+
assert len(invite_queryset) == 1
|
|
289
|
+
sleep(FADE_TIME)
|
|
290
|
+
# delete
|
|
291
|
+
delete_invite_button = WebDriverWait(self.selenium, WAIT_TIME).until(
|
|
292
|
+
EC.element_to_be_clickable((By.ID, "delete-invite"))
|
|
293
|
+
)
|
|
294
|
+
delete_invite_button.click()
|
|
295
|
+
|
|
296
|
+
empty_invite_queryset = SchoolTeacherInvitation.objects.filter(invited_teacher_first_name="Adam")
|
|
297
|
+
assert len(empty_invite_queryset) == 0
|
|
298
|
+
|
|
299
|
+
def test_resend_invite(self):
|
|
300
|
+
teacher_email, teacher_password = signup_teacher_directly()
|
|
301
|
+
create_organisation_directly(teacher_email)
|
|
302
|
+
class_name = "Test Class"
|
|
303
|
+
klass, _, _ = create_class_directly(teacher_email, class_name)
|
|
304
|
+
|
|
305
|
+
page = self.go_to_homepage()
|
|
306
|
+
page = page.go_to_teacher_login_page().login(teacher_email, teacher_password)
|
|
307
|
+
|
|
308
|
+
# Generate an invite
|
|
309
|
+
invite_data = {"teacher_first_name": "Adam", "teacher_last_name": "NotAdam", "teacher_email": "adam@adam.not"}
|
|
310
|
+
for key in invite_data.keys():
|
|
311
|
+
field = page.browser.find_element(By.NAME, key)
|
|
312
|
+
field.send_keys(invite_data[key])
|
|
313
|
+
|
|
314
|
+
page.browser.find_element(By.ID, "invite_teacher_button").click()
|
|
315
|
+
|
|
316
|
+
banner = page.browser.find_element(By.XPATH, '//*[@id="messages"]/div/div/div/div/div/p')
|
|
317
|
+
assert (
|
|
318
|
+
banner.text
|
|
319
|
+
== f"You have invited {invite_data['teacher_first_name']} {invite_data['teacher_last_name']} to your school."
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# resend an invite
|
|
323
|
+
page.browser.find_element(By.ID, "resend-invite").click()
|
|
324
|
+
|
|
325
|
+
# check if invite was updated by 30 days (used 29 for rounding errors)
|
|
326
|
+
new_invite_expiry = SchoolTeacherInvitation.objects.filter(invited_teacher_first_name="Adam")[0].expiry
|
|
327
|
+
assert timezone.now() + timedelta(days=29) <= new_invite_expiry
|
portal/tests/test_middleware.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import time
|
|
2
|
+
from typing import Tuple
|
|
2
3
|
from unittest import mock
|
|
4
|
+
|
|
3
5
|
from _pytest.monkeypatch import MonkeyPatch
|
|
4
6
|
from common.tests.utils.classes import create_class_directly
|
|
5
7
|
from common.tests.utils.organisation import create_organisation_directly
|
|
@@ -9,6 +11,7 @@ from django.contrib import auth
|
|
|
9
11
|
from django.contrib.auth.models import User
|
|
10
12
|
from django.http import HttpResponseRedirect
|
|
11
13
|
from django.test import Client, TestCase
|
|
14
|
+
from django.urls import reverse
|
|
12
15
|
|
|
13
16
|
MOCKED_SESSION_EXPIRY_TIME = 5
|
|
14
17
|
|
|
@@ -29,9 +32,11 @@ class TestAdminAccessMiddleware(TestCase):
|
|
|
29
32
|
self.email, self.password = self._setup_user()
|
|
30
33
|
|
|
31
34
|
self.monkeypatch = MonkeyPatch()
|
|
32
|
-
self.monkeypatch.setattr(
|
|
35
|
+
self.monkeypatch.setattr(
|
|
36
|
+
"deploy.middleware.admin_access.MODULE_NAME", "test"
|
|
37
|
+
)
|
|
33
38
|
|
|
34
|
-
def _setup_user(self) ->
|
|
39
|
+
def _setup_user(self) -> Tuple[str, str]:
|
|
35
40
|
email, password = signup_teacher_directly()
|
|
36
41
|
create_organisation_directly(email)
|
|
37
42
|
_, _, access_code = create_class_directly(email)
|
|
@@ -97,7 +102,9 @@ class TestAdminAccessMiddleware(TestCase):
|
|
|
97
102
|
return_value=True,
|
|
98
103
|
autospec=True,
|
|
99
104
|
)
|
|
100
|
-
def test_superuser_with_2FA_can_access_admin_site(
|
|
105
|
+
def test_superuser_with_2FA_can_access_admin_site(
|
|
106
|
+
self, mock_using_two_factor
|
|
107
|
+
):
|
|
101
108
|
self._make_user_superuser()
|
|
102
109
|
|
|
103
110
|
self.client.login(username=self.email, password=self.password)
|
|
@@ -120,10 +127,10 @@ class TestSecurityMiddleware(TestCase):
|
|
|
120
127
|
response = client.get("/")
|
|
121
128
|
|
|
122
129
|
assert response.status_code == 200
|
|
123
|
-
assert response.
|
|
124
|
-
assert response.
|
|
125
|
-
assert response.
|
|
126
|
-
assert response.
|
|
130
|
+
assert response.headers["cache-control"] == "private"
|
|
131
|
+
assert response.headers["x-content-type-options"] == "nosniff"
|
|
132
|
+
assert response.headers["x-frame-options"] == "DENY"
|
|
133
|
+
assert response.headers["x-xss-protection"] == "1; mode=block"
|
|
127
134
|
|
|
128
135
|
|
|
129
136
|
class TestSessionTimeoutMiddleware(TestCase):
|
|
@@ -141,7 +148,7 @@ class TestSessionTimeoutMiddleware(TestCase):
|
|
|
141
148
|
"deploy.middleware.session_timeout.SESSION_EXPIRY_TIME", 5
|
|
142
149
|
)
|
|
143
150
|
|
|
144
|
-
def _setup_user(self) ->
|
|
151
|
+
def _setup_user(self) -> Tuple[str, str]:
|
|
145
152
|
email, password = signup_teacher_directly()
|
|
146
153
|
create_organisation_directly(email)
|
|
147
154
|
_, _, access_code = create_class_directly(email)
|
|
@@ -159,7 +166,87 @@ class TestSessionTimeoutMiddleware(TestCase):
|
|
|
159
166
|
|
|
160
167
|
time.sleep(MOCKED_SESSION_EXPIRY_TIME)
|
|
161
168
|
|
|
162
|
-
self.client.get("/")
|
|
169
|
+
response = self.client.get("/")
|
|
163
170
|
user = auth.get_user(self.client)
|
|
164
171
|
|
|
165
172
|
assert not user.is_authenticated
|
|
173
|
+
|
|
174
|
+
messages = list(response.context["messages"])
|
|
175
|
+
assert len(messages) > 0
|
|
176
|
+
assert str(messages[0]) == "You have been logged out due to inactivity."
|
|
177
|
+
|
|
178
|
+
def test_session_reset(self):
|
|
179
|
+
self.client.login(username=self.email, password=self.password)
|
|
180
|
+
|
|
181
|
+
self.client.get("/")
|
|
182
|
+
user = auth.get_user(self.client)
|
|
183
|
+
|
|
184
|
+
assert user.is_authenticated
|
|
185
|
+
|
|
186
|
+
time.sleep(MOCKED_SESSION_EXPIRY_TIME * 0.66)
|
|
187
|
+
|
|
188
|
+
url = reverse("reset_session_time")
|
|
189
|
+
self.client.get(url)
|
|
190
|
+
|
|
191
|
+
time.sleep(MOCKED_SESSION_EXPIRY_TIME * 0.66)
|
|
192
|
+
|
|
193
|
+
self.client.get("/")
|
|
194
|
+
user = auth.get_user(self.client)
|
|
195
|
+
|
|
196
|
+
assert user.is_authenticated
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class TestScreentimeWarningMiddleware(TestCase):
|
|
200
|
+
"""
|
|
201
|
+
This tests the ScreentimeWarningMiddleware class and popup timeout is set properly.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def setUp(self) -> None:
|
|
205
|
+
self.client = Client()
|
|
206
|
+
self.email, self.password = self._setup_user()
|
|
207
|
+
|
|
208
|
+
def _setup_user(self) -> Tuple[str, str]:
|
|
209
|
+
email, password = signup_teacher_directly()
|
|
210
|
+
create_organisation_directly(email)
|
|
211
|
+
_, _, access_code = create_class_directly(email)
|
|
212
|
+
create_school_student_directly(access_code)
|
|
213
|
+
|
|
214
|
+
return email, password
|
|
215
|
+
|
|
216
|
+
def test_screentime_warning_timeout(self):
|
|
217
|
+
# Timeout should not be there if the user is not logged in
|
|
218
|
+
session = self.client.session
|
|
219
|
+
assert "screentime_warning_timeout" not in session
|
|
220
|
+
|
|
221
|
+
# Log in as a teacher
|
|
222
|
+
self.client.login(username=self.email, password=self.password)
|
|
223
|
+
|
|
224
|
+
# Check the screentime_warning_timeout decreases after consecutive requests
|
|
225
|
+
self.client.get("/")
|
|
226
|
+
session = self.client.session
|
|
227
|
+
assert "screentime_warning_timeout" in session
|
|
228
|
+
previous_screentime_warning_timeout = session[
|
|
229
|
+
"screentime_warning_timeout"
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
self.client.get("/")
|
|
233
|
+
session = self.client.session
|
|
234
|
+
assert "screentime_warning_timeout" in session
|
|
235
|
+
new_screentime_warning_timeout = session["screentime_warning_timeout"]
|
|
236
|
+
|
|
237
|
+
assert (
|
|
238
|
+
new_screentime_warning_timeout < previous_screentime_warning_timeout
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Check the reset_screentime_warning API resets the timeout
|
|
242
|
+
url = reverse("reset_screentime_warning")
|
|
243
|
+
self.client.get(url)
|
|
244
|
+
self.client.get("/")
|
|
245
|
+
session = self.client.session
|
|
246
|
+
assert "screentime_warning_timeout" in session
|
|
247
|
+
renewed_screentime_warning_timeout = session[
|
|
248
|
+
"screentime_warning_timeout"
|
|
249
|
+
]
|
|
250
|
+
assert (
|
|
251
|
+
renewed_screentime_warning_timeout > new_screentime_warning_timeout
|
|
252
|
+
)
|