codeforlife-portal 5.33.5__py2.py3-none-any.whl → 8.9.9__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cfl_common/common/__init__.py +1 -0
- cfl_common/common/app_settings.py +66 -0
- cfl_common/common/apps.py +6 -0
- cfl_common/common/context_processors.py +9 -0
- cfl_common/common/csp_config.py +85 -0
- cfl_common/common/helpers/__init__.py +0 -0
- cfl_common/common/helpers/data_migration_loader.py +42 -0
- cfl_common/common/helpers/emails.py +393 -0
- cfl_common/common/helpers/generators.py +52 -0
- cfl_common/common/helpers/organisation.py +10 -0
- cfl_common/common/mail.py +201 -0
- cfl_common/common/migrations/0001_initial.py +240 -0
- cfl_common/common/migrations/0002_emailverification.py +55 -0
- cfl_common/common/migrations/0003_aimmocharacter.py +31 -0
- cfl_common/common/migrations/0004_add_aimmocharacters.py +17 -0
- cfl_common/common/migrations/0005_add_worksheets.py +8 -0
- cfl_common/common/migrations/0006_update_aimmo_character_image_path.py +17 -0
- cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +8 -0
- cfl_common/common/migrations/0008_unlock_worksheet_3.py +11 -0
- cfl_common/common/migrations/0009_add_blocked_time_to_teacher_and_student.py +24 -0
- cfl_common/common/migrations/0010_remove_teacher_title.py +18 -0
- cfl_common/common/migrations/0011_student_login_id.py +18 -0
- cfl_common/common/migrations/0012_usersession.py +39 -0
- cfl_common/common/migrations/0013_class_school.py +42 -0
- cfl_common/common/migrations/0014_login_type.py +29 -0
- cfl_common/common/migrations/0015_dailyactivity.py +31 -0
- cfl_common/common/migrations/0016_joinreleasestudent.py +42 -0
- cfl_common/common/migrations/0017_copy_email_to_username.py +18 -0
- cfl_common/common/migrations/0018_update_aimmo_character_image_path.py +15 -0
- cfl_common/common/migrations/0019_aimmocharacter_alt.py +16 -0
- cfl_common/common/migrations/0020_class_is_active_and_null_access_code.py +23 -0
- cfl_common/common/migrations/0021_school_is_active.py +28 -0
- cfl_common/common/migrations/0022_school_cleanup.py +29 -0
- cfl_common/common/migrations/0023_userprofile_aimmo_badges.py +22 -0
- cfl_common/common/migrations/0024_teacher_invited_by.py +25 -0
- cfl_common/common/migrations/0025_schoolteacherinvitation.py +47 -0
- cfl_common/common/migrations/0026_teacher_remove_join_request.py +22 -0
- cfl_common/common/migrations/0027_class_created_by.py +25 -0
- cfl_common/common/migrations/0028_coding_club_downloads.py +23 -0
- cfl_common/common/migrations/0029_dynamicelement.py +22 -0
- cfl_common/common/migrations/0030_add_maintenance_banner.py +25 -0
- cfl_common/common/migrations/0031_improve_admin_panel.py +56 -0
- cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py +18 -0
- cfl_common/common/migrations/0033_password_reset_tracking_fields.py +23 -0
- cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +18 -0
- cfl_common/common/migrations/0035_rename_lockout_fields.py +27 -0
- cfl_common/common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +17 -0
- cfl_common/common/migrations/0037_migrate_email_verification.py +21 -0
- cfl_common/common/migrations/0038_delete_emailverification.py +16 -0
- cfl_common/common/migrations/0039_copy_email_to_username.py +18 -0
- cfl_common/common/migrations/0040_school_county.py +18 -0
- cfl_common/common/migrations/0041_populate_gb_counties.py +27 -0
- cfl_common/common/migrations/0042_totalactivity.py +25 -0
- cfl_common/common/migrations/0043_add_total_activity.py +30 -0
- cfl_common/common/migrations/0044_update_activity_models.py +33 -0
- cfl_common/common/migrations/0045_otp.py +23 -0
- cfl_common/common/migrations/0046_alter_school_country.py +19 -0
- cfl_common/common/migrations/0047_delete_school_postcode.py +16 -0
- cfl_common/common/migrations/0048_unique_school_names.py +42 -0
- cfl_common/common/migrations/0049_anonymise_orphan_users.py +29 -0
- cfl_common/common/migrations/0050_anonymise_orphan_schools.py +30 -0
- cfl_common/common/migrations/0051_verify_returning_users.py +26 -0
- cfl_common/common/migrations/0052_add_cse_fields.py +68 -0
- cfl_common/common/migrations/0053_clean_class_data.py +24 -0
- cfl_common/common/migrations/0054_delete_aimmo_models.py +20 -0
- cfl_common/common/migrations/0055_alter_schoolteacherinvitation_token.py +18 -0
- cfl_common/common/migrations/0056_set_non_school_teachers_as_non_admins.py +25 -0
- cfl_common/common/migrations/0057_teacher_teacher__is_admin.py +19 -0
- cfl_common/common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
- cfl_common/common/migrations/__init__.py +0 -0
- cfl_common/common/models.py +557 -0
- cfl_common/common/permissions.py +84 -0
- cfl_common/common/tests/__init__.py +0 -0
- cfl_common/common/tests/test_migration_anonymise_orphan_schools.py +30 -0
- cfl_common/common/tests/test_migration_anonymise_orphan_users.py +30 -0
- cfl_common/common/tests/test_migration_blocked_time.py +15 -0
- cfl_common/common/tests/test_migration_remove_teacher_title.py +13 -0
- cfl_common/common/tests/test_migration_unique_school_names.py +33 -0
- cfl_common/common/tests/test_migration_verify_returning_users.py +59 -0
- cfl_common/common/tests/test_models.py +87 -0
- cfl_common/common/tests/utils/__init__.py +0 -0
- cfl_common/common/tests/utils/classes.py +38 -0
- cfl_common/common/tests/utils/email.py +67 -0
- cfl_common/common/tests/utils/organisation.py +41 -0
- cfl_common/common/tests/utils/student.py +123 -0
- cfl_common/common/tests/utils/teacher.py +73 -0
- cfl_common/common/tests/utils/user.py +27 -0
- cfl_common/common/utils.py +56 -0
- cfl_common/setup.py +61 -0
- codeforlife_portal-8.9.9.dist-info/METADATA +226 -0
- {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/RECORD +339 -241
- {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/WHEEL +1 -1
- codeforlife_portal-8.9.9.dist-info/licenses/LICENSE.md +3 -0
- {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/top_level.txt +1 -0
- deploy/middleware/maintenance.py +25 -0
- deploy/middleware/screentime_warning.py +29 -0
- deploy/middleware/security.py +5 -6
- deploy/middleware/session_timeout.py +4 -2
- deploy/middleware/tmp_basic_auth.py +41 -0
- example_project/portal_test_settings.py +239 -0
- example_project/settings.py +156 -17
- example_project/urls.py +5 -6
- portal/__init__.py +1 -1
- portal/admin.py +142 -29
- portal/app_settings.py +8 -7
- portal/forms/dotmailer.py +6 -4
- portal/forms/invite_teacher.py +19 -10
- portal/forms/organisation.py +137 -68
- portal/forms/play.py +53 -98
- portal/forms/registration.py +70 -164
- portal/forms/teach.py +147 -121
- portal/handlers.py +1 -2
- portal/helpers/decorators.py +30 -10
- portal/helpers/password.py +86 -47
- portal/helpers/ratelimit.py +32 -15
- portal/helpers/regexes.py +5 -0
- portal/helpers/request_handlers.py +10 -0
- portal/migrations/0044_auto_20150430_0959.py +6 -2
- portal/mixins/__init__.py +1 -0
- portal/mixins/cron_mixin.py +12 -0
- portal/permissions/__init__.py +1 -0
- portal/permissions/is_cron_request_from_google.py +14 -0
- portal/static/portal/img/10_years_anniversary.png +0 -0
- portal/static/portal/img/RR_logo_grass_background.png +0 -0
- portal/static/portal/img/coding_club_hero.jpg +0 -0
- portal/static/portal/img/coding_club_python_pack.png +0 -0
- portal/static/portal/img/facebook.png +0 -0
- portal/static/portal/img/gitbook.png +0 -0
- portal/static/portal/img/howe_dell_1.png +0 -0
- portal/static/portal/img/howe_dell_2.png +0 -0
- portal/static/portal/img/howe_dell_3.png +0 -0
- portal/static/portal/img/logo_cfl.png +0 -0
- portal/static/portal/img/logo_cfl_powered.svg +35 -0
- portal/static/portal/img/logo_cfl_reminder_cards.jpg +0 -0
- portal/static/portal/img/logo_ocado_group.png +0 -0
- portal/static/portal/img/logo_python_den.svg +21 -0
- portal/static/portal/img/long_europe_map.png +0 -0
- portal/static/portal/img/python_den.png +0 -0
- portal/static/portal/img/python_den_banner.svg +26 -0
- portal/static/portal/img/rapid_router_landing_hero.png +0 -0
- portal/static/portal/img/rr_advanced.png +0 -0
- portal/static/portal/img/ten_year_map_pin.svg +1 -0
- portal/static/portal/img/thumbnail_educate_rapid_router.png +0 -0
- portal/static/portal/img/thumbnail_educate_resources.png +0 -0
- portal/static/portal/img/thumbnail_play_rapid_router.png +0 -0
- portal/static/portal/img/thumbnail_python_den.png +0 -0
- portal/static/portal/img/twitter.png +0 -0
- portal/static/portal/js/carouselCards.js +25 -0
- portal/static/portal/js/common.js +96 -1
- portal/static/portal/js/independentLogin.js +16 -0
- portal/static/portal/js/independentRegistration.js +86 -0
- portal/static/portal/js/levelControl.js +77 -0
- portal/static/portal/js/lib/jquery.min.js +2 -0
- portal/static/portal/js/organisation_manage.js +142 -14
- portal/static/portal/js/passwordStrength.js +154 -64
- portal/static/portal/js/resetPassword.js +23 -0
- portal/static/portal/js/riveted.min.js +238 -239
- portal/static/portal/js/school.js +13 -0
- portal/static/portal/js/studentLogin.js +16 -0
- portal/static/portal/js/teacherEditStudent.js +23 -0
- portal/static/portal/js/teacherLogin.js +16 -0
- portal/static/portal/js/tenYearMap.js +14 -0
- portal/static/portal/sass/colorbox.scss +0 -1
- portal/static/portal/sass/modules/_colours.scss +1 -0
- portal/static/portal/sass/modules/_levels.scss +1 -1
- portal/static/portal/sass/modules/_mixins.scss +21 -0
- portal/static/portal/sass/partials/_banners.scss +4 -177
- portal/static/portal/sass/partials/_buttons.scss +12 -15
- portal/static/portal/sass/partials/_carousel.scss +129 -0
- portal/static/portal/sass/partials/_footer.scss +21 -22
- portal/static/portal/sass/partials/_forms.scss +60 -5
- portal/static/portal/sass/partials/_grids.scss +34 -61
- portal/static/portal/sass/partials/_header.scss +28 -20
- portal/static/portal/sass/partials/_images.scss +292 -39
- portal/static/portal/sass/partials/_popup.scss +18 -15
- portal/static/portal/sass/partials/_tables.scss +12 -20
- portal/static/portal/sass/partials/_text.scss +6 -10
- portal/static/portal/sass/styles.scss +0 -1
- portal/static/portal/video/code for life .pdf +0 -0
- portal/strings/about.py +5 -0
- portal/strings/coding_club.py +9 -0
- portal/strings/play.py +6 -5
- portal/strings/teach.py +1 -1
- portal/strings/teacher_resources.py +2 -8
- portal/strings/ten_year_map.py +13 -0
- portal/templates/403.html +2 -2
- portal/templates/404.html +1 -1
- portal/templates/500.html +2 -2
- portal/templates/{captcha → django_recaptcha}/includes/js_v2_invisible.html +3 -3
- portal/templates/{captcha → django_recaptcha}/widget_v2_invisible.html +2 -2
- portal/templates/email.html +4 -2
- portal/templates/maintenance.html +34 -0
- portal/templates/portal/about.html +94 -62
- portal/templates/portal/base.html +176 -152
- portal/templates/portal/coding_club.html +100 -0
- portal/templates/portal/contribute.html +56 -52
- portal/templates/portal/email_invitation_sent.html +1 -1
- portal/templates/portal/email_style_template.html +374 -0
- portal/templates/portal/email_verification_failed.html +1 -1
- portal/templates/portal/email_verification_needed.html +9 -9
- portal/templates/portal/form_shapes.html +20 -8
- portal/templates/portal/getinvolved.html +6 -6
- portal/templates/portal/home.html +35 -10
- portal/templates/portal/home_learning.html +19 -19
- portal/templates/portal/locked_out.html +0 -1
- portal/templates/portal/locked_out_school_student.html +16 -0
- portal/templates/portal/login/independent_student.html +31 -15
- portal/templates/portal/login/student.html +10 -7
- portal/templates/portal/login/student_class_code.html +7 -4
- portal/templates/portal/login/teacher.html +34 -17
- portal/templates/portal/partials/banner.html +18 -4
- portal/templates/portal/partials/benefits.html +1 -1
- portal/templates/portal/partials/card_list.html +34 -24
- portal/templates/portal/partials/character_list.html +5 -5
- portal/templates/portal/partials/cookie_list.html +161 -0
- portal/templates/portal/partials/delete_popup.html +18 -0
- portal/templates/portal/partials/footer.html +57 -26
- portal/templates/portal/partials/header.html +118 -117
- portal/templates/portal/partials/hero_card.html +4 -3
- portal/templates/portal/partials/info_popup.html +3 -3
- portal/templates/portal/partials/invite_admin_teacher.html +23 -0
- portal/templates/portal/partials/popup.html +7 -2
- portal/templates/portal/partials/register_newsletter_tickbox.html +2 -5
- portal/templates/portal/partials/screentime_popup.html +14 -0
- portal/templates/portal/partials/service_unavailable_popup.html +17 -0
- portal/templates/portal/partials/session_popup.html +19 -0
- portal/templates/portal/play/student_dashboard.html +42 -29
- portal/templates/portal/play/student_edit_account.html +64 -9
- portal/templates/portal/play.html +61 -41
- portal/templates/portal/privacy_notice.html +697 -0
- portal/templates/portal/register.html +122 -92
- portal/templates/portal/reset_password.html +20 -40
- portal/templates/portal/reset_password_confirm.html +9 -4
- portal/templates/portal/reset_password_email_sent.html +15 -13
- portal/templates/portal/teach/base_registering.html +1 -1
- portal/templates/portal/teach/class.html +4 -6
- portal/templates/portal/teach/dashboard.html +212 -117
- portal/templates/portal/teach/invited.html +90 -0
- portal/templates/portal/teach/onboarding_classes.html +5 -3
- portal/templates/portal/teach/onboarding_print.html +1 -1
- portal/templates/portal/teach/onboarding_school.html +26 -139
- portal/templates/portal/teach/onboarding_students.html +1 -1
- portal/templates/portal/teach/teacher_dismiss_students.html +73 -55
- portal/templates/portal/teach/teacher_edit_class.html +168 -11
- portal/templates/portal/teach/teacher_edit_student.html +12 -5
- portal/templates/portal/teach/teacher_move_all_classes.html +25 -38
- portal/templates/portal/teach/teacher_move_students_to_class.html +1 -1
- portal/templates/portal/teach.html +61 -42
- portal/templates/portal/ten_year_map.html +147 -0
- portal/templates/portal/terms.html +191 -42
- portal/templates/two_factor/core/login.html +71 -59
- portal/templates/two_factor/core/setup.html +58 -49
- portal/templates/two_factor/profile/disable.html +1 -1
- portal/templates/two_factor/profile/profile.html +35 -17
- portal/templatetags/app_tags.py +59 -84
- portal/templatetags/card_list_tags.py +0 -4
- portal/tests/base_test.py +14 -3
- portal/tests/conftest.py +0 -15
- portal/tests/migrations/test_migration_make_portaladmin_teacher.py +2 -6
- portal/tests/migrations/test_migration_preview_users.py +3 -9
- portal/tests/migrations/test_migration_remove_guardian.py +1 -3
- portal/tests/migrations/test_migration_use_common_models.py +2 -6
- portal/tests/migrations/test_migration_verify_portaladmin.py +1 -3
- portal/tests/pageObjects/portal/admin/admin_base_page.py +0 -21
- portal/tests/pageObjects/portal/base_page.py +16 -26
- portal/tests/pageObjects/portal/email_verification_needed_page.py +3 -2
- portal/tests/pageObjects/portal/game_page.py +12 -19
- portal/tests/pageObjects/portal/home_page.py +13 -15
- portal/tests/pageObjects/portal/independent_login_page.py +13 -17
- portal/tests/pageObjects/portal/password_reset_form_page.py +20 -4
- portal/tests/pageObjects/portal/password_reset_page.py +25 -0
- portal/tests/pageObjects/portal/play/account_page.py +18 -27
- portal/tests/pageObjects/portal/play/dashboard_page.py +4 -4
- portal/tests/pageObjects/portal/play/join_school_or_club_page.py +8 -10
- portal/tests/pageObjects/portal/play/play_base_page.py +5 -3
- portal/tests/pageObjects/portal/signup_page.py +28 -59
- portal/tests/pageObjects/portal/student_login_class_code.py +6 -9
- portal/tests/pageObjects/portal/student_login_page.py +6 -8
- portal/tests/pageObjects/portal/teach/add_independent_student_to_class_page.py +3 -3
- portal/tests/pageObjects/portal/teach/added_independent_student_to_class_page.py +3 -1
- portal/tests/pageObjects/portal/teach/class_page.py +36 -13
- portal/tests/pageObjects/portal/teach/dashboard_page.py +43 -84
- portal/tests/pageObjects/portal/teach/dismiss_students_page.py +7 -5
- portal/tests/pageObjects/portal/teach/edit_student_page.py +10 -8
- portal/tests/pageObjects/portal/teach/move_class_page.py +5 -10
- portal/tests/pageObjects/portal/teach/move_classes_page.py +4 -2
- portal/tests/pageObjects/portal/teach/move_students_disambiguate_page.py +4 -2
- portal/tests/pageObjects/portal/teach/move_students_page.py +6 -13
- portal/tests/pageObjects/portal/teach/onboarding_classes_page.py +5 -3
- portal/tests/pageObjects/portal/teach/onboarding_organisation_page.py +11 -49
- portal/tests/pageObjects/portal/teach/onboarding_student_list_page.py +7 -12
- portal/tests/pageObjects/portal/teach/onboarding_students_page.py +4 -27
- portal/tests/pageObjects/portal/teach/teach_base_page.py +6 -4
- portal/tests/pageObjects/portal/teacher_login_page.py +10 -16
- portal/tests/selenium_test_case.py +3 -43
- portal/tests/snapshots/snap_test_partials.py +11 -165
- portal/tests/test_2FA.py +15 -33
- portal/tests/test_admin.py +15 -97
- portal/tests/test_api.py +212 -91
- portal/tests/test_captcha_forms.py +2 -2
- portal/tests/test_class.py +374 -24
- portal/tests/test_emails.py +83 -20
- portal/tests/{test_newsletter_footer.py → test_global_forms.py} +5 -5
- portal/tests/test_helper_methods.py +30 -0
- portal/tests/test_independent_student.py +255 -144
- portal/tests/test_invite_teacher.py +318 -10
- portal/tests/test_middleware.py +96 -9
- portal/tests/test_organisation.py +78 -262
- portal/tests/test_partials.py +0 -88
- portal/tests/test_ratelimit.py +218 -36
- portal/tests/test_school_student.py +35 -40
- portal/tests/test_security.py +12 -31
- portal/tests/test_teacher.py +425 -325
- portal/tests/test_teacher_student.py +103 -91
- portal/tests/test_views.py +900 -76
- portal/tests/utils/classes.py +2 -2
- portal/tests/utils/messages.py +13 -28
- portal/urls.py +235 -166
- portal/views/admin.py +0 -332
- portal/views/api.py +82 -48
- portal/views/cron/__init__.py +1 -0
- portal/views/cron/user.py +322 -0
- portal/views/dotmailer.py +9 -1
- portal/views/email.py +33 -77
- portal/views/google_analytics.py +28 -0
- portal/views/home.py +126 -97
- portal/views/legal.py +13 -0
- portal/views/login/independent_student.py +5 -5
- portal/views/login/student.py +51 -14
- portal/views/login/teacher.py +2 -6
- portal/views/organisation.py +20 -189
- portal/views/registration.py +97 -17
- portal/views/student/edit_account_details.py +99 -72
- portal/views/student/play.py +81 -62
- portal/views/teacher/dashboard.py +421 -149
- portal/views/teacher/teach.py +226 -177
- portal/views/two_factor/core.py +22 -19
- portal/views/two_factor/profile.py +2 -2
- codeforlife_portal-5.33.5.dist-info/LICENSE.md +0 -577
- codeforlife_portal-5.33.5.dist-info/METADATA +0 -38
- deploy/permissions.py +0 -2
- example_project/manage.py +0 -10
- portal/autoconfig.py +0 -141
- portal/csp_config.py +0 -60
- portal/forms/add_game.py +0 -33
- portal/helpers/location.py +0 -121
- portal/static/portal/img/kurono_hero.jpg +0 -0
- portal/static/portal/img/kurono_landing_hero.png +0 -0
- portal/static/portal/img/kurono_logo.svg +0 -1
- portal/static/portal/img/kurono_logo_grey_background.svg +0 -1
- portal/static/portal/img/kurono_logo_mark.svg +0 -1
- portal/static/portal/img/kurono_resources_hero.jpg +0 -0
- portal/static/portal/img/kurono_story.png +0 -0
- portal/static/portal/img/ocado-swirl.svg +0 -22
- portal/static/portal/img/thumbnail_educate_kurono.png +0 -0
- portal/static/portal/img/thumbnail_educate_resources_and_progress_tracking.png +0 -0
- portal/static/portal/img/thumbnail_kurono_resources.png +0 -0
- portal/static/portal/img/thumbnail_play_kurono.png +0 -0
- portal/static/portal/img/x_close_video.png +0 -0
- portal/static/portal/js/aimmoGame.js +0 -106
- portal/static/portal/js/deleteWorkspaces.js +0 -14
- portal/static/portal/js/fuzzySchoolLookup.js +0 -46
- portal/static/portal/js/lib/jquery-3.5.1.min.js +0 -2
- portal/static/portal/js/lib/jquery-ui-1.12.1.min.js +0 -13
- portal/static/portal/sass/partials/_videos.scss +0 -10
- portal/static/portal/video/aimmo_play_now_background_video.mp4 +0 -0
- portal/strings/student_aimmo_dashboard.py +0 -6
- portal/templates/portal/admin/aggregated_data.html +0 -35
- portal/templates/portal/admin/map.html +0 -70
- portal/templates/portal/mouseflow.html +0 -9
- portal/templates/portal/partials/aimmo_games_table.html +0 -83
- portal/templates/portal/partials/register_over_required_age_tickbox.html +0 -9
- portal/templates/portal/play/independent_student_dashboard.html +0 -64
- portal/templates/portal/play/student_aimmo_dashboard.html +0 -63
- portal/templates/portal/privacy_policy.html +0 -483
- portal/templates/portal/reset_password_email.html +0 -9
- portal/templates/portal/teach/invite.html +0 -25
- portal/templates/portal/teach/teacher_aimmo_dashboard.html +0 -95
- portal/templates/portal/teach/teacher_resources.html +0 -68
- portal/templatetags/character_list_tags.py +0 -16
- portal/tests/pageObjects/portal/kurono_teacher_dashboard_page.py +0 -49
- portal/tests/pageObjects/portal/student_password_reset_form_page.py +0 -23
- portal/tests/pageObjects/portal/teach/onboarding_revoke_request_page.py +0 -20
- portal/tests/pageObjects/portal/teacher_password_reset_form_page.py +0 -23
- portal/tests/test_aimmo_dashboards.py +0 -172
- portal/tests/test_location.py +0 -217
- portal/tests/utils/aimmo_games.py +0 -30
- portal/views/aimmo/dashboard.py +0 -119
- portal/views/privacy_policy.py +0 -9
- portal/views/teacher/teacher_resources.py +0 -42
- {portal/views/aimmo → cfl_common}/__init__.py +0 -0
portal/forms/play.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from datetime import timedelta
|
|
2
|
+
from datetime import timedelta, date
|
|
3
3
|
|
|
4
|
-
from captcha.fields import ReCaptchaField
|
|
5
|
-
from captcha.widgets import ReCaptchaV2Invisible
|
|
6
4
|
from common.helpers.emails import send_verification_email
|
|
7
5
|
from common.models import Class, Student, stripStudentName
|
|
8
6
|
from common.permissions import logged_in_as_independent_student
|
|
@@ -10,21 +8,17 @@ from django import forms
|
|
|
10
8
|
from django.contrib.auth import authenticate
|
|
11
9
|
from django.contrib.auth.forms import AuthenticationForm
|
|
12
10
|
from django.utils import timezone
|
|
11
|
+
from django_recaptcha.fields import ReCaptchaField
|
|
12
|
+
from django_recaptcha.widgets import ReCaptchaV2Invisible
|
|
13
13
|
|
|
14
14
|
from portal.forms.error_messages import INVALID_LOGIN_MESSAGE
|
|
15
15
|
from portal.helpers.password import PasswordStrength, form_clean_password
|
|
16
16
|
from portal.helpers.regexes import ACCESS_CODE_PATTERN
|
|
17
|
-
from portal.templatetags.app_tags import is_verified
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
class StudentClassCodeForm(forms.Form):
|
|
21
20
|
access_code = forms.CharField(
|
|
22
|
-
widget=forms.TextInput(
|
|
23
|
-
attrs={
|
|
24
|
-
"autocomplete": "off",
|
|
25
|
-
"placeholder": "Class code",
|
|
26
|
-
}
|
|
27
|
-
),
|
|
21
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Class code"}),
|
|
28
22
|
help_text="Enter your class code",
|
|
29
23
|
)
|
|
30
24
|
|
|
@@ -33,30 +27,18 @@ class StudentClassCodeForm(forms.Form):
|
|
|
33
27
|
|
|
34
28
|
if access_code:
|
|
35
29
|
if re.fullmatch(ACCESS_CODE_PATTERN, access_code.upper()) is None:
|
|
36
|
-
raise forms.ValidationError(
|
|
37
|
-
"Uh oh! You didn't input a valid class code."
|
|
38
|
-
)
|
|
30
|
+
raise forms.ValidationError("Uh oh! You didn't input a valid class code.")
|
|
39
31
|
|
|
40
32
|
return self.cleaned_data
|
|
41
33
|
|
|
42
34
|
|
|
43
35
|
class StudentLoginForm(AuthenticationForm):
|
|
44
36
|
username = forms.CharField(
|
|
45
|
-
widget=forms.TextInput(
|
|
46
|
-
attrs={
|
|
47
|
-
"autocomplete": "off",
|
|
48
|
-
"placeholder": "Username",
|
|
49
|
-
}
|
|
50
|
-
),
|
|
37
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Username"}),
|
|
51
38
|
help_text="Enter your username",
|
|
52
39
|
)
|
|
53
40
|
password = forms.CharField(
|
|
54
|
-
widget=forms.PasswordInput(
|
|
55
|
-
attrs={
|
|
56
|
-
"autocomplete": "off",
|
|
57
|
-
"placeholder": "Password",
|
|
58
|
-
}
|
|
59
|
-
),
|
|
41
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
|
|
60
42
|
help_text="Enter your password",
|
|
61
43
|
)
|
|
62
44
|
|
|
@@ -69,7 +51,6 @@ class StudentLoginForm(AuthenticationForm):
|
|
|
69
51
|
password = self.cleaned_data.get("password", None)
|
|
70
52
|
|
|
71
53
|
if name and self.access_code and password:
|
|
72
|
-
|
|
73
54
|
student, user = self.check_for_errors(name, self.access_code, password)
|
|
74
55
|
|
|
75
56
|
self.student = student
|
|
@@ -84,16 +65,12 @@ class StudentLoginForm(AuthenticationForm):
|
|
|
84
65
|
|
|
85
66
|
name = stripStudentName(name)
|
|
86
67
|
|
|
87
|
-
students = Student.objects.filter(
|
|
88
|
-
new_user__first_name__iexact=name, class_field=klass
|
|
89
|
-
)
|
|
68
|
+
students = Student.objects.filter(new_user__first_name__iexact=name, class_field=klass)
|
|
90
69
|
if len(students) != 1:
|
|
91
70
|
raise forms.ValidationError("Invalid name, class access code or password")
|
|
92
71
|
|
|
93
72
|
student = students[0]
|
|
94
|
-
user = authenticate(
|
|
95
|
-
username=student.new_user.username, password=password.lower()
|
|
96
|
-
)
|
|
73
|
+
user = authenticate(username=student.new_user.username, password=password.lower())
|
|
97
74
|
|
|
98
75
|
# Try the case sensitive password too, for previous accounts that don't have the lowercase one stored
|
|
99
76
|
if user is None:
|
|
@@ -110,16 +87,16 @@ class StudentLoginForm(AuthenticationForm):
|
|
|
110
87
|
class StudentEditAccountForm(forms.Form):
|
|
111
88
|
password = forms.CharField(
|
|
112
89
|
required=True,
|
|
113
|
-
widget=forms.PasswordInput(attrs={"placeholder": "New password"}),
|
|
90
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "New password"}),
|
|
114
91
|
help_text="Enter new password",
|
|
115
92
|
)
|
|
116
93
|
confirm_password = forms.CharField(
|
|
117
94
|
required=True,
|
|
118
|
-
widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password"}),
|
|
95
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm new password"}),
|
|
119
96
|
help_text="Confirm new password",
|
|
120
97
|
)
|
|
121
98
|
current_password = forms.CharField(
|
|
122
|
-
widget=forms.PasswordInput(attrs={"placeholder": "Current password"}),
|
|
99
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Current password"}),
|
|
123
100
|
help_text="Enter your current password",
|
|
124
101
|
)
|
|
125
102
|
|
|
@@ -138,28 +115,28 @@ class IndependentStudentEditAccountForm(forms.Form):
|
|
|
138
115
|
name = forms.CharField(
|
|
139
116
|
max_length=100,
|
|
140
117
|
required=False,
|
|
141
|
-
widget=forms.TextInput(attrs={"placeholder": "Name"}),
|
|
118
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Name"}),
|
|
142
119
|
help_text="Enter your name",
|
|
143
120
|
)
|
|
144
121
|
email = forms.EmailField(
|
|
145
122
|
required=False,
|
|
146
|
-
widget=forms.EmailInput(attrs={"placeholder": "New email address (optional)"}),
|
|
123
|
+
widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "New email address (optional)"}),
|
|
147
124
|
help_text="Enter new email address (optional)",
|
|
148
125
|
)
|
|
149
126
|
password = forms.CharField(
|
|
150
127
|
required=False,
|
|
151
|
-
widget=forms.PasswordInput(attrs={"placeholder": "New password (optional)"}),
|
|
128
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "New password (optional)"}),
|
|
152
129
|
help_text="Enter new password (optional)",
|
|
153
130
|
)
|
|
154
131
|
confirm_password = forms.CharField(
|
|
155
132
|
label="Confirm new password",
|
|
156
133
|
required=False,
|
|
157
|
-
widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password"}),
|
|
134
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm new password"}),
|
|
158
135
|
help_text="Confirm new password",
|
|
159
136
|
)
|
|
160
137
|
current_password = forms.CharField(
|
|
161
138
|
label="Current password",
|
|
162
|
-
widget=forms.PasswordInput(attrs={"placeholder": "Current password"}),
|
|
139
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Current password"}),
|
|
163
140
|
help_text="Enter your current password",
|
|
164
141
|
)
|
|
165
142
|
|
|
@@ -174,9 +151,7 @@ class IndependentStudentEditAccountForm(forms.Form):
|
|
|
174
151
|
raise forms.ValidationError("This field is required")
|
|
175
152
|
|
|
176
153
|
if re.match(re.compile("^[\w ]+$"), name) is None:
|
|
177
|
-
raise forms.ValidationError(
|
|
178
|
-
"Names may only contain letters, numbers, dashes, underscores, and spaces."
|
|
179
|
-
)
|
|
154
|
+
raise forms.ValidationError("Names may only contain letters, numbers, dashes, underscores, and spaces.")
|
|
180
155
|
|
|
181
156
|
return name
|
|
182
157
|
|
|
@@ -215,45 +190,36 @@ def are_password_and_confirm_password_different(password, confirm_password):
|
|
|
215
190
|
|
|
216
191
|
|
|
217
192
|
class IndependentStudentSignupForm(forms.Form):
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
attrs={"autocomplete": "off", "placeholder": "Full name"}
|
|
193
|
+
date_of_birth = forms.DateField(
|
|
194
|
+
help_text="Please enter your date of birth (we do not store this information).",
|
|
195
|
+
widget=forms.SelectDateWidget(
|
|
196
|
+
years=range(date.today().year, date.today().year - 100, -1), empty_label=("Year", "Month", "Day")
|
|
223
197
|
),
|
|
198
|
+
required=False,
|
|
224
199
|
)
|
|
225
200
|
|
|
226
|
-
|
|
201
|
+
name = forms.CharField(
|
|
227
202
|
max_length=100,
|
|
228
|
-
help_text="Enter
|
|
229
|
-
widget=forms.TextInput(
|
|
230
|
-
attrs={"autocomplete": "off", "placeholder": "Username"}
|
|
231
|
-
),
|
|
203
|
+
help_text="Enter full name",
|
|
204
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Full name"}),
|
|
232
205
|
)
|
|
233
206
|
|
|
234
207
|
email = forms.EmailField(
|
|
235
208
|
help_text="Enter your email address",
|
|
236
|
-
widget=forms.EmailInput(
|
|
237
|
-
attrs={"autocomplete": "off", "placeholder": "Email address"}
|
|
238
|
-
),
|
|
209
|
+
widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
|
|
239
210
|
)
|
|
240
211
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
is_over_required_age = forms.BooleanField(initial=False, required=True)
|
|
212
|
+
consent_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=True)
|
|
213
|
+
newsletter_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=False)
|
|
244
214
|
|
|
245
215
|
password = forms.CharField(
|
|
246
216
|
help_text="Enter a password",
|
|
247
|
-
widget=forms.PasswordInput(
|
|
248
|
-
attrs={"autocomplete": "off", "placeholder": "Password"}
|
|
249
|
-
),
|
|
217
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
|
|
250
218
|
)
|
|
251
219
|
|
|
252
220
|
confirm_password = forms.CharField(
|
|
253
221
|
help_text="Repeat password",
|
|
254
|
-
widget=forms.PasswordInput(
|
|
255
|
-
attrs={"autocomplete": "off", "placeholder": "Repeat password"}
|
|
256
|
-
),
|
|
222
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Repeat password"}),
|
|
257
223
|
)
|
|
258
224
|
|
|
259
225
|
captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
|
|
@@ -261,22 +227,10 @@ class IndependentStudentSignupForm(forms.Form):
|
|
|
261
227
|
def clean_name(self):
|
|
262
228
|
name = self.cleaned_data.get("name", None)
|
|
263
229
|
if re.match(re.compile("^[\w ]+$"), name) is None:
|
|
264
|
-
raise forms.ValidationError(
|
|
265
|
-
"Names may only contain letters, numbers, dashes, underscores, and spaces."
|
|
266
|
-
)
|
|
230
|
+
raise forms.ValidationError("Names may only contain letters, numbers, dashes, underscores, and spaces.")
|
|
267
231
|
|
|
268
232
|
return name
|
|
269
233
|
|
|
270
|
-
def clean_username(self):
|
|
271
|
-
username = self.cleaned_data.get("username", None)
|
|
272
|
-
|
|
273
|
-
if re.match(re.compile("[\w]+"), username) is None:
|
|
274
|
-
raise forms.ValidationError(
|
|
275
|
-
"Usernames may only contain letters, numbers, dashes, and underscores."
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
return username
|
|
279
|
-
|
|
280
234
|
def clean_password(self):
|
|
281
235
|
return form_clean_password(self, "password", PasswordStrength.INDEPENDENT)
|
|
282
236
|
|
|
@@ -291,8 +245,14 @@ class IndependentStudentSignupForm(forms.Form):
|
|
|
291
245
|
|
|
292
246
|
|
|
293
247
|
class IndependentStudentLoginForm(AuthenticationForm):
|
|
294
|
-
username = forms.
|
|
295
|
-
|
|
248
|
+
username = forms.EmailField(
|
|
249
|
+
widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
|
|
250
|
+
help_text="Enter your email address",
|
|
251
|
+
)
|
|
252
|
+
password = forms.CharField(
|
|
253
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
|
|
254
|
+
help_text="Enter your password",
|
|
255
|
+
)
|
|
296
256
|
|
|
297
257
|
def clean(self):
|
|
298
258
|
super().clean()
|
|
@@ -301,8 +261,8 @@ class IndependentStudentLoginForm(AuthenticationForm):
|
|
|
301
261
|
if not logged_in_as_independent_student(user):
|
|
302
262
|
self.show_invalid_login_message()
|
|
303
263
|
|
|
304
|
-
if not is_verified
|
|
305
|
-
send_verification_email(self.request, user)
|
|
264
|
+
if not user.userprofile.is_verified:
|
|
265
|
+
send_verification_email(self.request, user, self.data)
|
|
306
266
|
self.show_invalid_login_message()
|
|
307
267
|
|
|
308
268
|
def get_invalid_login_error(self):
|
|
@@ -313,28 +273,23 @@ class IndependentStudentLoginForm(AuthenticationForm):
|
|
|
313
273
|
|
|
314
274
|
|
|
315
275
|
class StudentJoinOrganisationForm(forms.Form):
|
|
316
|
-
access_code = forms.CharField(
|
|
317
|
-
label="Class Access Code",
|
|
318
|
-
widget=forms.TextInput(attrs={"placeholder": "AB123"}),
|
|
319
|
-
)
|
|
276
|
+
access_code = forms.CharField(label="Class Access Code", widget=forms.TextInput(attrs={"placeholder": "AB123"}))
|
|
320
277
|
|
|
321
278
|
def clean(self):
|
|
322
279
|
access_code = self.cleaned_data.get("access_code", None)
|
|
280
|
+
join_error_text = "The class code you entered either does not exist or is not currently accepting join requests. Please double check that you have entered the correct class code and contact the teacher of the class to ensure their class is currently accepting join requests."
|
|
323
281
|
|
|
324
282
|
if access_code:
|
|
325
283
|
classes = Class.objects.filter(access_code=access_code)
|
|
326
284
|
if len(classes) != 1:
|
|
327
|
-
raise forms.ValidationError(
|
|
328
|
-
|
|
329
|
-
)
|
|
285
|
+
raise forms.ValidationError(join_error_text)
|
|
286
|
+
|
|
330
287
|
self.klass = classes[0]
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
"Cannot find the school or club and/or class"
|
|
339
|
-
)
|
|
288
|
+
|
|
289
|
+
if not self.klass.always_accept_requests and (
|
|
290
|
+
self.klass.accept_requests_until is None
|
|
291
|
+
or self.klass.accept_requests_until - timezone.now()
|
|
292
|
+
< timedelta()
|
|
293
|
+
):
|
|
294
|
+
raise forms.ValidationError(join_error_text)
|
|
340
295
|
return self.cleaned_data
|
portal/forms/registration.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
from
|
|
2
|
-
from captcha.widgets import ReCaptchaV2Invisible
|
|
1
|
+
from common.mail import campaign_ids, send_dotdigital_email
|
|
3
2
|
from common.models import Student, Teacher
|
|
4
3
|
from django import forms
|
|
5
4
|
from django.contrib.auth import forms as django_auth_forms
|
|
6
5
|
from django.contrib.auth import get_user_model
|
|
7
|
-
from django.contrib.auth.models import User
|
|
8
6
|
from django.contrib.auth.tokens import default_token_generator
|
|
9
7
|
from django.contrib.sites.shortcuts import get_current_site
|
|
10
|
-
from django.
|
|
11
|
-
from django.template import loader
|
|
8
|
+
from django.urls import reverse_lazy
|
|
12
9
|
from django.utils.encoding import force_bytes
|
|
13
10
|
from django.utils.http import urlsafe_base64_encode
|
|
11
|
+
from django_recaptcha.fields import ReCaptchaField
|
|
12
|
+
from django_recaptcha.widgets import ReCaptchaV2Invisible
|
|
14
13
|
|
|
15
14
|
from portal.helpers.password import PasswordStrength, form_clean_password
|
|
16
15
|
|
|
@@ -20,8 +19,10 @@ class TeacherPasswordResetSetPasswordForm(django_auth_forms.SetPasswordForm):
|
|
|
20
19
|
super(TeacherPasswordResetSetPasswordForm, self).__init__(user, *args, **kwargs)
|
|
21
20
|
self.fields["new_password1"].help_text = "Enter your new password"
|
|
22
21
|
self.fields["new_password1"].widget.attrs["placeholder"] = "New password"
|
|
22
|
+
self.fields["new_password1"].widget.attrs["autocomplete"] = "off"
|
|
23
23
|
self.fields["new_password2"].help_text = "Confirm your new password"
|
|
24
24
|
self.fields["new_password2"].widget.attrs["placeholder"] = "Confirm password"
|
|
25
|
+
self.fields["new_password2"].widget.attrs["autocomplete"] = "off"
|
|
25
26
|
|
|
26
27
|
def clean_new_password1(self):
|
|
27
28
|
return form_clean_password(self, "new_password1", PasswordStrength.TEACHER)
|
|
@@ -32,69 +33,33 @@ class StudentPasswordResetSetPasswordForm(django_auth_forms.SetPasswordForm):
|
|
|
32
33
|
super(StudentPasswordResetSetPasswordForm, self).__init__(user, *args, **kwargs)
|
|
33
34
|
self.fields["new_password1"].help_text = "Enter your new password"
|
|
34
35
|
self.fields["new_password1"].widget.attrs["placeholder"] = "New password"
|
|
36
|
+
self.fields["new_password1"].widget.attrs["autocomplete"] = "off"
|
|
35
37
|
self.fields["new_password2"].help_text = "Confirm your new password"
|
|
36
38
|
self.fields["new_password2"].widget.attrs["placeholder"] = "Confirm password"
|
|
39
|
+
self.fields["new_password2"].widget.attrs["autocomplete"] = "off"
|
|
37
40
|
|
|
38
41
|
def clean_new_password1(self):
|
|
39
42
|
return form_clean_password(self, "new_password1", PasswordStrength.INDEPENDENT)
|
|
40
43
|
|
|
41
44
|
|
|
42
|
-
class
|
|
45
|
+
class PasswordResetForm(forms.Form):
|
|
43
46
|
email = forms.EmailField(
|
|
44
|
-
widget=forms.EmailInput(
|
|
45
|
-
attrs={"autocomplete": "off", "placeholder": "Email address"}
|
|
46
|
-
),
|
|
47
|
+
widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
|
|
47
48
|
help_text="Enter your email address",
|
|
48
49
|
)
|
|
49
50
|
|
|
50
51
|
captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
|
|
51
52
|
|
|
52
|
-
def clean_email(self):
|
|
53
|
-
email = self.cleaned_data.get("email", None)
|
|
54
|
-
self.username = ""
|
|
55
|
-
# Check such an email exists
|
|
56
|
-
if User.objects.filter(email=email).exists():
|
|
57
|
-
teacher = Teacher.objects.filter(new_user__email=email)
|
|
58
|
-
# Check such an email is associated with a teacher
|
|
59
|
-
if teacher.exists():
|
|
60
|
-
self.username = teacher[0].new_user.username
|
|
61
|
-
|
|
62
|
-
return email
|
|
63
|
-
|
|
64
|
-
def send_mail(
|
|
65
|
-
self,
|
|
66
|
-
subject_template_name,
|
|
67
|
-
email_template_name,
|
|
68
|
-
context,
|
|
69
|
-
from_email,
|
|
70
|
-
to_email,
|
|
71
|
-
html_email_template_name=None,
|
|
72
|
-
):
|
|
73
|
-
"""
|
|
74
|
-
Sends a django.core.mail.EmailMultiAlternatives to `to_email`.
|
|
75
|
-
"""
|
|
76
|
-
subject = loader.render_to_string(subject_template_name, context)
|
|
77
|
-
# Email subject *must not* contain newlines
|
|
78
|
-
subject = "".join(subject.splitlines())
|
|
79
|
-
body = loader.render_to_string(email_template_name, context)
|
|
80
|
-
|
|
81
|
-
email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
|
|
82
|
-
if html_email_template_name is not None:
|
|
83
|
-
html_email = loader.render_to_string(html_email_template_name, context)
|
|
84
|
-
email_message.attach_alternative(html_email, "text/html")
|
|
85
|
-
|
|
86
|
-
email_message.send()
|
|
87
|
-
|
|
88
53
|
def save(
|
|
89
54
|
self,
|
|
90
55
|
domain_override=None,
|
|
91
|
-
subject_template_name=
|
|
92
|
-
email_template_name="portal/reset_password_email.
|
|
56
|
+
subject_template_name=None,
|
|
57
|
+
email_template_name="portal/reset_password_email.txt",
|
|
93
58
|
use_https=False,
|
|
94
59
|
token_generator=default_token_generator,
|
|
95
60
|
from_email=None,
|
|
96
61
|
request=None,
|
|
97
|
-
html_email_template_name=
|
|
62
|
+
html_email_template_name="portal/reset_password_email.html",
|
|
98
63
|
):
|
|
99
64
|
"""
|
|
100
65
|
Generates a one-use only link for resetting password and sends to the
|
|
@@ -102,9 +67,7 @@ class TeacherPasswordResetForm(forms.Form):
|
|
|
102
67
|
"""
|
|
103
68
|
UserModel = get_user_model()
|
|
104
69
|
if self.username:
|
|
105
|
-
active_users = UserModel._default_manager.filter(
|
|
106
|
-
username=self.username, is_active=True
|
|
107
|
-
)
|
|
70
|
+
active_users = UserModel._default_manager.filter(username=self.username, is_active=True)
|
|
108
71
|
for user in active_users:
|
|
109
72
|
# Make sure that no email is sent to a user that actually has
|
|
110
73
|
# a password marked as unusable
|
|
@@ -112,128 +75,71 @@ class TeacherPasswordResetForm(forms.Form):
|
|
|
112
75
|
continue
|
|
113
76
|
if not domain_override:
|
|
114
77
|
current_site = get_current_site(request)
|
|
115
|
-
site_name = current_site.name
|
|
116
78
|
domain = current_site.domain
|
|
117
79
|
else:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"protocol": compute_protocol(use_https),
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
self.send_mail(
|
|
130
|
-
subject_template_name,
|
|
131
|
-
email_template_name,
|
|
132
|
-
context,
|
|
133
|
-
from_email,
|
|
134
|
-
user.email,
|
|
135
|
-
html_email_template_name=html_email_template_name,
|
|
80
|
+
domain = domain_override
|
|
81
|
+
|
|
82
|
+
reset_password_uri = reverse_lazy(
|
|
83
|
+
"password_reset_check_and_confirm",
|
|
84
|
+
kwargs={
|
|
85
|
+
"uidb64": urlsafe_base64_encode(force_bytes(user.pk)),
|
|
86
|
+
"token": token_generator.make_token(user),
|
|
87
|
+
},
|
|
136
88
|
)
|
|
89
|
+
protocol = self._compute_protocol(use_https)
|
|
90
|
+
reset_password_url = f"{protocol}://{domain}{reset_password_uri}"
|
|
137
91
|
|
|
92
|
+
send_dotdigital_email(
|
|
93
|
+
campaign_ids["reset_password"],
|
|
94
|
+
[user.email],
|
|
95
|
+
personalization_values={"RESET_PASSWORD_LINK": reset_password_url},
|
|
96
|
+
)
|
|
138
97
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
widget=forms.TextInput(
|
|
142
|
-
attrs={"autocomplete": "off", "placeholder": "Username"}
|
|
143
|
-
),
|
|
144
|
-
help_text="Enter your username",
|
|
145
|
-
)
|
|
98
|
+
def _compute_protocol(self, use_https):
|
|
99
|
+
return "https" if use_https else "http"
|
|
146
100
|
|
|
147
|
-
captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
|
|
148
101
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
):
|
|
159
|
-
username = ""
|
|
160
|
-
return username
|
|
161
|
-
|
|
162
|
-
def send_mail(
|
|
163
|
-
self,
|
|
164
|
-
subject_template_name,
|
|
165
|
-
email_template_name,
|
|
166
|
-
context,
|
|
167
|
-
from_email,
|
|
168
|
-
to_email,
|
|
169
|
-
html_email_template_name=None,
|
|
170
|
-
):
|
|
171
|
-
"""
|
|
172
|
-
Sends a django.core.mail.EmailMultiAlternatives to `to_email`.
|
|
173
|
-
"""
|
|
174
|
-
subject = loader.render_to_string(subject_template_name, context)
|
|
175
|
-
# Email subject *must not* contain newlines
|
|
176
|
-
subject = "".join(subject.splitlines())
|
|
177
|
-
body = loader.render_to_string(email_template_name, context)
|
|
102
|
+
class TeacherPasswordResetForm(PasswordResetForm):
|
|
103
|
+
def clean_email(self):
|
|
104
|
+
email = self.cleaned_data.get("email", None)
|
|
105
|
+
self.username = ""
|
|
106
|
+
teacher = Teacher.objects.filter(new_user__email=email)
|
|
107
|
+
# Check such an email exists
|
|
108
|
+
if teacher.exists():
|
|
109
|
+
self.username = teacher[0].new_user.username
|
|
110
|
+
return email
|
|
178
111
|
|
|
179
|
-
email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
|
|
180
|
-
if html_email_template_name is not None:
|
|
181
|
-
html_email = loader.render_to_string(html_email_template_name, context)
|
|
182
|
-
email_message.attach_alternative(html_email, "text/html")
|
|
183
112
|
|
|
184
|
-
|
|
113
|
+
class StudentPasswordResetForm(PasswordResetForm):
|
|
114
|
+
def clean_email(self):
|
|
115
|
+
email = self.cleaned_data.get("email", None)
|
|
116
|
+
self.username = ""
|
|
117
|
+
student = Student.objects.filter(new_user__email=email)
|
|
118
|
+
# Check such an email exists
|
|
119
|
+
if student.exists():
|
|
120
|
+
self.username = student[0].new_user.username
|
|
121
|
+
return email
|
|
185
122
|
|
|
186
|
-
def save(
|
|
187
|
-
self,
|
|
188
|
-
domain_override=None,
|
|
189
|
-
subject_template_name="registration/password_reset_subject.txt",
|
|
190
|
-
email_template_name="portal/reset_password_email.html",
|
|
191
|
-
use_https=False,
|
|
192
|
-
token_generator=default_token_generator,
|
|
193
|
-
from_email=None,
|
|
194
|
-
request=None,
|
|
195
|
-
html_email_template_name=None,
|
|
196
|
-
):
|
|
197
|
-
"""
|
|
198
|
-
Generates a one-use only link for resetting password and sends to the
|
|
199
|
-
user.
|
|
200
|
-
"""
|
|
201
|
-
UserModel = get_user_model()
|
|
202
|
-
username = self.cleaned_data["username"]
|
|
203
|
-
if username:
|
|
204
|
-
active_users = UserModel._default_manager.filter(
|
|
205
|
-
username=username, is_active=True
|
|
206
|
-
)
|
|
207
|
-
for user in active_users:
|
|
208
|
-
# Make sure that no email is sent to a user that actually has
|
|
209
|
-
# a password marked as unusable
|
|
210
|
-
if not user.has_usable_password():
|
|
211
|
-
continue
|
|
212
|
-
if not domain_override:
|
|
213
|
-
current_site = get_current_site(request)
|
|
214
|
-
site_name = current_site.name
|
|
215
|
-
domain = current_site.domain
|
|
216
|
-
else:
|
|
217
|
-
site_name = domain = domain_override
|
|
218
|
-
context = {
|
|
219
|
-
"email": user.email,
|
|
220
|
-
"domain": domain,
|
|
221
|
-
"site_name": site_name,
|
|
222
|
-
"uid": urlsafe_base64_encode(force_bytes(user.pk)),
|
|
223
|
-
"user": user,
|
|
224
|
-
"token": token_generator.make_token(user),
|
|
225
|
-
"protocol": compute_protocol(use_https),
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
self.send_mail(
|
|
229
|
-
subject_template_name,
|
|
230
|
-
email_template_name,
|
|
231
|
-
context,
|
|
232
|
-
from_email,
|
|
233
|
-
user.email,
|
|
234
|
-
html_email_template_name=html_email_template_name,
|
|
235
|
-
)
|
|
236
123
|
|
|
124
|
+
class DeleteAccountForm(forms.Form):
|
|
125
|
+
delete_password = forms.CharField(
|
|
126
|
+
required=True,
|
|
127
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm password"}),
|
|
128
|
+
help_text="Confirm password",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
unsubscribe_newsletter = forms.BooleanField(
|
|
132
|
+
label="Please remove me from the newsletter and marketing emails too.",
|
|
133
|
+
widget=forms.CheckboxInput(),
|
|
134
|
+
initial=True,
|
|
135
|
+
required=False,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def __init__(self, user, *args, **kwargs):
|
|
139
|
+
self.user = user
|
|
140
|
+
super(DeleteAccountForm, self).__init__(*args, **kwargs)
|
|
237
141
|
|
|
238
|
-
def
|
|
239
|
-
|
|
142
|
+
def clean(self):
|
|
143
|
+
delete_password = self.cleaned_data.get("delete_password", None)
|
|
144
|
+
if not self.user.check_password(delete_password):
|
|
145
|
+
raise forms.ValidationError("Incorrect password")
|