codeforlife-portal 5.32.2__py2.py3-none-any.whl → 8.9.9__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.32.2.dist-info → codeforlife_portal-8.9.9.dist-info}/RECORD +341 -238
- {codeforlife_portal-5.32.2.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.32.2.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 +31 -0
- 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 +10 -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 -73
- 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 +138 -15
- 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 +237 -164
- portal/views/admin.py +0 -332
- portal/views/api.py +82 -68
- 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/__init__.py +0 -0
- portal/views/two_factor/core.py +28 -0
- portal/views/two_factor/form.py +7 -0
- portal/views/two_factor/profile.py +11 -0
- codeforlife_portal-5.32.2.dist-info/LICENSE.md +0 -577
- codeforlife_portal-5.32.2.dist-info/METADATA +0 -38
- deploy/permissions.py +0 -2
- example_project/manage.py +0 -10
- portal/autoconfig.py +0 -140
- 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
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@pytest.mark.django_db
|
|
5
|
+
def test_blocked_time_added(migrator):
|
|
6
|
+
migrator.apply_initial_migration(("common", "0008_unlock_worksheet_3"))
|
|
7
|
+
new_state = migrator.apply_tested_migration(("common", "0009_add_blocked_time_to_teacher_and_student"))
|
|
8
|
+
|
|
9
|
+
teacher_model = new_state.apps.get_model("common", "Teacher")
|
|
10
|
+
|
|
11
|
+
assert teacher_model._meta.get_field("blocked_time").get_internal_type() == "DateTimeField"
|
|
12
|
+
|
|
13
|
+
student_model = new_state.apps.get_model("common", "Student")
|
|
14
|
+
|
|
15
|
+
assert student_model._meta.get_field("blocked_time").get_internal_type() == "DateTimeField"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from django.db.models.query import QuerySet
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@pytest.mark.django_db
|
|
6
|
+
def test_teacher_title_removed(migrator):
|
|
7
|
+
old_state = migrator.apply_initial_migration(("common", "0009_add_blocked_time_to_teacher_and_student"))
|
|
8
|
+
Teacher = old_state.apps.get_model("common", "Teacher")
|
|
9
|
+
assert hasattr(Teacher, "title")
|
|
10
|
+
|
|
11
|
+
new_state = migrator.apply_tested_migration(("common", "0010_remove_teacher_title"))
|
|
12
|
+
Teacher = new_state.apps.get_model("common", "Teacher")
|
|
13
|
+
assert not hasattr(Teacher, "title")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from django_test_migrations.migrator import Migrator
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@pytest.mark.django_db
|
|
6
|
+
def test_migration_unique_school_names(migrator: Migrator):
|
|
7
|
+
state = migrator.apply_initial_migration(
|
|
8
|
+
("common", "0047_delete_school_postcode")
|
|
9
|
+
)
|
|
10
|
+
School = state.apps.get_model("common", "School")
|
|
11
|
+
|
|
12
|
+
school_name = "ExampleSchool"
|
|
13
|
+
School.objects.bulk_create(
|
|
14
|
+
[
|
|
15
|
+
School(name=school_name),
|
|
16
|
+
School(name=school_name),
|
|
17
|
+
School(name=f"{school_name} 1"),
|
|
18
|
+
]
|
|
19
|
+
)
|
|
20
|
+
school_ids = list(
|
|
21
|
+
School.objects.order_by("-id")[:3].values_list("id", flat=True)
|
|
22
|
+
)
|
|
23
|
+
school_ids.reverse()
|
|
24
|
+
|
|
25
|
+
migrator.apply_tested_migration(("common", "0048_unique_school_names"))
|
|
26
|
+
School = state.apps.get_model("common", "School")
|
|
27
|
+
|
|
28
|
+
def assert_school_name(index: int, name: str):
|
|
29
|
+
assert School.objects.get(id=school_ids[index]).name == name
|
|
30
|
+
|
|
31
|
+
assert_school_name(0, school_name)
|
|
32
|
+
assert_school_name(1, f"{school_name} 2")
|
|
33
|
+
assert_school_name(2, f"{school_name} 1")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from django_test_migrations.migrator import Migrator
|
|
5
|
+
|
|
6
|
+
from portal.views.api import __anonymise_user
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.django_db
|
|
10
|
+
def test_migration_verify_returning_users(migrator: Migrator):
|
|
11
|
+
state = migrator.apply_initial_migration(
|
|
12
|
+
("common", "0050_anonymise_orphan_schools")
|
|
13
|
+
)
|
|
14
|
+
User = state.apps.get_model("auth", "User")
|
|
15
|
+
UserProfile = state.apps.get_model("common", "UserProfile")
|
|
16
|
+
|
|
17
|
+
returning_user = User.objects.create_user(
|
|
18
|
+
"ReturningUser",
|
|
19
|
+
password="password",
|
|
20
|
+
last_login=datetime.now(tz=timezone.utc),
|
|
21
|
+
)
|
|
22
|
+
returning_userprofile = UserProfile.objects.create(user=returning_user)
|
|
23
|
+
|
|
24
|
+
non_returning_user = User.objects.create_user(
|
|
25
|
+
"NonReturningUser", password="password"
|
|
26
|
+
)
|
|
27
|
+
non_returning_userprofile = UserProfile.objects.create(
|
|
28
|
+
user=non_returning_user
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
anonymised_returning_user = User.objects.create_user(
|
|
32
|
+
"AnonReturningUser",
|
|
33
|
+
password="password",
|
|
34
|
+
last_login=datetime.now(tz=timezone.utc),
|
|
35
|
+
)
|
|
36
|
+
anonymised_returning_userprofile = UserProfile.objects.create(
|
|
37
|
+
user=anonymised_returning_user
|
|
38
|
+
)
|
|
39
|
+
__anonymise_user(anonymised_returning_user)
|
|
40
|
+
|
|
41
|
+
anonymised_non_returning_user = User.objects.create_user(
|
|
42
|
+
"AnonNonReturningUser", password="password"
|
|
43
|
+
)
|
|
44
|
+
anonymised_non_returning_userprofile = UserProfile.objects.create(
|
|
45
|
+
user=anonymised_non_returning_user
|
|
46
|
+
)
|
|
47
|
+
__anonymise_user(anonymised_non_returning_user)
|
|
48
|
+
|
|
49
|
+
migrator.apply_tested_migration(("common", "0051_verify_returning_users"))
|
|
50
|
+
|
|
51
|
+
def assert_userprofile_is_verified(pk: int, verified: bool):
|
|
52
|
+
assert UserProfile.objects.get(pk=pk).is_verified == verified
|
|
53
|
+
|
|
54
|
+
assert_userprofile_is_verified(returning_userprofile.pk, True)
|
|
55
|
+
assert_userprofile_is_verified(non_returning_userprofile.pk, False)
|
|
56
|
+
assert_userprofile_is_verified(anonymised_returning_userprofile.pk, True)
|
|
57
|
+
assert_userprofile_is_verified(
|
|
58
|
+
anonymised_non_returning_userprofile.pk, False
|
|
59
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from common.models import DailyActivity, Student, Teacher
|
|
2
|
+
from django.test import TestCase
|
|
3
|
+
from django.utils import timezone
|
|
4
|
+
|
|
5
|
+
from ..helpers.organisation import sanitise_uk_postcode
|
|
6
|
+
from .utils.classes import create_class_directly
|
|
7
|
+
from .utils.organisation import create_organisation_directly, join_teacher_to_organisation
|
|
8
|
+
from .utils.student import create_independent_student_directly
|
|
9
|
+
from .utils.teacher import signup_teacher_directly
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestModels(TestCase):
|
|
13
|
+
def test_indep_student_pending_class_request_on_delete(self):
|
|
14
|
+
"""
|
|
15
|
+
Given a class and an independent student,
|
|
16
|
+
When the student makes a request to join the class and the class is deleted,
|
|
17
|
+
Then the student's pending class request field is set to null.
|
|
18
|
+
"""
|
|
19
|
+
teacher_email, _ = signup_teacher_directly()
|
|
20
|
+
create_organisation_directly(teacher_email)
|
|
21
|
+
class_name = "Test Class"
|
|
22
|
+
klass, _, _ = create_class_directly(teacher_email, class_name)
|
|
23
|
+
|
|
24
|
+
username, _, indep_student = create_independent_student_directly()
|
|
25
|
+
|
|
26
|
+
assert indep_student.is_independent()
|
|
27
|
+
assert indep_student.pending_class_request is None
|
|
28
|
+
|
|
29
|
+
indep_student.pending_class_request = klass
|
|
30
|
+
indep_student.save()
|
|
31
|
+
|
|
32
|
+
klass.anonymise()
|
|
33
|
+
|
|
34
|
+
indep_student = Student.objects.get(new_user__username=username)
|
|
35
|
+
|
|
36
|
+
assert indep_student.pending_class_request is None
|
|
37
|
+
|
|
38
|
+
def test_creation_time(self):
|
|
39
|
+
teacher_email, _ = signup_teacher_directly()
|
|
40
|
+
|
|
41
|
+
sometime = timezone.now() # mark time before the school creation
|
|
42
|
+
school = create_organisation_directly(teacher_email)
|
|
43
|
+
|
|
44
|
+
# check the creation time
|
|
45
|
+
assert school.creation_time > sometime
|
|
46
|
+
|
|
47
|
+
sometime = timezone.now() # mark time before the class creation
|
|
48
|
+
klass, name, access_code = create_class_directly(teacher_email)
|
|
49
|
+
# check the creation time
|
|
50
|
+
assert klass.creation_time > sometime
|
|
51
|
+
|
|
52
|
+
def test_school_admins(self):
|
|
53
|
+
"""
|
|
54
|
+
Test that only the admins of a school are returned by the school.admins() function.
|
|
55
|
+
"""
|
|
56
|
+
email1, password1 = signup_teacher_directly()
|
|
57
|
+
email2, password2 = signup_teacher_directly()
|
|
58
|
+
email3, password3 = signup_teacher_directly()
|
|
59
|
+
school = create_organisation_directly(email1)
|
|
60
|
+
join_teacher_to_organisation(email2, school.name)
|
|
61
|
+
join_teacher_to_organisation(email3, school.name, is_admin=True)
|
|
62
|
+
|
|
63
|
+
teacher1 = Teacher.objects.get(new_user__username=email1)
|
|
64
|
+
teacher2 = Teacher.objects.get(new_user__username=email2)
|
|
65
|
+
teacher3 = Teacher.objects.get(new_user__username=email3)
|
|
66
|
+
|
|
67
|
+
assert len(school.admins()) == 2
|
|
68
|
+
assert teacher1 in school.admins()
|
|
69
|
+
assert teacher2 not in school.admins()
|
|
70
|
+
assert teacher3 in school.admins()
|
|
71
|
+
|
|
72
|
+
def test_sanitise_uk_postcode(self):
|
|
73
|
+
postcode_with_space = "AL10 9NE"
|
|
74
|
+
postcode_without_space = "AL109UL"
|
|
75
|
+
invalid_postcode = "123"
|
|
76
|
+
|
|
77
|
+
assert sanitise_uk_postcode(postcode_with_space) == "AL10 9NE" # Check it stays the same
|
|
78
|
+
assert sanitise_uk_postcode(postcode_without_space) == "AL10 9UL" # Check a space is added
|
|
79
|
+
assert sanitise_uk_postcode(invalid_postcode) == "123" # Check nothing happens
|
|
80
|
+
|
|
81
|
+
def test_daily_activity_serializer(self):
|
|
82
|
+
daily_activity = DailyActivity()
|
|
83
|
+
|
|
84
|
+
assert (
|
|
85
|
+
str(daily_activity)
|
|
86
|
+
== f"Activity on {daily_activity.date}: CSV clicks: 0, login cards clicks: 0, primary pack downloads: 0, python pack downloads: 0, level control submits: 0, teacher lockout resets: 0, indy lockout resets: 0, school student lockout resets: 0, unverified teachers anonymised: 0, unverified independents anonymised: 0"
|
|
87
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
from common.helpers.generators import generate_access_code
|
|
4
|
+
from common.models import Class, Teacher
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def generate_details():
|
|
8
|
+
name = "Class %d" % generate_details.next_id
|
|
9
|
+
access_code = generate_access_code()
|
|
10
|
+
|
|
11
|
+
generate_details.next_id += 1
|
|
12
|
+
|
|
13
|
+
return name, access_code
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
generate_details.next_id = 1
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_class_directly(teacher_email: str, class_name: str = None) -> Tuple[Class, str, str]:
|
|
20
|
+
"""Generate a class with the details given.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
teacher_email (str): The email of the teacher that will own the class.
|
|
24
|
+
class_name (str, optional): The name of the class. Defaults to auto-generated name.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
(class: Class, name: str, access_code: str): A tuple with the class model instance, name and access code.
|
|
28
|
+
"""
|
|
29
|
+
name, access_code = generate_details()
|
|
30
|
+
|
|
31
|
+
if class_name is not None:
|
|
32
|
+
name = class_name
|
|
33
|
+
|
|
34
|
+
teacher = Teacher.objects.get(new_user__email=teacher_email)
|
|
35
|
+
|
|
36
|
+
klass = Class.objects.create(name=name, access_code=access_code, teacher=teacher)
|
|
37
|
+
|
|
38
|
+
return klass, name, access_code
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from builtins import str
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def follow_verify_email_link_to_onboarding(page, url):
|
|
5
|
+
page.browser.get(url)
|
|
6
|
+
|
|
7
|
+
return go_to_teacher_login_page(page.browser)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def follow_verify_email_link_to_login(page, url, user_type):
|
|
11
|
+
page.browser.get(url)
|
|
12
|
+
|
|
13
|
+
if user_type == "teacher":
|
|
14
|
+
return go_to_teacher_login_page(page.browser)
|
|
15
|
+
elif user_type == "independent":
|
|
16
|
+
return go_to_independent_student_login_page(page.browser)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def follow_duplicate_account_link_to_login(page, url, user_type):
|
|
20
|
+
page.browser.get(url)
|
|
21
|
+
|
|
22
|
+
if user_type == "teacher":
|
|
23
|
+
return go_to_teacher_login_page(page.browser)
|
|
24
|
+
elif user_type == "independent":
|
|
25
|
+
return go_to_independent_student_login_page(page.browser)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def follow_reset_email_link(browser, link):
|
|
29
|
+
browser.get(link)
|
|
30
|
+
|
|
31
|
+
from portal.tests.pageObjects.portal.password_reset_form_page import (
|
|
32
|
+
PasswordResetPage,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return PasswordResetPage(browser)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def follow_change_email_link_to_dashboard(page, url):
|
|
39
|
+
page.browser.get(url)
|
|
40
|
+
|
|
41
|
+
return go_to_teacher_login_page(page.browser)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def follow_change_email_link_to_independent_dashboard(page, url):
|
|
45
|
+
page.browser.get(url)
|
|
46
|
+
|
|
47
|
+
return go_to_independent_student_login_page(page.browser)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def go_to_teacher_login_page(browser):
|
|
51
|
+
from portal.tests.pageObjects.portal.teacher_login_page import TeacherLoginPage
|
|
52
|
+
|
|
53
|
+
return TeacherLoginPage(browser)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def go_to_teacher_dashboard_page(browser):
|
|
57
|
+
from portal.tests.pageObjects.portal.teach.dashboard_page import TeachDashboardPage
|
|
58
|
+
|
|
59
|
+
return TeachDashboardPage(browser)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def go_to_independent_student_login_page(browser):
|
|
63
|
+
from portal.tests.pageObjects.portal.independent_login_page import (
|
|
64
|
+
IndependentStudentLoginPage,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return IndependentStudentLoginPage(browser)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from common.models import School, Teacher
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def generate_details(**kwargs):
|
|
5
|
+
name = kwargs.get("name", "School %d" % generate_details.next_id)
|
|
6
|
+
|
|
7
|
+
generate_details.next_id += 1
|
|
8
|
+
|
|
9
|
+
return name
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
generate_details.next_id = 1
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_organisation_directly(teacher_email, **kwargs):
|
|
16
|
+
name = generate_details(**kwargs)
|
|
17
|
+
|
|
18
|
+
school = School.objects.create(name=name, country="GB")
|
|
19
|
+
|
|
20
|
+
teacher = Teacher.objects.get(new_user__email=teacher_email)
|
|
21
|
+
teacher.school = school
|
|
22
|
+
teacher.is_admin = True
|
|
23
|
+
teacher.save()
|
|
24
|
+
|
|
25
|
+
return school
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def join_teacher_to_organisation(teacher_email, org_name, is_admin=False):
|
|
29
|
+
teacher = Teacher.objects.get(new_user__email=teacher_email)
|
|
30
|
+
school = School.objects.get(name=org_name)
|
|
31
|
+
|
|
32
|
+
teacher.school = school
|
|
33
|
+
teacher.is_admin = is_admin
|
|
34
|
+
teacher.save()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def create_organisation(page, password):
|
|
38
|
+
name = generate_details()
|
|
39
|
+
page = page.create_organisation(name, password)
|
|
40
|
+
|
|
41
|
+
return page, name
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from builtins import range
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
from common.helpers.emails import generate_token
|
|
6
|
+
from common.helpers.generators import generate_login_id
|
|
7
|
+
from common.models import Class, Student
|
|
8
|
+
|
|
9
|
+
from . import email
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_school_details():
|
|
13
|
+
name = "Student %d" % generate_school_details.next_id
|
|
14
|
+
password = "Password2"
|
|
15
|
+
|
|
16
|
+
generate_school_details.next_id += 1
|
|
17
|
+
|
|
18
|
+
return name, password
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
generate_school_details.next_id = 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_school_student_directly(access_code) -> Tuple[str, str, Student]:
|
|
25
|
+
"""Creates a new student in the class with the specified access code.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
access_code (str): The access code of the class the created student will be in
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple[str, str, Student]: (name, password, student)
|
|
32
|
+
"""
|
|
33
|
+
name, password = generate_school_details()
|
|
34
|
+
|
|
35
|
+
klass = Class.objects.get(access_code=access_code)
|
|
36
|
+
|
|
37
|
+
student = Student.objects.schoolFactory(klass, name, password)
|
|
38
|
+
return name, password, student
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_student_with_direct_login(access_code) -> Tuple[Student, str]:
|
|
42
|
+
name, password = generate_school_details()
|
|
43
|
+
klass = Class.objects.get(access_code=access_code)
|
|
44
|
+
|
|
45
|
+
# use random string for direct login)
|
|
46
|
+
login_id, hashed_login_id = generate_login_id()
|
|
47
|
+
student = Student.objects.schoolFactory(klass, name, password, hashed_login_id)
|
|
48
|
+
|
|
49
|
+
return student, login_id, name, password
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def create_independent_student_directly(preverified=True):
|
|
53
|
+
"""
|
|
54
|
+
Creates a Student object and makes it independent by generating random details.
|
|
55
|
+
Also verifies the student's email if preverified is True.
|
|
56
|
+
:param preverified: whether or not the independent student's email should be
|
|
57
|
+
verified.
|
|
58
|
+
:return: the student's username, password and the student object itself.
|
|
59
|
+
"""
|
|
60
|
+
name, username, email, password = generate_independent_student_details()
|
|
61
|
+
|
|
62
|
+
student = Student.objects.independentStudentFactory(name, email, password)
|
|
63
|
+
|
|
64
|
+
# verify student
|
|
65
|
+
generate_token(student.new_user, preverified=preverified)
|
|
66
|
+
|
|
67
|
+
return username, password, student
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def create_school_student(page):
|
|
71
|
+
name, _ = generate_school_details()
|
|
72
|
+
|
|
73
|
+
page = page.type_student_name(name).create_students()
|
|
74
|
+
|
|
75
|
+
return page, name
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def create_many_school_students(page, number_of_students):
|
|
79
|
+
names = ["" for i in range(number_of_students)]
|
|
80
|
+
|
|
81
|
+
for i in range(number_of_students):
|
|
82
|
+
names[i], _ = generate_school_details()
|
|
83
|
+
page = page.type_student_name(names[i])
|
|
84
|
+
|
|
85
|
+
page = page.create_students()
|
|
86
|
+
|
|
87
|
+
return page, names
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def generate_independent_student_details():
|
|
91
|
+
name = "Independent Student %d" % generate_independent_student_details.next_id
|
|
92
|
+
email_address = "student%d@codeforlife.com" % generate_independent_student_details.next_id
|
|
93
|
+
username = email_address
|
|
94
|
+
password = "$RFVBGT%^YHNmju7$RFVBGT%^YHNmju7$RFVBGT%^YHNmju7"
|
|
95
|
+
|
|
96
|
+
generate_independent_student_details.next_id += 1
|
|
97
|
+
|
|
98
|
+
return name, username, email_address, password
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
generate_independent_student_details.next_id = 1
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@patch("common.helpers.emails.send_dotdigital_email")
|
|
105
|
+
def create_independent_student(page, mock_send_dotdigital_email):
|
|
106
|
+
page = page.go_to_signup_page()
|
|
107
|
+
|
|
108
|
+
name, username, email_address, password = generate_independent_student_details()
|
|
109
|
+
page = page.independent_student_signup(name, email_address, password=password, confirm_password=password)
|
|
110
|
+
|
|
111
|
+
page = page.return_to_home_page()
|
|
112
|
+
|
|
113
|
+
verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
|
|
114
|
+
|
|
115
|
+
page = email.follow_verify_email_link_to_login(page, verification_url, "independent")
|
|
116
|
+
|
|
117
|
+
return page, name, username, email_address, password
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def verify_email(page, verification_url):
|
|
121
|
+
page = email.follow_verify_email_link_to_login(page, verification_url, "independent")
|
|
122
|
+
|
|
123
|
+
return page
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import sys
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
from common.helpers.emails import generate_token
|
|
6
|
+
from common.models import Teacher
|
|
7
|
+
|
|
8
|
+
from . import email
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_details(**kwargs):
|
|
12
|
+
random_int = random.randint(1, sys.maxsize)
|
|
13
|
+
first_name = kwargs.get("first_name", "Test")
|
|
14
|
+
last_name = kwargs.get("last_name", f"Teacher {random_int}")
|
|
15
|
+
email_address = kwargs.get("email_address", f"testteacher{random_int}@codeforlife.com")
|
|
16
|
+
password = kwargs.get("password", "$RFVBGT%6yhn$RFVBGT%6yhn$RFVBGT%6yhn$RFVBGT%6yhn")
|
|
17
|
+
|
|
18
|
+
return first_name, last_name, email_address, password
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def signup_teacher_directly(preverified=True, **kwargs):
|
|
22
|
+
"""
|
|
23
|
+
Creates a Teacher object by using the details passed in as kwargs, or by
|
|
24
|
+
generating random details. Also verifies the teacher's email if preverified
|
|
25
|
+
is True.
|
|
26
|
+
:param preverified: whether or not the teacher's email should be verified.
|
|
27
|
+
:return: the teacher's email and password.
|
|
28
|
+
"""
|
|
29
|
+
first_name, last_name, email_address, password = generate_details(**kwargs)
|
|
30
|
+
teacher = Teacher.objects.factory(first_name, last_name, email_address, password)
|
|
31
|
+
generate_token(teacher.new_user, preverified=preverified)
|
|
32
|
+
teacher.user.save()
|
|
33
|
+
return email_address, password
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@patch("portal.views.home.send_dotdigital_email")
|
|
37
|
+
def signup_duplicate_teacher_fail(page, duplicate_email, mock_send_dotdigital_email):
|
|
38
|
+
page = page.go_to_signup_page()
|
|
39
|
+
|
|
40
|
+
first_name, last_name, email_address, password = generate_details()
|
|
41
|
+
page = page.signup(first_name, last_name, duplicate_email, password, password)
|
|
42
|
+
|
|
43
|
+
page = page.return_to_home_page()
|
|
44
|
+
|
|
45
|
+
login_link = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["LOGIN_URL"]
|
|
46
|
+
|
|
47
|
+
page = email.follow_duplicate_account_link_to_login(page, login_link, "teacher")
|
|
48
|
+
|
|
49
|
+
return page, email_address, password
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@patch("common.helpers.emails.send_dotdigital_email")
|
|
53
|
+
def signup_teacher(page, mock_send_dotdigital_email, newsletter=False):
|
|
54
|
+
page = page.go_to_signup_page()
|
|
55
|
+
|
|
56
|
+
first_name, last_name, email_address, password = generate_details()
|
|
57
|
+
page = page.signup(
|
|
58
|
+
first_name, last_name, email_address, password=password, confirm_password=password, newsletter=newsletter
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
page = page.return_to_home_page()
|
|
62
|
+
|
|
63
|
+
verification_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"]["VERIFICATION_LINK"]
|
|
64
|
+
|
|
65
|
+
page = email.follow_verify_email_link_to_onboarding(page, verification_url)
|
|
66
|
+
|
|
67
|
+
return page, email_address, password
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def verify_email(page, verification_url):
|
|
71
|
+
page = email.follow_verify_email_link_to_login(page, verification_url, "teacher")
|
|
72
|
+
|
|
73
|
+
return page
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from django.contrib.auth.models import User
|
|
2
|
+
from django.utils import timezone
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_superuser():
|
|
6
|
+
"""Get a superuser for testing, or create one if there isn't one."""
|
|
7
|
+
try:
|
|
8
|
+
return User.objects.get(username="superuser")
|
|
9
|
+
except User.DoesNotExist:
|
|
10
|
+
return User.objects.create_superuser("superuser", "superuser@codeforlife.education", "password")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_user_directly(active=True, **kwargs):
|
|
14
|
+
"""Create a user in the database."""
|
|
15
|
+
days_to_subtract = 10 if active else 2000
|
|
16
|
+
username = "old_user+{:d}".format(create_user_directly.next_id)
|
|
17
|
+
user = User.objects.create_user(username, password="password")
|
|
18
|
+
user.last_login = timezone.now() - timezone.timedelta(days=days_to_subtract)
|
|
19
|
+
user.date_joined = timezone.now() - timezone.timedelta(days=days_to_subtract - 1)
|
|
20
|
+
user.save()
|
|
21
|
+
|
|
22
|
+
create_user_directly.next_id += 1
|
|
23
|
+
|
|
24
|
+
return user
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
create_user_directly.next_id = 1
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
2
|
+
from django.contrib.auth.views import redirect_to_login
|
|
3
|
+
from django.core.cache import cache
|
|
4
|
+
from django.core.exceptions import FieldDoesNotExist
|
|
5
|
+
from two_factor.utils import default_device
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def two_factor_cache_key(user):
|
|
9
|
+
"""Cache key for using_two_factor."""
|
|
10
|
+
return "using-two-factor-%s" % user.pk
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _using_two_factor(user):
|
|
14
|
+
"""Returns whether the user is using 2fa or not."""
|
|
15
|
+
return default_device(user)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def using_two_factor(user):
|
|
19
|
+
"""Returns whether the user is using 2fa or not (Cached)."""
|
|
20
|
+
if hasattr(user, "using_two_factor_cache"):
|
|
21
|
+
# First try local memory, as we call this a lot in one request
|
|
22
|
+
return user.using_two_factor_cache
|
|
23
|
+
cache_key = two_factor_cache_key(user)
|
|
24
|
+
val = cache.get(cache_key)
|
|
25
|
+
if val is not None:
|
|
26
|
+
# If local memory failed, but we got it from memcache, set local memory
|
|
27
|
+
user.using_two_factor_cache = val
|
|
28
|
+
return val
|
|
29
|
+
val = bool(_using_two_factor(user))
|
|
30
|
+
|
|
31
|
+
# We didn't find it in the cache, so set it there and local memory
|
|
32
|
+
cache.set(cache_key, val, None) # Cache forever
|
|
33
|
+
user.using_two_factor_cache = val
|
|
34
|
+
return val
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def field_exists(model, field):
|
|
38
|
+
try:
|
|
39
|
+
field = model._meta.get_field(field)
|
|
40
|
+
except FieldDoesNotExist:
|
|
41
|
+
return False
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class LoginRequiredNoErrorMixin(LoginRequiredMixin):
|
|
46
|
+
"""
|
|
47
|
+
Overwrites Django's 2.2 LoginRequiredMixin so as to not raise an error and
|
|
48
|
+
redirect instead.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def handle_no_permission(self):
|
|
52
|
+
return redirect_to_login(
|
|
53
|
+
self.request.get_full_path(),
|
|
54
|
+
self.get_login_url(),
|
|
55
|
+
self.get_redirect_field_name(),
|
|
56
|
+
)
|