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/views/admin.py
CHANGED
|
@@ -1,25 +1,13 @@
|
|
|
1
|
-
from builtins import str
|
|
2
|
-
from datetime import timedelta
|
|
3
|
-
from time import sleep
|
|
4
|
-
|
|
5
|
-
from common.models import Teacher, School, Class, Student
|
|
6
1
|
from django.contrib import messages as messages
|
|
7
2
|
from django.contrib.auth import logout
|
|
8
|
-
from django.contrib.auth.decorators import permission_required, login_required
|
|
9
|
-
from django.contrib.auth.models import User
|
|
10
3
|
from django.contrib.auth.views import (
|
|
11
4
|
PasswordChangeView,
|
|
12
5
|
PasswordChangeDoneView,
|
|
13
6
|
)
|
|
14
|
-
from django.db.models import Avg, Count, Q
|
|
15
7
|
from django.http import HttpResponseRedirect
|
|
16
|
-
from django.shortcuts import render
|
|
17
|
-
from django.utils import timezone
|
|
18
|
-
from django_otp import device_classes
|
|
19
8
|
from rest_framework.reverse import reverse_lazy
|
|
20
9
|
|
|
21
10
|
from portal.forms.admin import AdminChangeOwnPasswordForm
|
|
22
|
-
from portal.helpers.location import lookup_coord
|
|
23
11
|
|
|
24
12
|
block_limit = 5
|
|
25
13
|
|
|
@@ -40,323 +28,3 @@ class AdminChangePasswordDoneView(PasswordChangeDoneView):
|
|
|
40
28
|
"Password updated successfully. Please login using your new password.",
|
|
41
29
|
)
|
|
42
30
|
return HttpResponseRedirect(reverse_lazy("teacher_login"))
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@login_required(login_url=reverse_lazy("teacher_login"))
|
|
46
|
-
@permission_required("common.view_aggregated_data", raise_exception=True)
|
|
47
|
-
def aggregated_data(request):
|
|
48
|
-
|
|
49
|
-
tables = []
|
|
50
|
-
|
|
51
|
-
table_head = ["Data description", "Value", "More info"]
|
|
52
|
-
table_data = []
|
|
53
|
-
|
|
54
|
-
"""
|
|
55
|
-
Overall statistics
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
teacher_count = Teacher.objects.count()
|
|
59
|
-
student_count = Student.objects.count()
|
|
60
|
-
new_profiles_count = User.objects.filter(
|
|
61
|
-
date_joined__gte=timezone.now() - timedelta(days=7)
|
|
62
|
-
).count()
|
|
63
|
-
|
|
64
|
-
table_data.append(
|
|
65
|
-
[
|
|
66
|
-
"Number of users",
|
|
67
|
-
teacher_count + student_count,
|
|
68
|
-
"Number of teachers + Number of students",
|
|
69
|
-
]
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
table_data.append(
|
|
73
|
-
[
|
|
74
|
-
"Number of new users (past week)",
|
|
75
|
-
new_profiles_count,
|
|
76
|
-
"Number of user profiles",
|
|
77
|
-
]
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
tables.append(
|
|
81
|
-
{
|
|
82
|
-
"title": "Overall Statistics",
|
|
83
|
-
"description": "CFL site overall statistics",
|
|
84
|
-
"header": table_head,
|
|
85
|
-
"data": table_data,
|
|
86
|
-
}
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
"""
|
|
90
|
-
School statistics
|
|
91
|
-
"""
|
|
92
|
-
table_data = []
|
|
93
|
-
table_data.append(["Number of schools signed up", School.objects.count(), ""])
|
|
94
|
-
num_of_teachers_per_school = School.objects.annotate(
|
|
95
|
-
num_teachers=Count("teacher_school")
|
|
96
|
-
)
|
|
97
|
-
stats_teachers_per_school = num_of_teachers_per_school.aggregate(
|
|
98
|
-
Avg("num_teachers")
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
table_data.append(
|
|
102
|
-
[
|
|
103
|
-
"Average number of teachers per school",
|
|
104
|
-
stats_teachers_per_school["num_teachers__avg"],
|
|
105
|
-
"",
|
|
106
|
-
]
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
tables.append(
|
|
110
|
-
{
|
|
111
|
-
"title": "Schools or Clubs",
|
|
112
|
-
"description": "",
|
|
113
|
-
"header": table_head,
|
|
114
|
-
"data": table_data,
|
|
115
|
-
}
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
"""
|
|
119
|
-
Teacher statistics
|
|
120
|
-
"""
|
|
121
|
-
table_data = []
|
|
122
|
-
table_data.append(["Number of teachers signed up", teacher_count, ""])
|
|
123
|
-
|
|
124
|
-
table_data.append(
|
|
125
|
-
[
|
|
126
|
-
"Number of teachers not in a school",
|
|
127
|
-
Teacher.objects.filter(school=None).count(),
|
|
128
|
-
"",
|
|
129
|
-
]
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
table_data.append(
|
|
133
|
-
[
|
|
134
|
-
"Number of teachers with request pending to join a school",
|
|
135
|
-
Teacher.objects.exclude(pending_join_request=None).count(),
|
|
136
|
-
"",
|
|
137
|
-
]
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
table_data.append(
|
|
141
|
-
[
|
|
142
|
-
"Number of teachers with unverified email address",
|
|
143
|
-
Teacher.objects.exclude(
|
|
144
|
-
new_user__email_verifications__verified=True
|
|
145
|
-
).count(),
|
|
146
|
-
"",
|
|
147
|
-
]
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
otp_model_names = [model._meta.model_name for model in device_classes()]
|
|
151
|
-
otp_query = Q()
|
|
152
|
-
for model_name in otp_model_names:
|
|
153
|
-
otp_query = otp_query | Q(**{"new_user__%s__name" % model_name: "default"})
|
|
154
|
-
two_factor_teachers = Teacher.objects.filter(otp_query).distinct().count()
|
|
155
|
-
table_data.append(["Number of teachers setup with 2FA", two_factor_teachers, ""])
|
|
156
|
-
num_of_classes_per_teacher = Teacher.objects.annotate(
|
|
157
|
-
num_classes=Count("class_teacher")
|
|
158
|
-
)
|
|
159
|
-
stats_classes_per_teacher = num_of_classes_per_teacher.aggregate(Avg("num_classes"))
|
|
160
|
-
num_of_classes_per_active_teacher = num_of_classes_per_teacher.exclude(school=None)
|
|
161
|
-
stats_classes_per_active_teacher = num_of_classes_per_active_teacher.aggregate(
|
|
162
|
-
Avg("num_classes")
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
table_data.append(
|
|
166
|
-
[
|
|
167
|
-
"Average number of classes per teacher",
|
|
168
|
-
stats_classes_per_teacher["num_classes__avg"],
|
|
169
|
-
"",
|
|
170
|
-
]
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
table_data.append(
|
|
174
|
-
[
|
|
175
|
-
"Average number of classes per active teacher",
|
|
176
|
-
stats_classes_per_active_teacher["num_classes__avg"],
|
|
177
|
-
"Excludes teachers without a school",
|
|
178
|
-
]
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
table_data.append(
|
|
182
|
-
[
|
|
183
|
-
"Number of of teachers with no classes",
|
|
184
|
-
num_of_classes_per_teacher.filter(num_classes=0).count(),
|
|
185
|
-
"",
|
|
186
|
-
]
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
table_data.append(
|
|
190
|
-
[
|
|
191
|
-
"Number of of active teachers with no classes",
|
|
192
|
-
num_of_classes_per_active_teacher.filter(num_classes=0).count(),
|
|
193
|
-
"Excludes teachers without a school",
|
|
194
|
-
]
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
tables.append(
|
|
198
|
-
{
|
|
199
|
-
"title": "Teachers",
|
|
200
|
-
"description": "",
|
|
201
|
-
"header": table_head,
|
|
202
|
-
"data": table_data,
|
|
203
|
-
}
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
"""
|
|
207
|
-
Class statistics
|
|
208
|
-
"""
|
|
209
|
-
table_data = []
|
|
210
|
-
table_data.append(["Number of classes", Class.objects.count(), ""])
|
|
211
|
-
|
|
212
|
-
num_students_per_class = Class.objects.annotate(num_students=Count("students"))
|
|
213
|
-
stats_students_per_class = num_students_per_class.aggregate(Avg("num_students"))
|
|
214
|
-
stats_students_per_active_class = num_students_per_class.exclude(
|
|
215
|
-
num_students=0
|
|
216
|
-
).aggregate(Avg("num_students"))
|
|
217
|
-
|
|
218
|
-
table_data.append(
|
|
219
|
-
[
|
|
220
|
-
"Average number of students per class",
|
|
221
|
-
stats_students_per_class["num_students__avg"],
|
|
222
|
-
"",
|
|
223
|
-
]
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
table_data.append(
|
|
227
|
-
[
|
|
228
|
-
"Average number of students per active class",
|
|
229
|
-
stats_students_per_active_class["num_students__avg"],
|
|
230
|
-
"Excludes classes which are empty",
|
|
231
|
-
]
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
tables.append(
|
|
235
|
-
{
|
|
236
|
-
"title": "Classes",
|
|
237
|
-
"description": "",
|
|
238
|
-
"header": table_head,
|
|
239
|
-
"data": table_data,
|
|
240
|
-
}
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
"""
|
|
244
|
-
Student statistics
|
|
245
|
-
"""
|
|
246
|
-
table_data = []
|
|
247
|
-
table_data.append(["Number of students", student_count, ""])
|
|
248
|
-
|
|
249
|
-
independent_students = Student.objects.filter(class_field=None)
|
|
250
|
-
|
|
251
|
-
table_data.append(
|
|
252
|
-
["Number of independent students", independent_students.count(), ""]
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
table_data.append(
|
|
256
|
-
[
|
|
257
|
-
"Number of independent students with unverified email address",
|
|
258
|
-
Student.objects.exclude(
|
|
259
|
-
new_user__email_verifications__verified=True
|
|
260
|
-
).count(),
|
|
261
|
-
"",
|
|
262
|
-
]
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
table_data.append(
|
|
266
|
-
[
|
|
267
|
-
"Number of school students",
|
|
268
|
-
Student.objects.exclude(class_field=None).count(),
|
|
269
|
-
"",
|
|
270
|
-
]
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
tables.append(
|
|
274
|
-
{
|
|
275
|
-
"title": "Students",
|
|
276
|
-
"description": "",
|
|
277
|
-
"header": table_head,
|
|
278
|
-
"data": table_data,
|
|
279
|
-
}
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
"""
|
|
283
|
-
Rapid Router Student Progress statistics
|
|
284
|
-
"""
|
|
285
|
-
table_data = []
|
|
286
|
-
|
|
287
|
-
students_with_attempts = Student.objects.annotate(
|
|
288
|
-
num_attempts=Count("attempts")
|
|
289
|
-
).exclude(num_attempts=0)
|
|
290
|
-
table_data.append(
|
|
291
|
-
["Number of students who have started RR", students_with_attempts.count(), ""]
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
school_students_with_attempts = students_with_attempts.exclude(class_field=None)
|
|
295
|
-
table_data.append(
|
|
296
|
-
[
|
|
297
|
-
"Number of school students who have started RR",
|
|
298
|
-
school_students_with_attempts.count(),
|
|
299
|
-
"",
|
|
300
|
-
]
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
independent_students_with_attempts = students_with_attempts.filter(class_field=None)
|
|
304
|
-
table_data.append(
|
|
305
|
-
[
|
|
306
|
-
"Number of independent students who have started RR",
|
|
307
|
-
independent_students_with_attempts.count(),
|
|
308
|
-
"",
|
|
309
|
-
]
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
tables.append(
|
|
313
|
-
{
|
|
314
|
-
"title": "Rapid Router Student Progress",
|
|
315
|
-
"description": "",
|
|
316
|
-
"header": table_head,
|
|
317
|
-
"data": table_data,
|
|
318
|
-
}
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
return render(request, "portal/admin/aggregated_data.html", {"tables": tables})
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def fill_in_missing_school_locations(request):
|
|
325
|
-
schools = School.objects.filter(latitude="0", longitude="0")
|
|
326
|
-
|
|
327
|
-
requests = 0
|
|
328
|
-
failures = []
|
|
329
|
-
town0 = 0
|
|
330
|
-
|
|
331
|
-
for school in schools:
|
|
332
|
-
requests += 1
|
|
333
|
-
sleep(0.2) # so we execute a bit less than 5/sec
|
|
334
|
-
|
|
335
|
-
(
|
|
336
|
-
error,
|
|
337
|
-
school.country,
|
|
338
|
-
school.town,
|
|
339
|
-
school.latitude,
|
|
340
|
-
school.longitude,
|
|
341
|
-
) = lookup_coord(school.postcode, school.country.code)
|
|
342
|
-
|
|
343
|
-
if error is None:
|
|
344
|
-
school.save()
|
|
345
|
-
|
|
346
|
-
if error is not None:
|
|
347
|
-
failures += [(school.id, school.postcode, error)]
|
|
348
|
-
|
|
349
|
-
if school.town == "0":
|
|
350
|
-
town0 += 1
|
|
351
|
-
|
|
352
|
-
messages.info(request, "Made %d requests" % requests)
|
|
353
|
-
messages.info(request, "There were %d errors: %s" % (len(failures), str(failures)))
|
|
354
|
-
messages.info(request, "%d school have no town" % town0)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
@login_required(login_url=reverse_lazy("teacher_login"))
|
|
358
|
-
@permission_required("common.view_map_data", raise_exception=True)
|
|
359
|
-
def schools_map(request):
|
|
360
|
-
fill_in_missing_school_locations(request)
|
|
361
|
-
|
|
362
|
-
return render(request, "portal/admin/map.html", {"schools": School.objects.all()})
|
portal/views/api.py
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import uuid
|
|
3
3
|
|
|
4
|
-
from common.models import Student, Teacher
|
|
4
|
+
from common.models import Class, School, Student, Teacher, UserProfile
|
|
5
5
|
from django.contrib.auth.decorators import login_required
|
|
6
6
|
from django.contrib.auth.models import User
|
|
7
7
|
from django.http import HttpRequest, HttpResponse
|
|
8
8
|
from django.utils import timezone
|
|
9
|
-
from portal.app_settings import IS_CLOUD_SCHEDULER_FUNCTION
|
|
10
9
|
from rest_framework import generics, permissions, serializers, status
|
|
11
10
|
from rest_framework.authentication import SessionAuthentication
|
|
12
11
|
from rest_framework.decorators import api_view
|
|
13
12
|
from rest_framework.response import Response
|
|
14
13
|
from rest_framework.reverse import reverse_lazy
|
|
15
14
|
|
|
15
|
+
from portal.app_settings import IS_CLOUD_SCHEDULER_FUNCTION
|
|
16
|
+
|
|
16
17
|
THREE_YEARS_IN_DAYS = 1095
|
|
17
18
|
|
|
18
19
|
|
|
@@ -21,7 +22,9 @@ THREE_YEARS_IN_DAYS = 1095
|
|
|
21
22
|
def registered_users(request, year, month, day):
|
|
22
23
|
try:
|
|
23
24
|
nbr_reg = User.objects.filter(
|
|
24
|
-
date_joined__startswith=datetime.date(
|
|
25
|
+
date_joined__startswith=datetime.date(
|
|
26
|
+
int(year), int(month), int(day)
|
|
27
|
+
)
|
|
25
28
|
).count()
|
|
26
29
|
return Response(nbr_reg)
|
|
27
30
|
except ValueError:
|
|
@@ -70,11 +73,14 @@ class IsAdminOrGoogleAppEngine(permissions.IsAdminUser):
|
|
|
70
73
|
"""Checks whether the request is from a Google App Engine cron job."""
|
|
71
74
|
|
|
72
75
|
def has_permission(self, request: HttpRequest, view):
|
|
73
|
-
is_admin = super(IsAdminOrGoogleAppEngine, self).has_permission(
|
|
76
|
+
is_admin = super(IsAdminOrGoogleAppEngine, self).has_permission(
|
|
77
|
+
request, view
|
|
78
|
+
)
|
|
74
79
|
return IS_CLOUD_SCHEDULER_FUNCTION(request) or is_admin
|
|
75
80
|
|
|
76
81
|
|
|
77
|
-
def
|
|
82
|
+
def __anonymise_user(user):
|
|
83
|
+
# the actual user anonymisation
|
|
78
84
|
user.username = uuid.uuid4().hex
|
|
79
85
|
user.first_name = "Deleted"
|
|
80
86
|
user.last_name = "User"
|
|
@@ -83,6 +89,52 @@ def _anonymise(user):
|
|
|
83
89
|
user.save()
|
|
84
90
|
|
|
85
91
|
|
|
92
|
+
def anonymise(user):
|
|
93
|
+
"""Anonymise user. If admin teacher, pass the admin role to another teacher (if exists).
|
|
94
|
+
If the only teacher, anonymise the school.
|
|
95
|
+
"""
|
|
96
|
+
is_admin = False
|
|
97
|
+
teacher = None
|
|
98
|
+
# Find the teacher even if they're anonymised
|
|
99
|
+
teacher_set = Teacher._base_manager.filter(new_user=user)
|
|
100
|
+
if teacher_set:
|
|
101
|
+
is_admin = teacher_set[0].is_admin
|
|
102
|
+
school = teacher_set[0].school
|
|
103
|
+
teacher = teacher_set[0]
|
|
104
|
+
|
|
105
|
+
__anonymise_user(user)
|
|
106
|
+
|
|
107
|
+
# if teacher, anonymise classes and students
|
|
108
|
+
if teacher:
|
|
109
|
+
classes = Class.objects.filter(teacher=teacher)
|
|
110
|
+
for klass in classes:
|
|
111
|
+
students = Student.objects.filter(class_field=klass)
|
|
112
|
+
for student in students:
|
|
113
|
+
__anonymise_user(student.new_user)
|
|
114
|
+
klass.anonymise()
|
|
115
|
+
|
|
116
|
+
# if user is admin and the school does not have another admin, appoint another teacher as admin
|
|
117
|
+
if is_admin:
|
|
118
|
+
teachers = Teacher.objects.filter(school=school).order_by(
|
|
119
|
+
"new_user__last_name", "new_user__first_name"
|
|
120
|
+
)
|
|
121
|
+
if not teachers:
|
|
122
|
+
# no other teacher, anonymise the school
|
|
123
|
+
school.anonymise()
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
admin_exists = False
|
|
127
|
+
for teacher in teachers:
|
|
128
|
+
if teacher.is_admin:
|
|
129
|
+
admin_exists = True
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
# if no admin, appoint the first teacher as admin
|
|
133
|
+
if not admin_exists:
|
|
134
|
+
teachers[0].is_admin = True
|
|
135
|
+
teachers[0].save()
|
|
136
|
+
|
|
137
|
+
|
|
86
138
|
class InactiveUsersView(generics.ListAPIView):
|
|
87
139
|
"""
|
|
88
140
|
This API view endpoint allows us to see our inactive users.
|
|
@@ -110,62 +162,44 @@ class InactiveUsersView(generics.ListAPIView):
|
|
|
110
162
|
"""Delete all personal data from inactive users and mark them as inactive."""
|
|
111
163
|
inactive_users = self.get_queryset()
|
|
112
164
|
for user in inactive_users:
|
|
113
|
-
|
|
165
|
+
anonymise(user)
|
|
114
166
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
115
167
|
|
|
116
168
|
|
|
117
|
-
class
|
|
169
|
+
class RemoveFakeAccounts(generics.ListAPIView):
|
|
118
170
|
"""
|
|
119
|
-
This API endpoint
|
|
171
|
+
This API endpoint will delete suspicious accounts that have the same first and last name and who are not verified
|
|
120
172
|
"""
|
|
121
173
|
|
|
122
|
-
queryset = Student.objects.filter(
|
|
123
|
-
class_field__isnull=True, new_user__is_active=True
|
|
124
|
-
).select_related("new_user")
|
|
125
|
-
|
|
126
174
|
authentication_classes = (SessionAuthentication,)
|
|
175
|
+
serializer_class = InactiveUserSerializer
|
|
127
176
|
permission_classes = (IsAdminOrGoogleAppEngine,)
|
|
128
177
|
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
_anonymise(usrone)
|
|
137
|
-
# else: should not happen, but if it does, leave them
|
|
138
|
-
# if there's one with login, keep that one
|
|
139
|
-
elif usrone.last_login and not usrtwo.last_login:
|
|
140
|
-
_anonymise(usrtwo)
|
|
141
|
-
elif not usrone.last_login and usrtwo.last_login:
|
|
142
|
-
_anonymise(usrone)
|
|
143
|
-
# else: both have logged in, we don't want to automatically choose for teacher+indy duplicates
|
|
144
|
-
|
|
145
|
-
def _tidyup_students(students):
|
|
146
|
-
for student in students:
|
|
147
|
-
email = student.new_user.email
|
|
148
|
-
assert email != ""
|
|
178
|
+
def get(self, request):
|
|
179
|
+
userprofiles = UserProfile.objects.filter(is_verified=False)
|
|
180
|
+
[
|
|
181
|
+
userprofile.user.delete()
|
|
182
|
+
for userprofile in userprofiles
|
|
183
|
+
if userprofile.user.first_name == userprofile.user.last_name
|
|
184
|
+
]
|
|
149
185
|
|
|
150
|
-
|
|
151
|
-
new_user__is_active=True, new_user__email=email
|
|
152
|
-
).select_related("new_user")
|
|
186
|
+
return HttpResponse(status=204)
|
|
153
187
|
|
|
154
|
-
if not teachers.exists():
|
|
155
|
-
continue # no duplicate
|
|
156
188
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
189
|
+
class AnonymiseOrphanSchoolsView(generics.ListAPIView):
|
|
190
|
+
authentication_classes = (SessionAuthentication,)
|
|
191
|
+
serializer_class = InactiveUserSerializer
|
|
192
|
+
permission_classes = (IsAdminOrGoogleAppEngine,)
|
|
160
193
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
194
|
+
def get(self, request: HttpRequest, start_id):
|
|
195
|
+
# Re-anonymise all inactive teachers so their schools (if necessary) and classes/students are anonymised
|
|
196
|
+
for teacher in Teacher._base_manager.filter(
|
|
197
|
+
pk__gte=start_id, new_user__is_active=False
|
|
198
|
+
):
|
|
199
|
+
anonymise(teacher.new_user)
|
|
164
200
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
offset += LIMIT
|
|
169
|
-
indystudents = self.get_queryset()[offset : (offset + LIMIT)]
|
|
201
|
+
# Anonymise schools without any teachers
|
|
202
|
+
for school in School.objects.filter(teacher_school__isnull=True):
|
|
203
|
+
school.anonymise()
|
|
170
204
|
|
|
171
205
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .user import *
|