codeforlife-portal 7.4.1__py2.py3-none-any.whl → 7.4.3__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.4.1.dist-info → codeforlife_portal-7.4.3.dist-info}/METADATA +2 -2
- {codeforlife_portal-7.4.1.dist-info → codeforlife_portal-7.4.3.dist-info}/RECORD +12 -13
- portal/__init__.py +1 -1
- portal/forms/teach.py +7 -0
- portal/templates/portal/coding_club.html +16 -6
- portal/templates/portal/teach/invited.html +15 -7
- portal/tests/test_invite_teacher.py +3 -3
- portal/views/home.py +16 -53
- portal/views/teacher/teach.py +59 -177
- portal/tests/test_daily_activities.py +0 -41
- {codeforlife_portal-7.4.1.dist-info → codeforlife_portal-7.4.3.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-7.4.1.dist-info → codeforlife_portal-7.4.3.dist-info}/WHEEL +0 -0
- {codeforlife_portal-7.4.1.dist-info → codeforlife_portal-7.4.3.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.4.
|
|
3
|
+
Version: 7.4.3
|
|
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.4.
|
|
24
|
+
Requires-Dist: cfl-common==7.4.3
|
|
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
|
|
@@ -108,7 +108,7 @@ example_project/portal_test_settings.py,sha256=_gI7-HMoPJ-cDGO6n5UlEIHKHuTR5SC_X
|
|
|
108
108
|
example_project/settings.py,sha256=HgGSG0n6Xggd0NieFVoJgn8vKGqyRbCddoB3CRuoT-Y,5640
|
|
109
109
|
example_project/urls.py,sha256=3SsP5jvPAXV5xmduka4zbSZB5PbXggjsalu1ojY5KIo,434
|
|
110
110
|
example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
|
|
111
|
-
portal/__init__.py,sha256=
|
|
111
|
+
portal/__init__.py,sha256=olYEIni3LYPkdTYbQN19dX_D-AAeoyTsiUuVAO9gQPQ,22
|
|
112
112
|
portal/admin.py,sha256=on1-zNRnZvf2cwBN6GVRVYRhkaksrCgfzX8XPWtkvz8,6062
|
|
113
113
|
portal/app_settings.py,sha256=DhWLQOwM0zVOXE3O5TNKbMM9K6agfLuCsHOdr1J7xEI,651
|
|
114
114
|
portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
|
|
@@ -126,7 +126,7 @@ portal/forms/invite_teacher.py,sha256=jkDNcCfkts4_lXRzhcI3xBam21Zn2yX9wMpMVhDtW1
|
|
|
126
126
|
portal/forms/organisation.py,sha256=QcQyd7AiqBmvt4y8uQSQylguUbKOKqo2pjqWIkpWjDg,7433
|
|
127
127
|
portal/forms/play.py,sha256=z9P5LzyS3jjYcnfco84d2x8ptgLxRmh94Dnj05plmbY,11505
|
|
128
128
|
portal/forms/registration.py,sha256=gWcY7rllhWO3c9as6QHUDWZx1Jme7DqtGHYaKcvxe-U,5990
|
|
129
|
-
portal/forms/teach.py,sha256=
|
|
129
|
+
portal/forms/teach.py,sha256=Fd7zOdwpKTFmxxa3q_Tsj1E9B_M5DZlStF5YXiCA-ys,22688
|
|
130
130
|
portal/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
131
131
|
portal/helpers/captcha.py,sha256=Amwg3HZ9eIh1LGYVYWYruk1ccNj6P3nYP_evufOY8BY,254
|
|
132
132
|
portal/helpers/decorators.py,sha256=AnHbcRg42cUYkpMnJMImEMueSKHZRBZ0o67B7_Wp_Zs,3695
|
|
@@ -435,7 +435,7 @@ portal/templates/captcha/includes/js_v2_invisible.html,sha256=FUyDf1RDyS7whzW2B_
|
|
|
435
435
|
portal/templates/portal/about.html,sha256=_iD0GCP6q3-XuZ2LC-9O0KYY-mKL6c9qk3O-NRRqsRM,10321
|
|
436
436
|
portal/templates/portal/base.html,sha256=4xLACNgKmRQTdEsdNNGYhLzMozzYzWIIzk31HrLGBf0,12109
|
|
437
437
|
portal/templates/portal/base_no_userprofile.html,sha256=PlRufyYmUUGWBZ6CvbYhJWOMTqKqdcee4xnO5--AogA,447
|
|
438
|
-
portal/templates/portal/coding_club.html,sha256=
|
|
438
|
+
portal/templates/portal/coding_club.html,sha256=A9_hiD-ChBRnictFvAaQiSgSANfrnQ_zFY6hqBtK2rg,5767
|
|
439
439
|
portal/templates/portal/contribute.html,sha256=UIC_N3Lun9wB_6jEra0wvlT9fDiiIeMby7_onXYo6k0,4176
|
|
440
440
|
portal/templates/portal/dotmailer_consent_form.html,sha256=UDdizPoKYZGybr6z9nzDV4WPhJPa-S3bIKywvVgFrxg,918
|
|
441
441
|
portal/templates/portal/email_invitation_sent.html,sha256=hAMzQXE3NFGnOsQlCGuo3Aps-vazSJb5BhAN7bWT48M,641
|
|
@@ -490,7 +490,7 @@ portal/templates/portal/tag_manager/tag_manager_head.html,sha256=kWQ4ccJ062cWRJO
|
|
|
490
490
|
portal/templates/portal/teach/base_registering.html,sha256=jpANyRQbsmWO404l9Mez91zolNP9ic1khWi27WW0FMQ,582
|
|
491
491
|
portal/templates/portal/teach/class.html,sha256=zir7a7y4hbg9E6-zFIC0iv8kccIzF-qkni4DXlvkjgM,6726
|
|
492
492
|
portal/templates/portal/teach/dashboard.html,sha256=SwNdLbTDPaz0O7wVlSVkU8KwIWPzBYsOo6ysOxkOwOc,32674
|
|
493
|
-
portal/templates/portal/teach/invited.html,sha256=
|
|
493
|
+
portal/templates/portal/teach/invited.html,sha256=lTsydONG5se1E099KaRgsxGuG12qVHxFUL2X0E3HBmQ,4188
|
|
494
494
|
portal/templates/portal/teach/onboarding_classes.html,sha256=K212dunXG7eByyCg5lhv4ne-SniMz3kcKXrfianIY6g,2872
|
|
495
495
|
portal/templates/portal/teach/onboarding_print.html,sha256=4vD2ZDNRxERNdCNj3ZNXJWYAFBQPn9C7Pzsz5TZjZHw,8862
|
|
496
496
|
portal/templates/portal/teach/onboarding_school.html,sha256=EyvnQ13R4ovf3-152pCvjxJJIGeT47ygs0VU9ftvJlQ,2491
|
|
@@ -535,12 +535,11 @@ portal/tests/test_admin.py,sha256=AM2dgv8j9m4L-SDO-sMA9tQvQH9GwRBrlwRG9OgqtfI,14
|
|
|
535
535
|
portal/tests/test_api.py,sha256=Yo5s_nEGOoG35jA39yZ6nuDOUZvuCZ8o8o8XhZos61w,13819
|
|
536
536
|
portal/tests/test_captcha_forms.py,sha256=lirhIli-sHovun8VdrF0he7KRFTAd8DMCpkJ8cQNotg,1015
|
|
537
537
|
portal/tests/test_class.py,sha256=43g2HslNosbSuSnnMBzM6VWk4LaV0HXkWS3f8VKVRmw,15930
|
|
538
|
-
portal/tests/test_daily_activities.py,sha256=-siDCMGBD1ijjccHVk7eEmrk4bgTsvbh0B6hDoj2fo0,1803
|
|
539
538
|
portal/tests/test_emails.py,sha256=pLr06j3uMBxP1raoZQWzUTBVFvsEDFtUh85J8OnqCwE,9238
|
|
540
539
|
portal/tests/test_global_forms.py,sha256=A5JpAe4AYK-wpu0o1qU4THmeNv_wr7lhzaMbjz5czpY,1543
|
|
541
540
|
portal/tests/test_helper_methods.py,sha256=-SQCDZm2XUtyXGEp0CHIb_SSC9CPD-XOSnpnY8QclHk,890
|
|
542
541
|
portal/tests/test_independent_student.py,sha256=NrRjTEr6V4WXpCE74N8LYNVocvLSvddkjuo3dYpfAZc,27245
|
|
543
|
-
portal/tests/test_invite_teacher.py,sha256=
|
|
542
|
+
portal/tests/test_invite_teacher.py,sha256=gUe1spFp60v3i6kMqGoNgJd0OlBEcwplPPNYLomTJS4,12269
|
|
544
543
|
portal/tests/test_middleware.py,sha256=b6jfNmiRZ2snqLKsyJUG-RivoX5fmrqLlQkG9MeVnqM,8034
|
|
545
544
|
portal/tests/test_organisation.py,sha256=kCMUNzLN6EzaMUBcFkqXwnqLGgOuQxQWIHHt63nhqBs,7574
|
|
546
545
|
portal/tests/test_partials.py,sha256=cSLNLjdsriROjPxkZlM3HKD3CSKDuKgpaDIdL3fPyBs,1794
|
|
@@ -610,7 +609,7 @@ portal/views/admin.py,sha256=4Xt3zEyQH7sUwQSrwuRtoCodWidjOzd7gJUwWU96pXY,957
|
|
|
610
609
|
portal/views/api.py,sha256=lCwiclR98G-yTgK55u8IjkueIH8iremeiZSa3jAvO-M,6990
|
|
611
610
|
portal/views/dotmailer.py,sha256=x49p89TtwA1MLysRLtq5yRRzVtIpzGoU__Xb5hPuHak,3208
|
|
612
611
|
portal/views/email.py,sha256=V3wXRxIjeZ4OJBVqGCQrPn-GQWKZK1PCXbR1f2Zpa_4,2174
|
|
613
|
-
portal/views/home.py,sha256=
|
|
612
|
+
portal/views/home.py,sha256=qt5kW7TAaRFXz7BA2BrnHe5oYc69jjoLksEbfr0Aa3c,9715
|
|
614
613
|
portal/views/legal.py,sha256=nUunsTHnhMcXBcDlg1GmUal86k9Vhinne4A2FWfq78M,342
|
|
615
614
|
portal/views/organisation.py,sha256=sPDbiM7hdtpF8GKyh_4n4VPl2a-WnAgnF4q9aSvQCVI,3341
|
|
616
615
|
portal/views/play_landing_page.py,sha256=FFmjUFub3ZdlbMqkB8yX3jAImCzqrUqgb8AZcpKywZ4,308
|
|
@@ -627,13 +626,13 @@ portal/views/student/edit_account_details.py,sha256=Ba-3D_zzKbX5N01NG5qqBS0ud10B
|
|
|
627
626
|
portal/views/student/play.py,sha256=GMxk65bxWOe1Ds2kb6rvuOd1GoAtt5v_9AihLNXoUL0,8768
|
|
628
627
|
portal/views/teacher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
629
628
|
portal/views/teacher/dashboard.py,sha256=pjzOOCz29d4VHukaCI5QhkDsu-RPvy7SXC9dDcPo50k,27422
|
|
630
|
-
portal/views/teacher/teach.py,sha256=
|
|
629
|
+
portal/views/teacher/teach.py,sha256=yWe2K0FHEQB_K3vzPJO8MR0gU2ZaDrXGw8gXMJBH0T0,35765
|
|
631
630
|
portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
632
631
|
portal/views/two_factor/core.py,sha256=O_wcBeFqdPYSGNGv-pT_vbs5-Dj1Z-Jfkd6f9-E5yZI,760
|
|
633
632
|
portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
|
|
634
633
|
portal/views/two_factor/profile.py,sha256=tkl_ludo8arMtd5LKNmohM66vpC_YQiP-0nspTSJiJ4,383
|
|
635
|
-
codeforlife_portal-7.4.
|
|
636
|
-
codeforlife_portal-7.4.
|
|
637
|
-
codeforlife_portal-7.4.
|
|
638
|
-
codeforlife_portal-7.4.
|
|
639
|
-
codeforlife_portal-7.4.
|
|
634
|
+
codeforlife_portal-7.4.3.dist-info/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
|
|
635
|
+
codeforlife_portal-7.4.3.dist-info/METADATA,sha256=UWFujJxxfaXyqmfcwg4-DYbkouqg-eSRo3i0SWVNCTo,3317
|
|
636
|
+
codeforlife_portal-7.4.3.dist-info/WHEEL,sha256=fS9sRbCBHs7VFcwJLnLXN1MZRR0_TVTxvXKzOnaSFs8,110
|
|
637
|
+
codeforlife_portal-7.4.3.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
|
|
638
|
+
codeforlife_portal-7.4.3.dist-info/RECORD,,
|
portal/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "7.4.
|
|
1
|
+
__version__ = "7.4.3"
|
portal/forms/teach.py
CHANGED
|
@@ -18,6 +18,13 @@ from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class InvitedTeacherForm(forms.Form):
|
|
21
|
+
prefix = 'teacher_signup'
|
|
22
|
+
|
|
23
|
+
def __init__(self, *args, **kwargs):
|
|
24
|
+
super().__init__(*args, **kwargs)
|
|
25
|
+
for field_name, field in self.fields.items():
|
|
26
|
+
field.widget.attrs['id'] = f'id_teacher_signup-{field_name}'
|
|
27
|
+
|
|
21
28
|
teacher_password = forms.CharField(
|
|
22
29
|
help_text="Enter a password",
|
|
23
30
|
widget=forms.PasswordInput(
|
|
@@ -86,12 +86,22 @@
|
|
|
86
86
|
</p>
|
|
87
87
|
</div>
|
|
88
88
|
<div>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
</
|
|
94
|
-
</
|
|
89
|
+
<!-- TODO: improve responsiveness -->
|
|
90
|
+
<div style="margin-bottom: 10px;">
|
|
91
|
+
<a id="grass_snakes_pack" download class="button button--primary button--icon" href="https://3289537671-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS5kw31UTGL8CPHU9skBS%2Fuploads%2FKsC7tdyT6htj5JX1ig28%2FGrass%20Snakes%20Club.zip?alt=media&token=1f6e6f0e-1a10-4bef-a8f8-a20b0a59e4c0">
|
|
92
|
+
Lvl 1: Grass Snakes<span class="iconify" data-icon="mdi:tray-arrow-down"></span>
|
|
93
|
+
</a>
|
|
94
|
+
</div>
|
|
95
|
+
<div style="margin-bottom: 10px;">
|
|
96
|
+
<a id="tiger_snakes_pack" download type="submit" class="button button--primary button--icon" href="https://3289537671-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS5kw31UTGL8CPHU9skBS%2Fuploads%2FSIeSsvz348nXjNMcVPxx%2FTiger%20Snakes%20Club.zip?alt=media&token=872b383f-a209-4864-a0e0-26d7ba3a8d74">
|
|
97
|
+
Lvl 2: Tiger Snakes<span class="iconify" data-icon="mdi:tray-arrow-down"></span>
|
|
98
|
+
</a>
|
|
99
|
+
</div>
|
|
100
|
+
<div>
|
|
101
|
+
<a id="cobra_snakes_pack" download type="submit" class="button button--primary button--icon" href="https://3289537671-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS5kw31UTGL8CPHU9skBS%2Fuploads%2FITTbTC8DVwr8LdCkN82d%2FCobra%20Club.zip?alt=media&token=bfd94842-cba9-45c7-892b-331bc82c33e9">
|
|
102
|
+
Lvl 3: Cobra Snakes<span class="iconify" data-icon="mdi:tray-arrow-down"></span>
|
|
103
|
+
</a>
|
|
104
|
+
</div>
|
|
95
105
|
</div>
|
|
96
106
|
</div>
|
|
97
107
|
</div>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
</p>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
|
-
<form class="d-flex flex-column" method="post" id="
|
|
23
|
+
<form class="d-flex flex-column" method="post" id="teacher-register-form" autocomplete="off">
|
|
24
24
|
|
|
25
25
|
{% csrf_token %}
|
|
26
26
|
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
<div class="row form--row">
|
|
30
30
|
<label for="{{ invited_teacher_form.teacher_password.auto_id }}"></label>
|
|
31
31
|
<div class="input--icon">
|
|
32
|
-
{{ invited_teacher_form.teacher_password }}
|
|
32
|
+
{{ invited_teacher_form.teacher_password }}
|
|
33
|
+
<span id="teacher-password-field-icon" class="iconify"
|
|
34
|
+
data-icon="material-symbols:visibility-off" ></span>
|
|
33
35
|
</div>
|
|
34
36
|
<small>{{ invited_teacher_form.teacher_password.help_text }}</small>
|
|
35
37
|
{{ invited_teacher_form.teacher_password.errors }}
|
|
@@ -37,7 +39,9 @@
|
|
|
37
39
|
<div class="row form--row">
|
|
38
40
|
<label for="{{ invited_teacher_form.teacher_confirm_password.auto_id }}"></label>
|
|
39
41
|
<div class="input--icon">
|
|
40
|
-
{{ invited_teacher_form.teacher_confirm_password }}
|
|
42
|
+
{{ invited_teacher_form.teacher_confirm_password }}
|
|
43
|
+
<span id="teacher-confirm-password-field-icon" class="iconify"
|
|
44
|
+
data-icon="material-symbols:visibility-off" ></span>
|
|
41
45
|
</div>
|
|
42
46
|
<small>{{ invited_teacher_form.teacher_confirm_password.help_text }}</small>
|
|
43
47
|
{{ invited_teacher_form.teacher_confirm_password.errors }}
|
|
@@ -73,10 +77,14 @@
|
|
|
73
77
|
</div>
|
|
74
78
|
</div>
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
{% include "portal/partials/service_unavailable_popup.html" %}
|
|
81
|
+
|
|
77
82
|
<script>
|
|
78
|
-
|
|
79
|
-
var
|
|
83
|
+
const password_id = '{{ invited_teacher_form.teacher_password.auto_id }}';
|
|
84
|
+
var TEACHER_PASSWORD_FIELD_ID = password_id;
|
|
85
|
+
var INDEP_STUDENT_PASSWORD_FIELD_ID = password_id;
|
|
80
86
|
</script>
|
|
81
87
|
|
|
82
|
-
{%
|
|
88
|
+
<script type="text/javascript" src="{% static 'portal/js/passwordStrength.js' %}"></script>
|
|
89
|
+
|
|
90
|
+
{% endblock content %}
|
|
@@ -63,9 +63,9 @@ class TestInviteTeacher(TestCase):
|
|
|
63
63
|
response = client.post(
|
|
64
64
|
invitation_url,
|
|
65
65
|
{
|
|
66
|
-
"teacher_password": invited_teacher_password,
|
|
67
|
-
"teacher_confirm_password": invited_teacher_password,
|
|
68
|
-
"consent_ticked": "on",
|
|
66
|
+
"teacher_signup-teacher_password": invited_teacher_password,
|
|
67
|
+
"teacher_signup-teacher_confirm_password": invited_teacher_password,
|
|
68
|
+
"teacher_signup-consent_ticked": "on",
|
|
69
69
|
},
|
|
70
70
|
)
|
|
71
71
|
|
portal/views/home.py
CHANGED
|
@@ -33,15 +33,9 @@ from portal.helpers.ratelimit import (
|
|
|
33
33
|
)
|
|
34
34
|
from portal.strings.coding_club import CODING_CLUB_BANNER
|
|
35
35
|
from portal.strings.home_learning import HOME_LEARNING_BANNER
|
|
36
|
-
from portal.strings.ten_year_map import
|
|
37
|
-
TEN_YEAR_MAP_BANNER,
|
|
38
|
-
TEN_YEAR_MAP_HEADLINE,
|
|
39
|
-
)
|
|
36
|
+
from portal.strings.ten_year_map import TEN_YEAR_MAP_BANNER, TEN_YEAR_MAP_HEADLINE
|
|
40
37
|
from portal.templatetags.app_tags import cloud_storage
|
|
41
|
-
from portal.views.teacher.teach import
|
|
42
|
-
DownloadType,
|
|
43
|
-
count_student_pack_downloads_click,
|
|
44
|
-
)
|
|
38
|
+
from portal.views.teacher.teach import DownloadType, count_student_pack_downloads_click
|
|
45
39
|
|
|
46
40
|
LOGGER = logging.getLogger(__name__)
|
|
47
41
|
|
|
@@ -71,15 +65,11 @@ def render_signup_form(request):
|
|
|
71
65
|
invalid_form = False
|
|
72
66
|
|
|
73
67
|
teacher_signup_form = TeacherSignupForm(prefix="teacher_signup")
|
|
74
|
-
independent_student_signup_form = IndependentStudentSignupForm(
|
|
75
|
-
prefix="independent_student_signup"
|
|
76
|
-
)
|
|
68
|
+
independent_student_signup_form = IndependentStudentSignupForm(prefix="independent_student_signup")
|
|
77
69
|
|
|
78
70
|
if request.method == "POST":
|
|
79
71
|
if "teacher_signup-teacher_email" in request.POST:
|
|
80
|
-
teacher_signup_form = TeacherSignupForm(
|
|
81
|
-
request.POST, prefix="teacher_signup"
|
|
82
|
-
)
|
|
72
|
+
teacher_signup_form = TeacherSignupForm(request.POST, prefix="teacher_signup")
|
|
83
73
|
|
|
84
74
|
if not captcha.CAPTCHA_ENABLED:
|
|
85
75
|
remove_captcha_from_forms(teacher_signup_form)
|
|
@@ -133,15 +123,11 @@ def process_signup_form(request, data):
|
|
|
133
123
|
[email],
|
|
134
124
|
personalization_values={
|
|
135
125
|
"EMAIL": email,
|
|
136
|
-
"LOGIN_URL": request.build_absolute_uri(
|
|
137
|
-
reverse("teacher_login")
|
|
138
|
-
),
|
|
126
|
+
"LOGIN_URL": request.build_absolute_uri(reverse("teacher_login")),
|
|
139
127
|
},
|
|
140
128
|
)
|
|
141
129
|
else:
|
|
142
|
-
LOGGER.warn(
|
|
143
|
-
f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}"
|
|
144
|
-
)
|
|
130
|
+
LOGGER.warn(f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}")
|
|
145
131
|
else:
|
|
146
132
|
teacher = Teacher.objects.factory(
|
|
147
133
|
first_name=data["teacher_first_name"],
|
|
@@ -152,9 +138,7 @@ def process_signup_form(request, data):
|
|
|
152
138
|
|
|
153
139
|
send_verification_email(request, teacher.user.user, data)
|
|
154
140
|
|
|
155
|
-
TotalActivity.objects.update(
|
|
156
|
-
teacher_registrations=F("teacher_registrations") + 1
|
|
157
|
-
)
|
|
141
|
+
TotalActivity.objects.update(teacher_registrations=F("teacher_registrations") + 1)
|
|
158
142
|
|
|
159
143
|
return render(
|
|
160
144
|
request,
|
|
@@ -182,15 +166,11 @@ def process_independent_student_signup_form(request, data):
|
|
|
182
166
|
[email],
|
|
183
167
|
personalization_values={
|
|
184
168
|
"EMAIL": email,
|
|
185
|
-
"LOGIN_URL": request.build_absolute_uri(
|
|
186
|
-
reverse("independent_student_login")
|
|
187
|
-
),
|
|
169
|
+
"LOGIN_URL": request.build_absolute_uri(reverse("independent_student_login")),
|
|
188
170
|
},
|
|
189
171
|
)
|
|
190
172
|
else:
|
|
191
|
-
LOGGER.warning(
|
|
192
|
-
f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}"
|
|
193
|
-
)
|
|
173
|
+
LOGGER.warning(f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}")
|
|
194
174
|
return render(
|
|
195
175
|
request,
|
|
196
176
|
"portal/email_verification_needed.html",
|
|
@@ -208,9 +188,7 @@ def process_independent_student_signup_form(request, data):
|
|
|
208
188
|
|
|
209
189
|
send_verification_email(request, student.new_user, data, age=age)
|
|
210
190
|
|
|
211
|
-
TotalActivity.objects.update(
|
|
212
|
-
independent_registrations=F("independent_registrations") + 1
|
|
213
|
-
)
|
|
191
|
+
TotalActivity.objects.update(independent_registrations=F("independent_registrations") + 1)
|
|
214
192
|
|
|
215
193
|
return render(
|
|
216
194
|
request,
|
|
@@ -221,10 +199,7 @@ def process_independent_student_signup_form(request, data):
|
|
|
221
199
|
|
|
222
200
|
|
|
223
201
|
def is_developer(request):
|
|
224
|
-
return (
|
|
225
|
-
hasattr(request.user, "userprofile")
|
|
226
|
-
and request.user.userprofile.developer
|
|
227
|
-
)
|
|
202
|
+
return hasattr(request.user, "userprofile") and request.user.userprofile.developer
|
|
228
203
|
|
|
229
204
|
|
|
230
205
|
def redirect_teacher_to_correct_page(request, teacher):
|
|
@@ -254,14 +229,10 @@ def home(request):
|
|
|
254
229
|
# tests where the first Selenium test passes, but any following test
|
|
255
230
|
# fails because it cannot find the Maintenance banner instance.
|
|
256
231
|
try:
|
|
257
|
-
maintenance_banner = DynamicElement.objects.get(
|
|
258
|
-
name="Maintenance banner"
|
|
259
|
-
)
|
|
232
|
+
maintenance_banner = DynamicElement.objects.get(name="Maintenance banner")
|
|
260
233
|
|
|
261
234
|
if maintenance_banner.active:
|
|
262
|
-
messages.info(
|
|
263
|
-
request, format_html(maintenance_banner.text), extra_tags="safe"
|
|
264
|
-
)
|
|
235
|
+
messages.info(request, format_html(maintenance_banner.text), extra_tags="safe")
|
|
265
236
|
except ObjectDoesNotExist:
|
|
266
237
|
pass
|
|
267
238
|
|
|
@@ -279,19 +250,13 @@ def home(request):
|
|
|
279
250
|
|
|
280
251
|
|
|
281
252
|
def coding_club(request):
|
|
282
|
-
return render(
|
|
283
|
-
request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER}
|
|
284
|
-
)
|
|
253
|
+
return render(request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER})
|
|
285
254
|
|
|
286
255
|
|
|
287
256
|
def download_student_pack(request, student_pack_type):
|
|
288
257
|
if request.method == "POST":
|
|
289
258
|
count_student_pack_downloads_click(int(student_pack_type))
|
|
290
|
-
link = (
|
|
291
|
-
cloud_storage("club_packs/PythonCodingClub.zip")
|
|
292
|
-
if DownloadType(int(student_pack_type)) == DownloadType.PYTHON_PACK
|
|
293
|
-
else cloud_storage("club_packs/PrimaryCodingClub.zip")
|
|
294
|
-
)
|
|
259
|
+
link = cloud_storage("club_packs/PrimaryCodingClub.zip")
|
|
295
260
|
return redirect(link)
|
|
296
261
|
|
|
297
262
|
|
|
@@ -304,9 +269,7 @@ def home_learning(request):
|
|
|
304
269
|
|
|
305
270
|
|
|
306
271
|
def ten_year_map_page(request):
|
|
307
|
-
messages.info(
|
|
308
|
-
request, "This page is currently under construction.", extra_tags="safe"
|
|
309
|
-
)
|
|
272
|
+
messages.info(request, "This page is currently under construction.", extra_tags="safe")
|
|
310
273
|
return render(
|
|
311
274
|
request,
|
|
312
275
|
"portal/ten_year_map.html",
|
portal/views/teacher/teach.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import csv
|
|
2
2
|
import json
|
|
3
|
-
import pytz
|
|
4
3
|
from datetime import datetime, timedelta
|
|
5
4
|
from enum import Enum
|
|
6
5
|
from functools import partial, wraps
|
|
7
6
|
from uuid import uuid4
|
|
8
7
|
|
|
8
|
+
import pytz
|
|
9
9
|
from common.helpers.emails import send_verification_email
|
|
10
10
|
from common.helpers.generators import (
|
|
11
11
|
generate_access_code,
|
|
@@ -34,12 +34,10 @@ from django.urls import reverse, reverse_lazy
|
|
|
34
34
|
from django.utils import timezone
|
|
35
35
|
from django.views.decorators.http import require_POST
|
|
36
36
|
from game.views.level_selection import get_blockly_episodes, get_python_episodes
|
|
37
|
-
from portal.views.registration import handle_reset_password_tracking
|
|
38
37
|
from reportlab.lib.colors import black, red
|
|
39
38
|
from reportlab.lib.pagesizes import A4
|
|
40
39
|
from reportlab.lib.utils import ImageReader
|
|
41
40
|
from reportlab.pdfgen import canvas
|
|
42
|
-
from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
43
41
|
|
|
44
42
|
from portal.forms.teach import (
|
|
45
43
|
BaseTeacherDismissStudentsFormSet,
|
|
@@ -55,13 +53,13 @@ from portal.forms.teach import (
|
|
|
55
53
|
TeacherMoveStudentsDestinationForm,
|
|
56
54
|
TeacherSetStudentPass,
|
|
57
55
|
)
|
|
56
|
+
from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
|
|
57
|
+
from portal.views.registration import handle_reset_password_tracking
|
|
58
58
|
|
|
59
59
|
STUDENT_PASSWORD_LENGTH = 6
|
|
60
60
|
REMINDER_CARDS_PDF_ROWS = 8
|
|
61
61
|
REMINDER_CARDS_PDF_COLUMNS = 1
|
|
62
|
-
REMINDER_CARDS_PDF_WARNING_TEXT =
|
|
63
|
-
"Please ensure students keep login details in a secure place"
|
|
64
|
-
)
|
|
62
|
+
REMINDER_CARDS_PDF_WARNING_TEXT = "Please ensure students keep login details in a secure place"
|
|
65
63
|
|
|
66
64
|
|
|
67
65
|
@login_required(login_url=reverse_lazy("teacher_login"))
|
|
@@ -71,9 +69,7 @@ def teacher_onboarding_create_class(request):
|
|
|
71
69
|
Onboarding view for creating a class (and organisation if there isn't one, yet)
|
|
72
70
|
"""
|
|
73
71
|
teacher = request.user.new_teacher
|
|
74
|
-
requests = Student.objects.filter(
|
|
75
|
-
pending_class_request__teacher=teacher, new_user__is_active=True
|
|
76
|
-
)
|
|
72
|
+
requests = Student.objects.filter(pending_class_request__teacher=teacher, new_user__is_active=True)
|
|
77
73
|
|
|
78
74
|
if not teacher.school:
|
|
79
75
|
return HttpResponseRedirect(reverse_lazy("onboarding-organisation"))
|
|
@@ -84,9 +80,7 @@ def teacher_onboarding_create_class(request):
|
|
|
84
80
|
created_class = create_class(form, teacher)
|
|
85
81
|
messages.success(
|
|
86
82
|
request,
|
|
87
|
-
"The class '{className}' has been created successfully.".format(
|
|
88
|
-
className=created_class.name
|
|
89
|
-
),
|
|
83
|
+
"The class '{className}' has been created successfully.".format(className=created_class.name),
|
|
90
84
|
)
|
|
91
85
|
return HttpResponseRedirect(
|
|
92
86
|
reverse_lazy(
|
|
@@ -133,9 +127,7 @@ def process_edit_class(request, access_code, onboarding_done, next_url):
|
|
|
133
127
|
"""
|
|
134
128
|
klass = get_object_or_404(Class, access_code=access_code)
|
|
135
129
|
teacher = request.user.new_teacher
|
|
136
|
-
students = Student.objects.filter(
|
|
137
|
-
class_field=klass, new_user__is_active=True
|
|
138
|
-
).order_by("new_user__first_name")
|
|
130
|
+
students = Student.objects.filter(class_field=klass, new_user__is_active=True).order_by("new_user__first_name")
|
|
139
131
|
|
|
140
132
|
check_teacher_authorised(request, klass.teacher)
|
|
141
133
|
|
|
@@ -156,9 +148,7 @@ def process_edit_class(request, access_code, onboarding_done, next_url):
|
|
|
156
148
|
login_id=hashed_login_id,
|
|
157
149
|
)
|
|
158
150
|
|
|
159
|
-
TotalActivity.objects.update(
|
|
160
|
-
student_registrations=F("student_registrations") + 1
|
|
161
|
-
)
|
|
151
|
+
TotalActivity.objects.update(student_registrations=F("student_registrations") + 1)
|
|
162
152
|
|
|
163
153
|
login_url = generate_student_url(request, new_student, login_id)
|
|
164
154
|
students_info.append(
|
|
@@ -241,16 +231,12 @@ def teacher_delete_class(request, access_code):
|
|
|
241
231
|
# check user authorised to see class
|
|
242
232
|
check_teacher_authorised(request, klass.teacher)
|
|
243
233
|
|
|
244
|
-
if Student.objects.filter(
|
|
245
|
-
class_field=klass, new_user__is_active=True
|
|
246
|
-
).exists():
|
|
234
|
+
if Student.objects.filter(class_field=klass, new_user__is_active=True).exists():
|
|
247
235
|
messages.info(
|
|
248
236
|
request,
|
|
249
237
|
"This class still has students, please remove or delete them all before deleting the class.",
|
|
250
238
|
)
|
|
251
|
-
return HttpResponseRedirect(
|
|
252
|
-
reverse_lazy("view_class", kwargs={"access_code": access_code})
|
|
253
|
-
)
|
|
239
|
+
return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": access_code}))
|
|
254
240
|
|
|
255
241
|
klass.anonymise()
|
|
256
242
|
|
|
@@ -267,9 +253,7 @@ def teacher_delete_students(request, access_code):
|
|
|
267
253
|
|
|
268
254
|
# get student objects for students to be deleted, confirming they are in the class
|
|
269
255
|
student_ids = json.loads(request.POST.get("transfer_students", "[]"))
|
|
270
|
-
students = [
|
|
271
|
-
get_object_or_404(Student, id=i, class_field=klass) for i in student_ids
|
|
272
|
-
]
|
|
256
|
+
students = [get_object_or_404(Student, id=i, class_field=klass) for i in student_ids]
|
|
273
257
|
|
|
274
258
|
def __anonymise(user):
|
|
275
259
|
# Delete all personal data from inactive user and mark as inactive.
|
|
@@ -291,9 +275,7 @@ def teacher_delete_students(request, access_code):
|
|
|
291
275
|
else: # otherwise, just delete
|
|
292
276
|
student.new_user.delete()
|
|
293
277
|
|
|
294
|
-
return HttpResponseRedirect(
|
|
295
|
-
reverse_lazy("view_class", kwargs={"access_code": access_code})
|
|
296
|
-
)
|
|
278
|
+
return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": access_code}))
|
|
297
279
|
|
|
298
280
|
|
|
299
281
|
@login_required(login_url=reverse_lazy("teacher_login"))
|
|
@@ -307,9 +289,7 @@ def teacher_edit_class(request, access_code):
|
|
|
307
289
|
"""
|
|
308
290
|
klass = get_object_or_404(Class, access_code=access_code)
|
|
309
291
|
old_teacher = klass.teacher
|
|
310
|
-
other_teachers = Teacher.objects.filter(school=old_teacher.school).exclude(
|
|
311
|
-
user=old_teacher.user
|
|
312
|
-
)
|
|
292
|
+
other_teachers = Teacher.objects.filter(school=old_teacher.school).exclude(user=old_teacher.user)
|
|
313
293
|
|
|
314
294
|
# check user authorised to see class
|
|
315
295
|
check_teacher_authorised(request, klass.teacher)
|
|
@@ -339,9 +319,7 @@ def teacher_edit_class(request, access_code):
|
|
|
339
319
|
elif "level_control_submit" in request.POST:
|
|
340
320
|
level_control_form = ClassLevelControlForm(request.POST)
|
|
341
321
|
if level_control_form.is_valid():
|
|
342
|
-
return process_level_control_form(
|
|
343
|
-
request, klass, blockly_episodes, python_episodes
|
|
344
|
-
)
|
|
322
|
+
return process_level_control_form(request, klass, blockly_episodes, python_episodes)
|
|
345
323
|
elif "class_move_submit" in request.POST:
|
|
346
324
|
class_move_form = ClassMoveForm(other_teachers, request.POST)
|
|
347
325
|
if class_move_form.is_valid():
|
|
@@ -383,9 +361,7 @@ def process_edit_class_form(request, klass, form):
|
|
|
383
361
|
elif hours < 1000:
|
|
384
362
|
# Setting to number of hours
|
|
385
363
|
klass.always_accept_requests = False
|
|
386
|
-
klass.accept_requests_until = timezone.now() + timedelta(
|
|
387
|
-
hours=hours
|
|
388
|
-
)
|
|
364
|
+
klass.accept_requests_until = timezone.now() + timedelta(hours=hours)
|
|
389
365
|
messages.info(
|
|
390
366
|
request,
|
|
391
367
|
"Class set successfully to receive requests from external students until "
|
|
@@ -407,18 +383,12 @@ def process_edit_class_form(request, klass, form):
|
|
|
407
383
|
klass.classmates_data_viewable = classmate_progress
|
|
408
384
|
klass.save()
|
|
409
385
|
|
|
410
|
-
messages.success(
|
|
411
|
-
request, "The class's settings have been changed successfully."
|
|
412
|
-
)
|
|
386
|
+
messages.success(request, "The class's settings have been changed successfully.")
|
|
413
387
|
|
|
414
|
-
return HttpResponseRedirect(
|
|
415
|
-
reverse_lazy("view_class", kwargs={"access_code": klass.access_code})
|
|
416
|
-
)
|
|
388
|
+
return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": klass.access_code}))
|
|
417
389
|
|
|
418
390
|
|
|
419
|
-
def process_level_control_form(
|
|
420
|
-
request, klass, blockly_episodes, python_episodes
|
|
421
|
-
):
|
|
391
|
+
def process_level_control_form(request, klass, blockly_episodes, python_episodes):
|
|
422
392
|
"""
|
|
423
393
|
Find the levels that the user wants to lock and lock them for the specific class.
|
|
424
394
|
:param request: The request sent by the user submitting the form.
|
|
@@ -429,23 +399,14 @@ def process_level_control_form(
|
|
|
429
399
|
"""
|
|
430
400
|
levels_to_lock_ids = []
|
|
431
401
|
|
|
432
|
-
mark_levels_to_lock_in_episodes(
|
|
433
|
-
|
|
434
|
-
)
|
|
435
|
-
mark_levels_to_lock_in_episodes(
|
|
436
|
-
request, python_episodes, levels_to_lock_ids
|
|
437
|
-
)
|
|
402
|
+
mark_levels_to_lock_in_episodes(request, blockly_episodes, levels_to_lock_ids)
|
|
403
|
+
mark_levels_to_lock_in_episodes(request, python_episodes, levels_to_lock_ids)
|
|
438
404
|
|
|
439
405
|
klass.locked_levels.clear()
|
|
440
|
-
[
|
|
441
|
-
klass.locked_levels.add(levels_to_lock_id)
|
|
442
|
-
for levels_to_lock_id in levels_to_lock_ids
|
|
443
|
-
]
|
|
406
|
+
[klass.locked_levels.add(levels_to_lock_id) for levels_to_lock_id in levels_to_lock_ids]
|
|
444
407
|
|
|
445
408
|
messages.success(request, "Your level preferences have been saved.")
|
|
446
|
-
activity_today = DailyActivity.objects.get_or_create(
|
|
447
|
-
date=datetime.now().date()
|
|
448
|
-
)[0]
|
|
409
|
+
activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
|
|
449
410
|
activity_today.level_control_submits += 1
|
|
450
411
|
activity_today.save()
|
|
451
412
|
|
|
@@ -468,14 +429,10 @@ def mark_levels_to_lock_in_episodes(request, episodes, levels_to_lock_ids):
|
|
|
468
429
|
[
|
|
469
430
|
levels_to_lock_ids.append(episode_level["id"])
|
|
470
431
|
for episode_level in episode_levels
|
|
471
|
-
if str(episode_level["id"])
|
|
472
|
-
not in request.POST.getlist(episode_name)
|
|
432
|
+
if str(episode_level["id"]) not in request.POST.getlist(episode_name)
|
|
473
433
|
]
|
|
474
434
|
else:
|
|
475
|
-
[
|
|
476
|
-
levels_to_lock_ids.append(episode_level["id"])
|
|
477
|
-
for episode_level in episode_levels
|
|
478
|
-
]
|
|
435
|
+
[levels_to_lock_ids.append(episode_level["id"]) for episode_level in episode_levels]
|
|
479
436
|
|
|
480
437
|
|
|
481
438
|
def process_move_class_form(request, klass, form):
|
|
@@ -501,9 +458,7 @@ def teacher_edit_student(request, pk):
|
|
|
501
458
|
student = get_object_or_404(Student, id=pk)
|
|
502
459
|
check_teacher_authorised(request, student.class_field.teacher)
|
|
503
460
|
|
|
504
|
-
name_form = TeacherEditStudentForm(
|
|
505
|
-
student, initial={"name": student.new_user.first_name}
|
|
506
|
-
)
|
|
461
|
+
name_form = TeacherEditStudentForm(student, initial={"name": student.new_user.first_name})
|
|
507
462
|
|
|
508
463
|
password_form = TeacherSetStudentPass()
|
|
509
464
|
set_password_mode = False
|
|
@@ -532,9 +487,7 @@ def teacher_edit_student(request, pk):
|
|
|
532
487
|
else:
|
|
533
488
|
password_form = TeacherSetStudentPass(request.POST)
|
|
534
489
|
if password_form.is_valid():
|
|
535
|
-
return process_reset_password_form(
|
|
536
|
-
request, student, password_form
|
|
537
|
-
)
|
|
490
|
+
return process_reset_password_form(request, student, password_form)
|
|
538
491
|
set_password_mode = True
|
|
539
492
|
|
|
540
493
|
return render(
|
|
@@ -577,9 +530,7 @@ def process_reset_password_form(request, student, password_form):
|
|
|
577
530
|
student.new_user.set_password(new_password)
|
|
578
531
|
student.new_user.save()
|
|
579
532
|
student.login_id = login_id
|
|
580
|
-
clear_ratelimit_cache_for_user(
|
|
581
|
-
f"{student.new_user.first_name},{student.class_field.access_code}"
|
|
582
|
-
)
|
|
533
|
+
clear_ratelimit_cache_for_user(f"{student.new_user.first_name},{student.class_field.access_code}")
|
|
583
534
|
student.blocked_time = datetime.now(tz=pytz.utc) - timedelta(days=1)
|
|
584
535
|
student.save()
|
|
585
536
|
|
|
@@ -613,9 +564,7 @@ def teacher_dismiss_students(request, access_code):
|
|
|
613
564
|
|
|
614
565
|
# get student objects for students to be dismissed, confirming they are in the class
|
|
615
566
|
student_ids = json.loads(request.POST.get("transfer_students", "[]"))
|
|
616
|
-
students = [
|
|
617
|
-
get_object_or_404(Student, id=i, class_field=klass) for i in student_ids
|
|
618
|
-
]
|
|
567
|
+
students = [get_object_or_404(Student, id=i, class_field=klass) for i in student_ids]
|
|
619
568
|
|
|
620
569
|
TeacherDismissStudentsFormSet = formset_factory(
|
|
621
570
|
wraps(TeacherDismissStudentsForm)(partial(TeacherDismissStudentsForm)),
|
|
@@ -626,9 +575,7 @@ def teacher_dismiss_students(request, access_code):
|
|
|
626
575
|
if is_right_dismiss_form(request):
|
|
627
576
|
formset = TeacherDismissStudentsFormSet(request.POST)
|
|
628
577
|
if formset.is_valid():
|
|
629
|
-
return process_dismiss_student_form(
|
|
630
|
-
request, formset, klass, access_code
|
|
631
|
-
)
|
|
578
|
+
return process_dismiss_student_form(request, formset, klass, access_code)
|
|
632
579
|
|
|
633
580
|
else:
|
|
634
581
|
initial_data = [
|
|
@@ -679,14 +626,10 @@ def process_dismiss_student_form(request, formset, klass, access_code):
|
|
|
679
626
|
student.user.save()
|
|
680
627
|
|
|
681
628
|
# log the data
|
|
682
|
-
joinrelease = JoinReleaseStudent.objects.create(
|
|
683
|
-
student=student, action_type=JoinReleaseStudent.RELEASE
|
|
684
|
-
)
|
|
629
|
+
joinrelease = JoinReleaseStudent.objects.create(student=student, action_type=JoinReleaseStudent.RELEASE)
|
|
685
630
|
joinrelease.save()
|
|
686
631
|
|
|
687
|
-
send_verification_email(
|
|
688
|
-
request, student.new_user, data, school=klass.teacher.school
|
|
689
|
-
)
|
|
632
|
+
send_verification_email(request, student.new_user, data, school=klass.teacher.school)
|
|
690
633
|
|
|
691
634
|
if not failed_users:
|
|
692
635
|
messages.success(
|
|
@@ -700,9 +643,7 @@ def process_dismiss_student_form(request, formset, klass, access_code):
|
|
|
700
643
|
"Please make sure the email has not been registered to another account.",
|
|
701
644
|
)
|
|
702
645
|
|
|
703
|
-
return HttpResponseRedirect(
|
|
704
|
-
reverse_lazy("view_class", kwargs={"access_code": access_code})
|
|
705
|
-
)
|
|
646
|
+
return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": access_code}))
|
|
706
647
|
|
|
707
648
|
|
|
708
649
|
@login_required(login_url=reverse_lazy("teacher_login"))
|
|
@@ -717,9 +658,7 @@ def teacher_class_password_reset(request, access_code):
|
|
|
717
658
|
check_teacher_authorised(request, klass.teacher)
|
|
718
659
|
|
|
719
660
|
student_ids = json.loads(request.POST.get("transfer_students", "[]"))
|
|
720
|
-
students = [
|
|
721
|
-
get_object_or_404(Student, id=i, class_field=klass) for i in student_ids
|
|
722
|
-
]
|
|
661
|
+
students = [get_object_or_404(Student, id=i, class_field=klass) for i in student_ids]
|
|
723
662
|
|
|
724
663
|
students_info = []
|
|
725
664
|
handle_reset_password_tracking(request, "SCHOOL_STUDENT", access_code)
|
|
@@ -741,9 +680,7 @@ def teacher_class_password_reset(request, access_code):
|
|
|
741
680
|
student.new_user.set_password(password)
|
|
742
681
|
student.new_user.save()
|
|
743
682
|
student.login_id = hashed_login_id
|
|
744
|
-
clear_ratelimit_cache_for_user(
|
|
745
|
-
f"{student.new_user.first_name},{access_code}"
|
|
746
|
-
)
|
|
683
|
+
clear_ratelimit_cache_for_user(f"{student.new_user.first_name},{access_code}")
|
|
747
684
|
student.blocked_time = datetime.now(tz=pytz.utc) - timedelta(days=1)
|
|
748
685
|
student.save()
|
|
749
686
|
|
|
@@ -757,9 +694,7 @@ def teacher_class_password_reset(request, access_code):
|
|
|
757
694
|
"students_info": students_info,
|
|
758
695
|
"query_data": json.dumps(students_info),
|
|
759
696
|
"class_url": request.build_absolute_uri(
|
|
760
|
-
reverse(
|
|
761
|
-
"student_login", kwargs={"access_code": klass.access_code}
|
|
762
|
-
)
|
|
697
|
+
reverse("student_login", kwargs={"access_code": klass.access_code})
|
|
763
698
|
),
|
|
764
699
|
},
|
|
765
700
|
)
|
|
@@ -809,37 +744,26 @@ def teacher_move_students_to_class(request, access_code):
|
|
|
809
744
|
|
|
810
745
|
check_if_move_authorised(request, old_class, new_class)
|
|
811
746
|
|
|
812
|
-
transfer_students_ids = json.loads(
|
|
813
|
-
request.POST.get("transfer_students", "[]")
|
|
814
|
-
)
|
|
747
|
+
transfer_students_ids = json.loads(request.POST.get("transfer_students", "[]"))
|
|
815
748
|
|
|
816
749
|
# get student objects for students to be transferred, confirming they are in the old class still
|
|
817
|
-
transfer_students = [
|
|
818
|
-
get_object_or_404(Student, id=i, class_field=old_class)
|
|
819
|
-
for i in transfer_students_ids
|
|
820
|
-
]
|
|
750
|
+
transfer_students = [get_object_or_404(Student, id=i, class_field=old_class) for i in transfer_students_ids]
|
|
821
751
|
|
|
822
752
|
# get new class' students
|
|
823
|
-
new_class_students = Student.objects.filter(
|
|
824
|
-
|
|
825
|
-
)
|
|
753
|
+
new_class_students = Student.objects.filter(class_field=new_class, new_user__is_active=True).order_by(
|
|
754
|
+
"new_user__first_name"
|
|
755
|
+
)
|
|
826
756
|
|
|
827
757
|
TeacherMoveStudentDisambiguationFormSet = formset_factory(
|
|
828
|
-
wraps(TeacherMoveStudentDisambiguationForm)(
|
|
829
|
-
partial(TeacherMoveStudentDisambiguationForm)
|
|
830
|
-
),
|
|
758
|
+
wraps(TeacherMoveStudentDisambiguationForm)(partial(TeacherMoveStudentDisambiguationForm)),
|
|
831
759
|
extra=0,
|
|
832
760
|
formset=BaseTeacherMoveStudentsDisambiguationFormSet,
|
|
833
761
|
)
|
|
834
762
|
|
|
835
763
|
if is_right_move_form(request):
|
|
836
|
-
formset = TeacherMoveStudentDisambiguationFormSet(
|
|
837
|
-
new_class, request.POST
|
|
838
|
-
)
|
|
764
|
+
formset = TeacherMoveStudentDisambiguationFormSet(new_class, request.POST)
|
|
839
765
|
if formset.is_valid():
|
|
840
|
-
return process_move_students_form(
|
|
841
|
-
request, formset, old_class, new_class
|
|
842
|
-
)
|
|
766
|
+
return process_move_students_form(request, formset, old_class, new_class)
|
|
843
767
|
else:
|
|
844
768
|
# format the students for the form
|
|
845
769
|
initial_data = [
|
|
@@ -850,9 +774,7 @@ def teacher_move_students_to_class(request, access_code):
|
|
|
850
774
|
for student in transfer_students
|
|
851
775
|
]
|
|
852
776
|
|
|
853
|
-
formset = TeacherMoveStudentDisambiguationFormSet(
|
|
854
|
-
new_class, initial=initial_data
|
|
855
|
-
)
|
|
777
|
+
formset = TeacherMoveStudentDisambiguationFormSet(new_class, initial=initial_data)
|
|
856
778
|
|
|
857
779
|
return render(
|
|
858
780
|
request,
|
|
@@ -872,9 +794,7 @@ def check_if_move_authorised(request, old_class, new_class):
|
|
|
872
794
|
|
|
873
795
|
# check teacher has permission to edit old_class and that both classes
|
|
874
796
|
# are in the same school
|
|
875
|
-
if (
|
|
876
|
-
not teacher.is_admin and teacher != old_class.teacher
|
|
877
|
-
) or teacher.school != new_class.teacher.school:
|
|
797
|
+
if (not teacher.is_admin and teacher != old_class.teacher) or teacher.school != new_class.teacher.school:
|
|
878
798
|
raise Http404
|
|
879
799
|
|
|
880
800
|
|
|
@@ -898,14 +818,8 @@ def process_move_students_form(request, formset, old_class, new_class):
|
|
|
898
818
|
student.save()
|
|
899
819
|
student.new_user.save()
|
|
900
820
|
|
|
901
|
-
messages.success(
|
|
902
|
-
|
|
903
|
-
)
|
|
904
|
-
return HttpResponseRedirect(
|
|
905
|
-
reverse_lazy(
|
|
906
|
-
"view_class", kwargs={"access_code": old_class.access_code}
|
|
907
|
-
)
|
|
908
|
-
)
|
|
821
|
+
messages.success(request, "The students have been transferred successfully.")
|
|
822
|
+
return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": old_class.access_code}))
|
|
909
823
|
|
|
910
824
|
|
|
911
825
|
class DownloadType(Enum):
|
|
@@ -938,9 +852,7 @@ def teacher_print_reminder_cards(request, access_code):
|
|
|
938
852
|
|
|
939
853
|
CARD_INNER_HEIGHT = CARD_HEIGHT - CARD_PADDING * 2
|
|
940
854
|
|
|
941
|
-
logo_image = ImageReader(
|
|
942
|
-
staticfiles_storage.path("portal/img/logo_cfl_reminder_cards.jpg")
|
|
943
|
-
)
|
|
855
|
+
logo_image = ImageReader(staticfiles_storage.path("portal/img/logo_cfl_reminder_cards.jpg"))
|
|
944
856
|
|
|
945
857
|
klass = get_object_or_404(Class, access_code=access_code)
|
|
946
858
|
# Check auth
|
|
@@ -948,12 +860,8 @@ def teacher_print_reminder_cards(request, access_code):
|
|
|
948
860
|
|
|
949
861
|
# Use data from the query string if given
|
|
950
862
|
student_data = get_student_data(request)
|
|
951
|
-
student_login_link = request.build_absolute_uri(
|
|
952
|
-
|
|
953
|
-
)
|
|
954
|
-
class_login_link = request.build_absolute_uri(
|
|
955
|
-
reverse("student_login", kwargs={"access_code": access_code})
|
|
956
|
-
)
|
|
863
|
+
student_login_link = request.build_absolute_uri(reverse("student_login_access_code"))
|
|
864
|
+
class_login_link = request.build_absolute_uri(reverse("student_login", kwargs={"access_code": access_code}))
|
|
957
865
|
|
|
958
866
|
# Now draw everything
|
|
959
867
|
x = 0
|
|
@@ -965,17 +873,10 @@ def teacher_print_reminder_cards(request, access_code):
|
|
|
965
873
|
if current_student_count % (NUM_X * NUM_Y) == 0:
|
|
966
874
|
p.setFillColor(red)
|
|
967
875
|
p.setFont("Helvetica-Bold", 10)
|
|
968
|
-
p.drawString(
|
|
969
|
-
PAGE_MARGIN, PAGE_MARGIN / 2, REMINDER_CARDS_PDF_WARNING_TEXT
|
|
970
|
-
)
|
|
876
|
+
p.drawString(PAGE_MARGIN, PAGE_MARGIN / 2, REMINDER_CARDS_PDF_WARNING_TEXT)
|
|
971
877
|
|
|
972
878
|
left = PAGE_MARGIN + x * CARD_WIDTH + x * INTER_CARD_MARGIN * 2
|
|
973
|
-
bottom = (
|
|
974
|
-
PAGE_HEIGHT
|
|
975
|
-
- PAGE_MARGIN
|
|
976
|
-
- (y + 1) * CARD_HEIGHT
|
|
977
|
-
- y * INTER_CARD_MARGIN
|
|
978
|
-
)
|
|
879
|
+
bottom = PAGE_HEIGHT - PAGE_MARGIN - (y + 1) * CARD_HEIGHT - y * INTER_CARD_MARGIN
|
|
979
880
|
|
|
980
881
|
inner_bottom = bottom + CARD_PADDING
|
|
981
882
|
|
|
@@ -995,12 +896,7 @@ def teacher_print_reminder_cards(request, access_code):
|
|
|
995
896
|
anchor="w",
|
|
996
897
|
)
|
|
997
898
|
|
|
998
|
-
text_left = (
|
|
999
|
-
left
|
|
1000
|
-
+ INTER_CARD_MARGIN
|
|
1001
|
-
+ (logo_image.getSize()[0] / logo_image.getSize()[1])
|
|
1002
|
-
* card_logo_height
|
|
1003
|
-
)
|
|
899
|
+
text_left = left + INTER_CARD_MARGIN + (logo_image.getSize()[0] / logo_image.getSize()[1]) * card_logo_height
|
|
1004
900
|
|
|
1005
901
|
# student details
|
|
1006
902
|
p.setFillColor(black)
|
|
@@ -1023,9 +919,7 @@ def teacher_print_reminder_cards(request, access_code):
|
|
|
1023
919
|
inner_bottom + CARD_INNER_HEIGHT * 0.3,
|
|
1024
920
|
f"Name: {student['name']}",
|
|
1025
921
|
)
|
|
1026
|
-
p.drawString(
|
|
1027
|
-
text_left, inner_bottom, f"Password: {student['password']}"
|
|
1028
|
-
)
|
|
922
|
+
p.drawString(text_left, inner_bottom, f"Password: {student['password']}")
|
|
1029
923
|
|
|
1030
924
|
x = (x + 1) % NUM_X
|
|
1031
925
|
y = compute_show_page_character(p, x, y, NUM_Y)
|
|
@@ -1044,17 +938,13 @@ def teacher_print_reminder_cards(request, access_code):
|
|
|
1044
938
|
@user_passes_test(logged_in_as_teacher, login_url=reverse_lazy("teacher_login"))
|
|
1045
939
|
def teacher_download_csv(request, access_code):
|
|
1046
940
|
response = HttpResponse(content_type="text/csv")
|
|
1047
|
-
response[
|
|
1048
|
-
"Content-Disposition"
|
|
1049
|
-
] = 'attachment; filename="student_login_urls.csv"'
|
|
941
|
+
response["Content-Disposition"] = 'attachment; filename="student_login_urls.csv"'
|
|
1050
942
|
|
|
1051
943
|
klass = get_object_or_404(Class, access_code=access_code)
|
|
1052
944
|
# Check auth
|
|
1053
945
|
check_teacher_authorised(request, klass.teacher)
|
|
1054
946
|
|
|
1055
|
-
class_url = request.build_absolute_uri(
|
|
1056
|
-
reverse("student_login", kwargs={"access_code": access_code})
|
|
1057
|
-
)
|
|
947
|
+
class_url = request.build_absolute_uri(reverse("student_login", kwargs={"access_code": access_code}))
|
|
1058
948
|
|
|
1059
949
|
# Use data from the query string if given
|
|
1060
950
|
student_data = get_student_data(request)
|
|
@@ -1062,9 +952,7 @@ def teacher_download_csv(request, access_code):
|
|
|
1062
952
|
writer = csv.writer(response)
|
|
1063
953
|
writer.writerow([access_code, class_url])
|
|
1064
954
|
for student in student_data:
|
|
1065
|
-
writer.writerow(
|
|
1066
|
-
[student["name"], student["password"], student["login_url"]]
|
|
1067
|
-
)
|
|
955
|
+
writer.writerow([student["name"], student["password"], student["login_url"]])
|
|
1068
956
|
|
|
1069
957
|
count_student_details_click(DownloadType.CSV)
|
|
1070
958
|
|
|
@@ -1092,22 +980,16 @@ def compute_show_page_end(p, x, y):
|
|
|
1092
980
|
|
|
1093
981
|
|
|
1094
982
|
def count_student_pack_downloads_click(student_pack_type):
|
|
1095
|
-
activity_today = DailyActivity.objects.get_or_create(
|
|
1096
|
-
date=datetime.now().date()
|
|
1097
|
-
)[0]
|
|
983
|
+
activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
|
|
1098
984
|
if DownloadType(student_pack_type) == DownloadType.PRIMARY_PACK:
|
|
1099
985
|
activity_today.primary_coding_club_downloads += 1
|
|
1100
|
-
elif DownloadType(student_pack_type) == DownloadType.PYTHON_PACK:
|
|
1101
|
-
activity_today.python_coding_club_downloads += 1
|
|
1102
986
|
else:
|
|
1103
987
|
raise Exception("Unknown download type")
|
|
1104
988
|
activity_today.save()
|
|
1105
989
|
|
|
1106
990
|
|
|
1107
991
|
def count_student_details_click(download_type):
|
|
1108
|
-
activity_today = DailyActivity.objects.get_or_create(
|
|
1109
|
-
date=datetime.now().date()
|
|
1110
|
-
)[0]
|
|
992
|
+
activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
|
|
1111
993
|
|
|
1112
994
|
if download_type == DownloadType.CSV:
|
|
1113
995
|
activity_today.csv_click_count += 1
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from datetime import timedelta, datetime
|
|
2
|
-
|
|
3
|
-
from common.models import DailyActivity
|
|
4
|
-
from selenium.webdriver.common.by import By
|
|
5
|
-
from selenium.webdriver.support import expected_conditions as EC
|
|
6
|
-
from selenium.webdriver.support.ui import WebDriverWait
|
|
7
|
-
|
|
8
|
-
from portal.tests.base_test import BaseTest
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestDailyActivities(BaseTest):
|
|
12
|
-
def test_coding_club_increment(self):
|
|
13
|
-
|
|
14
|
-
# first create dailyActivity one day before datetime.now()
|
|
15
|
-
# to check if it can handle incrementing on different days
|
|
16
|
-
# then check if increments are done on the same day
|
|
17
|
-
old_date = datetime.now() - timedelta(days=1)
|
|
18
|
-
old_daily_activity = DailyActivity(date=old_date)
|
|
19
|
-
old_daily_activity.save()
|
|
20
|
-
|
|
21
|
-
for i in range(4):
|
|
22
|
-
# check both buttons
|
|
23
|
-
self.go_to_homepage()
|
|
24
|
-
button_id = "primary_pack" if i < 2 else "python_pack"
|
|
25
|
-
find_out_more_button = WebDriverWait(self.selenium, 10).until(
|
|
26
|
-
EC.element_to_be_clickable((By.ID, "find_out_more"))
|
|
27
|
-
)
|
|
28
|
-
find_out_more_button.click()
|
|
29
|
-
|
|
30
|
-
daily_count_button = WebDriverWait(self.selenium, 10).until(
|
|
31
|
-
EC.visibility_of_element_located((By.ID, button_id))
|
|
32
|
-
)
|
|
33
|
-
daily_count_button.click()
|
|
34
|
-
# check the old_date is still the same
|
|
35
|
-
old_daily_activity = DailyActivity.objects.get(date=old_date)
|
|
36
|
-
assert old_daily_activity.primary_coding_club_downloads == 0
|
|
37
|
-
assert old_daily_activity.python_coding_club_downloads == 0
|
|
38
|
-
# check the current_date is incremented to 2
|
|
39
|
-
current_daily_activity = DailyActivity.objects.get(date=datetime.now())
|
|
40
|
-
assert current_daily_activity.primary_coding_club_downloads == 2
|
|
41
|
-
assert current_daily_activity.python_coding_club_downloads == 2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|