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
portal/tests/test_teacher.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from __future__ import absolute_import
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
-
import
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
from unittest.mock import ANY, Mock, patch
|
|
6
|
+
from uuid import uuid4
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
from common.
|
|
8
|
+
import jwt
|
|
9
|
+
from common.mail import campaign_ids
|
|
8
10
|
from common.tests.utils import email as email_utils
|
|
9
11
|
from common.tests.utils.classes import create_class_directly
|
|
10
12
|
from common.tests.utils.organisation import (
|
|
@@ -21,284 +23,29 @@ from common.tests.utils.teacher import (
|
|
|
21
23
|
signup_teacher_directly,
|
|
22
24
|
verify_email,
|
|
23
25
|
)
|
|
26
|
+
from django.conf import settings
|
|
24
27
|
from django.core import mail
|
|
25
28
|
from django.test import Client, TestCase
|
|
26
29
|
from django.urls import reverse
|
|
30
|
+
from django.utils import timezone
|
|
31
|
+
from selenium.webdriver.common.by import By
|
|
32
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
27
33
|
from selenium.webdriver.support.wait import WebDriverWait
|
|
28
34
|
|
|
29
35
|
from portal.forms.error_messages import INVALID_LOGIN_MESSAGE
|
|
36
|
+
from portal.tests.test_invite_teacher import WAIT_TIME
|
|
30
37
|
from .base_test import BaseTest
|
|
31
38
|
from .pageObjects.portal.home_page import HomePage
|
|
32
39
|
from .utils.messages import (
|
|
33
|
-
is_email_verified_message_showing,
|
|
34
|
-
is_teacher_details_updated_message_showing,
|
|
35
40
|
is_email_updated_message_showing,
|
|
41
|
+
is_email_verified_message_showing,
|
|
42
|
+
is_message_showing,
|
|
36
43
|
is_password_updated_message_showing,
|
|
44
|
+
is_teacher_details_updated_message_showing,
|
|
37
45
|
)
|
|
38
46
|
|
|
39
47
|
|
|
40
48
|
class TestTeacher(TestCase):
|
|
41
|
-
def test_new_student_can_play_games(self):
|
|
42
|
-
"""
|
|
43
|
-
Given a teacher has an kurono game,
|
|
44
|
-
When they add a new student to their class,
|
|
45
|
-
Then the new student should be able to play that class's games
|
|
46
|
-
"""
|
|
47
|
-
email, password = signup_teacher_directly()
|
|
48
|
-
create_organisation_directly(email)
|
|
49
|
-
klass, _, access_code = create_class_directly(email)
|
|
50
|
-
create_school_student_directly(access_code)
|
|
51
|
-
|
|
52
|
-
c = Client()
|
|
53
|
-
c.login(username=email, password=password)
|
|
54
|
-
c.post(
|
|
55
|
-
reverse("teacher_aimmo_dashboard"),
|
|
56
|
-
{"game_class": klass.id},
|
|
57
|
-
)
|
|
58
|
-
c.post(
|
|
59
|
-
reverse("view_class", kwargs={"access_code": access_code}),
|
|
60
|
-
{"names": "Florian"},
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
game = Game.objects.get(id=1)
|
|
64
|
-
new_student = Student.objects.last()
|
|
65
|
-
assert game.can_user_play(new_student.new_user)
|
|
66
|
-
|
|
67
|
-
def test_accepted_independent_student_can_play_games(self):
|
|
68
|
-
"""
|
|
69
|
-
Given an independent student requests access to a class,
|
|
70
|
-
When the teacher for that class accepts the request,
|
|
71
|
-
Then the new student should have access to that class's games
|
|
72
|
-
"""
|
|
73
|
-
email, password = signup_teacher_directly()
|
|
74
|
-
create_organisation_directly(email)
|
|
75
|
-
klass, _, access_code = create_class_directly(email)
|
|
76
|
-
klass.always_accept_requests = True
|
|
77
|
-
klass.save()
|
|
78
|
-
create_school_student_directly(access_code)
|
|
79
|
-
(
|
|
80
|
-
indep_username,
|
|
81
|
-
indep_password,
|
|
82
|
-
indep_student,
|
|
83
|
-
) = create_independent_student_directly()
|
|
84
|
-
|
|
85
|
-
c = Client()
|
|
86
|
-
|
|
87
|
-
c.login(username=indep_username, password=indep_password)
|
|
88
|
-
c.post(
|
|
89
|
-
reverse("student_join_organisation"),
|
|
90
|
-
{"access_code": access_code, "class_join_request": "Request"},
|
|
91
|
-
)
|
|
92
|
-
c.logout()
|
|
93
|
-
|
|
94
|
-
c.login(username=email, password=password)
|
|
95
|
-
c.post(
|
|
96
|
-
reverse("teacher_aimmo_dashboard"),
|
|
97
|
-
{"game_class": klass.pk},
|
|
98
|
-
)
|
|
99
|
-
c.post(
|
|
100
|
-
reverse("teacher_accept_student_request", kwargs={"pk": indep_student.pk}),
|
|
101
|
-
{"name": "Florian"},
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
game: Game = Game.objects.get(id=1)
|
|
105
|
-
new_student = Student.objects.last()
|
|
106
|
-
assert game.can_user_play(new_student.new_user)
|
|
107
|
-
|
|
108
|
-
def test_moved_class_has_correct_permissions_for_students_and_teachers(self):
|
|
109
|
-
"""
|
|
110
|
-
Given two teachers each with a class and an aimmo game,
|
|
111
|
-
When teacher 1 transfers their class to teacher 2,
|
|
112
|
-
Then:
|
|
113
|
-
- Students in each class still only have access to their class games
|
|
114
|
-
- Teacher 2 has access to both games and teacher 1 has access to none
|
|
115
|
-
"""
|
|
116
|
-
|
|
117
|
-
# Create teacher 1 -> class 1 -> student 1
|
|
118
|
-
email1, password1 = signup_teacher_directly()
|
|
119
|
-
school_name, postcode = create_organisation_directly(email1)
|
|
120
|
-
klass1, _, access_code1 = create_class_directly(email1, "Class 1")
|
|
121
|
-
create_school_student_directly(access_code1)
|
|
122
|
-
|
|
123
|
-
# Create teacher 2 -> class 2 -> student 2
|
|
124
|
-
email2, password2 = signup_teacher_directly()
|
|
125
|
-
join_teacher_to_organisation(email2, school_name, postcode)
|
|
126
|
-
klass2, _, access_code2 = create_class_directly(email2, "Class 2")
|
|
127
|
-
create_school_student_directly(access_code2)
|
|
128
|
-
|
|
129
|
-
teacher1: Teacher = Teacher.objects.get(new_user__email=email1)
|
|
130
|
-
teacher2: Teacher = Teacher.objects.get(new_user__email=email2)
|
|
131
|
-
|
|
132
|
-
c = Client()
|
|
133
|
-
|
|
134
|
-
# Create game 1 under class 1
|
|
135
|
-
c.login(username=email1, password=password1)
|
|
136
|
-
c.post(
|
|
137
|
-
reverse("teacher_aimmo_dashboard"),
|
|
138
|
-
{"game_class": klass1.pk},
|
|
139
|
-
)
|
|
140
|
-
c.logout()
|
|
141
|
-
|
|
142
|
-
# Create game 2 under class 2
|
|
143
|
-
c.login(username=email2, password=password2)
|
|
144
|
-
c.post(
|
|
145
|
-
reverse("teacher_aimmo_dashboard"),
|
|
146
|
-
{"game_class": klass2.pk},
|
|
147
|
-
)
|
|
148
|
-
c.logout()
|
|
149
|
-
|
|
150
|
-
game1: Game = Game.objects.get(owner=teacher1.new_user)
|
|
151
|
-
game2: Game = Game.objects.get(owner=teacher2.new_user)
|
|
152
|
-
|
|
153
|
-
student1: Student = Student.objects.get(class_field=klass1)
|
|
154
|
-
student2: Student = Student.objects.get(class_field=klass2)
|
|
155
|
-
|
|
156
|
-
# Check student permissions for each game
|
|
157
|
-
assert game1.can_user_play(student1.new_user)
|
|
158
|
-
assert game2.can_user_play(student2.new_user)
|
|
159
|
-
assert not game1.can_user_play(student2.new_user)
|
|
160
|
-
assert not game2.can_user_play(student1.new_user)
|
|
161
|
-
|
|
162
|
-
# Check teacher permissions for each game
|
|
163
|
-
assert game1.can_user_play(teacher1.new_user)
|
|
164
|
-
assert game2.can_user_play(teacher2.new_user)
|
|
165
|
-
assert not game1.can_user_play(teacher2.new_user)
|
|
166
|
-
assert not game2.can_user_play(teacher1.new_user)
|
|
167
|
-
|
|
168
|
-
# Transfer class 1 over to teacher 2
|
|
169
|
-
c.login(username=email1, password=password1)
|
|
170
|
-
response = c.post(
|
|
171
|
-
reverse("teacher_edit_class", kwargs={"access_code": access_code1}),
|
|
172
|
-
{"new_teacher": teacher2.pk, "class_move_submit": ""},
|
|
173
|
-
)
|
|
174
|
-
assert response.status_code == 302
|
|
175
|
-
c.logout()
|
|
176
|
-
|
|
177
|
-
# Refresh model instances
|
|
178
|
-
klass1: Class = Class.objects.get(pk=klass1.pk)
|
|
179
|
-
klass2: Class = Class.objects.get(pk=klass2.pk)
|
|
180
|
-
game1 = Game.objects.get(pk=game1.pk)
|
|
181
|
-
game2 = Game.objects.get(pk=game2.pk)
|
|
182
|
-
|
|
183
|
-
# Check teacher 2 is the teacher for class 1
|
|
184
|
-
assert klass1.teacher == teacher2
|
|
185
|
-
|
|
186
|
-
# Check that the students' permissions have not changed
|
|
187
|
-
assert game1.can_user_play(student1.new_user)
|
|
188
|
-
assert game2.can_user_play(student2.new_user)
|
|
189
|
-
assert not game1.can_user_play(student2.new_user)
|
|
190
|
-
assert not game2.can_user_play(student1.new_user)
|
|
191
|
-
|
|
192
|
-
# Check that teacher 1 cannot access class 1's game 1 anymore
|
|
193
|
-
assert not game1.can_user_play(teacher1.new_user)
|
|
194
|
-
|
|
195
|
-
# Check that teacher 2 can access game 1
|
|
196
|
-
assert game1.can_user_play(teacher2.new_user)
|
|
197
|
-
|
|
198
|
-
def test_moved_student_has_access_to_only_new_teacher_games(self):
|
|
199
|
-
"""
|
|
200
|
-
Given a student in a class,
|
|
201
|
-
When a teacher transfers them to another class with a new teacher,
|
|
202
|
-
Then the student should only have access to the new teacher's games
|
|
203
|
-
"""
|
|
204
|
-
|
|
205
|
-
email1, password1 = signup_teacher_directly()
|
|
206
|
-
school_name, postcode = create_organisation_directly(email1)
|
|
207
|
-
klass1, _, access_code1 = create_class_directly(email1, "Class 1")
|
|
208
|
-
create_school_student_directly(access_code1)
|
|
209
|
-
|
|
210
|
-
email2, password2 = signup_teacher_directly()
|
|
211
|
-
join_teacher_to_organisation(email2, school_name, postcode)
|
|
212
|
-
klass2, _, access_code2 = create_class_directly(email2, "Class 2")
|
|
213
|
-
create_school_student_directly(access_code2)
|
|
214
|
-
|
|
215
|
-
teacher1 = Teacher.objects.get(new_user__email=email1)
|
|
216
|
-
teacher2 = Teacher.objects.get(new_user__email=email2)
|
|
217
|
-
|
|
218
|
-
c = Client()
|
|
219
|
-
c.login(username=email2, password=password2)
|
|
220
|
-
c.post(
|
|
221
|
-
reverse("teacher_aimmo_dashboard"),
|
|
222
|
-
{"game_class": klass2.pk},
|
|
223
|
-
)
|
|
224
|
-
c.logout()
|
|
225
|
-
|
|
226
|
-
c.login(username=email1, password=password1)
|
|
227
|
-
c.post(
|
|
228
|
-
reverse("teacher_aimmo_dashboard"),
|
|
229
|
-
{"game_class": klass1.pk},
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
game1 = Game.objects.get(owner=teacher1.new_user)
|
|
233
|
-
game2 = Game.objects.get(owner=teacher2.new_user)
|
|
234
|
-
|
|
235
|
-
student1 = Student.objects.get(class_field=klass1)
|
|
236
|
-
student2 = Student.objects.get(class_field=klass2)
|
|
237
|
-
|
|
238
|
-
assert game1.can_user_play(student1.new_user)
|
|
239
|
-
assert game2.can_user_play(student2.new_user)
|
|
240
|
-
|
|
241
|
-
c.post(
|
|
242
|
-
reverse("teacher_move_students", kwargs={"access_code": access_code1}),
|
|
243
|
-
{"transfer_students": student1.pk},
|
|
244
|
-
)
|
|
245
|
-
c.post(
|
|
246
|
-
reverse(
|
|
247
|
-
"teacher_move_students_to_class", kwargs={"access_code": access_code1}
|
|
248
|
-
),
|
|
249
|
-
{
|
|
250
|
-
"form-0-name": student1.user.user.first_name,
|
|
251
|
-
"form-MAX_NUM_FORMS": 1000,
|
|
252
|
-
"form-0-orig_name": student1.user.user.first_name,
|
|
253
|
-
"form-TOTAL_FORMS": 1,
|
|
254
|
-
"form-MIN_NUM_FORMS": 0,
|
|
255
|
-
"submit_disambiguation": "",
|
|
256
|
-
"form-INITIAL_FORMS": 1,
|
|
257
|
-
"new_class": klass2.pk,
|
|
258
|
-
},
|
|
259
|
-
)
|
|
260
|
-
c.logout()
|
|
261
|
-
|
|
262
|
-
game1 = Game.objects.get(owner=teacher1.new_user)
|
|
263
|
-
game2 = Game.objects.get(owner=teacher2.new_user)
|
|
264
|
-
|
|
265
|
-
assert not game1.can_user_play(student1.new_user)
|
|
266
|
-
assert game2.can_user_play(student1.new_user)
|
|
267
|
-
|
|
268
|
-
def test_teacher_cannot_create_duplicate_game(self):
|
|
269
|
-
"""
|
|
270
|
-
Given a teacher, a class and a worksheet,
|
|
271
|
-
When the teacher creates a game for that class and worksheet, and then tries to
|
|
272
|
-
create the exact same game again,
|
|
273
|
-
Then the class should only have one game, and an error message should appear.
|
|
274
|
-
"""
|
|
275
|
-
|
|
276
|
-
email, password = signup_teacher_directly()
|
|
277
|
-
_, _ = create_organisation_directly(email)
|
|
278
|
-
klass, _, _ = create_class_directly(email)
|
|
279
|
-
|
|
280
|
-
c = Client()
|
|
281
|
-
c.login(username=email, password=password)
|
|
282
|
-
game1_response = c.post(
|
|
283
|
-
reverse("teacher_aimmo_dashboard"),
|
|
284
|
-
{"game_class": klass.pk},
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
assert game1_response.status_code == 302
|
|
288
|
-
assert Game.objects.filter(game_class=klass, is_archived=False).count() == 1
|
|
289
|
-
assert klass.active_game != None
|
|
290
|
-
messages = list(game1_response.wsgi_request._messages)
|
|
291
|
-
assert len([m for m in messages if m.tags == "warning"]) == 0
|
|
292
|
-
|
|
293
|
-
game2_response = c.post(
|
|
294
|
-
reverse("teacher_aimmo_dashboard"),
|
|
295
|
-
{"game_class": klass.pk},
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
messages = list(game2_response.wsgi_request._messages)
|
|
299
|
-
assert len([m for m in messages if m.tags == "warning"]) == 1
|
|
300
|
-
assert messages[0].message == "An active game already exists for this class"
|
|
301
|
-
|
|
302
49
|
def test_signup_short_password_fails(self):
|
|
303
50
|
c = Client()
|
|
304
51
|
|
|
@@ -308,6 +55,7 @@ class TestTeacher(TestCase):
|
|
|
308
55
|
"teacher_signup-teacher_first_name": "Test Name",
|
|
309
56
|
"teacher_signup-teacher_last_name": "Test Last Name",
|
|
310
57
|
"teacher_signup-teacher_email": "test@email.com",
|
|
58
|
+
"teacher_signup-consent_ticked": "on",
|
|
311
59
|
"teacher_signup-teacher_password": "test",
|
|
312
60
|
"teacher_signup-teacher_confirm_password": "test",
|
|
313
61
|
"g-recaptcha-response": "something",
|
|
@@ -326,6 +74,7 @@ class TestTeacher(TestCase):
|
|
|
326
74
|
"teacher_signup-teacher_first_name": "Test Name",
|
|
327
75
|
"teacher_signup-teacher_last_name": "Test Last Name",
|
|
328
76
|
"teacher_signup-teacher_email": "test@email.com",
|
|
77
|
+
"teacher_signup-consent_ticked": "on",
|
|
329
78
|
"teacher_signup-teacher_password": "Password1",
|
|
330
79
|
"teacher_signup-teacher_confirm_password": "Password1",
|
|
331
80
|
"g-recaptcha-response": "something",
|
|
@@ -335,6 +84,22 @@ class TestTeacher(TestCase):
|
|
|
335
84
|
# Assert response isn't a redirect (submit failure)
|
|
336
85
|
assert response.status_code == 200
|
|
337
86
|
|
|
87
|
+
response = c.post(
|
|
88
|
+
reverse("register"),
|
|
89
|
+
{
|
|
90
|
+
"independent_student_signup-date_of_birth_day": 7,
|
|
91
|
+
"independent_student_signup-date_of_birth_month": 10,
|
|
92
|
+
"independent_student_signup-date_of_birth_year": 1997,
|
|
93
|
+
"independent_student_signup-name": "Test Name",
|
|
94
|
+
"independent_student_signup-email": "test@email.com",
|
|
95
|
+
"independent_student_signup-consent_ticked": "on",
|
|
96
|
+
"independent_student_signup-password": "Password123$",
|
|
97
|
+
"independent_student_signup-confirm_password": "Password123$",
|
|
98
|
+
"g-recaptcha-response": "something",
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
assert response.status_code == 200
|
|
102
|
+
|
|
338
103
|
def test_signup_passwords_do_not_match_fails(self):
|
|
339
104
|
c = Client()
|
|
340
105
|
|
|
@@ -344,6 +109,7 @@ class TestTeacher(TestCase):
|
|
|
344
109
|
"teacher_signup-teacher_first_name": "Test Name",
|
|
345
110
|
"teacher_signup-teacher_last_name": "Test Last Name",
|
|
346
111
|
"teacher_signup-teacher_email": "test@email.com",
|
|
112
|
+
"teacher_signup-consent_ticked": "on",
|
|
347
113
|
"teacher_signup-teacher_password": "StrongPassword1!",
|
|
348
114
|
"teacher_signup-teacher_confirm_password": "StrongPassword2!",
|
|
349
115
|
"g-recaptcha-response": "something",
|
|
@@ -353,7 +119,7 @@ class TestTeacher(TestCase):
|
|
|
353
119
|
# Assert response isn't a redirect (submit failure)
|
|
354
120
|
assert response.status_code == 200
|
|
355
121
|
|
|
356
|
-
def
|
|
122
|
+
def test_signup_fails_without_consent(self):
|
|
357
123
|
c = Client()
|
|
358
124
|
|
|
359
125
|
response = c.post(
|
|
@@ -368,19 +134,52 @@ class TestTeacher(TestCase):
|
|
|
368
134
|
},
|
|
369
135
|
)
|
|
370
136
|
|
|
137
|
+
# Assert response isn't a redirect (submit failure)
|
|
138
|
+
assert response.status_code == 200
|
|
139
|
+
|
|
140
|
+
@patch("common.helpers.emails.send_dotdigital_email")
|
|
141
|
+
def test_signup_email_verification(self, mock_send_dotdigital_email: Mock):
|
|
142
|
+
c = Client()
|
|
143
|
+
|
|
144
|
+
response = c.post(
|
|
145
|
+
reverse("register"),
|
|
146
|
+
{
|
|
147
|
+
"teacher_signup-teacher_first_name": "Test Name",
|
|
148
|
+
"teacher_signup-teacher_last_name": "Test Last Name",
|
|
149
|
+
"teacher_signup-teacher_email": "test@email.com",
|
|
150
|
+
"teacher_signup-consent_ticked": "on",
|
|
151
|
+
"teacher_signup-teacher_password": "czYuH)g0FbD_5E9/",
|
|
152
|
+
"teacher_signup-teacher_confirm_password": "czYuH)g0FbD_5E9/",
|
|
153
|
+
"g-recaptcha-response": "something",
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
|
+
|
|
371
157
|
assert response.status_code == 302
|
|
372
|
-
|
|
158
|
+
mock_send_dotdigital_email.assert_called_once_with(
|
|
159
|
+
campaign_ids["verify_new_user"], ANY, personalization_values=ANY
|
|
160
|
+
)
|
|
373
161
|
|
|
374
162
|
# Try verification URL with a fake token
|
|
375
|
-
|
|
163
|
+
fake_token = jwt.encode(
|
|
164
|
+
{
|
|
165
|
+
"email": "fake_email",
|
|
166
|
+
"new_email": "",
|
|
167
|
+
"email_verification_token": uuid4().hex[:30],
|
|
168
|
+
"expires": (timezone.now() + timedelta(hours=1)).timestamp(),
|
|
169
|
+
},
|
|
170
|
+
settings.SECRET_KEY,
|
|
171
|
+
algorithm="HS256",
|
|
172
|
+
)
|
|
173
|
+
bad_url = reverse("verify_email", kwargs={"token": fake_token})
|
|
376
174
|
bad_verification_response = c.get(bad_url)
|
|
377
175
|
|
|
378
176
|
# Assert response isn't a redirect (get failure)
|
|
379
177
|
assert bad_verification_response.status_code == 200
|
|
380
178
|
|
|
381
|
-
# Get verification link from
|
|
382
|
-
|
|
383
|
-
|
|
179
|
+
# Get verification link from function call
|
|
180
|
+
verification_url = mock_send_dotdigital_email.call_args.kwargs[
|
|
181
|
+
"personalization_values"
|
|
182
|
+
]["VERIFICATION_LINK"]
|
|
384
183
|
|
|
385
184
|
# Verify the email properly
|
|
386
185
|
verification_response = c.get(verification_url)
|
|
@@ -397,6 +196,28 @@ class TestTeacher(TestCase):
|
|
|
397
196
|
|
|
398
197
|
# Class for Selenium tests. We plan to replace these and turn them into Cypress tests
|
|
399
198
|
class TestTeacherFrontend(BaseTest):
|
|
199
|
+
def test_password_too_common(self):
|
|
200
|
+
self.selenium.get(self.live_server_url)
|
|
201
|
+
page = HomePage(self.selenium).go_to_signup_page()
|
|
202
|
+
page = page.signup(
|
|
203
|
+
"first_name",
|
|
204
|
+
"last_name",
|
|
205
|
+
"e@ma.il",
|
|
206
|
+
"Password123$",
|
|
207
|
+
"Password123$",
|
|
208
|
+
success=False,
|
|
209
|
+
)
|
|
210
|
+
try:
|
|
211
|
+
submit_button = WebDriverWait(self.selenium, 10).until(
|
|
212
|
+
EC.element_to_be_clickable((By.NAME, "teacher_signup"))
|
|
213
|
+
)
|
|
214
|
+
submit_button.click()
|
|
215
|
+
except:
|
|
216
|
+
assert page.was_form_invalid(
|
|
217
|
+
"form-reg-teacher",
|
|
218
|
+
"Password is too common, consider using a different password.",
|
|
219
|
+
)
|
|
220
|
+
|
|
400
221
|
def test_signup_without_newsletter(self):
|
|
401
222
|
self.selenium.get(self.live_server_url)
|
|
402
223
|
page = HomePage(self.selenium)
|
|
@@ -432,7 +253,9 @@ class TestTeacherFrontend(BaseTest):
|
|
|
432
253
|
page = page.login_failure(
|
|
433
254
|
"non-existent-email@codeforlife.com", "Incorrect password"
|
|
434
255
|
)
|
|
435
|
-
assert page.has_login_failed(
|
|
256
|
+
assert page.has_login_failed(
|
|
257
|
+
"form-login-teacher", INVALID_LOGIN_MESSAGE
|
|
258
|
+
)
|
|
436
259
|
|
|
437
260
|
def test_login_success(self):
|
|
438
261
|
email, password = signup_teacher_directly()
|
|
@@ -445,7 +268,8 @@ class TestTeacherFrontend(BaseTest):
|
|
|
445
268
|
page = page.login(email, password)
|
|
446
269
|
assert self.is_dashboard_page(page)
|
|
447
270
|
|
|
448
|
-
|
|
271
|
+
@patch("common.helpers.emails.send_dotdigital_email")
|
|
272
|
+
def test_login_not_verified(self, mock_send_dotdigital_email):
|
|
449
273
|
email, password = signup_teacher_directly(preverified=False)
|
|
450
274
|
create_organisation_directly(email)
|
|
451
275
|
_, _, access_code = create_class_directly(email)
|
|
@@ -455,9 +279,15 @@ class TestTeacherFrontend(BaseTest):
|
|
|
455
279
|
page = page.go_to_teacher_login_page()
|
|
456
280
|
page = page.login_failure(email, password)
|
|
457
281
|
|
|
458
|
-
assert page.has_login_failed(
|
|
282
|
+
assert page.has_login_failed(
|
|
283
|
+
"form-login-teacher", INVALID_LOGIN_MESSAGE
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
verification_url = mock_send_dotdigital_email.call_args.kwargs[
|
|
287
|
+
"personalization_values"
|
|
288
|
+
]["VERIFICATION_LINK"]
|
|
459
289
|
|
|
460
|
-
verify_email(page)
|
|
290
|
+
verify_email(page, verification_url)
|
|
461
291
|
|
|
462
292
|
assert is_email_verified_message_showing(self.selenium)
|
|
463
293
|
|
|
@@ -472,26 +302,6 @@ class TestTeacherFrontend(BaseTest):
|
|
|
472
302
|
page = page.login_no_school(email, password)
|
|
473
303
|
assert self.is_onboarding_page(page)
|
|
474
304
|
|
|
475
|
-
def test_view_resources(self):
|
|
476
|
-
email, password = signup_teacher_directly()
|
|
477
|
-
create_organisation_directly(email)
|
|
478
|
-
_, _, access_code = create_class_directly(email)
|
|
479
|
-
create_school_student_directly(access_code)
|
|
480
|
-
self.selenium.get(self.live_server_url)
|
|
481
|
-
page = HomePage(self.selenium)
|
|
482
|
-
page = page.go_to_teacher_login_page()
|
|
483
|
-
page = page.login(email, password)
|
|
484
|
-
|
|
485
|
-
assert self.is_dashboard_page(page)
|
|
486
|
-
|
|
487
|
-
page = page.go_to_rapid_router_resources_page()
|
|
488
|
-
|
|
489
|
-
assert self.is_resources_page(page)
|
|
490
|
-
|
|
491
|
-
page = page.go_to_kurono_resources_page()
|
|
492
|
-
|
|
493
|
-
assert self.is_resources_page(page)
|
|
494
|
-
|
|
495
305
|
def test_edit_details(self):
|
|
496
306
|
email, password = signup_teacher_directly()
|
|
497
307
|
create_organisation_directly(email)
|
|
@@ -510,7 +320,7 @@ class TestTeacherFrontend(BaseTest):
|
|
|
510
320
|
{
|
|
511
321
|
"first_name": "Paulina",
|
|
512
322
|
"last_name": "Koch",
|
|
513
|
-
"current_password": "
|
|
323
|
+
"current_password": "$RFVBGT%6yhn$RFVBGT%6yhn$RFVBGT%6yhn$RFVBGT%6yhn",
|
|
514
324
|
}
|
|
515
325
|
)
|
|
516
326
|
assert self.is_dashboard_page(page)
|
|
@@ -523,10 +333,10 @@ class TestTeacherFrontend(BaseTest):
|
|
|
523
333
|
def test_edit_details_non_admin(self):
|
|
524
334
|
email_1, _ = signup_teacher_directly()
|
|
525
335
|
email_2, password_2 = signup_teacher_directly()
|
|
526
|
-
|
|
336
|
+
school = create_organisation_directly(email_1)
|
|
527
337
|
_, _, access_code_1 = create_class_directly(email_1)
|
|
528
338
|
create_school_student_directly(access_code_1)
|
|
529
|
-
join_teacher_to_organisation(email_2, name
|
|
339
|
+
join_teacher_to_organisation(email_2, school.name)
|
|
530
340
|
_, _, access_code_2 = create_class_directly(email_2)
|
|
531
341
|
create_school_student_directly(access_code_2)
|
|
532
342
|
|
|
@@ -542,7 +352,7 @@ class TestTeacherFrontend(BaseTest):
|
|
|
542
352
|
{
|
|
543
353
|
"first_name": "Florian",
|
|
544
354
|
"last_name": "Aucomte",
|
|
545
|
-
"current_password":
|
|
355
|
+
"current_password": password_2,
|
|
546
356
|
}
|
|
547
357
|
)
|
|
548
358
|
assert self.is_dashboard_page(page)
|
|
@@ -552,7 +362,8 @@ class TestTeacherFrontend(BaseTest):
|
|
|
552
362
|
{"first_name": "Florian", "last_name": "Aucomte"}
|
|
553
363
|
)
|
|
554
364
|
|
|
555
|
-
|
|
365
|
+
@patch("common.helpers.emails.send_dotdigital_email")
|
|
366
|
+
def test_change_email(self, mock_send_dotdigital_email):
|
|
556
367
|
email, password = signup_teacher_directly()
|
|
557
368
|
create_organisation_directly(email)
|
|
558
369
|
_, _, access_code = create_class_directly(email)
|
|
@@ -561,32 +372,48 @@ class TestTeacherFrontend(BaseTest):
|
|
|
561
372
|
other_email, _ = signup_teacher_directly()
|
|
562
373
|
|
|
563
374
|
page = self.go_to_homepage()
|
|
564
|
-
page =
|
|
375
|
+
page = (
|
|
376
|
+
page.go_to_teacher_login_page()
|
|
377
|
+
.login(email, password)
|
|
378
|
+
.open_account_tab()
|
|
379
|
+
)
|
|
565
380
|
|
|
566
381
|
# Try changing email to an existing email, should fail
|
|
567
382
|
page = page.change_email("Test", "Teacher", other_email, password)
|
|
568
383
|
assert self.is_email_verification_page(page)
|
|
569
384
|
assert is_email_updated_message_showing(self.selenium)
|
|
570
385
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
386
|
+
mock_send_dotdigital_email.assert_called_with(
|
|
387
|
+
campaign_ids["email_change_notification"],
|
|
388
|
+
ANY,
|
|
389
|
+
personalization_values=ANY,
|
|
390
|
+
)
|
|
574
391
|
|
|
575
392
|
# Try changing email to an existing indy student's email, should fail
|
|
576
393
|
indy_email, _, _ = create_independent_student_directly()
|
|
577
394
|
page = self.go_to_homepage()
|
|
578
|
-
page =
|
|
395
|
+
page = (
|
|
396
|
+
page.go_to_teacher_login_page()
|
|
397
|
+
.login(email, password)
|
|
398
|
+
.open_account_tab()
|
|
399
|
+
)
|
|
579
400
|
|
|
580
401
|
page = page.change_email("Test", "Teacher", indy_email, password)
|
|
581
402
|
assert self.is_email_verification_page(page)
|
|
582
403
|
assert is_email_updated_message_showing(self.selenium)
|
|
583
404
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
405
|
+
mock_send_dotdigital_email.assert_called_with(
|
|
406
|
+
campaign_ids["email_change_notification"],
|
|
407
|
+
ANY,
|
|
408
|
+
personalization_values=ANY,
|
|
409
|
+
)
|
|
587
410
|
|
|
588
411
|
page = self.go_to_homepage()
|
|
589
|
-
page =
|
|
412
|
+
page = (
|
|
413
|
+
page.go_to_teacher_login_page()
|
|
414
|
+
.login(email, password)
|
|
415
|
+
.open_account_tab()
|
|
416
|
+
)
|
|
590
417
|
|
|
591
418
|
# Try changing email to a new one, should succeed
|
|
592
419
|
new_email = "another-email@codeforlife.com"
|
|
@@ -596,13 +423,27 @@ class TestTeacherFrontend(BaseTest):
|
|
|
596
423
|
|
|
597
424
|
# Check user can still log in with old account before verifying new email
|
|
598
425
|
self.selenium.get(self.live_server_url)
|
|
599
|
-
page =
|
|
426
|
+
page = (
|
|
427
|
+
HomePage(self.selenium)
|
|
428
|
+
.go_to_teacher_login_page()
|
|
429
|
+
.login(email, password)
|
|
430
|
+
)
|
|
600
431
|
assert self.is_dashboard_page(page)
|
|
601
432
|
|
|
602
433
|
page = page.logout()
|
|
603
434
|
|
|
604
|
-
|
|
605
|
-
|
|
435
|
+
mock_send_dotdigital_email.assert_called_with(
|
|
436
|
+
campaign_ids["email_change_verification"],
|
|
437
|
+
ANY,
|
|
438
|
+
personalization_values=ANY,
|
|
439
|
+
)
|
|
440
|
+
verification_url = mock_send_dotdigital_email.call_args.kwargs[
|
|
441
|
+
"personalization_values"
|
|
442
|
+
]["VERIFICATION_LINK"]
|
|
443
|
+
|
|
444
|
+
page = email_utils.follow_change_email_link_to_dashboard(
|
|
445
|
+
page, verification_url
|
|
446
|
+
)
|
|
606
447
|
|
|
607
448
|
page = page.login(new_email, password).open_account_tab()
|
|
608
449
|
|
|
@@ -633,7 +474,8 @@ class TestTeacherFrontend(BaseTest):
|
|
|
633
474
|
|
|
634
475
|
assert self.is_dashboard_page(page)
|
|
635
476
|
|
|
636
|
-
|
|
477
|
+
@patch("portal.forms.registration.send_dotdigital_email")
|
|
478
|
+
def test_reset_password(self, mock_send_dotdigital_email: Mock):
|
|
637
479
|
email, _ = signup_teacher_directly()
|
|
638
480
|
create_organisation_directly(email)
|
|
639
481
|
_, _, access_code = create_class_directly(email)
|
|
@@ -643,9 +485,17 @@ class TestTeacherFrontend(BaseTest):
|
|
|
643
485
|
|
|
644
486
|
page.reset_email_submit(email)
|
|
645
487
|
|
|
646
|
-
|
|
488
|
+
mock_send_dotdigital_email.assert_called_with(
|
|
489
|
+
campaign_ids["reset_password"], ANY, personalization_values=ANY
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
reset_password_url = mock_send_dotdigital_email.call_args.kwargs[
|
|
493
|
+
"personalization_values"
|
|
494
|
+
]["RESET_PASSWORD_LINK"]
|
|
647
495
|
|
|
648
|
-
page = email_utils.follow_reset_email_link(
|
|
496
|
+
page = email_utils.follow_reset_email_link(
|
|
497
|
+
self.selenium, reset_password_url
|
|
498
|
+
)
|
|
649
499
|
|
|
650
500
|
new_password = "AnotherPassword12!"
|
|
651
501
|
|
|
@@ -659,32 +509,280 @@ class TestTeacherFrontend(BaseTest):
|
|
|
659
509
|
)
|
|
660
510
|
assert self.is_dashboard_page(page)
|
|
661
511
|
|
|
662
|
-
|
|
512
|
+
@patch("portal.forms.registration.send_dotdigital_email")
|
|
513
|
+
def test_reset_with_same_password(self, mock_send_dotdigital_email: Mock):
|
|
514
|
+
email, password = signup_teacher_directly()
|
|
515
|
+
create_organisation_directly(email)
|
|
516
|
+
_, _, access_code = create_class_directly(email)
|
|
517
|
+
create_school_student_directly(access_code)
|
|
518
|
+
|
|
519
|
+
page = self.get_to_forgotten_password_page()
|
|
520
|
+
|
|
521
|
+
page.reset_email_submit(email)
|
|
522
|
+
|
|
523
|
+
mock_send_dotdigital_email.assert_called_with(
|
|
524
|
+
campaign_ids["reset_password"], ANY, personalization_values=ANY
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
reset_password_url = mock_send_dotdigital_email.call_args.kwargs[
|
|
528
|
+
"personalization_values"
|
|
529
|
+
]["RESET_PASSWORD_LINK"]
|
|
530
|
+
|
|
531
|
+
page = email_utils.follow_reset_email_link(
|
|
532
|
+
self.selenium, reset_password_url
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
page.reset_password_fail(password)
|
|
536
|
+
|
|
537
|
+
message = page.browser.find_element(By.CLASS_NAME, "errorlist")
|
|
538
|
+
assert (
|
|
539
|
+
"Please choose a password that you haven't used before"
|
|
540
|
+
in message.text
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
@patch("portal.forms.registration.send_dotdigital_email")
|
|
544
|
+
def test_reset_password_fail(self, mock_send_dotdigital_email: Mock):
|
|
663
545
|
page = self.get_to_forgotten_password_page()
|
|
664
546
|
fake_email = "fake_email@fakeemail.com"
|
|
665
547
|
page.reset_email_submit(fake_email)
|
|
666
548
|
|
|
667
|
-
|
|
549
|
+
mock_send_dotdigital_email.assert_not_called()
|
|
550
|
+
|
|
551
|
+
def test_admin_sees_all_school_classes(self):
|
|
552
|
+
email, password = signup_teacher_directly()
|
|
553
|
+
school = create_organisation_directly(email)
|
|
554
|
+
klass, _, access_code = create_class_directly(email, "class123")
|
|
668
555
|
|
|
669
|
-
|
|
556
|
+
# create non_admin account to join the school
|
|
557
|
+
# check if they cannot see classes
|
|
558
|
+
standard_email, standard_password = signup_teacher_directly()
|
|
559
|
+
join_teacher_to_organisation(standard_email, school.name)
|
|
560
|
+
|
|
561
|
+
page = (
|
|
562
|
+
self.go_to_homepage()
|
|
563
|
+
.go_to_teacher_login_page()
|
|
564
|
+
.login(standard_email, standard_password)
|
|
565
|
+
.open_classes_tab()
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
assert page.element_does_not_exist_by_id(f"class-code-{access_code}")
|
|
569
|
+
|
|
570
|
+
self.go_to_homepage().teacher_logout()
|
|
571
|
+
# then make an admin account and check
|
|
572
|
+
# if the teacher can see the classes
|
|
573
|
+
|
|
574
|
+
admin_email, admin_password = signup_teacher_directly()
|
|
575
|
+
join_teacher_to_organisation(admin_email, school.name, is_admin=True)
|
|
576
|
+
|
|
577
|
+
page = (
|
|
578
|
+
self.go_to_homepage()
|
|
579
|
+
.go_to_teacher_login_page()
|
|
580
|
+
.login(admin_email, admin_password)
|
|
581
|
+
.open_classes_tab()
|
|
582
|
+
)
|
|
583
|
+
class_code_field = page.browser.find_element(
|
|
584
|
+
By.ID, f"class-code-{access_code}"
|
|
585
|
+
)
|
|
586
|
+
assert class_code_field.text == access_code
|
|
587
|
+
|
|
588
|
+
def test_admin_student_edit(self):
|
|
589
|
+
email, password = signup_teacher_directly()
|
|
590
|
+
school = create_organisation_directly(email)
|
|
591
|
+
|
|
592
|
+
klass, _, access_code = create_class_directly(email, "class123")
|
|
593
|
+
(
|
|
594
|
+
student_name,
|
|
595
|
+
student_password,
|
|
596
|
+
student_student,
|
|
597
|
+
) = create_school_student_directly(access_code)
|
|
598
|
+
|
|
599
|
+
joining_email, joining_password = signup_teacher_directly()
|
|
600
|
+
join_teacher_to_organisation(joining_email, school.name, is_admin=True)
|
|
601
|
+
|
|
602
|
+
page = (
|
|
603
|
+
self.go_to_homepage()
|
|
604
|
+
.go_to_teacher_login_page()
|
|
605
|
+
.login(joining_email, joining_password)
|
|
606
|
+
.open_classes_tab()
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
class_button = WebDriverWait(self.selenium, WAIT_TIME).until(
|
|
610
|
+
EC.element_to_be_clickable((By.ID, "class_button"))
|
|
611
|
+
)
|
|
612
|
+
class_button.click()
|
|
613
|
+
|
|
614
|
+
edit_student_button = WebDriverWait(self.selenium, WAIT_TIME).until(
|
|
615
|
+
EC.element_to_be_clickable((By.ID, "edit_student_button"))
|
|
616
|
+
)
|
|
617
|
+
edit_student_button.click()
|
|
618
|
+
|
|
619
|
+
title = page.browser.find_element(By.ID, "student_details")
|
|
620
|
+
assert (
|
|
621
|
+
title.text
|
|
622
|
+
== f"Edit student details for {student_name} from class {klass} ({access_code})"
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
def test_make_admin_popup(self):
|
|
626
|
+
email, password = signup_teacher_directly()
|
|
627
|
+
school = create_organisation_directly(email)
|
|
628
|
+
page = (
|
|
629
|
+
self.go_to_homepage()
|
|
630
|
+
.go_to_teacher_login_page()
|
|
631
|
+
.login(email, password)
|
|
632
|
+
)
|
|
633
|
+
joining_email, _ = signup_teacher_directly()
|
|
634
|
+
|
|
635
|
+
invite_data = {
|
|
636
|
+
"teacher_first_name": "Real",
|
|
637
|
+
"teacher_last_name": "Name",
|
|
638
|
+
"teacher_email": "ren@me.me",
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
for key in invite_data.keys():
|
|
642
|
+
field = page.browser.find_element(By.NAME, key)
|
|
643
|
+
field.send_keys(invite_data[key])
|
|
644
|
+
|
|
645
|
+
page.browser.find_element(By.NAME, "invite_teacher_button").click()
|
|
646
|
+
# Once invite sent test the make admin button
|
|
647
|
+
page.browser.find_element(By.ID, "make_admin_button_invite").click()
|
|
648
|
+
time.sleep(1)
|
|
649
|
+
page.browser.find_element(By.ID, "cancel_admin_popup_button").click()
|
|
650
|
+
time.sleep(1)
|
|
651
|
+
page.browser.find_element(By.ID, "delete-invite").click()
|
|
652
|
+
|
|
653
|
+
# Delete the invite and check if the form invite with
|
|
654
|
+
# admin checked also makes a popup
|
|
655
|
+
|
|
656
|
+
for key in invite_data.keys():
|
|
657
|
+
field = page.browser.find_element(By.NAME, key)
|
|
658
|
+
field.send_keys(invite_data[key])
|
|
659
|
+
checkbox = page.browser.find_element(By.NAME, "make_admin_ticked")
|
|
660
|
+
checkbox.click()
|
|
661
|
+
|
|
662
|
+
page.browser.find_element(By.ID, "invite_teacher_button").click()
|
|
663
|
+
time.sleep(1)
|
|
664
|
+
page.browser.find_element(By.ID, "cancel_admin_popup_button").click()
|
|
665
|
+
|
|
666
|
+
# Non admin teacher joined - make admin should also make a popup
|
|
667
|
+
join_teacher_to_organisation(joining_email, school.name)
|
|
668
|
+
|
|
669
|
+
# refresh the page and scroll to the buttons
|
|
670
|
+
time.sleep(1)
|
|
671
|
+
page.browser.find_element(By.CSS_SELECTOR, ".logo").click()
|
|
672
|
+
page.browser.find_element(By.ID, "make_admin_button").click()
|
|
673
|
+
|
|
674
|
+
assert page.element_exists((By.CLASS_NAME, "popup-box__msg"))
|
|
675
|
+
|
|
676
|
+
def test_delete_account(self):
|
|
677
|
+
FADE_TIME = 0.9 # often fails if lower
|
|
670
678
|
|
|
671
|
-
def test_onboarding_complete(self):
|
|
672
679
|
email, password = signup_teacher_directly()
|
|
673
680
|
create_organisation_directly(email)
|
|
674
|
-
create_class_directly(email)
|
|
675
681
|
|
|
676
|
-
|
|
682
|
+
self.selenium.get(self.live_server_url)
|
|
683
|
+
page = (
|
|
684
|
+
HomePage(self.selenium)
|
|
685
|
+
.go_to_teacher_login_page()
|
|
686
|
+
.login(email, password)
|
|
687
|
+
.open_account_tab()
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
# test incorrect password
|
|
691
|
+
page.browser.find_element(By.ID, "id_delete_password").send_keys(
|
|
692
|
+
"IncorrectPassword"
|
|
693
|
+
)
|
|
694
|
+
page.browser.find_element(By.ID, "delete_account_button").click()
|
|
695
|
+
is_message_showing(page.browser, "Your account was not deleted")
|
|
696
|
+
|
|
697
|
+
# test cancel (no class)
|
|
698
|
+
time.sleep(FADE_TIME)
|
|
699
|
+
page.browser.find_element(By.ID, "id_delete_password").clear()
|
|
700
|
+
page.browser.find_element(By.ID, "id_delete_password").send_keys(
|
|
701
|
+
password
|
|
702
|
+
)
|
|
703
|
+
page.browser.find_element(By.ID, "delete_account_button").click()
|
|
704
|
+
|
|
705
|
+
time.sleep(FADE_TIME)
|
|
706
|
+
assert page.browser.find_element(
|
|
707
|
+
By.ID, "popup-delete-review"
|
|
708
|
+
).is_displayed()
|
|
709
|
+
page.browser.find_element(By.ID, "cancel_popup_button").click()
|
|
710
|
+
time.sleep(FADE_TIME)
|
|
711
|
+
|
|
712
|
+
# test close button in the corner
|
|
713
|
+
page.browser.find_element(By.ID, "id_delete_password").clear()
|
|
714
|
+
page.browser.find_element(By.ID, "id_delete_password").send_keys(
|
|
715
|
+
password
|
|
716
|
+
)
|
|
717
|
+
page.browser.find_element(By.ID, "delete_account_button").click()
|
|
718
|
+
|
|
719
|
+
time.sleep(FADE_TIME)
|
|
720
|
+
page.browser.find_element(By.ID, "close_popup_button").click()
|
|
721
|
+
time.sleep(FADE_TIME)
|
|
722
|
+
|
|
723
|
+
# create class
|
|
724
|
+
_, _, access_code = create_class_directly(email)
|
|
725
|
+
create_school_student_directly(access_code)
|
|
726
|
+
|
|
727
|
+
# delete then review classes
|
|
728
|
+
page.browser.find_element(By.ID, "id_delete_password").send_keys(
|
|
729
|
+
password
|
|
730
|
+
)
|
|
731
|
+
page.browser.find_element(By.ID, "delete_account_button").click()
|
|
732
|
+
|
|
733
|
+
time.sleep(FADE_TIME)
|
|
734
|
+
assert page.browser.find_element(
|
|
735
|
+
By.ID, "popup-delete-review"
|
|
736
|
+
).is_displayed()
|
|
737
|
+
page.browser.find_element(By.ID, "review_button").click()
|
|
738
|
+
time.sleep(FADE_TIME)
|
|
739
|
+
|
|
740
|
+
assert page.has_classes()
|
|
741
|
+
page = page.open_account_tab()
|
|
742
|
+
|
|
743
|
+
# test actual deletion
|
|
744
|
+
page.browser.find_element(By.ID, "id_delete_password").send_keys(
|
|
745
|
+
password
|
|
746
|
+
)
|
|
747
|
+
page.browser.find_element(By.ID, "delete_account_button").click()
|
|
748
|
+
|
|
749
|
+
time.sleep(FADE_TIME)
|
|
750
|
+
page.browser.find_element(By.ID, "delete_button").click()
|
|
751
|
+
|
|
752
|
+
# back to homepage
|
|
753
|
+
assert page.browser.find_element(By.CLASS_NAME, "banner--homepage")
|
|
754
|
+
|
|
755
|
+
# user should not be able to login now
|
|
756
|
+
page = (
|
|
757
|
+
HomePage(self.selenium)
|
|
758
|
+
.go_to_teacher_login_page()
|
|
759
|
+
.login_failure(email, password)
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
assert page.has_login_failed(
|
|
763
|
+
"form-login-teacher", INVALID_LOGIN_MESSAGE
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
def test_onboarding_complete(self):
|
|
767
|
+
email, password = signup_teacher_directly()
|
|
677
768
|
|
|
678
769
|
self.selenium.get(self.live_server_url)
|
|
679
770
|
page = (
|
|
680
771
|
HomePage(self.selenium)
|
|
681
772
|
.go_to_teacher_login_page()
|
|
682
|
-
.
|
|
683
|
-
|
|
773
|
+
.login_no_school(email, password)
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
page = page.create_organisation("Test school")
|
|
777
|
+
page = page.create_class("Test class", True)
|
|
778
|
+
page = (
|
|
779
|
+
page.type_student_name("Test Student")
|
|
684
780
|
.create_students()
|
|
685
781
|
.complete_setup()
|
|
686
782
|
)
|
|
687
783
|
|
|
784
|
+
time.sleep(1)
|
|
785
|
+
|
|
688
786
|
assert page.has_onboarding_complete_popup()
|
|
689
787
|
|
|
690
788
|
def get_to_forgotten_password_page(self):
|
|
@@ -697,7 +795,9 @@ class TestTeacherFrontend(BaseTest):
|
|
|
697
795
|
return page
|
|
698
796
|
|
|
699
797
|
def wait_for_email(self):
|
|
700
|
-
WebDriverWait(self.selenium, 2).until(
|
|
798
|
+
WebDriverWait(self.selenium, 2).until(
|
|
799
|
+
lambda driver: len(mail.outbox) == 1
|
|
800
|
+
)
|
|
701
801
|
|
|
702
802
|
def is_dashboard_page(self, page):
|
|
703
803
|
return page.__class__.__name__ == "TeachDashboardPage"
|