codeforlife-portal 8.8.1__py2.py3-none-any.whl → 8.8.2__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.

Potentially problematic release.


This version of codeforlife-portal might be problematic. Click here for more details.

@@ -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)
@@ -1,24 +1,26 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeforlife-portal
3
- Version: 8.8.1
3
+ Version: 8.8.2
4
4
  Classifier: Programming Language :: Python
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Framework :: Django
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE.md
9
9
  Requires-Dist: amqp==5.3.1; python_version >= "3.6"
10
- Requires-Dist: asgiref==3.8.1; python_version >= "3.8"
10
+ Requires-Dist: asgiref==3.9.1; python_version >= "3.9"
11
11
  Requires-Dist: billiard==4.2.1; python_version >= "3.7"
12
12
  Requires-Dist: boto3==1.36.14; python_version >= "3.8"
13
13
  Requires-Dist: botocore==1.36.26; python_version >= "3.8"
14
14
  Requires-Dist: celery[sqs]==5.4.0; python_version >= "3.8"
15
- Requires-Dist: certifi==2025.6.15; python_version >= "3.7"
16
- Requires-Dist: cfl-common==8.8.1
17
- Requires-Dist: charset-normalizer==3.4.2; python_version >= "3.7"
15
+ Requires-Dist: certifi==2025.8.3; python_version >= "3.7"
16
+ Requires-Dist: cffi==1.17.1; platform_python_implementation != "PyPy"
17
+ Requires-Dist: cfl-common==8.8.2
18
+ Requires-Dist: charset-normalizer==3.4.3; python_version >= "3.7"
18
19
  Requires-Dist: click==8.2.1; python_version >= "3.10"
19
20
  Requires-Dist: click-didyoumean==0.3.1; python_full_version >= "3.6.2"
20
21
  Requires-Dist: click-plugins==1.1.1.2
21
22
  Requires-Dist: click-repl==0.3.0; python_version >= "3.6"
23
+ Requires-Dist: cryptography==44.0.1; python_version >= "3.7" and python_full_version not in "3.9.0, 3.9.1"
22
24
  Requires-Dist: diff-match-patch==20241021; python_version >= "3.7"
23
25
  Requires-Dist: django==5.1.10; python_version >= "3.10"
24
26
  Requires-Dist: django-classy-tags==4.1.0; python_version >= "3.8"
@@ -26,7 +28,7 @@ Requires-Dist: django-countries==7.6.1
26
28
  Requires-Dist: django-csp==3.8
27
29
  Requires-Dist: django-formtools==2.5.1; python_version >= "3.8"
28
30
  Requires-Dist: django-import-export==4.2.0; python_version >= "3.9"
29
- Requires-Dist: django-otp==1.6.0; python_version >= "3.7"
31
+ Requires-Dist: django-otp==1.6.1; python_version >= "3.7"
30
32
  Requires-Dist: django-phonenumber-field==8.1.0; python_version >= "3.9"
31
33
  Requires-Dist: django-pipeline==4.0.0; python_version >= "3.9"
32
34
  Requires-Dist: django-preventconcurrentlogins==0.8.2
@@ -44,18 +46,19 @@ Requires-Dist: jmespath==1.0.1; python_version >= "3.7"
44
46
  Requires-Dist: kombu[sqs]==5.5.4; python_version >= "3.8"
45
47
  Requires-Dist: libsass==0.23.0; python_version >= "3.8"
46
48
  Requires-Dist: more-itertools==8.7.0; python_version >= "3.5"
47
- Requires-Dist: numpy==2.3.0; python_version >= "3.11"
49
+ Requires-Dist: numpy==2.3.2; python_version >= "3.11"
48
50
  Requires-Dist: packaging==25.0; python_version >= "3.8"
49
- Requires-Dist: pandas==2.3.0; python_version >= "3.9"
51
+ Requires-Dist: pandas==2.3.1; python_version >= "3.9"
50
52
  Requires-Dist: pgeocode==0.4.0; python_version >= "3.8"
51
53
  Requires-Dist: phonenumbers==8.12.12
52
54
  Requires-Dist: pillow==11.3.0; python_version >= "3.9"
