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
portal/forms/teach.py
CHANGED
|
@@ -1,62 +1,41 @@
|
|
|
1
|
+
import itertools
|
|
1
2
|
import re
|
|
2
3
|
from builtins import map, range, str
|
|
3
4
|
|
|
4
|
-
from captcha.fields import ReCaptchaField
|
|
5
|
-
from captcha.widgets import ReCaptchaV2Invisible
|
|
6
5
|
from common.helpers.emails import send_verification_email
|
|
7
|
-
from common.models import Student,
|
|
6
|
+
from common.models import Student, Teacher, UserSession, stripStudentName
|
|
8
7
|
from django import forms
|
|
9
8
|
from django.contrib.auth import authenticate
|
|
10
9
|
from django.contrib.auth.forms import AuthenticationForm
|
|
11
10
|
from django.contrib.auth.models import User
|
|
11
|
+
from django_recaptcha.fields import ReCaptchaField
|
|
12
|
+
from django_recaptcha.widgets import ReCaptchaV2Invisible
|
|
13
|
+
from game.models import Episode, Worksheet
|
|
12
14
|
|
|
13
15
|
from portal.forms.error_messages import INVALID_LOGIN_MESSAGE
|
|
14
16
|
from portal.helpers.password import PasswordStrength, form_clean_password
|
|
15
17
|
from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
16
|
-
from portal.templatetags.app_tags import is_verified
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
class
|
|
20
|
+
class InvitedTeacherForm(forms.Form):
|
|
21
|
+
prefix = "teacher_signup"
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
attrs={"autocomplete": "off", "placeholder": "First name"}
|
|
26
|
-
),
|
|
27
|
-
)
|
|
28
|
-
teacher_last_name = forms.CharField(
|
|
29
|
-
help_text="Enter your last name",
|
|
30
|
-
max_length=100,
|
|
31
|
-
widget=forms.TextInput(
|
|
32
|
-
attrs={"autocomplete": "off", "placeholder": "Last name"}
|
|
33
|
-
),
|
|
34
|
-
)
|
|
35
|
-
teacher_email = forms.EmailField(
|
|
36
|
-
help_text="Enter your email address",
|
|
37
|
-
widget=forms.EmailInput(
|
|
38
|
-
attrs={"autocomplete": "off", "placeholder": "Email address"}
|
|
39
|
-
),
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
newsletter_ticked = forms.BooleanField(
|
|
43
|
-
widget=forms.CheckboxInput(), initial=False, required=False
|
|
44
|
-
)
|
|
23
|
+
def __init__(self, *args, **kwargs):
|
|
24
|
+
super().__init__(*args, **kwargs)
|
|
25
|
+
for field_name, field in self.fields.items():
|
|
26
|
+
field.widget.attrs["id"] = f"id_teacher_signup-{field_name}"
|
|
45
27
|
|
|
46
28
|
teacher_password = forms.CharField(
|
|
47
29
|
help_text="Enter a password",
|
|
48
|
-
widget=forms.PasswordInput(
|
|
49
|
-
attrs={"autocomplete": "off", "placeholder": "Password"}
|
|
50
|
-
),
|
|
30
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
|
|
51
31
|
)
|
|
52
32
|
teacher_confirm_password = forms.CharField(
|
|
53
33
|
help_text="Repeat password",
|
|
54
|
-
widget=forms.PasswordInput(
|
|
55
|
-
attrs={"autocomplete": "off", "placeholder": "Repeat password"}
|
|
56
|
-
),
|
|
34
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Repeat password"}),
|
|
57
35
|
)
|
|
58
36
|
|
|
59
|
-
|
|
37
|
+
consent_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=True)
|
|
38
|
+
newsletter_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=False)
|
|
60
39
|
|
|
61
40
|
def clean_teacher_password(self):
|
|
62
41
|
return form_clean_password(self, "teacher_password", PasswordStrength.TEACHER)
|
|
@@ -73,8 +52,26 @@ class TeacherSignupForm(forms.Form):
|
|
|
73
52
|
return self.cleaned_data
|
|
74
53
|
|
|
75
54
|
|
|
76
|
-
class
|
|
55
|
+
class TeacherSignupForm(InvitedTeacherForm):
|
|
56
|
+
teacher_first_name = forms.CharField(
|
|
57
|
+
help_text="Enter your first name",
|
|
58
|
+
max_length=100,
|
|
59
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "First name"}),
|
|
60
|
+
)
|
|
61
|
+
teacher_last_name = forms.CharField(
|
|
62
|
+
help_text="Enter your last name",
|
|
63
|
+
max_length=100,
|
|
64
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Last name"}),
|
|
65
|
+
)
|
|
66
|
+
teacher_email = forms.EmailField(
|
|
67
|
+
help_text="Enter your email address",
|
|
68
|
+
widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
|
|
69
|
+
)
|
|
77
70
|
|
|
71
|
+
captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TeacherEditAccountForm(forms.Form):
|
|
78
75
|
first_name = forms.CharField(
|
|
79
76
|
max_length=100,
|
|
80
77
|
widget=forms.TextInput(attrs={"placeholder": "First name", "class": "fName"}),
|
|
@@ -97,9 +94,7 @@ class TeacherEditAccountForm(forms.Form):
|
|
|
97
94
|
)
|
|
98
95
|
confirm_password = forms.CharField(
|
|
99
96
|
required=False,
|
|
100
|
-
widget=forms.PasswordInput(
|
|
101
|
-
attrs={"placeholder": "Confirm new password (optional)"}
|
|
102
|
-
),
|
|
97
|
+
widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password (optional)"}),
|
|
103
98
|
help_text="Confirm new password (optional)",
|
|
104
99
|
)
|
|
105
100
|
current_password = forms.CharField(
|
|
@@ -135,13 +130,12 @@ class TeacherEditAccountForm(forms.Form):
|
|
|
135
130
|
|
|
136
131
|
class TeacherLoginForm(AuthenticationForm):
|
|
137
132
|
username = forms.EmailField(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
attrs={"autocomplete": "off", "placeholder": "my.email@address.com"}
|
|
141
|
-
),
|
|
133
|
+
widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
|
|
134
|
+
help_text="Enter your email address",
|
|
142
135
|
)
|
|
143
136
|
password = forms.CharField(
|
|
144
|
-
|
|
137
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
|
|
138
|
+
help_text="Enter your password",
|
|
145
139
|
)
|
|
146
140
|
|
|
147
141
|
def clean(self):
|
|
@@ -149,7 +143,6 @@ class TeacherLoginForm(AuthenticationForm):
|
|
|
149
143
|
password = self.cleaned_data.get("password", None)
|
|
150
144
|
|
|
151
145
|
if email and password:
|
|
152
|
-
|
|
153
146
|
# Check it's a teacher and not a student using the same email address
|
|
154
147
|
user = None
|
|
155
148
|
|
|
@@ -175,9 +168,7 @@ class TeacherLoginForm(AuthenticationForm):
|
|
|
175
168
|
users = User.objects.filter(email=email)
|
|
176
169
|
|
|
177
170
|
for result in users:
|
|
178
|
-
if hasattr(result, "userprofile") and hasattr(
|
|
179
|
-
result.userprofile, "teacher"
|
|
180
|
-
):
|
|
171
|
+
if hasattr(result, "userprofile") and hasattr(result.userprofile, "teacher"):
|
|
181
172
|
user = result
|
|
182
173
|
break
|
|
183
174
|
|
|
@@ -190,8 +181,8 @@ class TeacherLoginForm(AuthenticationForm):
|
|
|
190
181
|
if user is None:
|
|
191
182
|
self.show_invalid_login_message()
|
|
192
183
|
|
|
193
|
-
if not is_verified
|
|
194
|
-
send_verification_email(self.request, user)
|
|
184
|
+
if not user.userprofile.is_verified:
|
|
185
|
+
send_verification_email(self.request, user, self.data)
|
|
195
186
|
self.show_invalid_login_message()
|
|
196
187
|
|
|
197
188
|
if not user.is_active:
|
|
@@ -206,6 +197,7 @@ class ClassCreationForm(forms.Form):
|
|
|
206
197
|
widget=forms.TextInput(attrs={"placeholder": "Class name"}),
|
|
207
198
|
help_text="Enter a class name",
|
|
208
199
|
)
|
|
200
|
+
teacher = forms.ChoiceField(help_text="Select teacher", required=False)
|
|
209
201
|
classmate_progress = forms.BooleanField(
|
|
210
202
|
label="Allow students to see their classmates' progress?",
|
|
211
203
|
widget=forms.CheckboxInput(),
|
|
@@ -213,6 +205,45 @@ class ClassCreationForm(forms.Form):
|
|
|
213
205
|
required=False,
|
|
214
206
|
)
|
|
215
207
|
|
|
208
|
+
def __init__(self, *args, teacher=None, **kwargs):
|
|
209
|
+
super().__init__(*args, **kwargs)
|
|
210
|
+
|
|
211
|
+
if teacher is not None:
|
|
212
|
+
# Place current teacher at the top
|
|
213
|
+
teacher_choices = [
|
|
214
|
+
(
|
|
215
|
+
teacher.id,
|
|
216
|
+
f"{teacher.new_user.first_name} {teacher.new_user.last_name}",
|
|
217
|
+
)
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
# Get coworkers and add them to the choices if the teacher is an admin
|
|
221
|
+
if teacher.is_admin:
|
|
222
|
+
coworkers = (
|
|
223
|
+
Teacher.objects.filter(school=teacher.school)
|
|
224
|
+
.exclude(id=teacher.id)
|
|
225
|
+
.order_by("new_user__last_name", "new_user__first_name")
|
|
226
|
+
)
|
|
227
|
+
for coworker in coworkers:
|
|
228
|
+
teacher_choices.append(
|
|
229
|
+
(
|
|
230
|
+
coworker.id,
|
|
231
|
+
f"{coworker.new_user.first_name} {coworker.new_user.last_name}",
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
self.fields["teacher"].choices = teacher_choices
|
|
236
|
+
|
|
237
|
+
def clean(self):
|
|
238
|
+
name = self.cleaned_data.get("class_name", "")
|
|
239
|
+
|
|
240
|
+
if re.match(re.compile("^[\w -]+$"), name) is None:
|
|
241
|
+
raise forms.ValidationError(
|
|
242
|
+
"Class name may only contain letters, numbers, dashes, underscores, and spaces."
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return self.cleaned_data
|
|
246
|
+
|
|
216
247
|
|
|
217
248
|
class ClassEditForm(forms.Form):
|
|
218
249
|
# select dropdown choices for potentially limiting time in which external students may join
|
|
@@ -229,9 +260,7 @@ class ClassEditForm(forms.Form):
|
|
|
229
260
|
[
|
|
230
261
|
(
|
|
231
262
|
str(hours),
|
|
232
|
-
"Allow external requests to this class for the next "
|
|
233
|
-
+ str(hours)
|
|
234
|
-
+ " hours",
|
|
263
|
+
"Allow external requests to this class for the next " + str(hours) + " hours",
|
|
235
264
|
)
|
|
236
265
|
for hours in range(4, 28, 4)
|
|
237
266
|
]
|
|
@@ -240,15 +269,16 @@ class ClassEditForm(forms.Form):
|
|
|
240
269
|
[
|
|
241
270
|
(
|
|
242
271
|
str(days * 24),
|
|
243
|
-
"Allow external requests to this class for the next "
|
|
244
|
-
+ str(days)
|
|
245
|
-
+ " days",
|
|
272
|
+
"Allow external requests to this class for the next " + str(days) + " days",
|
|
246
273
|
)
|
|
247
274
|
for days in range(2, 5)
|
|
248
275
|
]
|
|
249
276
|
)
|
|
250
277
|
join_choices.append(
|
|
251
|
-
(
|
|
278
|
+
(
|
|
279
|
+
"1000",
|
|
280
|
+
"Always allow external requests to this class (not recommended)",
|
|
281
|
+
)
|
|
252
282
|
)
|
|
253
283
|
name = forms.CharField(
|
|
254
284
|
widget=forms.TextInput(attrs={"placeholder": "Enter class name"}),
|
|
@@ -268,6 +298,41 @@ class ClassEditForm(forms.Form):
|
|
|
268
298
|
widget=forms.Select(),
|
|
269
299
|
)
|
|
270
300
|
|
|
301
|
+
def clean(self):
|
|
302
|
+
name = self.cleaned_data.get("name", "")
|
|
303
|
+
|
|
304
|
+
if re.match(re.compile("^[\w -]+$"), name) is None:
|
|
305
|
+
raise forms.ValidationError(
|
|
306
|
+
"Class name may only contain letters, numbers, dashes, underscores, and spaces."
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
return self.cleaned_data
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class ClassLevelControlForm(forms.Form):
|
|
313
|
+
def __init__(self, *args, **kwargs):
|
|
314
|
+
super(ClassLevelControlForm, self).__init__(*args, **kwargs)
|
|
315
|
+
|
|
316
|
+
episodes = Episode.objects.filter(pk__in=range(1, 25))
|
|
317
|
+
|
|
318
|
+
for episode in episodes:
|
|
319
|
+
choices = []
|
|
320
|
+
for level in episode.levels:
|
|
321
|
+
try:
|
|
322
|
+
choices.append((f"worksheet:{level.after_worksheet.id}", episode.name))
|
|
323
|
+
except Worksheet.DoesNotExist:
|
|
324
|
+
pass
|
|
325
|
+
choices.append((f"level:{level.id}", level.name))
|
|
326
|
+
|
|
327
|
+
for worksheet in episode.worksheets.filter(before_level__isnull=True):
|
|
328
|
+
choices.append((f"worksheet:{worksheet.id}", episode.name))
|
|
329
|
+
|
|
330
|
+
self.fields[f"episode{episode.id}"] = forms.MultipleChoiceField(
|
|
331
|
+
choices=itertools.chain(choices),
|
|
332
|
+
widget=forms.CheckboxSelectMultiple(),
|
|
333
|
+
required=False,
|
|
334
|
+
)
|
|
335
|
+
|
|
271
336
|
|
|
272
337
|
class ClassMoveForm(forms.Form):
|
|
273
338
|
new_teacher = forms.ChoiceField(
|
|
@@ -306,22 +371,14 @@ class TeacherEditStudentForm(forms.Form):
|
|
|
306
371
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
307
372
|
|
|
308
373
|
if name == "":
|
|
309
|
-
raise forms.ValidationError(
|
|
310
|
-
"'" + self.cleaned_data.get("name", "") + "' is not a valid name"
|
|
311
|
-
)
|
|
374
|
+
raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
|
|
312
375
|
|
|
313
376
|
if re.match(re.compile("^[\w -]+$"), name) is None:
|
|
314
|
-
raise forms.ValidationError(
|
|
315
|
-
"Names may only contain letters, numbers, dashes, underscores, and spaces."
|
|
316
|
-
)
|
|
377
|
+
raise forms.ValidationError("Names may only contain letters, numbers, dashes, underscores, and spaces.")
|
|
317
378
|
|
|
318
|
-
students = Student.objects.filter(
|
|
319
|
-
class_field=self.klass, new_user__first_name__iexact=name
|
|
320
|
-
)
|
|
379
|
+
students = Student.objects.filter(class_field=self.klass, new_user__first_name__iexact=name)
|
|
321
380
|
if students.exists() and students[0] != self.student:
|
|
322
|
-
raise forms.ValidationError(
|
|
323
|
-
"There is already a student called '" + name + "' in this class"
|
|
324
|
-
)
|
|
381
|
+
raise forms.ValidationError("There is already a student called '" + name + "' in this class")
|
|
325
382
|
|
|
326
383
|
return name
|
|
327
384
|
|
|
@@ -330,12 +387,12 @@ class TeacherSetStudentPass(forms.Form):
|
|
|
330
387
|
password = forms.CharField(
|
|
331
388
|
label="New password",
|
|
332
389
|
help_text="Enter new password",
|
|
333
|
-
widget=forms.PasswordInput(attrs={"placeholder": "Enter new password"}),
|
|
390
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Enter new password"}),
|
|
334
391
|
)
|
|
335
392
|
confirm_password = forms.CharField(
|
|
336
393
|
label="Confirm new password",
|
|
337
394
|
help_text="Confirm new password",
|
|
338
|
-
widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password"}),
|
|
395
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm new password"}),
|
|
339
396
|
)
|
|
340
397
|
|
|
341
398
|
def clean_password(self):
|
|
@@ -379,14 +436,9 @@ def validateStudentNames(klass, names):
|
|
|
379
436
|
|
|
380
437
|
def find_clashes(names, students, clashes_found, validationErrors):
|
|
381
438
|
for name in names:
|
|
382
|
-
if (
|
|
383
|
-
students.filter(new_user__first_name__iexact=name).exists()
|
|
384
|
-
and name not in clashes_found
|
|
385
|
-
):
|
|
439
|
+
if students.filter(new_user__first_name__iexact=name).exists() and name not in clashes_found:
|
|
386
440
|
validationErrors.append(
|
|
387
|
-
forms.ValidationError(
|
|
388
|
-
"There is already a student called '" + name + "' in this class"
|
|
389
|
-
)
|
|
441
|
+
forms.ValidationError("There is already a student called '" + name + "' in this class")
|
|
390
442
|
)
|
|
391
443
|
clashes_found.append(name)
|
|
392
444
|
|
|
@@ -396,9 +448,7 @@ def find_duplicates(names, lower_names, validationErrors):
|
|
|
396
448
|
for duplicate in [name for name in names if lower_names.count(name.lower()) > 1]:
|
|
397
449
|
if duplicate not in duplicates_found:
|
|
398
450
|
validationErrors.append(
|
|
399
|
-
forms.ValidationError(
|
|
400
|
-
"You cannot add more than one student called '" + duplicate + "'"
|
|
401
|
-
)
|
|
451
|
+
forms.ValidationError("You cannot add more than one student called '" + duplicate + "'")
|
|
402
452
|
)
|
|
403
453
|
duplicates_found.append(duplicate)
|
|
404
454
|
|
|
@@ -417,9 +467,7 @@ def find_illegal_characters(names, validationErrors):
|
|
|
417
467
|
|
|
418
468
|
def check_passwords(password, confirm_password):
|
|
419
469
|
if password is not None and password != confirm_password:
|
|
420
|
-
raise forms.ValidationError(
|
|
421
|
-
"The password and the confirmation password do not match"
|
|
422
|
-
)
|
|
470
|
+
raise forms.ValidationError("The password and the confirmation password do not match")
|
|
423
471
|
|
|
424
472
|
|
|
425
473
|
class TeacherMoveStudentsDestinationForm(forms.Form):
|
|
@@ -467,18 +515,14 @@ class TeacherMoveStudentDisambiguationForm(forms.Form):
|
|
|
467
515
|
def clean_name(self):
|
|
468
516
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
469
517
|
if name == "":
|
|
470
|
-
raise forms.ValidationError(
|
|
471
|
-
"'" + self.cleaned_data.get("name", "") + "' is not a valid name"
|
|
472
|
-
)
|
|
518
|
+
raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
|
|
473
519
|
return name
|
|
474
520
|
|
|
475
521
|
|
|
476
522
|
class BaseTeacherMoveStudentsDisambiguationFormSet(forms.BaseFormSet):
|
|
477
523
|
def __init__(self, destination, *args, **kwargs):
|
|
478
524
|
self.destination = destination
|
|
479
|
-
super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__(
|
|
480
|
-
*args, **kwargs
|
|
481
|
-
)
|
|
525
|
+
super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__(*args, **kwargs)
|
|
482
526
|
|
|
483
527
|
def clean(self):
|
|
484
528
|
if any(self.errors):
|
|
@@ -496,48 +540,35 @@ class BaseTeacherMoveStudentsDisambiguationFormSet(forms.BaseFormSet):
|
|
|
496
540
|
|
|
497
541
|
class TeacherDismissStudentsForm(forms.Form):
|
|
498
542
|
orig_name = forms.CharField(
|
|
499
|
-
label="Original Name",
|
|
500
543
|
help_text="Original student name",
|
|
501
544
|
widget=forms.TextInput(
|
|
502
545
|
attrs={
|
|
503
546
|
"readonly": "readonly",
|
|
504
547
|
"placeholder": "Original Name",
|
|
505
|
-
"
|
|
548
|
+
"class": "m-0",
|
|
506
549
|
}
|
|
507
550
|
),
|
|
508
551
|
)
|
|
509
552
|
name = forms.CharField(
|
|
510
|
-
label="New Name",
|
|
511
553
|
help_text="New student name",
|
|
512
|
-
widget=forms.TextInput(
|
|
513
|
-
attrs={"placeholder": "Enter new student name", "style": "margin : 0px"}
|
|
514
|
-
),
|
|
554
|
+
widget=forms.TextInput(attrs={"placeholder": "Enter new student name", "class": "m-0"}),
|
|
515
555
|
)
|
|
516
556
|
email = forms.EmailField(
|
|
517
557
|
label="Email",
|
|
518
|
-
help_text="
|
|
519
|
-
widget=forms.EmailInput(
|
|
520
|
-
attrs={"placeholder": "Enter email address", "style": "margin : 0px"}
|
|
521
|
-
),
|
|
558
|
+
help_text="New email address",
|
|
559
|
+
widget=forms.EmailInput(attrs={"placeholder": "Enter email address", "class": "m-0"}),
|
|
522
560
|
)
|
|
523
561
|
confirm_email = forms.EmailField(
|
|
524
562
|
label="Confirm Email",
|
|
525
|
-
help_text="
|
|
526
|
-
widget=forms.EmailInput(
|
|
527
|
-
attrs={"placeholder": "Confirm email address", "style": "margin : 0px"}
|
|
528
|
-
),
|
|
563
|
+
help_text="Confirm email address",
|
|
564
|
+
widget=forms.EmailInput(attrs={"placeholder": "Confirm email address", "class": "m-0"}),
|
|
529
565
|
)
|
|
530
566
|
|
|
531
567
|
def clean_name(self):
|
|
532
568
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
533
569
|
|
|
534
570
|
if name == "":
|
|
535
|
-
raise forms.ValidationError(
|
|
536
|
-
"'" + self.cleaned_data.get("name", "") + "' is not a valid name"
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
if User.objects.filter(username=name).exists():
|
|
540
|
-
raise forms.ValidationError("That username is already in use")
|
|
571
|
+
raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
|
|
541
572
|
|
|
542
573
|
return name
|
|
543
574
|
|
|
@@ -596,7 +627,8 @@ class StudentCreationForm(forms.Form):
|
|
|
596
627
|
|
|
597
628
|
class TeacherAddExternalStudentForm(forms.Form):
|
|
598
629
|
name = forms.CharField(
|
|
599
|
-
label="Student name",
|
|
630
|
+
label="Student name",
|
|
631
|
+
widget=forms.TextInput(attrs={"placeholder": "Name"}),
|
|
600
632
|
)
|
|
601
633
|
|
|
602
634
|
def __init__(self, klass, *args, **kwargs):
|
|
@@ -607,15 +639,9 @@ class TeacherAddExternalStudentForm(forms.Form):
|
|
|
607
639
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
608
640
|
|
|
609
641
|
if name == "":
|
|
610
|
-
raise forms.ValidationError(
|
|
611
|
-
"'" + self.cleaned_data.get("name", "") + "' is not a valid name"
|
|
612
|
-
)
|
|
642
|
+
raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
|
|
613
643
|
|
|
614
|
-
if Student.objects.filter(
|
|
615
|
-
|
|
616
|
-
).exists():
|
|
617
|
-
raise forms.ValidationError(
|
|
618
|
-
"There is already a student called '" + name + "' in this class"
|
|
619
|
-
)
|
|
644
|
+
if Student.objects.filter(class_field=self.klass, new_user__first_name__iexact=name).exists():
|
|
645
|
+
raise forms.ValidationError("There is already a student called '" + name + "' in this class")
|
|
620
646
|
|
|
621
647
|
return name
|
portal/handlers.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
from common.utils import two_factor_cache_key
|
|
1
2
|
from django.core.cache import cache
|
|
2
3
|
from django.db.models.signals import post_save, pre_delete
|
|
3
4
|
from django.dispatch import receiver
|
|
4
5
|
from django_otp.models import Device
|
|
5
6
|
|
|
6
|
-
from common.utils import two_factor_cache_key
|
|
7
|
-
|
|
8
7
|
|
|
9
8
|
@receiver([post_save, pre_delete])
|
|
10
9
|
def clear_two_factor_cache(sender, **kwargs):
|
portal/helpers/decorators.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
from __future__ import absolute_import
|
|
2
2
|
|
|
3
3
|
import datetime
|
|
4
|
-
import pytz
|
|
5
4
|
from functools import wraps
|
|
6
5
|
|
|
6
|
+
import pytz
|
|
7
7
|
from common.models import Teacher, Student
|
|
8
8
|
from django.contrib.auth import logout
|
|
9
9
|
from django.shortcuts import render
|
|
10
10
|
from ratelimit import ALL, UNSAFE
|
|
11
11
|
|
|
12
12
|
from portal.helpers.ratelimit import is_ratelimited
|
|
13
|
+
from portal.helpers.request_handlers import get_access_code_from_request
|
|
13
14
|
from portal.templatetags.app_tags import is_logged_in
|
|
14
15
|
|
|
15
16
|
__all__ = ["ratelimit"]
|
|
@@ -39,14 +40,15 @@ def ratelimit(
|
|
|
39
40
|
increment=True,
|
|
40
41
|
)
|
|
41
42
|
request.limited = ratelimited or old_limited
|
|
42
|
-
|
|
43
43
|
if ratelimited and block:
|
|
44
44
|
if is_teacher:
|
|
45
45
|
model = Teacher
|
|
46
46
|
else:
|
|
47
47
|
model = Student
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
user_to_lockout = None
|
|
50
|
+
|
|
51
|
+
lockout_template = "portal/locked_out.html"
|
|
50
52
|
|
|
51
53
|
if request.user.is_anonymous:
|
|
52
54
|
data = request.POST
|
|
@@ -55,21 +57,39 @@ def ratelimit(
|
|
|
55
57
|
else:
|
|
56
58
|
username = data.get("username")
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
access_code = get_access_code_from_request(request)
|
|
61
|
+
model_finder = model.objects.get
|
|
62
|
+
# look for school student if access code not found
|
|
63
|
+
# (AttributeError) if student not found (IndexError) move
|
|
64
|
+
# on to another try block similar logic followed afterwards
|
|
65
|
+
if access_code:
|
|
66
|
+
user_to_lockout = model_finder(
|
|
67
|
+
new_user__first_name=username,
|
|
68
|
+
class_field__access_code=access_code, # extract the found text from regex
|
|
69
|
+
)
|
|
70
|
+
lockout_template = (
|
|
71
|
+
"portal/locked_out_school_student.html"
|
|
72
|
+
)
|
|
73
|
+
# look for indy student or teacher
|
|
74
|
+
else:
|
|
75
|
+
user_to_lockout = model_finder(
|
|
76
|
+
new_user__username=username
|
|
77
|
+
)
|
|
60
78
|
else:
|
|
61
|
-
|
|
79
|
+
user_to_lockout = model.objects.get(new_user=request.user)
|
|
62
80
|
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
if user_to_lockout:
|
|
82
|
+
user_to_lockout.blocked_time = datetime.datetime.now(
|
|
83
|
+
tz=pytz.utc
|
|
84
|
+
)
|
|
85
|
+
user_to_lockout.save()
|
|
66
86
|
|
|
67
87
|
if is_logged_in(request.user):
|
|
68
88
|
logout(request)
|
|
69
89
|
|
|
70
90
|
return render(
|
|
71
91
|
request,
|
|
72
|
-
|
|
92
|
+
lockout_template,
|
|
73
93
|
{"is_teacher": is_teacher},
|
|
74
94
|
)
|
|
75
95
|
|