codeforlife-portal 8.8.0__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.
- cfl_common/common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
- cfl_common/common/models.py +109 -13
- {codeforlife_portal-8.8.0.dist-info → codeforlife_portal-8.8.2.dist-info}/METADATA +21 -59
- {codeforlife_portal-8.8.0.dist-info → codeforlife_portal-8.8.2.dist-info}/RECORD +11 -10
- deploy/middleware/tmp_basic_auth.py +17 -21
- example_project/portal_test_settings.py +8 -0
- example_project/settings.py +8 -0
- portal/__init__.py +1 -1
- {codeforlife_portal-8.8.0.dist-info → codeforlife_portal-8.8.2.dist-info}/WHEEL +0 -0
- {codeforlife_portal-8.8.0.dist-info → codeforlife_portal-8.8.2.dist-info}/licenses/LICENSE.md +0 -0
- {codeforlife_portal-8.8.0.dist-info → codeforlife_portal-8.8.2.dist-info}/top_level.txt +0 -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
|
+
]
|
cfl_common/common/models.py
CHANGED
|
@@ -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)
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeforlife-portal
|
|
3
|
-
Version: 8.8.
|
|
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.
|
|
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.
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist:
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
87
|
-
Requires-Dist:
|
|
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.
|
|
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-
|
|
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:
|
|
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:
|
|
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.
|
|
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=
|
|
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.
|
|
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
|
|
@@ -99,7 +100,7 @@ deploy/middleware/maintenance.py,sha256=JhlQD7FowzxnRxk1_uGXYRQ2Aas6v3C9BRxYgN1H
|
|
|
99
100
|
deploy/middleware/screentime_warning.py,sha256=-YmaOn9_RQwTU8C6VxDLWct_0G3Y5oHjAv9AvKZSvIw,1152
|
|
100
101
|
deploy/middleware/security.py,sha256=LOZ-OUFo2dVhbRzI_KdSCMh4TItcJhhqzZg5zNAH-6Q,870
|
|
101
102
|
deploy/middleware/session_timeout.py,sha256=Vfl6_9d8KwPsWRvCIcpBm7kWjgvHC8I7exJOejbyI4k,1023
|
|
102
|
-
deploy/middleware/tmp_basic_auth.py,sha256=
|
|
103
|
+
deploy/middleware/tmp_basic_auth.py,sha256=FmPw8XVovt7iS7iLxZAWSejWLHzzmjmA-SC8IESohEM,1435
|
|
103
104
|
deploy/static/apple-touch-icon-120x120.png,sha256=7GwelKO6CNbvjJJ1nqMvDGkPoQWqYomMKKP5EB5gmfM,24583
|
|
104
105
|
deploy/static/apple-touch-icon-152x152.png,sha256=V57OGP0VeeKHen_iYVa72sHe_rEaMAJzU1FAiHWWBwY,36276
|
|
105
106
|
deploy/static/apple-touch-icon-180x180.png,sha256=OIm3wwx0aL8fh1EIlMCKp2vVNpsuY-NDxV1QR73bf1g,47948
|
|
@@ -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=
|
|
112
|
-
example_project/settings.py,sha256=
|
|
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=
|
|
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.
|
|
639
|
-
codeforlife_portal-8.8.
|
|
640
|
-
codeforlife_portal-8.8.
|
|
641
|
-
codeforlife_portal-8.8.
|
|
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,,
|
|
@@ -6,6 +6,7 @@ from portal.app_settings import TMP_AUTH_TOKEN
|
|
|
6
6
|
class TempBasicAuthMiddleware:
|
|
7
7
|
"""
|
|
8
8
|
Middleware used as a basic auth for the new OTP app until we switch to the main domain.
|
|
9
|
+
Only meant for use in OTP, so include this in MIDDLEWARES only in OTP settings, not GCP.
|
|
9
10
|
Remove this middleware once the switch is done.
|
|
10
11
|
"""
|
|
11
12
|
|
|
@@ -18,24 +19,19 @@ class TempBasicAuthMiddleware:
|
|
|
18
19
|
url = request.build_absolute_uri()
|
|
19
20
|
temp_domain = "https://www.code4life.education"
|
|
20
21
|
|
|
21
|
-
#
|
|
22
|
-
if url
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return self.get_response(request)
|
|
38
|
-
|
|
39
|
-
return redirect("https://www.codeforlife.education")
|
|
40
|
-
|
|
41
|
-
return self.get_response(request)
|
|
22
|
+
# If the user inputs the token in the URL correctly, set a cookie to the token's value
|
|
23
|
+
if url == f"{temp_domain}/{TMP_AUTH_TOKEN}":
|
|
24
|
+
response = redirect(temp_domain)
|
|
25
|
+
response.set_cookie(
|
|
26
|
+
key="TMP_AUTH_TOKEN",
|
|
27
|
+
value=TMP_AUTH_TOKEN,
|
|
28
|
+
)
|
|
29
|
+
return response
|
|
30
|
+
# For any other request in the code4life domain, check that a cookie with the token's value exists.
|
|
31
|
+
# Redirect to the main site if it doesn't.
|
|
32
|
+
else:
|
|
33
|
+
value = request.COOKIES.get("TMP_AUTH_TOKEN")
|
|
34
|
+
if value is not None and value == TMP_AUTH_TOKEN:
|
|
35
|
+
return self.get_response(request)
|
|
36
|
+
|
|
37
|
+
return redirect("https://www.codeforlife.education")
|
|
@@ -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"]
|
example_project/settings.py
CHANGED
|
@@ -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
|
+
__version__ = "8.8.2"
|
|
File without changes
|
{codeforlife_portal-8.8.0.dist-info → codeforlife_portal-8.8.2.dist-info}/licenses/LICENSE.md
RENAMED
|
File without changes
|
|
File without changes
|