codeforlife-portal 7.2.1__py2.py3-none-any.whl → 7.3.0__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.
- {codeforlife_portal-7.2.1.dist-info → codeforlife_portal-7.3.0.dist-info}/METADATA +2 -2
- {codeforlife_portal-7.2.1.dist-info → codeforlife_portal-7.3.0.dist-info}/RECORD +10 -10
- example_project/settings.py +0 -1
- portal/__init__.py +1 -1
- portal/forms/teach.py +206 -56
- portal/templates/portal/teach/teacher_edit_class.html +57 -1
- portal/views/teacher/teach.py +294 -83
- {codeforlife_portal-7.2.1.dist-info → codeforlife_portal-7.3.0.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-7.2.1.dist-info → codeforlife_portal-7.3.0.dist-info}/WHEEL +0 -0
- {codeforlife_portal-7.2.1.dist-info → codeforlife_portal-7.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: codeforlife-portal
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.3.0
|
|
4
4
|
Classifier: Programming Language :: Python
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.12
|
|
6
6
|
Classifier: Framework :: Django
|
|
@@ -21,7 +21,7 @@ Requires-Dist: django-classy-tags==2.0.0
|
|
|
21
21
|
Requires-Dist: libsass==0.23.0
|
|
22
22
|
Requires-Dist: phonenumbers==8.12.12
|
|
23
23
|
Requires-Dist: more-itertools==8.7.0
|
|
24
|
-
Requires-Dist: cfl-common==7.
|
|
24
|
+
Requires-Dist: cfl-common==7.3.0
|
|
25
25
|
Requires-Dist: django-ratelimit==3.0.1
|
|
26
26
|
Requires-Dist: django-preventconcurrentlogins==0.8.2
|
|
27
27
|
Requires-Dist: django-csp==3.7
|
|
@@ -104,10 +104,10 @@ deploy/templates/deploy/csrf_failure.html,sha256=-pBRPn4Y7nUdYHGpTHCokT9Boi-isuw
|
|
|
104
104
|
example_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
105
105
|
example_project/manage.py,sha256=EUgybZlZ7xk2Zf2KCwBbK_z7gf7Ifqs0_bl4Kijhdgo,242
|
|
106
106
|
example_project/portal_test_settings.py,sha256=NfLY72mt1LR2c0_kxF-Yg5pCm2vQ52eceBMn573wW0Y,7247
|
|
107
|
-
example_project/settings.py,sha256=
|
|
107
|
+
example_project/settings.py,sha256=Smpr5FHAaQQhXPgh6-Zsy-jnH5_ZV9htPTWJWlGqptI,5575
|
|
108
108
|
example_project/urls.py,sha256=3SsP5jvPAXV5xmduka4zbSZB5PbXggjsalu1ojY5KIo,434
|
|
109
109
|
example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
|
|
110
|
-
portal/__init__.py,sha256=
|
|
110
|
+
portal/__init__.py,sha256=sdpc-9h8PmmRVaqNA9R2vvMOVXFYIKW9zf9YS8np6ig,22
|
|
111
111
|
portal/admin.py,sha256=on1-zNRnZvf2cwBN6GVRVYRhkaksrCgfzX8XPWtkvz8,6062
|
|
112
112
|
portal/app_settings.py,sha256=DhWLQOwM0zVOXE3O5TNKbMM9K6agfLuCsHOdr1J7xEI,651
|
|
113
113
|
portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
|
|
@@ -125,7 +125,7 @@ portal/forms/invite_teacher.py,sha256=jkDNcCfkts4_lXRzhcI3xBam21Zn2yX9wMpMVhDtW1
|
|
|
125
125
|
portal/forms/organisation.py,sha256=QcQyd7AiqBmvt4y8uQSQylguUbKOKqo2pjqWIkpWjDg,7433
|
|
126
126
|
portal/forms/play.py,sha256=z9P5LzyS3jjYcnfco84d2x8ptgLxRmh94Dnj05plmbY,11505
|
|
127
127
|
portal/forms/registration.py,sha256=gWcY7rllhWO3c9as6QHUDWZx1Jme7DqtGHYaKcvxe-U,5990
|
|
128
|
-
portal/forms/teach.py,sha256=
|
|
128
|
+
portal/forms/teach.py,sha256=lFGM9gk1gC_N_qJhzV2VdAVArCiAYfhOmrxvPkYC-co,22446
|
|
129
129
|
portal/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
130
130
|
portal/helpers/captcha.py,sha256=Amwg3HZ9eIh1LGYVYWYruk1ccNj6P3nYP_evufOY8BY,254
|
|
131
131
|
portal/helpers/decorators.py,sha256=AnHbcRg42cUYkpMnJMImEMueSKHZRBZ0o67B7_Wp_Zs,3695
|
|
@@ -497,7 +497,7 @@ portal/templates/portal/teach/onboarding_students.html,sha256=09q658n7U7Vuzy_M_0
|
|
|
497
497
|
portal/templates/portal/teach/teacher_add_external_student.html,sha256=Lrrh4W3EwxxXKe45kcqzf2BcPxYo_mLahGIQMNJjX9E,2453
|
|
498
498
|
portal/templates/portal/teach/teacher_added_external_student.html,sha256=f18RhP5PCE13yjBTrQTCwPLZnkcZb0bDUZ_Z2Oh3D3I,1189
|
|
499
499
|
portal/templates/portal/teach/teacher_dismiss_students.html,sha256=_5FvvtNjdYpZaM9lDzXrhhTjAD3NK_92kDYzh7xqowI,4283
|
|
500
|
-
portal/templates/portal/teach/teacher_edit_class.html,sha256=
|
|
500
|
+
portal/templates/portal/teach/teacher_edit_class.html,sha256=TcACMixI-kd0OyUThS5EJ81O_E8j8SOTciccjfpIx5I,12254
|
|
501
501
|
portal/templates/portal/teach/teacher_edit_student.html,sha256=JNF8JyoZh0J-oKH4dz7SGSxR0t7J1lbTGLvjhNbk2Rw,3237
|
|
502
502
|
portal/templates/portal/teach/teacher_move_all_classes.html,sha256=u9PNJHrKaIXd67vuwhgMmVXumgSnkKH4QKx1bd3O2VY,1780
|
|
503
503
|
portal/templates/portal/teach/teacher_move_students.html,sha256=toBtjtpiCwvxo2FTJX5oZDzJ9batXdi7ty5AiCHnq4s,1591
|
|
@@ -626,13 +626,13 @@ portal/views/student/edit_account_details.py,sha256=Ba-3D_zzKbX5N01NG5qqBS0ud10B
|
|
|
626
626
|
portal/views/student/play.py,sha256=GMxk65bxWOe1Ds2kb6rvuOd1GoAtt5v_9AihLNXoUL0,8768
|
|
627
627
|
portal/views/teacher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
628
628
|
portal/views/teacher/dashboard.py,sha256=8WglspwuHF__2LtoX5_XvoW1ulICSupjKv--MtjrvJk,25714
|
|
629
|
-
portal/views/teacher/teach.py,sha256=
|
|
629
|
+
portal/views/teacher/teach.py,sha256=DFQSiC3RokLMArR_jTinFOqdymMa9phUOmwK0xBoDdk,37039
|
|
630
630
|
portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
631
631
|
portal/views/two_factor/core.py,sha256=O_wcBeFqdPYSGNGv-pT_vbs5-Dj1Z-Jfkd6f9-E5yZI,760
|
|
632
632
|
portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
|
|
633
633
|
portal/views/two_factor/profile.py,sha256=tkl_ludo8arMtd5LKNmohM66vpC_YQiP-0nspTSJiJ4,383
|
|
634
|
-
codeforlife_portal-7.
|
|
635
|
-
codeforlife_portal-7.
|
|
636
|
-
codeforlife_portal-7.
|
|
637
|
-
codeforlife_portal-7.
|
|
638
|
-
codeforlife_portal-7.
|
|
634
|
+
codeforlife_portal-7.3.0.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
|
|
635
|
+
codeforlife_portal-7.3.0.dist-info/METADATA,sha256=U5wTnx79O8rTDWdJdjO8wTR9NRIHUF9rZLuHs0WiKrc,3317
|
|
636
|
+
codeforlife_portal-7.3.0.dist-info/WHEEL,sha256=fS9sRbCBHs7VFcwJLnLXN1MZRR0_TVTxvXKzOnaSFs8,110
|
|
637
|
+
codeforlife_portal-7.3.0.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
|
|
638
|
+
codeforlife_portal-7.3.0.dist-info/RECORD,,
|
example_project/settings.py
CHANGED
portal/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "7.
|
|
1
|
+
__version__ = "7.3.0"
|
portal/forms/teach.py
CHANGED
|
@@ -20,25 +20,37 @@ from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
|
20
20
|
class InvitedTeacherForm(forms.Form):
|
|
21
21
|
teacher_password = forms.CharField(
|
|
22
22
|
help_text="Enter a password",
|
|
23
|
-
widget=forms.PasswordInput(
|
|
23
|
+
widget=forms.PasswordInput(
|
|
24
|
+
attrs={"autocomplete": "off", "placeholder": "Password"}
|
|
25
|
+
),
|
|
24
26
|
)
|
|
25
27
|
teacher_confirm_password = forms.CharField(
|
|
26
28
|
help_text="Repeat password",
|
|
27
|
-
widget=forms.PasswordInput(
|
|
29
|
+
widget=forms.PasswordInput(
|
|
30
|
+
attrs={"autocomplete": "off", "placeholder": "Repeat password"}
|
|
31
|
+
),
|
|
28
32
|
)
|
|
29
33
|
|
|
30
|
-
consent_ticked = forms.BooleanField(
|
|
31
|
-
|
|
34
|
+
consent_ticked = forms.BooleanField(
|
|
35
|
+
widget=forms.CheckboxInput(), initial=False, required=True
|
|
36
|
+
)
|
|
37
|
+
newsletter_ticked = forms.BooleanField(
|
|
38
|
+
widget=forms.CheckboxInput(), initial=False, required=False
|
|
39
|
+
)
|
|
32
40
|
|
|
33
41
|
def clean_teacher_password(self):
|
|
34
|
-
return form_clean_password(
|
|
42
|
+
return form_clean_password(
|
|
43
|
+
self, "teacher_password", PasswordStrength.TEACHER
|
|
44
|
+
)
|
|
35
45
|
|
|
36
46
|
def clean(self):
|
|
37
47
|
if any(self.errors):
|
|
38
48
|
return
|
|
39
49
|
|
|
40
50
|
password = self.cleaned_data.get("teacher_password", None)
|
|
41
|
-
confirm_password = self.cleaned_data.get(
|
|
51
|
+
confirm_password = self.cleaned_data.get(
|
|
52
|
+
"teacher_confirm_password", None
|
|
53
|
+
)
|
|
42
54
|
|
|
43
55
|
check_passwords(password, confirm_password)
|
|
44
56
|
|
|
@@ -49,16 +61,22 @@ class TeacherSignupForm(InvitedTeacherForm):
|
|
|
49
61
|
teacher_first_name = forms.CharField(
|
|
50
62
|
help_text="Enter your first name",
|
|
51
63
|
max_length=100,
|
|
52
|
-
widget=forms.TextInput(
|
|
64
|
+
widget=forms.TextInput(
|
|
65
|
+
attrs={"autocomplete": "off", "placeholder": "First name"}
|
|
66
|
+
),
|
|
53
67
|
)
|
|
54
68
|
teacher_last_name = forms.CharField(
|
|
55
69
|
help_text="Enter your last name",
|
|
56
70
|
max_length=100,
|
|
57
|
-
widget=forms.TextInput(
|
|
71
|
+
widget=forms.TextInput(
|
|
72
|
+
attrs={"autocomplete": "off", "placeholder": "Last name"}
|
|
73
|
+
),
|
|
58
74
|
)
|
|
59
75
|
teacher_email = forms.EmailField(
|
|
60
76
|
help_text="Enter your email address",
|
|
61
|
-
widget=forms.EmailInput(
|
|
77
|
+
widget=forms.EmailInput(
|
|
78
|
+
attrs={"autocomplete": "off", "placeholder": "Email address"}
|
|
79
|
+
),
|
|
62
80
|
)
|
|
63
81
|
|
|
64
82
|
captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
|
|
@@ -67,31 +85,42 @@ class TeacherSignupForm(InvitedTeacherForm):
|
|
|
67
85
|
class TeacherEditAccountForm(forms.Form):
|
|
68
86
|
first_name = forms.CharField(
|
|
69
87
|
max_length=100,
|
|
70
|
-
widget=forms.TextInput(
|
|
88
|
+
widget=forms.TextInput(
|
|
89
|
+
attrs={"placeholder": "First name", "class": "fName"}
|
|
90
|
+
),
|
|
71
91
|
help_text="First name",
|
|
72
92
|
)
|
|
73
93
|
last_name = forms.CharField(
|
|
74
94
|
max_length=100,
|
|
75
|
-
widget=forms.TextInput(
|
|
95
|
+
widget=forms.TextInput(
|
|
96
|
+
attrs={"placeholder": "Last name", "class": "lName"}
|
|
97
|
+
),
|
|
76
98
|
help_text="Last name",
|
|
77
99
|
)
|
|
78
100
|
email = forms.EmailField(
|
|
79
101
|
required=False,
|
|
80
|
-
widget=forms.EmailInput(
|
|
102
|
+
widget=forms.EmailInput(
|
|
103
|
+
attrs={"placeholder": "New email address (optional)"}
|
|
104
|
+
),
|
|
81
105
|
help_text="New email address (optional)",
|
|
82
106
|
)
|
|
83
107
|
password = forms.CharField(
|
|
84
108
|
required=False,
|
|
85
|
-
widget=forms.PasswordInput(
|
|
109
|
+
widget=forms.PasswordInput(
|
|
110
|
+
attrs={"placeholder": "New password (optional)"}
|
|
111
|
+
),
|
|
86
112
|
help_text="New password (optional)",
|
|
87
113
|
)
|
|
88
114
|
confirm_password = forms.CharField(
|
|
89
115
|
required=False,
|
|
90
|
-
widget=forms.PasswordInput(
|
|
116
|
+
widget=forms.PasswordInput(
|
|
117
|
+
attrs={"placeholder": "Confirm new password (optional)"}
|
|
118
|
+
),
|
|
91
119
|
help_text="Confirm new password (optional)",
|
|
92
120
|
)
|
|
93
121
|
current_password = forms.CharField(
|
|
94
|
-
widget=forms.PasswordInput(attrs={"placeholder": "Current password"}),
|
|
122
|
+
widget=forms.PasswordInput(attrs={"placeholder": "Current password"}),
|
|
123
|
+
help_text="Enter your current password",
|
|
95
124
|
)
|
|
96
125
|
|
|
97
126
|
def __init__(self, user, *args, **kwargs):
|
|
@@ -113,7 +142,9 @@ class TeacherEditAccountForm(forms.Form):
|
|
|
113
142
|
|
|
114
143
|
return self.cleaned_data
|
|
115
144
|
|
|
116
|
-
def check_password_errors(
|
|
145
|
+
def check_password_errors(
|
|
146
|
+
self, password, confirm_password, current_password
|
|
147
|
+
):
|
|
117
148
|
check_passwords(password, confirm_password)
|
|
118
149
|
|
|
119
150
|
if not self.user.check_password(current_password):
|
|
@@ -122,11 +153,15 @@ class TeacherEditAccountForm(forms.Form):
|
|
|
122
153
|
|
|
123
154
|
class TeacherLoginForm(AuthenticationForm):
|
|
124
155
|
username = forms.EmailField(
|
|
125
|
-
widget=forms.EmailInput(
|
|
156
|
+
widget=forms.EmailInput(
|
|
157
|
+
attrs={"autocomplete": "off", "placeholder": "Email address"}
|
|
158
|
+
),
|
|
126
159
|
help_text="Enter your email address",
|
|
127
160
|
)
|
|
128
161
|
password = forms.CharField(
|
|
129
|
-
widget=forms.PasswordInput(
|
|
162
|
+
widget=forms.PasswordInput(
|
|
163
|
+
attrs={"autocomplete": "off", "placeholder": "Password"}
|
|
164
|
+
),
|
|
130
165
|
help_text="Enter your password",
|
|
131
166
|
)
|
|
132
167
|
|
|
@@ -160,7 +195,9 @@ class TeacherLoginForm(AuthenticationForm):
|
|
|
160
195
|
users = User.objects.filter(email=email)
|
|
161
196
|
|
|
162
197
|
for result in users:
|
|
163
|
-
if hasattr(result, "userprofile") and hasattr(
|
|
198
|
+
if hasattr(result, "userprofile") and hasattr(
|
|
199
|
+
result.userprofile, "teacher"
|
|
200
|
+
):
|
|
164
201
|
user = result
|
|
165
202
|
break
|
|
166
203
|
|
|
@@ -186,7 +223,8 @@ class TeacherLoginForm(AuthenticationForm):
|
|
|
186
223
|
|
|
187
224
|
class ClassCreationForm(forms.Form):
|
|
188
225
|
class_name = forms.CharField(
|
|
189
|
-
widget=forms.TextInput(attrs={"placeholder": "Class name"}),
|
|
226
|
+
widget=forms.TextInput(attrs={"placeholder": "Class name"}),
|
|
227
|
+
help_text="Enter a class name",
|
|
190
228
|
)
|
|
191
229
|
teacher = forms.ChoiceField(help_text="Select teacher", required=False)
|
|
192
230
|
classmate_progress = forms.BooleanField(
|
|
@@ -201,7 +239,12 @@ class ClassCreationForm(forms.Form):
|
|
|
201
239
|
|
|
202
240
|
if teacher is not None:
|
|
203
241
|
# Place current teacher at the top
|
|
204
|
-
teacher_choices = [
|
|
242
|
+
teacher_choices = [
|
|
243
|
+
(
|
|
244
|
+
teacher.id,
|
|
245
|
+
f"{teacher.new_user.first_name} {teacher.new_user.last_name}",
|
|
246
|
+
)
|
|
247
|
+
]
|
|
205
248
|
|
|
206
249
|
# Get coworkers and add them to the choices if the teacher is an admin
|
|
207
250
|
if teacher.is_admin:
|
|
@@ -212,7 +255,10 @@ class ClassCreationForm(forms.Form):
|
|
|
212
255
|
)
|
|
213
256
|
for coworker in coworkers:
|
|
214
257
|
teacher_choices.append(
|
|
215
|
-
(
|
|
258
|
+
(
|
|
259
|
+
coworker.id,
|
|
260
|
+
f"{coworker.new_user.first_name} {coworker.new_user.last_name}",
|
|
261
|
+
)
|
|
216
262
|
)
|
|
217
263
|
|
|
218
264
|
self.fields["teacher"].choices = teacher_choices
|
|
@@ -231,19 +277,35 @@ class ClassEditForm(forms.Form):
|
|
|
231
277
|
]
|
|
232
278
|
join_choices.extend(
|
|
233
279
|
[
|
|
234
|
-
(
|
|
280
|
+
(
|
|
281
|
+
str(hours),
|
|
282
|
+
"Allow external requests to this class for the next "
|
|
283
|
+
+ str(hours)
|
|
284
|
+
+ " hours",
|
|
285
|
+
)
|
|
235
286
|
for hours in range(4, 28, 4)
|
|
236
287
|
]
|
|
237
288
|
)
|
|
238
289
|
join_choices.extend(
|
|
239
290
|
[
|
|
240
|
-
(
|
|
291
|
+
(
|
|
292
|
+
str(days * 24),
|
|
293
|
+
"Allow external requests to this class for the next "
|
|
294
|
+
+ str(days)
|
|
295
|
+
+ " days",
|
|
296
|
+
)
|
|
241
297
|
for days in range(2, 5)
|
|
242
298
|
]
|
|
243
299
|
)
|
|
244
|
-
join_choices.append(
|
|
300
|
+
join_choices.append(
|
|
301
|
+
(
|
|
302
|
+
"1000",
|
|
303
|
+
"Always allow external requests to this class (not recommended)",
|
|
304
|
+
)
|
|
305
|
+
)
|
|
245
306
|
name = forms.CharField(
|
|
246
|
-
widget=forms.TextInput(attrs={"placeholder": "Enter class name"}),
|
|
307
|
+
widget=forms.TextInput(attrs={"placeholder": "Enter class name"}),
|
|
308
|
+
help_text="Enter class name",
|
|
247
309
|
)
|
|
248
310
|
classmate_progress = forms.BooleanField(
|
|
249
311
|
label="Allow students to see their classmates' progress?",
|
|
@@ -264,7 +326,7 @@ class ClassLevelControlForm(forms.Form):
|
|
|
264
326
|
def __init__(self, *args, **kwargs):
|
|
265
327
|
super(ClassLevelControlForm, self).__init__(*args, **kwargs)
|
|
266
328
|
|
|
267
|
-
episodes = Episode.objects.filter(pk__in=range(1,
|
|
329
|
+
episodes = Episode.objects.filter(pk__in=range(1, 22))
|
|
268
330
|
|
|
269
331
|
for episode in episodes:
|
|
270
332
|
levels = []
|
|
@@ -275,27 +337,40 @@ class ClassLevelControlForm(forms.Form):
|
|
|
275
337
|
levels_choices = [(level.id, level.name) for level in levels]
|
|
276
338
|
|
|
277
339
|
self.fields[episode.name] = forms.MultipleChoiceField(
|
|
278
|
-
choices=itertools.chain(levels_choices),
|
|
340
|
+
choices=itertools.chain(levels_choices),
|
|
341
|
+
widget=forms.CheckboxSelectMultiple(),
|
|
342
|
+
required=False,
|
|
279
343
|
)
|
|
280
344
|
|
|
281
345
|
|
|
282
346
|
class ClassMoveForm(forms.Form):
|
|
283
347
|
new_teacher = forms.ChoiceField(
|
|
284
|
-
label="New teacher to take over class",
|
|
348
|
+
label="New teacher to take over class",
|
|
349
|
+
help_text="Select teacher",
|
|
350
|
+
widget=forms.Select(),
|
|
285
351
|
)
|
|
286
352
|
|
|
287
353
|
def __init__(self, teachers, *args, **kwargs):
|
|
288
354
|
self.teachers = teachers
|
|
289
355
|
teacher_choices = []
|
|
290
356
|
for teacher in teachers:
|
|
291
|
-
teacher_choices.append(
|
|
357
|
+
teacher_choices.append(
|
|
358
|
+
(
|
|
359
|
+
teacher.id,
|
|
360
|
+
teacher.new_user.first_name
|
|
361
|
+
+ " "
|
|
362
|
+
+ teacher.new_user.last_name,
|
|
363
|
+
)
|
|
364
|
+
)
|
|
292
365
|
super(ClassMoveForm, self).__init__(*args, **kwargs)
|
|
293
366
|
self.fields["new_teacher"].choices = teacher_choices
|
|
294
367
|
|
|
295
368
|
|
|
296
369
|
class TeacherEditStudentForm(forms.Form):
|
|
297
370
|
name = forms.CharField(
|
|
298
|
-
label="Name",
|
|
371
|
+
label="Name",
|
|
372
|
+
widget=forms.TextInput(attrs={"placeholder": "Name"}),
|
|
373
|
+
help_text="Choose a name",
|
|
299
374
|
)
|
|
300
375
|
|
|
301
376
|
def __init__(self, student, *args, **kwargs):
|
|
@@ -307,14 +382,24 @@ class TeacherEditStudentForm(forms.Form):
|
|
|
307
382
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
308
383
|
|
|
309
384
|
if name == "":
|
|
310
|
-
raise forms.ValidationError(
|
|
385
|
+
raise forms.ValidationError(
|
|
386
|
+
"'"
|
|
387
|
+
+ self.cleaned_data.get("name", "")
|
|
388
|
+
+ "' is not a valid name"
|
|
389
|
+
)
|
|
311
390
|
|
|
312
391
|
if re.match(re.compile("^[\w -]+$"), name) is None:
|
|
313
|
-
raise forms.ValidationError(
|
|
392
|
+
raise forms.ValidationError(
|
|
393
|
+
"Names may only contain letters, numbers, dashes, underscores, and spaces."
|
|
394
|
+
)
|
|
314
395
|
|
|
315
|
-
students = Student.objects.filter(
|
|
396
|
+
students = Student.objects.filter(
|
|
397
|
+
class_field=self.klass, new_user__first_name__iexact=name
|
|
398
|
+
)
|
|
316
399
|
if students.exists() and students[0] != self.student:
|
|
317
|
-
raise forms.ValidationError(
|
|
400
|
+
raise forms.ValidationError(
|
|
401
|
+
"There is already a student called '" + name + "' in this class"
|
|
402
|
+
)
|
|
318
403
|
|
|
319
404
|
return name
|
|
320
405
|
|
|
@@ -323,12 +408,16 @@ class TeacherSetStudentPass(forms.Form):
|
|
|
323
408
|
password = forms.CharField(
|
|
324
409
|
label="New password",
|
|
325
410
|
help_text="Enter new password",
|
|
326
|
-
widget=forms.PasswordInput(
|
|
411
|
+
widget=forms.PasswordInput(
|
|
412
|
+
attrs={"autocomplete": "off", "placeholder": "Enter new password"}
|
|
413
|
+
),
|
|
327
414
|
)
|
|
328
415
|
confirm_password = forms.CharField(
|
|
329
416
|
label="Confirm new password",
|
|
330
417
|
help_text="Confirm new password",
|
|
331
|
-
widget=forms.PasswordInput(
|
|
418
|
+
widget=forms.PasswordInput(
|
|
419
|
+
attrs={"autocomplete": "off", "placeholder": "Confirm new password"}
|
|
420
|
+
),
|
|
332
421
|
)
|
|
333
422
|
|
|
334
423
|
def clean_password(self):
|
|
@@ -372,19 +461,32 @@ def validateStudentNames(klass, names):
|
|
|
372
461
|
|
|
373
462
|
def find_clashes(names, students, clashes_found, validationErrors):
|
|
374
463
|
for name in names:
|
|
375
|
-
if
|
|
464
|
+
if (
|
|
465
|
+
students.filter(new_user__first_name__iexact=name).exists()
|
|
466
|
+
and name not in clashes_found
|
|
467
|
+
):
|
|
376
468
|
validationErrors.append(
|
|
377
|
-
forms.ValidationError(
|
|
469
|
+
forms.ValidationError(
|
|
470
|
+
"There is already a student called '"
|
|
471
|
+
+ name
|
|
472
|
+
+ "' in this class"
|
|
473
|
+
)
|
|
378
474
|
)
|
|
379
475
|
clashes_found.append(name)
|
|
380
476
|
|
|
381
477
|
|
|
382
478
|
def find_duplicates(names, lower_names, validationErrors):
|
|
383
479
|
duplicates_found = []
|
|
384
|
-
for duplicate in [
|
|
480
|
+
for duplicate in [
|
|
481
|
+
name for name in names if lower_names.count(name.lower()) > 1
|
|
482
|
+
]:
|
|
385
483
|
if duplicate not in duplicates_found:
|
|
386
484
|
validationErrors.append(
|
|
387
|
-
forms.ValidationError(
|
|
485
|
+
forms.ValidationError(
|
|
486
|
+
"You cannot add more than one student called '"
|
|
487
|
+
+ duplicate
|
|
488
|
+
+ "'"
|
|
489
|
+
)
|
|
388
490
|
)
|
|
389
491
|
duplicates_found.append(duplicate)
|
|
390
492
|
|
|
@@ -403,7 +505,9 @@ def find_illegal_characters(names, validationErrors):
|
|
|
403
505
|
|
|
404
506
|
def check_passwords(password, confirm_password):
|
|
405
507
|
if password is not None and password != confirm_password:
|
|
406
|
-
raise forms.ValidationError(
|
|
508
|
+
raise forms.ValidationError(
|
|
509
|
+
"The password and the confirmation password do not match"
|
|
510
|
+
)
|
|
407
511
|
|
|
408
512
|
|
|
409
513
|
class TeacherMoveStudentsDestinationForm(forms.Form):
|
|
@@ -428,28 +532,47 @@ class TeacherMoveStudentsDestinationForm(forms.Form):
|
|
|
428
532
|
+ klass.teacher.new_user.last_name,
|
|
429
533
|
)
|
|
430
534
|
)
|
|
431
|
-
super(TeacherMoveStudentsDestinationForm, self).__init__(
|
|
535
|
+
super(TeacherMoveStudentsDestinationForm, self).__init__(
|
|
536
|
+
*args, **kwargs
|
|
537
|
+
)
|
|
432
538
|
self.fields["new_class"].choices = class_choices
|
|
433
539
|
|
|
434
540
|
|
|
435
541
|
class TeacherMoveStudentDisambiguationForm(forms.Form):
|
|
436
542
|
orig_name = forms.CharField(
|
|
437
543
|
label="Original Name",
|
|
438
|
-
widget=forms.TextInput(
|
|
544
|
+
widget=forms.TextInput(
|
|
545
|
+
attrs={
|
|
546
|
+
"readonly": "readonly",
|
|
547
|
+
"placeholder": "Original Name",
|
|
548
|
+
"type": "hidden",
|
|
549
|
+
}
|
|
550
|
+
),
|
|
551
|
+
)
|
|
552
|
+
name = forms.CharField(
|
|
553
|
+
label="Name",
|
|
554
|
+
widget=forms.TextInput(
|
|
555
|
+
attrs={"placeholder": "Name", "style": "margin : 0px"}
|
|
556
|
+
),
|
|
439
557
|
)
|
|
440
|
-
name = forms.CharField(label="Name", widget=forms.TextInput(attrs={"placeholder": "Name", "style": "margin : 0px"}))
|
|
441
558
|
|
|
442
559
|
def clean_name(self):
|
|
443
560
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
444
561
|
if name == "":
|
|
445
|
-
raise forms.ValidationError(
|
|
562
|
+
raise forms.ValidationError(
|
|
563
|
+
"'"
|
|
564
|
+
+ self.cleaned_data.get("name", "")
|
|
565
|
+
+ "' is not a valid name"
|
|
566
|
+
)
|
|
446
567
|
return name
|
|
447
568
|
|
|
448
569
|
|
|
449
570
|
class BaseTeacherMoveStudentsDisambiguationFormSet(forms.BaseFormSet):
|
|
450
571
|
def __init__(self, destination, *args, **kwargs):
|
|
451
572
|
self.destination = destination
|
|
452
|
-
super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__(
|
|
573
|
+
super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__(
|
|
574
|
+
*args, **kwargs
|
|
575
|
+
)
|
|
453
576
|
|
|
454
577
|
def clean(self):
|
|
455
578
|
if any(self.errors):
|
|
@@ -468,28 +591,44 @@ class BaseTeacherMoveStudentsDisambiguationFormSet(forms.BaseFormSet):
|
|
|
468
591
|
class TeacherDismissStudentsForm(forms.Form):
|
|
469
592
|
orig_name = forms.CharField(
|
|
470
593
|
help_text="Original student name",
|
|
471
|
-
widget=forms.TextInput(
|
|
594
|
+
widget=forms.TextInput(
|
|
595
|
+
attrs={
|
|
596
|
+
"readonly": "readonly",
|
|
597
|
+
"placeholder": "Original Name",
|
|
598
|
+
"class": "m-0",
|
|
599
|
+
}
|
|
600
|
+
),
|
|
472
601
|
)
|
|
473
602
|
name = forms.CharField(
|
|
474
603
|
help_text="New student name",
|
|
475
|
-
widget=forms.TextInput(
|
|
604
|
+
widget=forms.TextInput(
|
|
605
|
+
attrs={"placeholder": "Enter new student name", "class": "m-0"}
|
|
606
|
+
),
|
|
476
607
|
)
|
|
477
608
|
email = forms.EmailField(
|
|
478
609
|
label="Email",
|
|
479
610
|
help_text="New email address",
|
|
480
|
-
widget=forms.EmailInput(
|
|
611
|
+
widget=forms.EmailInput(
|
|
612
|
+
attrs={"placeholder": "Enter email address", "class": "m-0"}
|
|
613
|
+
),
|
|
481
614
|
)
|
|
482
615
|
confirm_email = forms.EmailField(
|
|
483
616
|
label="Confirm Email",
|
|
484
617
|
help_text="Confirm email address",
|
|
485
|
-
widget=forms.EmailInput(
|
|
618
|
+
widget=forms.EmailInput(
|
|
619
|
+
attrs={"placeholder": "Confirm email address", "class": "m-0"}
|
|
620
|
+
),
|
|
486
621
|
)
|
|
487
622
|
|
|
488
623
|
def clean_name(self):
|
|
489
624
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
490
625
|
|
|
491
626
|
if name == "":
|
|
492
|
-
raise forms.ValidationError(
|
|
627
|
+
raise forms.ValidationError(
|
|
628
|
+
"'"
|
|
629
|
+
+ self.cleaned_data.get("name", "")
|
|
630
|
+
+ "' is not a valid name"
|
|
631
|
+
)
|
|
493
632
|
|
|
494
633
|
return name
|
|
495
634
|
|
|
@@ -547,7 +686,10 @@ class StudentCreationForm(forms.Form):
|
|
|
547
686
|
|
|
548
687
|
|
|
549
688
|
class TeacherAddExternalStudentForm(forms.Form):
|
|
550
|
-
name = forms.CharField(
|
|
689
|
+
name = forms.CharField(
|
|
690
|
+
label="Student name",
|
|
691
|
+
widget=forms.TextInput(attrs={"placeholder": "Name"}),
|
|
692
|
+
)
|
|
551
693
|
|
|
552
694
|
def __init__(self, klass, *args, **kwargs):
|
|
553
695
|
self.klass = klass
|
|
@@ -557,9 +699,17 @@ class TeacherAddExternalStudentForm(forms.Form):
|
|
|
557
699
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
558
700
|
|
|
559
701
|
if name == "":
|
|
560
|
-
raise forms.ValidationError(
|
|
702
|
+
raise forms.ValidationError(
|
|
703
|
+
"'"
|
|
704
|
+
+ self.cleaned_data.get("name", "")
|
|
705
|
+
+ "' is not a valid name"
|
|
706
|
+
)
|
|
561
707
|
|
|
562
|
-
if Student.objects.filter(
|
|
563
|
-
|
|
708
|
+
if Student.objects.filter(
|
|
709
|
+
class_field=self.klass, new_user__first_name__iexact=name
|
|
710
|
+
).exists():
|
|
711
|
+
raise forms.ValidationError(
|
|
712
|
+
"There is already a student called '" + name + "' in this class"
|
|
713
|
+
)
|
|
564
714
|
|
|
565
715
|
return name
|