53
55
  Requires-Dist: prompt-toolkit==3.0.51; python_version >= "3.8"
54
56
  Requires-Dist: psycopg2-binary==2.9.9; python_version >= "3.7"
57
+ Requires-Dist: pycparser==2.22; python_version >= "3.8"
55
58
  Requires-Dist: pycurl==7.45.6
56
59
  Requires-Dist: pyjwt==2.6.0; python_version >= "3.7"
57
60
  Requires-Dist: pypng==0.20220715.0
58
- 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"
61
+ Requires-Dist: python-dateutil==2.9.0.post0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2"
59
62
  Requires-Dist: python-dotenv==1.0.1; python_version >= "3.8"
60
63
  Requires-Dist: pytz==2025.2
61
64
  Requires-Dist: pyyaml==6.0.2; python_version >= "3.8"
@@ -68,7 +71,7 @@ Requires-Dist: setuptools==80.9.0; python_version >= "3.9"
68
71
  Requires-Dist: six==1.17.0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
69
72
  Requires-Dist: sqlparse==0.5.3; python_version >= "3.8"
70
73
  Requires-Dist: tablib==3.7.0; python_version >= "3.9"
71
- Requires-Dist: typing-extensions==4.14.0; python_version >= "3.9"
74
+ Requires-Dist: typing-extensions==4.14.1; python_version >= "3.9"
72
75
  Requires-Dist: tzdata==2025.2; python_version >= "2"
73
76
  Requires-Dist: urllib3==2.5.0
74
77
  Requires-Dist: uvicorn==0.35.0; python_version >= "3.9"
@@ -79,62 +82,32 @@ Requires-Dist: wheel==0.45.1; python_version >= "3.8"
79
82
  Requires-Dist: whitenoise==6.9.0; python_version >= "3.9"
80
83
  Requires-Dist: zipp==3.23.0; python_version >= "3.9"
81
84
  Provides-Extra: dev
82
- Requires-Dist: asgiref==3.8.1; python_version >= "3.8" and extra == "dev"
83
- Requires-Dist: asttokens==3.0.0; python_version >= "3.8" and extra == "dev"
85
+ Requires-Dist: asgiref==3.9.1; python_version >= "3.9" and extra == "dev"
84
86
  Requires-Dist: attrs==25.3.0; python_version >= "3.8" and extra == "dev"
85
87
  Requires-Dist: black==25.1.0; python_version >= "3.9" and extra == "dev"
86
- Requires-Dist: certifi==2025.6.15; python_version >= "3.7" and extra == "dev"
87
- Requires-Dist: cfl-common==8.8.1; extra == "dev"
88
- Requires-Dist: charset-normalizer==3.4.2; python_version >= "3.7" and extra == "dev"
88
+ Requires-Dist: certifi==2025.8.3; python_version >= "3.7" and extra == "dev"
89
+ Requires-Dist: charset-normalizer==3.4.3; python_version >= "3.7" and extra == "dev"
89
90
  Requires-Dist: click==8.2.1; python_version >= "3.10" and extra == "dev"
90
- Requires-Dist: coverage[toml]==7.9.2; python_version >= "3.9" and extra == "dev"
91
- Requires-Dist: decorator==5.2.1; python_version >= "3.8" and extra == "dev"
92
- Requires-Dist: diff-match-patch==20241021; python_version >= "3.7" and extra == "dev"
91
+ Requires-Dist: coverage[toml]==7.10.3; python_version >= "3.9" and extra == "dev"
93
92
  Requires-Dist: django==5.1.10; python_version >= "3.10" and extra == "dev"
94
- Requires-Dist: django-countries==7.6.1; extra == "dev"
95
- Requires-Dist: django-csp==3.8; extra == "dev"
96
- Requires-Dist: django-formtools==2.5.1; python_version >= "3.8" and extra == "dev"
97
- Requires-Dist: django-import-export==4.2.0; python_version >= "3.9" and extra == "dev"
98
- Requires-Dist: django-otp==1.6.0; python_version >= "3.7" and extra == "dev"
99
- Requires-Dist: django-phonenumber-field==8.1.0; python_version >= "3.9" and extra == "dev"
100
- Requires-Dist: django-pipeline==4.0.0; python_version >= "3.9" and extra == "dev"
101
- Requires-Dist: django-reverse-js==0.1.8; python_version >= "3.10" and extra == "dev"
93
+ Requires-Dist: django-reverse-js==0.1.7; python_version >= "3.10" and extra == "dev"
102
94
  Requires-Dist: django-selenium-clean==1.0.1; extra == "dev"
