cfl-common 8.8.0__tar.gz → 8.8.2__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.
Files changed (105) hide show
  1. {cfl_common-8.8.0 → cfl_common-8.8.2}/PKG-INFO +11 -8
  2. {cfl_common-8.8.0 → cfl_common-8.8.2}/cfl_common.egg-info/PKG-INFO +11 -8
  3. {cfl_common-8.8.0 → cfl_common-8.8.2}/cfl_common.egg-info/SOURCES.txt +1 -0
  4. {cfl_common-8.8.0 → cfl_common-8.8.2}/cfl_common.egg-info/requires.txt +14 -7
  5. cfl_common-8.8.2/common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
  6. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/models.py +109 -13
  7. {cfl_common-8.8.0 → cfl_common-8.8.2}/MANIFEST.in +0 -0
  8. {cfl_common-8.8.0 → cfl_common-8.8.2}/cfl_common.egg-info/dependency_links.txt +0 -0
  9. {cfl_common-8.8.0 → cfl_common-8.8.2}/cfl_common.egg-info/top_level.txt +0 -0
  10. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/__init__.py +0 -0
  11. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/app_settings.py +0 -0
  12. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/apps.py +0 -0
  13. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/context_processors.py +0 -0
  14. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/csp_config.py +0 -0
  15. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/fixtures/aimmo_characters.json +0 -0
  16. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/fixtures/aimmo_characters2.json +0 -0
  17. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/fixtures/aimmo_characters3.json +0 -0
  18. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/helpers/__init__.py +0 -0
  19. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/helpers/data_migration_loader.py +0 -0
  20. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/helpers/emails.py +0 -0
  21. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/helpers/generators.py +0 -0
  22. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/helpers/organisation.py +0 -0
  23. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/mail.py +0 -0
  24. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0001_initial.py +0 -0
  25. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0002_emailverification.py +0 -0
  26. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0003_aimmocharacter.py +0 -0
  27. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0004_add_aimmocharacters.py +0 -0
  28. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0005_add_worksheets.py +0 -0
  29. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0006_update_aimmo_character_image_path.py +0 -0
  30. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +0 -0
  31. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0008_unlock_worksheet_3.py +0 -0
  32. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0009_add_blocked_time_to_teacher_and_student.py +0 -0
  33. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0010_remove_teacher_title.py +0 -0
  34. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0011_student_login_id.py +0 -0
  35. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0012_usersession.py +0 -0
  36. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0013_class_school.py +0 -0
  37. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0014_login_type.py +0 -0
  38. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0015_dailyactivity.py +0 -0
  39. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0016_joinreleasestudent.py +0 -0
  40. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0017_copy_email_to_username.py +0 -0
  41. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0018_update_aimmo_character_image_path.py +0 -0
  42. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0019_aimmocharacter_alt.py +0 -0
  43. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0020_class_is_active_and_null_access_code.py +0 -0
  44. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0021_school_is_active.py +0 -0
  45. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0022_school_cleanup.py +0 -0
  46. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0023_userprofile_aimmo_badges.py +0 -0
  47. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0024_teacher_invited_by.py +0 -0
  48. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0025_schoolteacherinvitation.py +0 -0
  49. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0026_teacher_remove_join_request.py +0 -0
  50. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0027_class_created_by.py +0 -0
  51. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0028_coding_club_downloads.py +0 -0
  52. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0029_dynamicelement.py +0 -0
  53. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0030_add_maintenance_banner.py +0 -0
  54. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0031_improve_admin_panel.py +0 -0
  55. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0032_dailyactivity_level_control_submits.py +0 -0
  56. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0033_password_reset_tracking_fields.py +0 -0
  57. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +0 -0
  58. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0035_rename_lockout_fields.py +0 -0
  59. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +0 -0
  60. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0037_migrate_email_verification.py +0 -0
  61. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0038_delete_emailverification.py +0 -0
  62. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0039_copy_email_to_username.py +0 -0
  63. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0040_school_county.py +0 -0
  64. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0041_populate_gb_counties.py +0 -0
  65. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0042_totalactivity.py +0 -0
  66. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0043_add_total_activity.py +0 -0
  67. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0044_update_activity_models.py +0 -0
  68. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0045_otp.py +0 -0
  69. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0046_alter_school_country.py +0 -0
  70. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0047_delete_school_postcode.py +0 -0
  71. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0048_unique_school_names.py +0 -0
  72. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0049_anonymise_orphan_users.py +0 -0
  73. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0050_anonymise_orphan_schools.py +0 -0
  74. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0051_verify_returning_users.py +0 -0
  75. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0052_add_cse_fields.py +0 -0
  76. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0053_clean_class_data.py +0 -0
  77. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0054_delete_aimmo_models.py +0 -0
  78. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0055_alter_schoolteacherinvitation_token.py +0 -0
  79. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0056_set_non_school_teachers_as_non_admins.py +0 -0
  80. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/0057_teacher_teacher__is_admin.py +0 -0
  81. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/migrations/__init__.py +0 -0
  82. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/permissions.py +0 -0
  83. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/static/common/img/RR_logo.svg +0 -0
  84. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/static/common/img/brain.svg +0 -0
  85. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/templates/common/freshdesk_widget.html +0 -0
  86. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/templates/common/onetrust_cookies_consent_notice.html +0 -0
  87. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/__init__.py +0 -0
  88. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/test_migration_anonymise_orphan_schools.py +0 -0
  89. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/test_migration_anonymise_orphan_users.py +0 -0
  90. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/test_migration_blocked_time.py +0 -0
  91. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/test_migration_remove_teacher_title.py +0 -0
  92. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/test_migration_unique_school_names.py +0 -0
  93. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/test_migration_verify_returning_users.py +0 -0
  94. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/test_models.py +0 -0
  95. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/utils/__init__.py +0 -0
  96. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/utils/classes.py +0 -0
  97. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/utils/email.py +0 -0
  98. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/utils/organisation.py +0 -0
  99. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/utils/student.py +0 -0
  100. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/utils/teacher.py +0 -0
  101. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/tests/utils/user.py +0 -0
  102. {cfl_common-8.8.0 → cfl_common-8.8.2}/common/utils.py +0 -0
  103. {cfl_common-8.8.0 → cfl_common-8.8.2}/pyproject.toml +0 -0
  104. {cfl_common-8.8.0 → cfl_common-8.8.2}/setup.cfg +0 -0
  105. {cfl_common-8.8.0 → cfl_common-8.8.2}/setup.py +0 -0
