codeforlife-portal 8.0.0__py2.py3-none-any.whl → 8.0.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/models.py +20 -43
- {codeforlife_portal-8.0.0.dist-info → codeforlife_portal-8.0.2.dist-info}/METADATA +1 -1
- {codeforlife_portal-8.0.0.dist-info → codeforlife_portal-8.0.2.dist-info}/RECORD +11 -11
- {codeforlife_portal-8.0.0.dist-info → codeforlife_portal-8.0.2.dist-info}/WHEEL +1 -1
- portal/__init__.py +1 -1
- portal/forms/teach.py +57 -152
- portal/templates/portal/teach/teacher_edit_class.html +18 -8
- portal/tests/test_class.py +244 -229
- portal/views/teacher/teach.py +17 -8
- {codeforlife_portal-8.0.0.dist-info → codeforlife_portal-8.0.2.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-8.0.0.dist-info → codeforlife_portal-8.0.2.dist-info}/top_level.txt +0 -0
portal/forms/teach.py
CHANGED
|
@@ -3,14 +3,14 @@ import re
|
|
|
3
3
|
from builtins import map, range, str
|
|
4
4
|
|
|
5
5
|
from common.helpers.emails import send_verification_email
|
|
6
|
-
from common.models import Student,
|
|
6
|
+
from common.models import Student, Teacher, UserSession, stripStudentName
|
|
7
7
|
from django import forms
|
|
8
8
|
from django.contrib.auth import authenticate
|
|
9
9
|
from django.contrib.auth.forms import AuthenticationForm
|
|
10
10
|
from django.contrib.auth.models import User
|
|
11
11
|
from django_recaptcha.fields import ReCaptchaField
|
|
12
12
|
from django_recaptcha.widgets import ReCaptchaV2Invisible
|
|
13
|
-
from game.models import Episode
|
|
13
|
+
from game.models import Episode, Worksheet
|
|
14
14
|
|
|
15
15
|
from portal.forms.error_messages import INVALID_LOGIN_MESSAGE
|
|
16
16
|
from portal.helpers.password import PasswordStrength, form_clean_password
|
|
@@ -18,46 +18,34 @@ from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class InvitedTeacherForm(forms.Form):
|
|
21
|
-
prefix =
|
|
21
|
+
prefix = "teacher_signup"
|
|
22
22
|
|
|
23
23
|
def __init__(self, *args, **kwargs):
|
|
24
24
|
super().__init__(*args, **kwargs)
|
|
25
25
|
for field_name, field in self.fields.items():
|
|
26
|
-
field.widget.attrs[
|
|
26
|
+
field.widget.attrs["id"] = f"id_teacher_signup-{field_name}"
|
|
27
27
|
|
|
28
28
|
teacher_password = forms.CharField(
|
|
29
29
|
help_text="Enter a password",
|
|
30
|
-
widget=forms.PasswordInput(
|
|
31
|
-
attrs={"autocomplete": "off", "placeholder": "Password"}
|
|
32
|
-
),
|
|
30
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
|
|
33
31
|
)
|
|
34
32
|
teacher_confirm_password = forms.CharField(
|
|
35
33
|
help_text="Repeat password",
|
|
36
|
-
widget=forms.PasswordInput(
|
|
37
|
-
attrs={"autocomplete": "off", "placeholder": "Repeat password"}
|
|
38
|
-
),
|
|
34
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Repeat password"}),
|
|
39
35
|
)
|
|
40
36
|
|
|
41
|
-
consent_ticked = forms.BooleanField(
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
newsletter_ticked = forms.BooleanField(
|
|
45
|
-
widget=forms.CheckboxInput(), initial=False, required=False
|
|
46
|
-
)
|
|
37
|
+
consent_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=True)
|
|
38
|
+
newsletter_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=False)
|
|
47
39
|
|
|
48
40
|
def clean_teacher_password(self):
|
|
49
|
-
return form_clean_password(
|
|
50
|
-
self, "teacher_password", PasswordStrength.TEACHER
|
|
51
|
-
)
|
|
41
|
+
return form_clean_password(self, "teacher_password", PasswordStrength.TEACHER)
|
|
52
42
|
|
|
53
43
|
def clean(self):
|
|
54
44
|
if any(self.errors):
|
|
55
45
|
return
|
|
56
46
|
|
|
57
47
|
password = self.cleaned_data.get("teacher_password", None)
|
|
58
|
-
confirm_password = self.cleaned_data.get(
|
|
59
|
-
"teacher_confirm_password", None
|
|
60
|
-
)
|
|
48
|
+
confirm_password = self.cleaned_data.get("teacher_confirm_password", None)
|
|
61
49
|
|
|
62
50
|
check_passwords(password, confirm_password)
|
|
63
51
|
|
|
@@ -68,22 +56,16 @@ class TeacherSignupForm(InvitedTeacherForm):
|
|
|
68
56
|
teacher_first_name = forms.CharField(
|
|
69
57
|
help_text="Enter your first name",
|
|
70
58
|
max_length=100,
|
|
71
|
-
widget=forms.TextInput(
|
|
72
|
-
attrs={"autocomplete": "off", "placeholder": "First name"}
|
|
73
|
-
),
|
|
59
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "First name"}),
|
|
74
60
|
)
|
|
75
61
|
teacher_last_name = forms.CharField(
|
|
76
62
|
help_text="Enter your last name",
|
|
77
63
|
max_length=100,
|
|
78
|
-
widget=forms.TextInput(
|
|
79
|
-
attrs={"autocomplete": "off", "placeholder": "Last name"}
|
|
80
|
-
),
|
|
64
|
+
widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Last name"}),
|
|
81
65
|
)
|
|
82
66
|
teacher_email = forms.EmailField(
|
|
83
67
|
help_text="Enter your email address",
|
|
84
|
-
widget=forms.EmailInput(
|
|
85
|
-
attrs={"autocomplete": "off", "placeholder": "Email address"}
|
|
86
|
-
),
|
|
68
|
+
widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
|
|
87
69
|
)
|
|
88
70
|
|
|
89
71
|
captcha = ReCaptchaField(widget=ReCaptchaV2Invisible)
|
|
@@ -92,37 +74,27 @@ class TeacherSignupForm(InvitedTeacherForm):
|
|
|
92
74
|
class TeacherEditAccountForm(forms.Form):
|
|
93
75
|
first_name = forms.CharField(
|
|
94
76
|
max_length=100,
|
|
95
|
-
widget=forms.TextInput(
|
|
96
|
-
attrs={"placeholder": "First name", "class": "fName"}
|
|
97
|
-
),
|
|
77
|
+
widget=forms.TextInput(attrs={"placeholder": "First name", "class": "fName"}),
|
|
98
78
|
help_text="First name",
|
|
99
79
|
)
|
|
100
80
|
last_name = forms.CharField(
|
|
101
81
|
max_length=100,
|
|
102
|
-
widget=forms.TextInput(
|
|
103
|
-
attrs={"placeholder": "Last name", "class": "lName"}
|
|
104
|
-
),
|
|
82
|
+
widget=forms.TextInput(attrs={"placeholder": "Last name", "class": "lName"}),
|
|
105
83
|
help_text="Last name",
|
|
106
84
|
)
|
|
107
85
|
email = forms.EmailField(
|
|
108
86
|
required=False,
|
|
109
|
-
widget=forms.EmailInput(
|
|
110
|
-
attrs={"placeholder": "New email address (optional)"}
|
|
111
|
-
),
|
|
87
|
+
widget=forms.EmailInput(attrs={"placeholder": "New email address (optional)"}),
|
|
112
88
|
help_text="New email address (optional)",
|
|
113
89
|
)
|
|
114
90
|
password = forms.CharField(
|
|
115
91
|
required=False,
|
|
116
|
-
widget=forms.PasswordInput(
|
|
117
|
-
attrs={"placeholder": "New password (optional)"}
|
|
118
|
-
),
|
|
92
|
+
widget=forms.PasswordInput(attrs={"placeholder": "New password (optional)"}),
|
|
119
93
|
help_text="New password (optional)",
|
|
120
94
|
)
|
|
121
95
|
confirm_password = forms.CharField(
|
|
122
96
|
required=False,
|
|
123
|
-
widget=forms.PasswordInput(
|
|
124
|
-
attrs={"placeholder": "Confirm new password (optional)"}
|
|
125
|
-
),
|
|
97
|
+
widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password (optional)"}),
|
|
126
98
|
help_text="Confirm new password (optional)",
|
|
127
99
|
)
|
|
128
100
|
current_password = forms.CharField(
|
|
@@ -149,9 +121,7 @@ class TeacherEditAccountForm(forms.Form):
|
|
|
149
121
|
|
|
150
122
|
return self.cleaned_data
|
|
151
123
|
|
|
152
|
-
def check_password_errors(
|
|
153
|
-
self, password, confirm_password, current_password
|
|
154
|
-
):
|
|
124
|
+
def check_password_errors(self, password, confirm_password, current_password):
|
|
155
125
|
check_passwords(password, confirm_password)
|
|
156
126
|
|
|
157
127
|
if not self.user.check_password(current_password):
|
|
@@ -160,15 +130,11 @@ class TeacherEditAccountForm(forms.Form):
|
|
|
160
130
|
|
|
161
131
|
class TeacherLoginForm(AuthenticationForm):
|
|
162
132
|
username = forms.EmailField(
|
|
163
|
-
widget=forms.EmailInput(
|
|
164
|
-
attrs={"autocomplete": "off", "placeholder": "Email address"}
|
|
165
|
-
),
|
|
133
|
+
widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}),
|
|
166
134
|
help_text="Enter your email address",
|
|
167
135
|
)
|
|
168
136
|
password = forms.CharField(
|
|
169
|
-
widget=forms.PasswordInput(
|
|
170
|
-
attrs={"autocomplete": "off", "placeholder": "Password"}
|
|
171
|
-
),
|
|
137
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}),
|
|
172
138
|
help_text="Enter your password",
|
|
173
139
|
)
|
|
174
140
|
|
|
@@ -202,9 +168,7 @@ class TeacherLoginForm(AuthenticationForm):
|
|
|
202
168
|
users = User.objects.filter(email=email)
|
|
203
169
|
|
|
204
170
|
for result in users:
|
|
205
|
-
if hasattr(result, "userprofile") and hasattr(
|
|
206
|
-
result.userprofile, "teacher"
|
|
207
|
-
):
|
|
171
|
+
if hasattr(result, "userprofile") and hasattr(result.userprofile, "teacher"):
|
|
208
172
|
user = result
|
|
209
173
|
break
|
|
210
174
|
|
|
@@ -286,9 +250,7 @@ class ClassEditForm(forms.Form):
|
|
|
286
250
|
[
|
|
287
251
|
(
|
|
288
252
|
str(hours),
|
|
289
|
-
"Allow external requests to this class for the next "
|
|
290
|
-
+ str(hours)
|
|
291
|
-
+ " hours",
|
|
253
|
+
"Allow external requests to this class for the next " + str(hours) + " hours",
|
|
292
254
|
)
|
|
293
255
|
for hours in range(4, 28, 4)
|
|
294
256
|
]
|
|
@@ -297,9 +259,7 @@ class ClassEditForm(forms.Form):
|
|
|
297
259
|
[
|
|
298
260
|
(
|
|
299
261
|
str(days * 24),
|
|
300
|
-
"Allow external requests to this class for the next "
|
|
301
|
-
+ str(days)
|
|
302
|
-
+ " days",
|
|
262
|
+
"Allow external requests to this class for the next " + str(days) + " days",
|
|
303
263
|
)
|
|
304
264
|
for days in range(2, 5)
|
|
305
265
|
]
|
|
@@ -333,18 +293,22 @@ class ClassLevelControlForm(forms.Form):
|
|
|
333
293
|
def __init__(self, *args, **kwargs):
|
|
334
294
|
super(ClassLevelControlForm, self).__init__(*args, **kwargs)
|
|
335
295
|
|
|
336
|
-
episodes = Episode.objects.filter(pk__in=range(1,
|
|
296
|
+
episodes = Episode.objects.filter(pk__in=range(1, 25))
|
|
337
297
|
|
|
338
298
|
for episode in episodes:
|
|
339
|
-
|
|
340
|
-
|
|
299
|
+
choices = []
|
|
341
300
|
for level in episode.levels:
|
|
342
|
-
|
|
301
|
+
try:
|
|
302
|
+
choices.append((f"worksheet:{level.after_worksheet.id}", episode.name))
|
|
303
|
+
except Worksheet.DoesNotExist:
|
|
304
|
+
pass
|
|
305
|
+
choices.append((f"level:{level.id}", level.name))
|
|
343
306
|
|
|
344
|
-
|
|
307
|
+
for worksheet in episode.worksheets.filter(before_level__isnull=True):
|
|
308
|
+
choices.append((f"worksheet:{worksheet.id}", episode.name))
|
|
345
309
|
|
|
346
310
|
self.fields[episode.name] = forms.MultipleChoiceField(
|
|
347
|
-
choices=itertools.chain(
|
|
311
|
+
choices=itertools.chain(choices),
|
|
348
312
|
widget=forms.CheckboxSelectMultiple(),
|
|
349
313
|
required=False,
|
|
350
314
|
)
|
|
@@ -364,9 +328,7 @@ class ClassMoveForm(forms.Form):
|
|
|
364
328
|
teacher_choices.append(
|
|
365
329
|
(
|
|
366
330
|
teacher.id,
|
|
367
|
-
teacher.new_user.first_name
|
|
368
|
-
+ " "
|
|
369
|
-
+ teacher.new_user.last_name,
|
|
331
|
+
teacher.new_user.first_name + " " + teacher.new_user.last_name,
|
|
370
332
|
)
|
|
371
333
|
)
|
|
372
334
|
super(ClassMoveForm, self).__init__(*args, **kwargs)
|
|
@@ -389,24 +351,14 @@ class TeacherEditStudentForm(forms.Form):
|
|
|
389
351
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
390
352
|
|
|
391
353
|
if name == "":
|
|
392
|
-
raise forms.ValidationError(
|
|
393
|
-
"'"
|
|
394
|
-
+ self.cleaned_data.get("name", "")
|
|
395
|
-
+ "' is not a valid name"
|
|
396
|
-
)
|
|
354
|
+
raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
|
|
397
355
|
|
|
398
356
|
if re.match(re.compile("^[\w -]+$"), name) is None:
|
|
399
|
-
raise forms.ValidationError(
|
|
400
|
-
"Names may only contain letters, numbers, dashes, underscores, and spaces."
|
|
401
|
-
)
|
|
357
|
+
raise forms.ValidationError("Names may only contain letters, numbers, dashes, underscores, and spaces.")
|
|
402
358
|
|
|
403
|
-
students = Student.objects.filter(
|
|
404
|
-
class_field=self.klass, new_user__first_name__iexact=name
|
|
405
|
-
)
|
|
359
|
+
students = Student.objects.filter(class_field=self.klass, new_user__first_name__iexact=name)
|
|
406
360
|
if students.exists() and students[0] != self.student:
|
|
407
|
-
raise forms.ValidationError(
|
|
408
|
-
"There is already a student called '" + name + "' in this class"
|
|
409
|
-
)
|
|
361
|
+
raise forms.ValidationError("There is already a student called '" + name + "' in this class")
|
|
410
362
|
|
|
411
363
|
return name
|
|
412
364
|
|
|
@@ -415,16 +367,12 @@ class TeacherSetStudentPass(forms.Form):
|
|
|
415
367
|
password = forms.CharField(
|
|
416
368
|
label="New password",
|
|
417
369
|
help_text="Enter new password",
|
|
418
|
-
widget=forms.PasswordInput(
|
|
419
|
-
attrs={"autocomplete": "off", "placeholder": "Enter new password"}
|
|
420
|
-
),
|
|
370
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Enter new password"}),
|
|
421
371
|
)
|
|
422
372
|
confirm_password = forms.CharField(
|
|
423
373
|
label="Confirm new password",
|
|
424
374
|
help_text="Confirm new password",
|
|
425
|
-
widget=forms.PasswordInput(
|
|
426
|
-
attrs={"autocomplete": "off", "placeholder": "Confirm new password"}
|
|
427
|
-
),
|
|
375
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm new password"}),
|
|
428
376
|
)
|
|
429
377
|
|
|
430
378
|
def clean_password(self):
|
|
@@ -468,32 +416,19 @@ def validateStudentNames(klass, names):
|
|
|
468
416
|
|
|
469
417
|
def find_clashes(names, students, clashes_found, validationErrors):
|
|
470
418
|
for name in names:
|
|
471
|
-
if (
|
|
472
|
-
students.filter(new_user__first_name__iexact=name).exists()
|
|
473
|
-
and name not in clashes_found
|
|
474
|
-
):
|
|
419
|
+
if students.filter(new_user__first_name__iexact=name).exists() and name not in clashes_found:
|
|
475
420
|
validationErrors.append(
|
|
476
|
-
forms.ValidationError(
|
|
477
|
-
"There is already a student called '"
|
|
478
|
-
+ name
|
|
479
|
-
+ "' in this class"
|
|
480
|
-
)
|
|
421
|
+
forms.ValidationError("There is already a student called '" + name + "' in this class")
|
|
481
422
|
)
|
|
482
423
|
clashes_found.append(name)
|
|
483
424
|
|
|
484
425
|
|
|
485
426
|
def find_duplicates(names, lower_names, validationErrors):
|
|
486
427
|
duplicates_found = []
|
|
487
|
-
for duplicate in [
|
|
488
|
-
name for name in names if lower_names.count(name.lower()) > 1
|
|
489
|
-
]:
|
|
428
|
+
for duplicate in [name for name in names if lower_names.count(name.lower()) > 1]:
|
|
490
429
|
if duplicate not in duplicates_found:
|
|
491
430
|
validationErrors.append(
|
|
492
|
-
forms.ValidationError(
|
|
493
|
-
"You cannot add more than one student called '"
|
|
494
|
-
+ duplicate
|
|
495
|
-
+ "'"
|
|
496
|
-
)
|
|
431
|
+
forms.ValidationError("You cannot add more than one student called '" + duplicate + "'")
|
|
497
432
|
)
|
|
498
433
|
duplicates_found.append(duplicate)
|
|
499
434
|
|
|
@@ -512,9 +447,7 @@ def find_illegal_characters(names, validationErrors):
|
|
|
512
447
|
|
|
513
448
|
def check_passwords(password, confirm_password):
|
|
514
449
|
if password is not None and password != confirm_password:
|
|
515
|
-
raise forms.ValidationError(
|
|
516
|
-
"The password and the confirmation password do not match"
|
|
517
|
-
)
|
|
450
|
+
raise forms.ValidationError("The password and the confirmation password do not match")
|
|
518
451
|
|
|
519
452
|
|
|
520
453
|
class TeacherMoveStudentsDestinationForm(forms.Form):
|
|
@@ -539,9 +472,7 @@ class TeacherMoveStudentsDestinationForm(forms.Form):
|
|
|
539
472
|
+ klass.teacher.new_user.last_name,
|
|
540
473
|
)
|
|
541
474
|
)
|
|
542
|
-
super(TeacherMoveStudentsDestinationForm, self).__init__(
|
|
543
|
-
*args, **kwargs
|
|
544
|
-
)
|
|
475
|
+
super(TeacherMoveStudentsDestinationForm, self).__init__(*args, **kwargs)
|
|
545
476
|
self.fields["new_class"].choices = class_choices
|
|
546
477
|
|
|
547
478
|
|
|
@@ -558,28 +489,20 @@ class TeacherMoveStudentDisambiguationForm(forms.Form):
|
|
|
558
489
|
)
|
|
559
490
|
name = forms.CharField(
|
|
560
491
|
label="Name",
|
|
561
|
-
widget=forms.TextInput(
|
|
562
|
-
attrs={"placeholder": "Name", "style": "margin : 0px"}
|
|
563
|
-
),
|
|
492
|
+
widget=forms.TextInput(attrs={"placeholder": "Name", "style": "margin : 0px"}),
|
|
564
493
|
)
|
|
565
494
|
|
|
566
495
|
def clean_name(self):
|
|
567
496
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
568
497
|
if name == "":
|
|
569
|
-
raise forms.ValidationError(
|
|
570
|
-
"'"
|
|
571
|
-
+ self.cleaned_data.get("name", "")
|
|
572
|
-
+ "' is not a valid name"
|
|
573
|
-
)
|
|
498
|
+
raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
|
|
574
499
|
return name
|
|
575
500
|
|
|
576
501
|
|
|
577
502
|
class BaseTeacherMoveStudentsDisambiguationFormSet(forms.BaseFormSet):
|
|
578
503
|
def __init__(self, destination, *args, **kwargs):
|
|
579
504
|
self.destination = destination
|
|
580
|
-
super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__(
|
|
581
|
-
*args, **kwargs
|
|
582
|
-
)
|
|
505
|
+
super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__(*args, **kwargs)
|
|
583
506
|
|
|
584
507
|
def clean(self):
|
|
585
508
|
if any(self.errors):
|
|
@@ -608,34 +531,24 @@ class TeacherDismissStudentsForm(forms.Form):
|
|
|
608
531
|
)
|
|
609
532
|
name = forms.CharField(
|
|
610
533
|
help_text="New student name",
|
|
611
|
-
widget=forms.TextInput(
|
|
612
|
-
attrs={"placeholder": "Enter new student name", "class": "m-0"}
|
|
613
|
-
),
|
|
534
|
+
widget=forms.TextInput(attrs={"placeholder": "Enter new student name", "class": "m-0"}),
|
|
614
535
|
)
|
|
615
536
|
email = forms.EmailField(
|
|
616
537
|
label="Email",
|
|
617
538
|
help_text="New email address",
|
|
618
|
-
widget=forms.EmailInput(
|
|
619
|
-
attrs={"placeholder": "Enter email address", "class": "m-0"}
|
|
620
|
-
),
|
|
539
|
+
widget=forms.EmailInput(attrs={"placeholder": "Enter email address", "class": "m-0"}),
|
|
621
540
|
)
|
|
622
541
|
confirm_email = forms.EmailField(
|
|
623
542
|
label="Confirm Email",
|
|
624
543
|
help_text="Confirm email address",
|
|
625
|
-
widget=forms.EmailInput(
|
|
626
|
-
attrs={"placeholder": "Confirm email address", "class": "m-0"}
|
|
627
|
-
),
|
|
544
|
+
widget=forms.EmailInput(attrs={"placeholder": "Confirm email address", "class": "m-0"}),
|
|
628
545
|
)
|
|
629
546
|
|
|
630
547
|
def clean_name(self):
|
|
631
548
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
632
549
|
|
|
633
550
|
if name == "":
|
|
634
|
-
raise forms.ValidationError(
|
|
635
|
-
"'"
|
|
636
|
-
+ self.cleaned_data.get("name", "")
|
|
637
|
-
+ "' is not a valid name"
|
|
638
|
-
)
|
|
551
|
+
raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
|
|
639
552
|
|
|
640
553
|
return name
|
|
641
554
|
|
|
@@ -706,17 +619,9 @@ class TeacherAddExternalStudentForm(forms.Form):
|
|
|
706
619
|
name = stripStudentName(self.cleaned_data.get("name", ""))
|
|
707
620
|
|
|
708
621
|
if name == "":
|
|
709
|
-
raise forms.ValidationError(
|
|
710
|
-
"'"
|
|
711
|
-
+ self.cleaned_data.get("name", "")
|
|
712
|
-
+ "' is not a valid name"
|
|
713
|
-
)
|
|
622
|
+
raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name")
|
|
714
623
|
|
|
715
|
-
if Student.objects.filter(
|
|
716
|
-
|
|
717
|
-
).exists():
|
|
718
|
-
raise forms.ValidationError(
|
|
719
|
-
"There is already a student called '" + name + "' in this class"
|
|
720
|
-
)
|
|
624
|
+
if Student.objects.filter(class_field=self.klass, new_user__first_name__iexact=name).exists():
|
|
625
|
+
raise forms.ValidationError("There is already a student called '" + name + "' in this class")
|
|
721
626
|
|
|
722
627
|
return name
|
|
@@ -147,20 +147,21 @@
|
|
|
147
147
|
</label>
|
|
148
148
|
</div>
|
|
149
149
|
{% for episode in python_episodes %}
|
|
150
|
-
{% if episode.levels %}
|
|
151
150
|
<div class="panel">
|
|
152
151
|
<div class="panel-header bg--{{ episode.difficulty }}" id="episode-{{episode.id}}">
|
|
153
152
|
<div class="d-flex align-items-center justify-content-end" data-toggle="collapse"
|
|
154
153
|
data-target="#collapse-{{episode.id}}" aria-expanded="false"
|
|
155
154
|
aria-controls="collapse-{{episode.id}}" data-parent="#episodes">
|
|
156
155
|
<p class="episode-title flex-grow-1">{{episode.name}}</p>
|
|
157
|
-
{% if episode.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
{% if episode.last_level > 1009 %}
|
|
161
|
-
<span>Levels {{episode.first_level|stringformat:"i"|slice:"3:4"}}-{{episode.last_level|stringformat:"i"|slice:"2:4"}}</span>
|
|
156
|
+
{% if episode.levels %}
|
|
157
|
+
{% if episode.first_level > 1009 %}
|
|
158
|
+
<span>Levels {{episode.first_level|stringformat:"i"|slice:"2:4"}}-{{episode.last_level|stringformat:"i"|slice:"2:4"}}</span>
|
|
162
159
|
{% else %}
|
|
163
|
-
|
|
160
|
+
{% if episode.last_level > 1009 %}
|
|
161
|
+
<span>Levels {{episode.first_level|stringformat:"i"|slice:"3:4"}}-{{episode.last_level|stringformat:"i"|slice:"2:4"}}</span>
|
|
162
|
+
{% else %}
|
|
163
|
+
<span>Levels {{episode.first_level|stringformat:"i"|slice:"3:4"}}-{{episode.last_level|stringformat:"i"|slice:"3:4"}}</span>
|
|
164
|
+
{% endif %}
|
|
164
165
|
{% endif %}
|
|
165
166
|
{% endif %}
|
|
166
167
|
<div class="episode_range_text collapsed d-flex align-items-center justify-content-end"
|
|
@@ -177,12 +178,22 @@
|
|
|
177
178
|
<div class="panel-body d-flex justify-content-between">
|
|
178
179
|
<div class="d-flex flex-grow-1 flex-column justify-content-between">
|
|
179
180
|
{% for level in episode.levels %}
|
|
181
|
+
{% for worksheet in episode.worksheets %}
|
|
182
|
+
{% if worksheet.before_level == level.id %}
|
|
183
|
+
<p>{{episode.name}}</p>
|
|
184
|
+
{% endif %}
|
|
185
|
+
{% endfor %}
|
|
180
186
|
{% if level.name < 1010 %}
|
|
181
187
|
<p>{{level.name|stringformat:"i"|slice:"3:4"}}: {{level.title.strip | safe}}</p>
|
|
182
188
|
{% else %}
|
|
183
189
|
<p>{{level.name|stringformat:"i"|slice:"2:4"}}: {{level.title.strip | safe}}</p>
|
|
184
190
|
{% endif %}
|
|
185
191
|
{% endfor %}
|
|
192
|
+
{% for worksheet in episode.worksheets %}
|
|
193
|
+
{% if not worksheet.before_level %}
|
|
194
|
+
<p>{{episode.name}}{% if episode.worksheets|length > 1 %} pt. {{ forloop.counter }}{% endif %}</p>
|
|
195
|
+
{% endif %}
|
|
196
|
+
{% endfor %}
|
|
186
197
|
</div>
|
|
187
198
|
<div class="form__checkbox flex-column justify-content-between p-0">
|
|
188
199
|
{% for input in level_control_form|get_dict_item:episode.name %}
|
|
@@ -194,7 +205,6 @@
|
|
|
194
205
|
</div>
|
|
195
206
|
</div>
|
|
196
207
|
</div>
|
|
197
|
-
{% endif %}
|
|
198
208
|
{% endfor %}
|
|
199
209
|
</div>
|
|
200
210
|
|