103
95
  Requires-Dist: django-test-migrations==1.4.0; (python_version >= "3.9" and python_version < "4.0") and extra == "dev"
104
- Requires-Dist: django-two-factor-auth==1.17.0; python_version >= "3.8" and extra == "dev"
105
- Requires-Dist: djangorestframework==3.16.0; python_version >= "3.9" and extra == "dev"
106
96
  Requires-Dist: execnet==2.1.1; python_version >= "3.8" and extra == "dev"
107
- Requires-Dist: executing==2.2.0; python_version >= "3.8" and extra == "dev"
108
97
  Requires-Dist: fastdiff==0.3.0; extra == "dev"
109
98
  Requires-Dist: h11==0.16.0; python_version >= "3.8" and extra == "dev"
110
99
  Requires-Dist: idna==3.10; python_version >= "3.6" and extra == "dev"
111
100
  Requires-Dist: iniconfig==2.1.0; python_version >= "3.8" and extra == "dev"
112
- Requires-Dist: ipython==9.3.0; python_version >= "3.11" and extra == "dev"
113
- Requires-Dist: ipython-pygments-lexers==1.1.1; python_version >= "3.8" and extra == "dev"
114
101
  Requires-Dist: isort==6.0.1; python_full_version >= "3.9.0" and extra == "dev"
115
- Requires-Dist: jedi==0.19.2; python_version >= "3.6" and extra == "dev"
116
- Requires-Dist: libsass==0.23.0; python_version >= "3.8" and extra == "dev"
117
- Requires-Dist: matplotlib-inline==0.1.7; python_version >= "3.8" and extra == "dev"
118
- Requires-Dist: more-itertools==8.7.0; python_version >= "3.5" and extra == "dev"
119
102
  Requires-Dist: mypy-extensions==1.1.0; python_version >= "3.8" and extra == "dev"
120
- Requires-Dist: numpy==2.3.0; python_version >= "3.11" and extra == "dev"
121
103
  Requires-Dist: outcome==1.3.0.post0; python_version >= "3.7" and extra == "dev"
122
104
  Requires-Dist: packaging==25.0; python_version >= "3.8" and extra == "dev"
123
- Requires-Dist: pandas==2.3.0; python_version >= "3.9" and extra == "dev"
124
- Requires-Dist: parso==0.8.4; python_version >= "3.6" and extra == "dev"
125
105
  Requires-Dist: pathspec==0.12.1; python_version >= "3.8" and extra == "dev"
126
- Requires-Dist: pexpect==4.9.0; (sys_platform != "win32" and sys_platform != "emscripten") and extra == "dev"
127
- Requires-Dist: pgeocode==0.4.0; python_version >= "3.8" and extra == "dev"
128
106
  Requires-Dist: platformdirs==4.3.8; python_version >= "3.9" and extra == "dev"
129
107
  Requires-Dist: pluggy==1.6.0; python_version >= "3.9" and extra == "dev"
130
- Requires-Dist: prompt-toolkit==3.0.51; python_version >= "3.8" and extra == "dev"
131
- Requires-Dist: ptyprocess==0.7.0; extra == "dev"
132
- Requires-Dist: pure-eval==0.2.3; extra == "dev"
133
- Requires-Dist: pygments==2.19.1; python_version >= "3.8" and extra == "dev"
108
+ Requires-Dist: pygments==2.19.2; python_version >= "3.8" and extra == "dev"
134
109
  Requires-Dist: pyhamcrest==2.0.2; python_version >= "3.5" and extra == "dev"
135
- Requires-Dist: pyjwt==2.6.0; python_version >= "3.7" and extra == "dev"
136
110
  Requires-Dist: pypdf==5.1.0; python_version >= "3.8" and extra == "dev"
