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