cfl-common 8.8.1__tar.gz → 8.8.3__tar.gz
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-8.8.1 → cfl_common-8.8.3}/PKG-INFO +11 -8
- {cfl_common-8.8.1 → cfl_common-8.8.3}/cfl_common.egg-info/PKG-INFO +11 -8
- {cfl_common-8.8.1 → cfl_common-8.8.3}/cfl_common.egg-info/SOURCES.txt +1 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/cfl_common.egg-info/requires.txt +14 -7
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/app_settings.py +3 -0
- cfl_common-8.8.3/common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/models.py +109 -13
- {cfl_common-8.8.1 → cfl_common-8.8.3}/MANIFEST.in +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/cfl_common.egg-info/dependency_links.txt +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/cfl_common.egg-info/top_level.txt +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/__init__.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/apps.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/context_processors.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/csp_config.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/fixtures/aimmo_characters.json +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/fixtures/aimmo_characters2.json +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/fixtures/aimmo_characters3.json +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/helpers/__init__.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/helpers/data_migration_loader.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/helpers/emails.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/helpers/generators.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/helpers/organisation.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/mail.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0001_initial.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0002_emailverification.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0003_aimmocharacter.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0004_add_aimmocharacters.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0005_add_worksheets.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0006_update_aimmo_character_image_path.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0008_unlock_worksheet_3.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0009_add_blocked_time_to_teacher_and_student.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0010_remove_teacher_title.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0011_student_login_id.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0012_usersession.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0013_class_school.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0014_login_type.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0015_dailyactivity.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0016_joinreleasestudent.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0017_copy_email_to_username.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0018_update_aimmo_character_image_path.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0019_aimmocharacter_alt.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0020_class_is_active_and_null_access_code.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0021_school_is_active.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0022_school_cleanup.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0023_userprofile_aimmo_badges.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0024_teacher_invited_by.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0025_schoolteacherinvitation.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0026_teacher_remove_join_request.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0027_class_created_by.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0028_coding_club_downloads.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0029_dynamicelement.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0030_add_maintenance_banner.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0031_improve_admin_panel.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0032_dailyactivity_level_control_submits.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0033_password_reset_tracking_fields.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0035_rename_lockout_fields.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0037_migrate_email_verification.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0038_delete_emailverification.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0039_copy_email_to_username.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0040_school_county.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0041_populate_gb_counties.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0042_totalactivity.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0043_add_total_activity.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0044_update_activity_models.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0045_otp.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0046_alter_school_country.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0047_delete_school_postcode.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0048_unique_school_names.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0049_anonymise_orphan_users.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0050_anonymise_orphan_schools.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0051_verify_returning_users.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0052_add_cse_fields.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0053_clean_class_data.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0054_delete_aimmo_models.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0055_alter_schoolteacherinvitation_token.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0056_set_non_school_teachers_as_non_admins.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0057_teacher_teacher__is_admin.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/__init__.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/permissions.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/static/common/img/RR_logo.svg +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/static/common/img/brain.svg +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/templates/common/freshdesk_widget.html +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/templates/common/onetrust_cookies_consent_notice.html +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/__init__.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/test_migration_anonymise_orphan_schools.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/test_migration_anonymise_orphan_users.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/test_migration_blocked_time.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/test_migration_remove_teacher_title.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/test_migration_unique_school_names.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/test_migration_verify_returning_users.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/test_models.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/utils/__init__.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/utils/classes.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/utils/email.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/utils/organisation.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/utils/student.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/utils/teacher.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/utils/user.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/common/utils.py +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/pyproject.toml +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/setup.cfg +0 -0
- {cfl_common-8.8.1 → cfl_common-8.8.3}/setup.py +0 -0
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cfl-common
|
|
3
|
-
Version: 8.8.
|
|
3
|
+
Version: 8.8.3
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Operating System :: OS Independent
|
|
6
|
-
Requires-Dist: asgiref==3.
|
|
7
|
-
Requires-Dist: certifi==2025.
|
|
8
|
-
Requires-Dist:
|
|
6
|
+
Requires-Dist: asgiref==3.9.1; python_version >= "3.9"
|
|
7
|
+
Requires-Dist: certifi==2025.8.3; python_version >= "3.7"
|
|
8
|
+
Requires-Dist: cffi==1.17.1; platform_python_implementation != "PyPy"
|
|
9
|
+
Requires-Dist: charset-normalizer==3.4.3; python_version >= "3.7"
|
|
10
|
+
Requires-Dist: cryptography==44.0.1; python_version >= "3.7" and python_full_version not in "3.9.0, 3.9.1"
|
|
9
11
|
Requires-Dist: diff-match-patch==20241021; python_version >= "3.7"
|
|
10
12
|
Requires-Dist: django==5.1.10; python_version >= "3.10"
|
|
11
13
|
Requires-Dist: django-countries==7.6.1
|
|
12
14
|
Requires-Dist: django-csp==3.8
|
|
13
15
|
Requires-Dist: django-formtools==2.5.1; python_version >= "3.8"
|
|
14
16
|
Requires-Dist: django-import-export==4.2.0; python_version >= "3.9"
|
|
15
|
-
Requires-Dist: django-otp==1.6.
|
|
17
|
+
Requires-Dist: django-otp==1.6.1; python_version >= "3.7"
|
|
16
18
|
Requires-Dist: django-phonenumber-field==8.1.0; python_version >= "3.9"
|
|
17
19
|
Requires-Dist: django-pipeline==4.0.0; python_version >= "3.9"
|
|
18
20
|
Requires-Dist: django-two-factor-auth==1.17.0; python_version >= "3.8"
|
|
@@ -20,9 +22,10 @@ Requires-Dist: djangorestframework==3.16.0; python_version >= "3.9"
|
|
|
20
22
|
Requires-Dist: idna==3.10; python_version >= "3.6"
|
|
21
23
|
Requires-Dist: libsass==0.23.0; python_version >= "3.8"
|
|
22
24
|
Requires-Dist: more-itertools==8.7.0; python_version >= "3.5"
|
|
23
|
-
Requires-Dist: numpy==2.3.
|
|
24
|
-
Requires-Dist: pandas==2.3.
|
|
25
|
+
Requires-Dist: numpy==2.3.2; python_version >= "3.11"
|
|
26
|
+
Requires-Dist: pandas==2.3.1; python_version >= "3.9"
|
|
25
27
|
Requires-Dist: pgeocode==0.4.0; python_version >= "3.8"
|
|
28
|
+
Requires-Dist: pycparser==2.22; python_version >= "3.8"
|
|
26
29
|
Requires-Dist: pyjwt==2.6.0; python_version >= "3.7"
|
|
27
30
|
Requires-Dist: pypng==0.20220715.0
|
|
28
31
|
Requires-Dist: python-dateutil==2.9.0.post0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
@@ -33,7 +36,7 @@ Requires-Dist: setuptools==80.9.0; python_version >= "3.9"
|
|
|
33
36
|
Requires-Dist: six==1.17.0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
34
37
|
Requires-Dist: sqlparse==0.5.3; python_version >= "3.8"
|
|
35
38
|
Requires-Dist: tablib==3.7.0; python_version >= "3.9"
|
|
36
|
-
Requires-Dist: typing-extensions==4.14.
|
|
39
|
+
Requires-Dist: typing-extensions==4.14.1; python_version >= "3.9"
|
|
37
40
|
Requires-Dist: tzdata==2025.2; python_version >= "2"
|
|
38
41
|
Requires-Dist: urllib3==2.5.0; python_version >= "3.9"
|
|
39
42
|
Requires-Dist: wheel==0.45.1; python_version >= "3.8"
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cfl-common
|
|
3
|
-
Version: 8.8.
|
|
3
|
+
Version: 8.8.3
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Operating System :: OS Independent
|
|
6
|
-
Requires-Dist: asgiref==3.
|
|
7
|
-
Requires-Dist: certifi==2025.
|
|
8
|
-
Requires-Dist:
|
|
6
|
+
Requires-Dist: asgiref==3.9.1; python_version >= "3.9"
|
|
7
|
+
Requires-Dist: certifi==2025.8.3; python_version >= "3.7"
|
|
8
|
+
Requires-Dist: cffi==1.17.1; platform_python_implementation != "PyPy"
|
|
9
|
+
Requires-Dist: charset-normalizer==3.4.3; python_version >= "3.7"
|
|
10
|
+
Requires-Dist: cryptography==44.0.1; python_version >= "3.7" and python_full_version not in "3.9.0, 3.9.1"
|
|
9
11
|
Requires-Dist: diff-match-patch==20241021; python_version >= "3.7"
|
|
10
12
|
Requires-Dist: django==5.1.10; python_version >= "3.10"
|
|
11
13
|
Requires-Dist: django-countries==7.6.1
|
|
12
14
|
Requires-Dist: django-csp==3.8
|
|
13
15
|
Requires-Dist: django-formtools==2.5.1; python_version >= "3.8"
|
|
14
16
|
Requires-Dist: django-import-export==4.2.0; python_version >= "3.9"
|
|
15
|
-
Requires-Dist: django-otp==1.6.
|
|
17
|
+
Requires-Dist: django-otp==1.6.1; python_version >= "3.7"
|
|
16
18
|
Requires-Dist: django-phonenumber-field==8.1.0; python_version >= "3.9"
|
|
17
19
|
Requires-Dist: django-pipeline==4.0.0; python_version >= "3.9"
|
|
18
20
|
Requires-Dist: django-two-factor-auth==1.17.0; python_version >= "3.8"
|
|
@@ -20,9 +22,10 @@ Requires-Dist: djangorestframework==3.16.0; python_version >= "3.9"
|
|
|
20
22
|
Requires-Dist: idna==3.10; python_version >= "3.6"
|
|
21
23
|
Requires-Dist: libsass==0.23.0; python_version >= "3.8"
|
|
22
24
|
Requires-Dist: more-itertools==8.7.0; python_version >= "3.5"
|
|
23
|
-
Requires-Dist: numpy==2.3.
|
|
24
|
-
Requires-Dist: pandas==2.3.
|
|
25
|
+
Requires-Dist: numpy==2.3.2; python_version >= "3.11"
|
|
26
|
+
Requires-Dist: pandas==2.3.1; python_version >= "3.9"
|
|
25
27
|
Requires-Dist: pgeocode==0.4.0; python_version >= "3.8"
|
|
28
|
+
Requires-Dist: pycparser==2.22; python_version >= "3.8"
|
|
26
29
|
Requires-Dist: pyjwt==2.6.0; python_version >= "3.7"
|
|
27
30
|
Requires-Dist: pypng==0.20220715.0
|
|
28
31
|
Requires-Dist: python-dateutil==2.9.0.post0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
@@ -33,7 +36,7 @@ Requires-Dist: setuptools==80.9.0; python_version >= "3.9"
|
|
|
33
36
|
Requires-Dist: six==1.17.0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
34
37
|
Requires-Dist: sqlparse==0.5.3; python_version >= "3.8"
|
|
35
38
|
Requires-Dist: tablib==3.7.0; python_version >= "3.9"
|
|
36
|
-
Requires-Dist: typing-extensions==4.14.
|
|
39
|
+
Requires-Dist: typing-extensions==4.14.1; python_version >= "3.9"
|
|
37
40
|
Requires-Dist: tzdata==2025.2; python_version >= "2"
|
|
38
41
|
Requires-Dist: urllib3==2.5.0; python_version >= "3.9"
|
|
39
42
|
Requires-Dist: wheel==0.45.1; python_version >= "3.8"
|
|
@@ -80,6 +80,7 @@ common/migrations/0054_delete_aimmo_models.py
|
|
|
80
80
|
common/migrations/0055_alter_schoolteacherinvitation_token.py
|
|
81
81
|
common/migrations/0056_set_non_school_teachers_as_non_admins.py
|
|
82
82
|
common/migrations/0057_teacher_teacher__is_admin.py
|
|
83
|
+
common/migrations/0058_userprofile_google_refresh_token_and_more.py
|
|
83
84
|
common/migrations/__init__.py
|
|
84
85
|
common/static/common/img/RR_logo.svg
|
|
85
86
|
common/static/common/img/brain.svg
|
|
@@ -3,6 +3,9 @@ django-csp==3.8
|
|
|
3
3
|
pypng==0.20220715.0
|
|
4
4
|
pytz==2025.2
|
|
5
5
|
|
|
6
|
+
[:platform_python_implementation != "PyPy"]
|
|
7
|
+
cffi==1.17.1
|
|
8
|
+
|
|
6
9
|
[:python_version >= "2"]
|
|
7
10
|
tzdata==2025.2
|
|
8
11
|
|
|
@@ -14,7 +17,7 @@ six==1.17.0
|
|
|
14
17
|
django==5.1.10
|
|
15
18
|
|
|
16
19
|
[:python_version >= "3.11"]
|
|
17
|
-
numpy==2.3.
|
|
20
|
+
numpy==2.3.2
|
|
18
21
|
|
|
19
22
|
[:python_version >= "3.5"]
|
|
20
23
|
more-itertools==8.7.0
|
|
@@ -23,30 +26,34 @@ more-itertools==8.7.0
|
|
|
23
26
|
idna==3.10
|
|
24
27
|
|
|
25
28
|
[:python_version >= "3.7"]
|
|
26
|
-
certifi==2025.
|
|
27
|
-
charset-normalizer==3.4.
|
|
29
|
+
certifi==2025.8.3
|
|
30
|
+
charset-normalizer==3.4.3
|
|
28
31
|
diff-match-patch==20241021
|
|
29
|
-
django-otp==1.6.
|
|
32
|
+
django-otp==1.6.1
|
|
30
33
|
pyjwt==2.6.0
|
|
31
34
|
qrcode==7.4.2
|
|
32
35
|
|
|
36
|
+
[:python_version >= "3.7" and python_full_version not in "3.9.0, 3.9.1"]
|
|
37
|
+
cryptography==44.0.1
|
|
38
|
+
|
|
33
39
|
[:python_version >= "3.8"]
|
|
34
|
-
asgiref==3.8.1
|
|
35
40
|
django-formtools==2.5.1
|
|
36
41
|
django-two-factor-auth==1.17.0
|
|
37
42
|
libsass==0.23.0
|
|
38
43
|
pgeocode==0.4.0
|
|
44
|
+
pycparser==2.22
|
|
39
45
|
requests==2.32.4
|
|
40
46
|
sqlparse==0.5.3
|
|
41
47
|
wheel==0.45.1
|
|
42
48
|
|
|
43
49
|
[:python_version >= "3.9"]
|
|
50
|
+
asgiref==3.9.1
|
|
44
51
|
django-import-export==4.2.0
|
|
45
52
|
django-phonenumber-field==8.1.0
|
|
46
53
|
django-pipeline==4.0.0
|
|
47
54
|
djangorestframework==3.16.0
|
|
48
|
-
pandas==2.3.
|
|
55
|
+
pandas==2.3.1
|
|
49
56
|
setuptools==80.9.0
|
|
50
57
|
tablib==3.7.0
|
|
51
|
-
typing-extensions==4.14.
|
|
58
|
+
typing-extensions==4.14.1
|
|
52
59
|
urllib3==2.5.0
|
|
@@ -44,6 +44,9 @@ DOTMAILER_SEND_CAMPAIGN_URL = getattr(settings, "DOTMAILER_SEND_CAMPAIGN_URL", "
|
|
|
44
44
|
# ID of the "Thanks for staying!" campaign in Dotmailer
|
|
45
45
|
DOTMAILER_THANKS_FOR_STAYING_CAMPAIGN_ID = getattr(settings, "DOTMAILER_THANKS_FOR_STAYING_CAMPAIGN_ID", "")
|
|
46
46
|
|
|
47
|
+
# Fernet encryption for OAuth2 sign in
|
|
48
|
+
ENCRYPTION_KEY = getattr(settings, "ENCRYPTION_KEY", "")
|
|
49
|
+
|
|
47
50
|
# The name of the google app engine service the application is running on, local otherwise
|
|
48
51
|
MODULE_NAME = getattr(settings, "MODULE_NAME", "local")
|
|
49
52
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated by Django 5.1.10 on 2025-08-12 12:51
|
|
2
|
+
|
|
3
|
+
import common.models
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("common", "0057_teacher_teacher__is_admin"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name="userprofile",
|
|
16
|
+
name="google_refresh_token",
|
|
17
|
+
field=common.models.EncryptedCharField(blank=True, max_length=1004, null=True),
|
|
18
|
+
),
|
|
19
|
+
migrations.AddField(
|
|
20
|
+
model_name="userprofile",
|
|
21
|
+
name="google_sub",
|
|
22
|
+
field=models.CharField(blank=True, max_length=255, null=True),
|
|
23
|
+
),
|
|
24
|
+
]
|
|
@@ -3,6 +3,8 @@ import typing as t
|
|
|
3
3
|
from datetime import timedelta
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
|
+
from cryptography.fernet import Fernet
|
|
7
|
+
from django.conf import settings
|
|
6
8
|
from django.contrib.auth.models import User
|
|
7
9
|
from django.db import models
|
|
8
10
|
from django.utils import timezone
|
|
@@ -13,6 +15,64 @@ if t.TYPE_CHECKING:
|
|
|
13
15
|
from game.models import Worksheet
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
class EncryptedCharField(models.CharField):
|
|
19
|
+
"""
|
|
20
|
+
A custom CharField that encrypts data before saving and decrypts it when
|
|
21
|
+
retrieved.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
_fernet = Fernet(settings.ENCRYPTION_KEY)
|
|
25
|
+
_prefix = "ENC:"
|
|
26
|
+
|
|
27
|
+
# pylint: disable-next=unused-argument
|
|
28
|
+
def from_db_value(self, value: t.Optional[str], expression, connection):
|
|
29
|
+
"""
|
|
30
|
+
Converts a value as returned by the database to a Python object. It is
|
|
31
|
+
the reverse of get_prep_value().
|
|
32
|
+
|
|
33
|
+
https://docs.djangoproject.com/en/5.1/howto/custom-model-fields/#converting-values-to-python-objects
|
|
34
|
+
"""
|
|
35
|
+
if isinstance(value, str):
|
|
36
|
+
return self.decrypt_value(value)
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
def to_python(self, value: t.Optional[str]):
|
|
40
|
+
"""
|
|
41
|
+
Converts the value into the correct Python object. It acts as the
|
|
42
|
+
reverse of value_to_string(), and is also called in clean().
|
|
43
|
+
|
|
44
|
+
https://docs.djangoproject.com/en/5.1/howto/custom-model-fields/#converting-values-to-python-objects
|
|
45
|
+
"""
|
|
46
|
+
if isinstance(value, str):
|
|
47
|
+
return self.decrypt_value(value)
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
def get_prep_value(self, value: t.Optional[str]):
|
|
51
|
+
"""
|
|
52
|
+
'value' is the current value of the model's attribute, and the method
|
|
53
|
+
should return data in a format that has been prepared for use as a
|
|
54
|
+
parameter in a query.
|
|
55
|
+
|
|
56
|
+
https://docs.djangoproject.com/en/5.1/howto/custom-model-fields/#converting-python-objects-to-query-values
|
|
57
|
+
"""
|
|
58
|
+
if isinstance(value, str):
|
|
59
|
+
return self.encrypt_value(value)
|
|
60
|
+
return value
|
|
61
|
+
|
|
62
|
+
def encrypt_value(self, value: str):
|
|
63
|
+
"""Encrypt the value if it's not encrypted."""
|
|
64
|
+
if not value.startswith(self._prefix):
|
|
65
|
+
return self._prefix + self._fernet.encrypt(value.encode()).decode()
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
def decrypt_value(self, value: str):
|
|
69
|
+
"""Decrpyt the value if it's encrypted.."""
|
|
70
|
+
if value.startswith(self._prefix):
|
|
71
|
+
value = value[len(self._prefix) :]
|
|
72
|
+
return self._fernet.decrypt(value).decode()
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
|
|
16
76
|
class UserProfile(models.Model):
|
|
17
77
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
|
18
78
|
|
|
@@ -32,6 +92,12 @@ class UserProfile(models.Model):
|
|
|
32
92
|
username = models.CharField(max_length=200, null=True, blank=True)
|
|
33
93
|
_username = models.BinaryField(null=True, blank=True)
|
|
34
94
|
|
|
95
|
+
# Google.
|
|
96
|
+
google_refresh_token = EncryptedCharField(
|
|
97
|
+
max_length=1000 + len(EncryptedCharField._prefix), null=True, blank=True
|
|
98
|
+
)
|
|
99
|
+
google_sub = models.CharField(max_length=255, null=True, blank=True)
|
|
100
|
+
|
|
35
101
|
def __str__(self):
|
|
36
102
|
return f"{self.user.first_name} {self.user.last_name}"
|
|
37
103
|
|
|
@@ -48,7 +114,9 @@ class SchoolModelManager(models.Manager):
|
|
|
48
114
|
|
|
49
115
|
class School(models.Model):
|
|
50
116
|
name = models.CharField(max_length=200, unique=True)
|
|
51
|
-
country = CountryField(
|
|
117
|
+
country = CountryField(
|
|
118
|
+
blank_label="(select country)", null=True, blank=True
|
|
119
|
+
)
|
|
52
120
|
# TODO: Create an Address model to house address details
|
|
53
121
|
county = models.CharField(max_length=50, blank=True, null=True)
|
|
54
122
|
creation_time = models.DateTimeField(default=timezone.now, null=True)
|
|
@@ -71,7 +139,11 @@ class School(models.Model):
|
|
|
71
139
|
|
|
72
140
|
def admins(self):
|
|
73
141
|
teachers = self.teacher_school.all()
|
|
74
|
-
return
|
|
142
|
+
return (
|
|
143
|
+
[teacher for teacher in teachers if teacher.is_admin]
|
|
144
|
+
if teachers
|
|
145
|
+
else None
|
|
146
|
+
)
|
|
75
147
|
|
|
76
148
|
def anonymise(self):
|
|
77
149
|
self.name = uuid4().hex
|
|
@@ -140,7 +212,10 @@ class Teacher(models.Model):
|
|
|
140
212
|
def teaches(self, userprofile):
|
|
141
213
|
if hasattr(userprofile, "student"):
|
|
142
214
|
student = userprofile.student
|
|
143
|
-
return
|
|
215
|
+
return (
|
|
216
|
+
not student.is_independent()
|
|
217
|
+
and student.class_field.teacher == self
|
|
218
|
+
)
|
|
144
219
|
|
|
145
220
|
def has_school(self):
|
|
146
221
|
return self.school is not (None or "")
|
|
@@ -172,10 +247,14 @@ class SchoolTeacherInvitation(models.Model):
|
|
|
172
247
|
null=True,
|
|
173
248
|
on_delete=models.SET_NULL,
|
|
174
249
|
)
|
|
175
|
-
invited_teacher_first_name = models.CharField(
|
|
250
|
+
invited_teacher_first_name = models.CharField(
|
|
251
|
+
max_length=150
|
|
252
|
+
) # Same as User model
|
|
176
253
|
# TODO: Make not nullable once data has been transferred
|
|
177
254
|
_invited_teacher_first_name = models.BinaryField(null=True, blank=True)
|
|
178
|
-
invited_teacher_last_name = models.CharField(
|
|
255
|
+
invited_teacher_last_name = models.CharField(
|
|
256
|
+
max_length=150
|
|
257
|
+
) # Same as User model
|
|
179
258
|
# TODO: Make not nullable once data has been transferred
|
|
180
259
|
_invited_teacher_last_name = models.BinaryField(null=True, blank=True)
|
|
181
260
|
# TODO: Switch to a CharField to be able to hold hashed value
|
|
@@ -228,7 +307,9 @@ class Class(models.Model):
|
|
|
228
307
|
locked_worksheets: "ManyToManyField[Worksheet]"
|
|
229
308
|
|
|
230
309
|
name = models.CharField(max_length=200)
|
|
231
|
-
teacher = models.ForeignKey(
|
|
310
|
+
teacher = models.ForeignKey(
|
|
311
|
+
Teacher, related_name="class_teacher", on_delete=models.CASCADE
|
|
312
|
+
)
|
|
232
313
|
access_code = models.CharField(max_length=5, null=True)
|
|
233
314
|
classmates_data_viewable = models.BooleanField(default=False)
|
|
234
315
|
always_accept_requests = models.BooleanField(default=False)
|
|
@@ -252,7 +333,9 @@ class Class(models.Model):
|
|
|
252
333
|
def active_game(self):
|
|
253
334
|
games = self.game_set.filter(game_class=self, is_archived=False)
|
|
254
335
|
if len(games) >= 1:
|
|
255
|
-
assert
|
|
336
|
+
assert (
|
|
337
|
+
len(games) == 1
|
|
338
|
+
) # there should NOT be more than one active game
|
|
256
339
|
return games[0]
|
|
257
340
|
return None
|
|
258
341
|
|
|
@@ -262,8 +345,13 @@ class Class(models.Model):
|
|
|
262
345
|
|
|
263
346
|
def get_requests_message(self):
|
|
264
347
|
if self.always_accept_requests:
|
|
265
|
-
external_requests_message =
|
|
266
|
-
|
|
348
|
+
external_requests_message = (
|
|
349
|
+
"This class is currently set to always accept requests."
|
|
350
|
+
)
|
|
351
|
+
elif (
|
|
352
|
+
self.accept_requests_until is not None
|
|
353
|
+
and (self.accept_requests_until - timezone.now()) >= timedelta()
|
|
354
|
+
):
|
|
267
355
|
external_requests_message = (
|
|
268
356
|
"This class is accepting external requests until "
|
|
269
357
|
+ self.accept_requests_until.strftime("%d-%m-%Y %H:%M")
|
|
@@ -271,7 +359,9 @@ class Class(models.Model):
|
|
|
271
359
|
+ timezone.get_current_timezone_name()
|
|
272
360
|
)
|
|
273
361
|
else:
|
|
274
|
-
external_requests_message =
|
|
362
|
+
external_requests_message = (
|
|
363
|
+
"This class is not currently accepting external requests."
|
|
364
|
+
)
|
|
275
365
|
|
|
276
366
|
return external_requests_message
|
|
277
367
|
|
|
@@ -293,7 +383,9 @@ class UserSession(models.Model):
|
|
|
293
383
|
login_time = models.DateTimeField(default=timezone.now)
|
|
294
384
|
school = models.ForeignKey(School, null=True, on_delete=models.SET_NULL)
|
|
295
385
|
class_field = models.ForeignKey(Class, null=True, on_delete=models.SET_NULL)
|
|
296
|
-
login_type = models.CharField(
|
|
386
|
+
login_type = models.CharField(
|
|
387
|
+
max_length=100, null=True
|
|
388
|
+
) # for student login
|
|
297
389
|
|
|
298
390
|
def __str__(self):
|
|
299
391
|
return f"{self.user} login: {self.login_time} type: {self.login_type}"
|
|
@@ -322,7 +414,9 @@ class StudentModelManager(models.Manager):
|
|
|
322
414
|
)
|
|
323
415
|
|
|
324
416
|
def independentStudentFactory(self, name, email, password):
|
|
325
|
-
user = User.objects.create_user(
|
|
417
|
+
user = User.objects.create_user(
|
|
418
|
+
username=email, email=email, password=password, first_name=name
|
|
419
|
+
)
|
|
326
420
|
|
|
327
421
|
user_profile = UserProfile.objects.create(user=user)
|
|
328
422
|
|
|
@@ -381,7 +475,9 @@ class JoinReleaseStudent(models.Model):
|
|
|
381
475
|
JOIN = "join"
|
|
382
476
|
RELEASE = "release"
|
|
383
477
|
|
|
384
|
-
student = models.ForeignKey(
|
|
478
|
+
student = models.ForeignKey(
|
|
479
|
+
Student, related_name="student", on_delete=models.CASCADE
|
|
480
|
+
)
|
|
385
481
|
# either "release" or "join"
|
|
386
482
|
action_type = models.CharField(max_length=64)
|
|
387
483
|
action_time = models.DateTimeField(default=timezone.now)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0006_update_aimmo_character_image_path.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0018_update_aimmo_character_image_path.py
RENAMED
|
File without changes
|
|
File without changes
|
{cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0020_class_is_active_and_null_access_code.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0032_dailyactivity_level_control_submits.py
RENAMED
|
File without changes
|
{cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0033_password_reset_tracking_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cfl_common-8.8.1 → cfl_common-8.8.3}/common/migrations/0055_alter_schoolteacherinvitation_token.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cfl_common-8.8.1 → cfl_common-8.8.3}/common/templates/common/onetrust_cookies_consent_notice.html
RENAMED
|
File without changes
|
|
File without changes
|
{cfl_common-8.8.1 → cfl_common-8.8.3}/common/tests/test_migration_anonymise_orphan_schools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|