137
- Requires-Dist: pypng==0.20220715.0; extra == "dev"
138
111
  Requires-Dist: pysocks==1.7.1; extra == "dev"
139
112
  Requires-Dist: pytest==8.4.1; python_version >= "3.9" and extra == "dev"
140
113
  Requires-Dist: pytest-cov==6.2.1; python_version >= "3.9" and extra == "dev"
@@ -142,34 +115,23 @@ Requires-Dist: pytest-django==4.8.0; python_version >= "3.8" and extra == "dev"
142
115
  Requires-Dist: pytest-mock==3.14.1; python_version >= "3.8" and extra == "dev"
143
116
  Requires-Dist: pytest-order==1.3.0; python_version >= "3.7" and extra == "dev"
144
117
  Requires-Dist: pytest-xdist==3.8.0; python_version >= "3.9" and extra == "dev"
145
- 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") and extra == "dev"
146
- Requires-Dist: pytz==2025.2; extra == "dev"
147
118
  Requires-Dist: pyvirtualdisplay==3.0; extra == "dev"
148
- Requires-Dist: qrcode==7.4.2; python_version >= "3.7" and extra == "dev"
149
- Requires-Dist: rapid-router==7.5.16; extra == "dev"
119
+ Requires-Dist: rapid-router==7.3.6; extra == "dev"
150
120
  Requires-Dist: requests==2.32.4; python_version >= "3.8" and extra == "dev"
151
121
  Requires-Dist: responses==0.18.0; python_version >= "3.7" and extra == "dev"
152
122
  Requires-Dist: selenium==4.29.0; python_version >= "3.9" and extra == "dev"