@@ -1,18 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cfl-common
3
- Version: 8.8.0
3
+ Version: 8.8.2
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Operating System :: OS Independent
6
- Requires-Dist: asgiref==3.8.1; python_version >= "3.8"
7
- Requires-Dist: certifi==2025.6.15; python_version >= "3.7"
8
- Requires-Dist: charset-normalizer==3.4.2; python_version >= "3.7"
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.0; python_version >= "3.7"
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.0; python_version >= "3.11"
24
- Requires-Dist: pandas==2.3.0; python_version >= "3.9"
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.0; python_version >= "3.9"
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.0
3
+ Version: 8.8.2
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Operating System :: OS Independent
6
- Requires-Dist: asgiref==3.8.1; python_version >= "3.8"
7
- Requires-Dist: certifi==2025.6.15; python_version >= "3.7"
8
- Requires-Dist: charset-normalizer==3.4.2; python_version >= "3.7"
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.0; python_version >= "3.7"
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.0; python_version >= "3.11"
24
- Requires-Dist: pandas==2.3.0; python_version >= "3.9"
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.0; python_version >= "3.9"
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.0
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.6.15
27
- charset-normalizer==3.4.2
29
+ certifi==2025.8.3
30
+ charset-normalizer==3.4.3
28
31
  diff-match-patch==20241021
29
- django-otp==1.6.0
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.0
55
+ pandas==2.3.1
49
56
  setuptools==80.9.0
50
57
  tablib==3.7.0
51
- typing-extensions==4.14.0
58
+ typing-extensions==4.14.1
52
59
  urllib3==2.5.0
@@ -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(blank_label="(select country)", null=True, blank=True)
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 [teacher for teacher in teachers if teacher.is_admin] if teachers else None
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 not student.is_independent() and student.class_field.teacher == self
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(max_length=150) # Same as User model
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(max_length=150) # Same as User model
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(Teacher, related_name="class_teacher", on_delete=models.CASCADE)
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 len(games) == 1 # there should NOT be more than one active game
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 = "This class is currently set to always accept requests."
266
- elif self.accept_requests_until is not None and (self.accept_requests_until - timezone.now()) >= timedelta():
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 = "This class is not currently accepting external requests."
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(max_length=100, null=True) # for student login
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(username=email, email=email, password=password, first_name=name)
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(Student, related_name="student", on_delete=models.CASCADE)
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