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/admin.py
CHANGED
|
@@ -1,41 +1,75 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
|
|
1
3
|
from common.models import (
|
|
2
4
|
Class,
|
|
3
|
-
|
|
5
|
+
DailyActivity,
|
|
6
|
+
DynamicElement,
|
|
4
7
|
School,
|
|
8
|
+
SchoolTeacherInvitation,
|
|
5
9
|
Student,
|
|
6
10
|
Teacher,
|
|
11
|
+
TotalActivity,
|
|
7
12
|
UserProfile,
|
|
8
13
|
)
|
|
9
14
|
from django.contrib import admin
|
|
10
|
-
from django.contrib.auth.admin import UserAdmin
|
|
15
|
+
from django.contrib.auth.admin import UserAdmin as _UserAdmin
|
|
11
16
|
from django.contrib.auth.models import User
|
|
17
|
+
from django.http import HttpResponse
|
|
18
|
+
from import_export.admin import ExportActionMixin
|
|
12
19
|
|
|
13
|
-
from portal.forms.admin import
|
|
20
|
+
from portal.forms.admin import (
|
|
21
|
+
AdminChangeUserPasswordForm,
|
|
22
|
+
AdminUserCreationForm,
|
|
23
|
+
)
|
|
24
|
+
from portal.views.api import anonymise
|
|
14
25
|
|
|
15
26
|
|
|
16
|
-
class ClassAdmin(admin.ModelAdmin):
|
|
27
|
+
class ClassAdmin(admin.ModelAdmin, ExportActionMixin):
|
|
17
28
|
search_fields = [
|
|
18
29
|
"name",
|
|
19
30
|
"teacher__new_user__first_name",
|
|
20
31
|
"teacher__new_user__last_name",
|
|
21
32
|
"teacher__school__name",
|
|
22
33
|
]
|
|
23
|
-
list_display = [
|
|
24
|
-
|
|
34
|
+
list_display = [
|
|
35
|
+
"__str__",
|
|
36
|
+
"teacher",
|
|
37
|
+
"teacher_school",
|
|
38
|
+
"number_of_students",
|
|
39
|
+
"id",
|
|
40
|
+
]
|
|
41
|
+
autocomplete_fields = ["teacher"]
|
|
25
42
|
|
|
26
43
|
def teacher_school(self, obj):
|
|
27
44
|
return obj.teacher.school
|
|
28
45
|
|
|
46
|
+
def number_of_students(self, obj):
|
|
47
|
+
return len(obj.students.all())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SchoolAdmin(admin.ModelAdmin, ExportActionMixin):
|
|
51
|
+
search_fields = ["name", "country", "county"]
|
|
52
|
+
list_filter = ["county", "country"]
|
|
53
|
+
list_display = [
|
|
54
|
+
"__str__",
|
|
55
|
+
"country",
|
|
56
|
+
"county",
|
|
57
|
+
"number_of_teachers",
|
|
58
|
+
"number_of_classes",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
def number_of_teachers(self, obj):
|
|
62
|
+
return len(obj.teacher_school.all())
|
|
29
63
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
list_filter = ["postcode", "country"]
|
|
64
|
+
def number_of_classes(self, obj):
|
|
65
|
+
return len(obj.classes()) if obj.classes() else 0
|
|
33
66
|
|
|
34
67
|
|
|
35
|
-
class StudentAdmin(admin.ModelAdmin):
|
|
68
|
+
class StudentAdmin(admin.ModelAdmin, ExportActionMixin):
|
|
36
69
|
search_fields = [
|
|
37
70
|
"new_user__first_name",
|
|
38
71
|
"new_user__last_name",
|
|
72
|
+
"new_user__username",
|
|
39
73
|
"class_field__name",
|
|
40
74
|
"class_field__teacher__new_user__first_name",
|
|
41
75
|
"class_field__teacher__new_user__last_name",
|
|
@@ -46,9 +80,10 @@ class StudentAdmin(admin.ModelAdmin):
|
|
|
46
80
|
"class_field",
|
|
47
81
|
"class_field_teacher",
|
|
48
82
|
"class_field_school",
|
|
83
|
+
"new_user",
|
|
49
84
|
]
|
|
50
|
-
readonly_fields = ["user", "new_user"]
|
|
51
|
-
|
|
85
|
+
readonly_fields = ["user", "new_user", "login_id"]
|
|
86
|
+
autocomplete_fields = ["class_field", "pending_class_request"]
|
|
52
87
|
|
|
53
88
|
def class_field_teacher(self, obj):
|
|
54
89
|
if obj.class_field:
|
|
@@ -63,39 +98,114 @@ class StudentAdmin(admin.ModelAdmin):
|
|
|
63
98
|
return "Independent"
|
|
64
99
|
|
|
65
100
|
|
|
66
|
-
class TeacherAdmin(admin.ModelAdmin):
|
|
67
|
-
search_fields = [
|
|
68
|
-
|
|
101
|
+
class TeacherAdmin(admin.ModelAdmin, ExportActionMixin):
|
|
102
|
+
search_fields = [
|
|
103
|
+
"new_user__first_name",
|
|
104
|
+
"new_user__last_name",
|
|
105
|
+
"school__name",
|
|
106
|
+
"new_user__username",
|
|
107
|
+
]
|
|
108
|
+
list_display = [
|
|
109
|
+
"__str__",
|
|
110
|
+
"school",
|
|
111
|
+
"new_user",
|
|
112
|
+
"number_of_classes",
|
|
113
|
+
"is_admin",
|
|
114
|
+
]
|
|
69
115
|
readonly_fields = ["user", "new_user"]
|
|
70
|
-
|
|
116
|
+
autocomplete_fields = ["school", "invited_by"]
|
|
71
117
|
|
|
118
|
+
def number_of_classes(self, obj):
|
|
119
|
+
return len(obj.class_teacher.all())
|
|
72
120
|
|
|
73
|
-
|
|
121
|
+
|
|
122
|
+
class UserProfileAdmin(admin.ModelAdmin, ExportActionMixin):
|
|
74
123
|
search_fields = [
|
|
124
|
+
"id",
|
|
75
125
|
"user__first_name",
|
|
76
126
|
"user__last_name",
|
|
77
127
|
"user__username",
|
|
78
128
|
"user__date_joined",
|
|
79
129
|
]
|
|
80
130
|
list_filter = ["user__date_joined"]
|
|
81
|
-
list_display = ["user", "__str__", "
|
|
131
|
+
list_display = ["user", "__str__", "is_verified", "type", "id"]
|
|
82
132
|
readonly_fields = ["user"]
|
|
83
133
|
|
|
134
|
+
def type(self, obj):
|
|
135
|
+
if hasattr(obj, "student"):
|
|
136
|
+
if obj.student.class_field is None:
|
|
137
|
+
return "INDEPENDENT"
|
|
138
|
+
return "STUDENT"
|
|
139
|
+
return "TEACHER"
|
|
140
|
+
|
|
84
141
|
|
|
85
|
-
class
|
|
142
|
+
class SchoolTeacherInvitationAdmin(admin.ModelAdmin, ExportActionMixin):
|
|
86
143
|
search_fields = [
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
144
|
+
"from_teacher__new_user__first_name",
|
|
145
|
+
"from_teacher__new_user__last_name",
|
|
146
|
+
"from_teacher__new_user__email",
|
|
147
|
+
"school__name",
|
|
148
|
+
"invited_teacher_first_name",
|
|
149
|
+
"invited_teacher_last_name",
|
|
150
|
+
"invited_teacher_email",
|
|
151
|
+
"expiry",
|
|
152
|
+
"creation_time",
|
|
91
153
|
]
|
|
92
|
-
readonly_fields = ["
|
|
154
|
+
readonly_fields = ["token", "school", "from_teacher"]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class DynamicElementAdmin(admin.ModelAdmin, ExportActionMixin):
|
|
158
|
+
def has_delete_permission(self, request, obj=None):
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class DailyActivityAdmin(admin.ModelAdmin, ExportActionMixin):
|
|
163
|
+
search_fields = ["date"]
|
|
164
|
+
list_display = ["__str__", "date"]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class TotalActivityAdmin(admin.ModelAdmin, ExportActionMixin):
|
|
168
|
+
def has_add_permission(self, request):
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
def has_delete_permission(self, request, obj=None):
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
_UserAdmin.list_display += ("date_joined", "id")
|
|
176
|
+
_UserAdmin.list_filter += ("date_joined",)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class UserAdmin(_UserAdmin):
|
|
180
|
+
actions = ["anonymise_user", "export_as_csv"]
|
|
181
|
+
add_form = AdminUserCreationForm
|
|
182
|
+
change_password_form = AdminChangeUserPasswordForm
|
|
183
|
+
|
|
184
|
+
def anonymise_user(self, request, queryset):
|
|
185
|
+
for user in queryset:
|
|
186
|
+
anonymise(user)
|
|
187
|
+
|
|
188
|
+
anonymise_user.short_description = "Anonymise selected users"
|
|
189
|
+
|
|
190
|
+
def export_as_csv(self, request, queryset):
|
|
191
|
+
meta = self.model._meta
|
|
192
|
+
field_names = [
|
|
193
|
+
field.name for field in meta.fields if field.name != "password"
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
response = HttpResponse(content_type="text/csv")
|
|
197
|
+
response["Content-Disposition"] = "attachment; filename={}.csv".format(
|
|
198
|
+
meta
|
|
199
|
+
)
|
|
200
|
+
writer = csv.writer(response)
|
|
201
|
+
|
|
202
|
+
writer.writerow(field_names)
|
|
203
|
+
for obj in queryset:
|
|
204
|
+
writer.writerow([getattr(obj, field) for field in field_names])
|
|
93
205
|
|
|
206
|
+
return response
|
|
94
207
|
|
|
95
|
-
|
|
96
|
-
UserAdmin.list_filter += ("date_joined",)
|
|
97
|
-
UserAdmin.add_form = AdminUserCreationForm
|
|
98
|
-
UserAdmin.change_password_form = AdminChangeUserPasswordForm
|
|
208
|
+
export_as_csv.short_description = "Export selected users data as CSV"
|
|
99
209
|
|
|
100
210
|
|
|
101
211
|
admin.site.register(Class, ClassAdmin)
|
|
@@ -105,4 +215,7 @@ admin.site.register(School, SchoolAdmin)
|
|
|
105
215
|
admin.site.unregister(User)
|
|
106
216
|
admin.site.register(User, UserAdmin)
|
|
107
217
|
admin.site.register(UserProfile, UserProfileAdmin)
|
|
108
|
-
admin.site.register(
|
|
218
|
+
admin.site.register(SchoolTeacherInvitation, SchoolTeacherInvitationAdmin)
|
|
219
|
+
admin.site.register(DynamicElement, DynamicElementAdmin)
|
|
220
|
+
admin.site.register(DailyActivity, DailyActivityAdmin)
|
|
221
|
+
admin.site.register(TotalActivity, TotalActivityAdmin)
|
portal/app_settings.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
2
|
|
|
3
|
-
CONTACT_FORM_EMAILS = getattr(
|
|
4
|
-
settings, "PORTAL_CONTACT_FORM_EMAIL", ("codeforlife@ocado.com",)
|
|
5
|
-
)
|
|
3
|
+
CONTACT_FORM_EMAILS = getattr(settings, "PORTAL_CONTACT_FORM_EMAIL", ("codeforlife@ocado.com",))
|
|
6
4
|
|
|
7
5
|
# Private key for Recaptcha
|
|
8
6
|
RECAPTCHA_PRIVATE_KEY = getattr(settings, "RECAPTCHA_PRIVATE_KEY", None)
|
|
@@ -13,9 +11,12 @@ RECAPTCHA_PUBLIC_KEY = getattr(settings, "RECAPTCHA_PUBLIC_KEY", None)
|
|
|
13
11
|
DEBUG = getattr(settings, "DEBUG", False)
|
|
14
12
|
|
|
15
13
|
# The permission function for checking if the request is coming from a cron job
|
|
16
|
-
IS_CLOUD_SCHEDULER_FUNCTION = getattr(
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
IS_CLOUD_SCHEDULER_FUNCTION = getattr(settings, "IS_CLOUD_SCHEDULER_FUNCTION", lambda _: False)
|
|
15
|
+
|
|
16
|
+
# Half an hour
|
|
17
|
+
SESSION_EXPIRY_TIME = 60 * 30
|
|
19
18
|
|
|
20
19
|
# One hour
|
|
21
|
-
|
|
20
|
+
SCREENTIME_WARNING_EXPIRY_TIME = 60 * 60
|
|
21
|
+
|
|
22
|
+
TMP_AUTH_TOKEN = getattr(settings, "TMP_AUTH_TOKEN", "token")
|
portal/forms/dotmailer.py
CHANGED
|
@@ -3,7 +3,7 @@ from django import forms
|
|
|
3
3
|
|
|
4
4
|
class NewsletterForm(forms.Form):
|
|
5
5
|
email = forms.EmailField(
|
|
6
|
-
label="Sign up to
|
|
6
|
+
label="Sign up to receive updates about Code for Life games and teaching resources.",
|
|
7
7
|
label_suffix="",
|
|
8
8
|
widget=forms.EmailInput(
|
|
9
9
|
attrs={
|
|
@@ -14,15 +14,17 @@ class NewsletterForm(forms.Form):
|
|
|
14
14
|
help_text="Enter email address above",
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
+
age_verification = forms.BooleanField(
|
|
18
|
+
widget=forms.CheckboxInput(), initial=False, required=True
|
|
19
|
+
)
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
class ConsentForm(forms.Form):
|
|
19
23
|
email = forms.EmailField(
|
|
20
24
|
label="Email",
|
|
21
25
|
label_suffix="",
|
|
22
26
|
widget=forms.EmailInput(
|
|
23
|
-
attrs={
|
|
24
|
-
"placeholder": "your.name@yourdomain.com",
|
|
25
|
-
}
|
|
27
|
+
attrs={"placeholder": "your.name@yourdomain.com"}
|
|
26
28
|
),
|
|
27
29
|
)
|
|
28
30
|
|
portal/forms/invite_teacher.py
CHANGED
|
@@ -3,15 +3,24 @@ from django import forms
|
|
|
3
3
|
|
|
4
4
|
class InviteTeacherForm(forms.Form):
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
teacher_first_name = forms.CharField(
|
|
7
|
+
help_text="Enter first name of teacher",
|
|
8
|
+
max_length=100,
|
|
9
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "First name of teacher"}),
|
|
10
|
+
)
|
|
11
|
+
teacher_last_name = forms.CharField(
|
|
12
|
+
help_text="Enter last name of teacher",
|
|
13
|
+
max_length=100,
|
|
14
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Last name of teacher"}),
|
|
15
|
+
)
|
|
16
|
+
teacher_email = forms.EmailField(
|
|
17
|
+
help_text="Enter email address",
|
|
18
|
+
widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
|
|
19
|
+
)
|
|
7
20
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"pattern": email_regex,
|
|
14
|
-
"type": "email",
|
|
15
|
-
}
|
|
16
|
-
)
|
|
21
|
+
make_admin_ticked = forms.BooleanField(
|
|
22
|
+
label="Make an administrator of the school",
|
|
23
|
+
widget=forms.CheckboxInput(),
|
|
24
|
+
initial=False,
|
|
25
|
+
required=False,
|
|
17
26
|
)
|
portal/forms/organisation.py
CHANGED
|
@@ -1,107 +1,176 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from builtins import object
|
|
2
3
|
|
|
3
4
|
from common.models import School
|
|
4
5
|
from django import forms
|
|
5
6
|
from django.core.exceptions import ObjectDoesNotExist
|
|
6
|
-
from django.core.validators import EmailValidator
|
|
7
7
|
from django_countries.widgets import CountrySelectWidget
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class OrganisationForm(forms.ModelForm):
|
|
11
|
-
|
|
11
|
+
county = forms.ChoiceField(
|
|
12
|
+
choices=[
|
|
13
|
+
[None, "(select county)"],
|
|
14
|
+
["Aberdeen City", "Aberdeen City"],
|
|
15
|
+
["Aberdeenshire", "Aberdeenshire"],
|
|
16
|
+
["Angus", "Angus"],
|
|
17
|
+
["Argyll and Bute", "Argyll and Bute"],
|
|
18
|
+
["Bedfordshire", "Bedfordshire"],
|
|
19
|
+
["Belfast", "Belfast"],
|
|
20
|
+
["Belfast Greater", "Belfast Greater"],
|
|
21
|
+
["Berkshire", "Berkshire"],
|
|
22
|
+
["Blaenau Gwent", "Blaenau Gwent"],
|
|
23
|
+
["Bridgend", "Bridgend"],
|
|
24
|
+
["Buckinghamshire", "Buckinghamshire"],
|
|
25
|
+
["Caerphilly", "Caerphilly"],
|
|
26
|
+
["Cambridgeshire", "Cambridgeshire"],
|
|
27
|
+
["Cardiff", "Cardiff"],
|
|
28
|
+
["Carmarthenshire", "Carmarthenshire"],
|
|
29
|
+
["Ceredigion", "Ceredigion"],
|
|
30
|
+
["Channel Islands", "Channel Islands"],
|
|
31
|
+
["Cheshire", "Cheshire"],
|
|
32
|
+
["City of Edinburgh", "City of Edinburgh"],
|
|
33
|
+
["Clackmannanshire", "Clackmannanshire"],
|
|
34
|
+
["Conwy", "Conwy"],
|
|
35
|
+
["Cornwall", "Cornwall"],
|
|
36
|
+
["County Antrim", "County Antrim"],
|
|
37
|
+
["County Armagh", "County Armagh"],
|
|
38
|
+
["County Down", "County Down"],
|
|
39
|
+
["County Fermanagh", "County Fermanagh"],
|
|
40
|
+
["County Londonderry", "County Londonderry"],
|
|
41
|
+
["County Tyrone", "County Tyrone"],
|
|
42
|
+
["County of Bristol", "County of Bristol"],
|
|
43
|
+
["Cumbria", "Cumbria"],
|
|
44
|
+
["Denbighshire", "Denbighshire"],
|
|
45
|
+
["Derbyshire", "Derbyshire"],
|
|
46
|
+
["Devon", "Devon"],
|
|
47
|
+
["Dorset", "Dorset"],
|
|
48
|
+
["Dumfries and Galloway", "Dumfries and Galloway"],
|
|
49
|
+
["Dunbartonshire", "Dunbartonshire"],
|
|
50
|
+
["Dundee City", "Dundee City"],
|
|
51
|
+
["Durham", "Durham"],
|
|
52
|
+
["East Ayrshire", "East Ayrshire"],
|
|
53
|
+
["East Dunbartonshire", "East Dunbartonshire"],
|
|
54
|
+
["East Lothian", "East Lothian"],
|
|
55
|
+
["East Renfrewshire", "East Renfrewshire"],
|
|
56
|
+
["East Riding of Yorkshire", "East Riding of Yorkshire"],
|
|
57
|
+
["East Sussex", "East Sussex"],
|
|
58
|
+
["Essex", "Essex"],
|
|
59
|
+
["Falkirk", "Falkirk"],
|
|
60
|
+
["Fife", "Fife"],
|
|
61
|
+
["Flintshire", "Flintshire"],
|
|
62
|
+
["Glasgow City", "Glasgow City"],
|
|
63
|
+
["Gloucestershire", "Gloucestershire"],
|
|
64
|
+
["Greater London", "Greater London"],
|
|
65
|
+
["Greater Manchester", "Greater Manchester"],
|
|
66
|
+
["Guernsey Channel Islands", "Guernsey Channel Islands"],
|
|
67
|
+
["Gwynedd", "Gwynedd"],
|
|
68
|
+
["Hampshire", "Hampshire"],
|
|
69
|
+
["Hereford and Worcester", "Hereford and Worcester"],
|
|
70
|
+
["Herefordshire", "Herefordshire"],
|
|
71
|
+
["Hertfordshire", "Hertfordshire"],
|
|
72
|
+
["Highland", "Highland"],
|
|
73
|
+
["Inverclyde", "Inverclyde"],
|
|
74
|
+
["Inverness", "Inverness"],
|
|
75
|
+
["Isle of Anglesey", "Isle of Anglesey"],
|
|
76
|
+
["Isle of Barra", "Isle of Barra"],
|
|
77
|
+
["Isle of Man", "Isle of Man"],
|
|
78
|
+
["Isle of Wight", "Isle of Wight"],
|
|
79
|
+
["Jersey Channel Islands", "Jersey Channel Islands"],
|
|
80
|
+
["Kent", "Kent"],
|
|
81
|
+
["Lancashire", "Lancashire"],
|
|
82
|
+
["Leicestershire", "Leicestershire"],
|
|
83
|
+
["Lincolnshire", "Lincolnshire"],
|
|
84
|
+
["Merseyside", "Merseyside"],
|
|
85
|
+
["Merthyr Tydfil", "Merthyr Tydfil"],
|
|
86
|
+
["Midlothian", "Midlothian"],
|
|
87
|
+
["Monmouthshire", "Monmouthshire"],
|
|
88
|
+
["Moray", "Moray"],
|
|
89
|
+
["Neath Port Talbot", "Neath Port Talbot"],
|
|
90
|
+
["Newport", "Newport"],
|
|
91
|
+
["Norfolk", "Norfolk"],
|
|
92
|
+
["North Ayrshire", "North Ayrshire"],
|
|
93
|
+
["North Lanarkshire", "North Lanarkshire"],
|
|
94
|
+
["North Yorkshire", "North Yorkshire"],
|
|
95
|
+
["Northamptonshire", "Northamptonshire"],
|
|
96
|
+
["Northumberland", "Northumberland"],
|
|
97
|
+
["Nottinghamshire", "Nottinghamshire"],
|
|
98
|
+
["Orkney", "Orkney"],
|
|
99
|
+
["Orkney Islands", "Orkney Islands"],
|
|
100
|
+
["Oxfordshire", "Oxfordshire"],
|
|
101
|
+
["Pembrokeshire", "Pembrokeshire"],
|
|
102
|
+
["Perth and Kinross", "Perth and Kinross"],
|
|
103
|
+
["Powys", "Powys"],
|
|
104
|
+
["Renfrewshire", "Renfrewshire"],
|
|
105
|
+
["Rhondda Cynon Taff", "Rhondda Cynon Taff"],
|
|
106
|
+
["Rutland", "Rutland"],
|
|
107
|
+
["Scottish Borders", "Scottish Borders"],
|
|
108
|
+
["Shetland Islands", "Shetland Islands"],
|
|
109
|
+
["Shropshire", "Shropshire"],
|
|
110
|
+
["Somerset", "Somerset"],
|
|
111
|
+
["South Ayrshire", "South Ayrshire"],
|
|
112
|
+
["South Lanarkshire", "South Lanarkshire"],
|
|
113
|
+
["South Yorkshire", "South Yorkshire"],
|
|
114
|
+
["Staffordshire", "Staffordshire"],
|
|
115
|
+
["Stirling", "Stirling"],
|
|
116
|
+
["Suffolk", "Suffolk"],
|
|
117
|
+
["Surrey", "Surrey"],
|
|
118
|
+
["Swansea", "Swansea"],
|
|
119
|
+
["Torfaen", "Torfaen"],
|
|
120
|
+
["Tyne and Wear", "Tyne and Wear"],
|
|
121
|
+
["Vale of Glamorgan", "Vale of Glamorgan"],
|
|
122
|
+
["Warwickshire", "Warwickshire"],
|
|
123
|
+
["West Dunbart", "West Dunbart"],
|
|
124
|
+
["West Lothian", "West Lothian"],
|
|
125
|
+
["West Midlands", "West Midlands"],
|
|
126
|
+
["West Sussex", "West Sussex"],
|
|
127
|
+
["West Yorkshire", "West Yorkshire"],
|
|
128
|
+
["Western Isles", "Western Isles"],
|
|
129
|
+
["Wiltshire", "Wiltshire"],
|
|
130
|
+
["Worcestershire", "Worcestershire"],
|
|
131
|
+
["Wrexham", "Wrexham"],
|
|
132
|
+
],
|
|
133
|
+
required=False,
|
|
134
|
+
help_text="County (optional)",
|
|
135
|
+
)
|
|
12
136
|
|
|
137
|
+
class Meta(object):
|
|
13
138
|
model = School
|
|
14
|
-
fields = ["name", "
|
|
139
|
+
fields = ["name", "country", "county"]
|
|
15
140
|
widgets = {
|
|
16
|
-
"name": forms.TextInput(
|
|
17
|
-
attrs={
|
|
18
|
-
"autocomplete": "off",
|
|
19
|
-
"placeholder": "Name of school or club",
|
|
20
|
-
},
|
|
21
|
-
),
|
|
22
|
-
"postcode": forms.TextInput(
|
|
23
|
-
attrs={"autocomplete": "off", "placeholder": "Postcode / Zipcode"}
|
|
24
|
-
),
|
|
141
|
+
"name": forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Name of school or club"}),
|
|
25
142
|
"country": CountrySelectWidget(layout="{widget}"),
|
|
26
143
|
}
|
|
27
144
|
help_texts = {
|
|
28
145
|
"name": "Name of school or club",
|
|
29
|
-
"
|
|
30
|
-
"country": "Country",
|
|
146
|
+
"country": "Country (optional)",
|
|
31
147
|
}
|
|
32
148
|
|
|
33
149
|
def __init__(self, *args, **kwargs):
|
|
34
150
|
self.user = kwargs.pop("user", None)
|
|
35
151
|
self.current_school = kwargs.pop("current_school", None)
|
|
36
152
|
super(OrganisationForm, self).__init__(*args, **kwargs)
|
|
37
|
-
self.fields["postcode"].strip = False
|
|
38
153
|
|
|
39
154
|
def clean(self):
|
|
40
155
|
name = self.cleaned_data.get("name", None)
|
|
41
|
-
postcode = self.cleaned_data.get("postcode", None)
|
|
42
156
|
|
|
43
|
-
if name
|
|
157
|
+
if name:
|
|
44
158
|
try:
|
|
45
|
-
school = School.objects.get(name=name
|
|
159
|
+
school = School.objects.get(name=name)
|
|
46
160
|
except ObjectDoesNotExist:
|
|
47
161
|
return self.cleaned_data
|
|
48
162
|
|
|
49
163
|
if not self.current_school or self.current_school.id != school.id:
|
|
50
|
-
raise forms.ValidationError(
|
|
51
|
-
"There is already a school or club registered with that name and postcode"
|
|
52
|
-
)
|
|
164
|
+
raise forms.ValidationError("There is already a school or club registered with that name")
|
|
53
165
|
|
|
54
166
|
return self.cleaned_data
|
|
55
167
|
|
|
56
168
|
def clean_name(self):
|
|
57
169
|
name = self.cleaned_data.get("name", None)
|
|
58
|
-
validator = EmailValidator()
|
|
59
170
|
|
|
60
|
-
if name:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
except forms.ValidationError:
|
|
65
|
-
is_email = False
|
|
66
|
-
|
|
67
|
-
if is_email:
|
|
68
|
-
raise forms.ValidationError(
|
|
69
|
-
"Please make sure your organisation name is valid"
|
|
70
|
-
)
|
|
171
|
+
if re.match(re.compile("^[\w\.' ]+$"), name) is None:
|
|
172
|
+
raise forms.ValidationError(
|
|
173
|
+
"School names cannot contain special characters except full stops and apostrophes."
|
|
174
|
+
)
|
|
71
175
|
|
|
72
176
|
return name
|
|
73
|
-
|
|
74
|
-
def clean_postcode(self):
|
|
75
|
-
postcode = self.cleaned_data.get("postcode", None)
|
|
76
|
-
|
|
77
|
-
if postcode:
|
|
78
|
-
if (
|
|
79
|
-
len(postcode.replace(" ", "")) > 10
|
|
80
|
-
or len(postcode.replace(" ", "")) == 0
|
|
81
|
-
):
|
|
82
|
-
raise forms.ValidationError("Please enter a valid postcode or ZIP code")
|
|
83
|
-
|
|
84
|
-
return postcode
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
class OrganisationJoinForm(forms.Form):
|
|
88
|
-
fuzzy_name = forms.CharField(
|
|
89
|
-
widget=forms.TextInput(
|
|
90
|
-
attrs={"placeholder": "School or club by name or postcode"}
|
|
91
|
-
),
|
|
92
|
-
help_text="Enter school or club by name or postcode",
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
# Note: the reason this is a CharField rather than a ChoiceField is to avoid having to
|
|
96
|
-
# provide choices which was problematic given that the options are dynamically generated.
|
|
97
|
-
chosen_org = forms.CharField(
|
|
98
|
-
widget=forms.Select(), help_text="Select school or club"
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
def clean_chosen_org(self):
|
|
102
|
-
chosen_org = self.cleaned_data.get("chosen_org", None)
|
|
103
|
-
|
|
104
|
-
if chosen_org and not School.objects.filter(id=int(chosen_org)).exists():
|
|
105
|
-
raise forms.ValidationError("That school or club was not recognised")
|
|
106
|
-
|
|
107
|
-
return chosen_org
|