153
- Requires-Dist: setuptools==80.9.0; python_version >= "3.9" and extra == "dev"
154
- Requires-Dist: six==1.17.0; (python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3") and extra == "dev"
155
123
  Requires-Dist: snapshottest==1.0.0a1; extra == "dev"
156
124
  Requires-Dist: sniffio==1.3.1; python_version >= "3.7" and extra == "dev"
157
125
  Requires-Dist: sortedcontainers==2.4.0; extra == "dev"
158
126
  Requires-Dist: sqlparse==0.5.3; python_version >= "3.8" and extra == "dev"
159
- Requires-Dist: stack-data==0.6.3; extra == "dev"
160
- Requires-Dist: tablib==3.7.0; python_version >= "3.9" and extra == "dev"
161
127
  Requires-Dist: termcolor==3.1.0; python_version >= "3.9" and extra == "dev"
162
- Requires-Dist: traitlets==5.14.3; python_version >= "3.8" and extra == "dev"
163
128
  Requires-Dist: trio==0.30.0; python_version >= "3.9" and extra == "dev"
164
129
  Requires-Dist: trio-websocket==0.12.2; python_version >= "3.8" and extra == "dev"
165
- Requires-Dist: typing-extensions==4.14.0; python_version >= "3.9" and extra == "dev"
166
- Requires-Dist: tzdata==2025.2; python_version >= "2" and extra == "dev"
130
+ Requires-Dist: typing-extensions==4.14.1; python_version >= "3.9" and extra == "dev"
167
131
  Requires-Dist: urllib3==2.5.0; extra == "dev"
168
132
  Requires-Dist: wasmer==1.1.0; extra == "dev"
169
133
  Requires-Dist: wasmer-compiler-cranelift==1.1.0; extra == "dev"
170
- Requires-Dist: wcwidth==0.2.13; extra == "dev"
171
134
  Requires-Dist: websocket-client==1.8.0; python_version >= "3.8" and extra == "dev"
172
- Requires-Dist: wheel==0.45.1; python_version >= "3.8" and extra == "dev"
173
135
  Requires-Dist: wsproto==1.2.0; python_full_version >= "3.7.0" and extra == "dev"
174
136
  Dynamic: classifier
175
137
  Dynamic: description
@@ -6,7 +6,7 @@ cfl_common/common/apps.py,sha256=49UXZ3bSkFKvIEOL4zM7y1sAhccQJyRtsoOg5XVd_8Y,129
6
6
  cfl_common/common/context_processors.py,sha256=X0iuX5qu9kMWa7q8osE9CJ2LgM7pPOYQFGdjm8X3rk0,236
7
7
  cfl_common/common/csp_config.py,sha256=saeg9LbRr5xw7NDJPlt6fqi8Zz0vI8Rpc4VCS6oJNe8,2976
8
8
  cfl_common/common/mail.py,sha256=pIRfUMVoJWxdv74UqToj_0_pTVTC51z6QlFVLI3QBOw,6874
9
- cfl_common/common/models.py,sha256=_uD8pIO5fGVbOD8NydJVIzpiEDAaR2hIfpOhDT6jB34,16106
9
+ cfl_common/common/models.py,sha256=JKPx3YeYTlmhuDteWBn6tfewajkK3z9ge95h8VGwh8M,18845
10
10
  cfl_common/common/permissions.py,sha256=gC6RQGZI2QDBbglx-xr_V4Hl2C2nf1V2_uPmEuoEcJo,2416
11
11
  cfl_common/common/utils.py,sha256=Nn2Npao9Uqad5Js_IdHwF-ow6wrPNpBLW4AO1LxoEBc,1727
12
12
  cfl_common/common/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -71,6 +71,7 @@ cfl_common/common/migrations/0054_delete_aimmo_models.py,sha256=fmn4mDdlHr5DhPbI
71
71
  cfl_common/common/migrations/0055_alter_schoolteacherinvitation_token.py,sha256=lzPAMaI3zU_q25RuIos_LV97NXOa3MIn_eWaEL71Y4I,403
72
72
  cfl_common/common/migrations/0056_set_non_school_teachers_as_non_admins.py,sha256=_BrC9yfSYpP-c7EGFZmz8a4Kx_SxnRhpLMxJ70RVFOY,603
73
73
  cfl_common/common/migrations/0057_teacher_teacher__is_admin.py,sha256=4pinm9T3JF3PRNLnbXI6mdFiNyxg1MnRs8lMKC4S5Bc,511
74
+ cfl_common/common/migrations/0058_userprofile_google_refresh_token_and_more.py,sha256=xHBv29aWKGkISxhQd_ao5RcZPg5UkirsVf9sqajkk_8,656
74
75
  cfl_common/common/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
76
  cfl_common/common/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
77
  cfl_common/common/tests/test_migration_anonymise_orphan_schools.py,sha256=wJRyPgRvBsXLSCFdbBi2GXjgSgDbKUTRiM31CXIvpqs,1194
@@ -87,7 +88,7 @@ cfl_common/common/tests/utils/organisation.py,sha256=vNgKFtU3VPcWRnZfh82yCS90PLA
87
88
  cfl_common/common/tests/utils/student.py,sha256=PLd980iSlxmMoB8J3C2pVjNC5xHdVxfAkJXzhv_dRhg,3814
88
89
  cfl_common/common/tests/utils/teacher.py,sha256=KQ_NAl4yQqiX_zwcULQjkovc29JPhnkLR5Nk3Ljzbpg,2661
89
90
  cfl_common/common/tests/utils/user.py,sha256=NvLzZLVP4jy5Hn1iztOYF_BTQ9WsbSmuWMEzGzhAsRU,919
90
- codeforlife_portal-8.8.1.dist-info/licenses/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
91
+ codeforlife_portal-8.8.2.dist-info/licenses/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
91
92
  deploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
93
  deploy/captcha.py,sha256=MbOBuGnbT_SOIltSjP1XMOLrfo1DldCilaVAEim0vM4,23
93
94
  deploy/views.py,sha256=7mY2zNPkaBFrlWGByJHp2zFqHIHSMc0YArjE_sFWMMU,131
@@ -108,11 +109,11 @@ deploy/static/apple-touch-icon.png,sha256=QjNQ8jDKI4BpfMMsrs2t3H0fXHa5KHV45fK_VS
108
109
  deploy/static/robots.txt,sha256=5cS4RITuQhbpNzvpk4AyDCXdlIBfmfCoBYRvCHY2VT8,24
109
110
  deploy/templates/deploy/csrf_failure.html,sha256=-pBRPn4Y7nUdYHGpTHCokT9Boi-isuwuivF8V2K1SgM,412
110
111
  example_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
111
- example_project/portal_test_settings.py,sha256=AaXwSSB2A6qu6My_7cBciQa_jONy084uqNuVmBvusjs,7668
112
- example_project/settings.py,sha256=RRSHhAgJVDn4uNZG395V6_Td7jwsSaBGNwQBZ_KJm0Y,5973
112
+ example_project/portal_test_settings.py,sha256=_6EDMwnYH2yCukgiop0sobqShbB8tivO9hcjZUYReec,7909
113
+ example_project/settings.py,sha256=IsicdosIyB_3K2sv60bQvhAm6qm5fYWYut7oWTDZ778,6214
113
114
  example_project/urls.py,sha256=FUTzHPlUS1O5kqMHjL5V4L552N2ln7uTDXcw9wjKUto,422
114
115
  example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
115
- portal/__init__.py,sha256=CYHRW7OT0rtrYP93UfZNuT4545JeoLfz4o_ztXhQMCw,22
116
+ portal/__init__.py,sha256=zhwbOyu2u3qgNJQq355YyLGTwwFJxFmr9I6gkQzux6g,22
116
117
  portal/admin.py,sha256=RKJizTF6dPJKmGPZw7nZUM0X8jkiTjgyKhLQxtvHJ0I,6148
117
118
  portal/app_settings.py,sha256=8P3r0ArkOzL_iVjoaLIHsNWG4uP1tT2z1kCUHPhAZ9s,714
118
119
  portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
@@ -635,7 +636,7 @@ portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
635
636
  portal/views/two_factor/core.py,sha256=Lk32z2SN2Pg0rRkK-N-LXMvXC1kKKsH3l692kiSDQ4E,964
636
637
  portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
637
638
  portal/views/two_factor/profile.py,sha256=SHSg_xHccE5PtD-OfuOkYhREYz_er4bj5ro1RjJ88Yw,393
638
- codeforlife_portal-8.8.1.dist-info/METADATA,sha256=VyRaxvyZFc7PDneYrstzeSWkOb7dBZmd6SYIFYE45v0,13661
639
- codeforlife_portal-8.8.1.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
640
- codeforlife_portal-8.8.1.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
641
- codeforlife_portal-8.8.1.dist-info/RECORD,,
639
+ codeforlife_portal-8.8.2.dist-info/METADATA,sha256=TPz2_m3zW1FeSQMQOVbFLPvYztek0tMJGgxgI7xtKZc,10801
640
+ codeforlife_portal-8.8.2.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
641
+ codeforlife_portal-8.8.2.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
642
+ codeforlife_portal-8.8.2.dist-info/RECORD,,
@@ -229,3 +229,11 @@ AUTOCONFIG_INDEX_VIEW = "home"
229
229
  SITE_ID = 1
230
230
 
231
231
  from common.csp_config import *
232
+
233
+ if MODULE_NAME == "local":
234
+ # NOTE: This is only used locally for testing purposes.
235
+ os.environ.setdefault(
236
+ "ENCRYPTION_KEY", "XTgWqMlZCMI_E5BvCArkif9nrJIIhe_6Ic6Q_UcWJDk="
237
+ )
238
+
239
+ ENCRYPTION_KEY = os.environ["ENCRYPTION_KEY"]
@@ -183,3 +183,11 @@ AUTOCONFIG_INDEX_VIEW = "home"
183
183
  SITE_ID = 1
184
184
 
185
185
  from common.csp_config import *
186
+
187
+ if MODULE_NAME == "local":
188
+ # NOTE: This is only used locally for testing purposes.
189
+ os.environ.setdefault(
190
+ "ENCRYPTION_KEY", "XTgWqMlZCMI_E5BvCArkif9nrJIIhe_6Ic6Q_UcWJDk="
191
+ )
192
+
193
+ ENCRYPTION_KEY = os.environ["ENCRYPTION_KEY"]
portal/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "8.8.1"
1
+ __version__ = "8.8.2"