codeforlife-portal 8.6.12__py2.py3-none-any.whl → 8.7.1__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-8.6.12.dist-info → codeforlife_portal-8.7.1.dist-info}/METADATA +3 -3
- {codeforlife_portal-8.6.12.dist-info → codeforlife_portal-8.7.1.dist-info}/RECORD +11 -11
- portal/__init__.py +1 -1
- portal/static/portal/img/gitbook.png +0 -0
- portal/templates/portal/contribute.html +1 -1
- portal/templates/portal/partials/footer.html +1 -0
- portal/tests/test_invite_teacher.py +60 -2
- portal/views/teacher/dashboard.py +5 -4
- {codeforlife_portal-8.6.12.dist-info → codeforlife_portal-8.7.1.dist-info}/WHEEL +0 -0
- {codeforlife_portal-8.6.12.dist-info → codeforlife_portal-8.7.1.dist-info}/licenses/LICENSE.md +0 -0
- {codeforlife_portal-8.6.12.dist-info → codeforlife_portal-8.7.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeforlife-portal
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.7.1
|
|
4
4
|
Classifier: Programming Language :: Python
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.12
|
|
6
6
|
Classifier: Framework :: Django
|
|
@@ -8,7 +8,7 @@ Description-Content-Type: text/markdown
|
|
|
8
8
|
License-File: LICENSE.md
|
|
9
9
|
Requires-Dist: asgiref==3.8.1; python_version >= "3.8"
|
|
10
10
|
Requires-Dist: certifi==2025.4.26; python_version >= "3.6"
|
|
11
|
-
Requires-Dist: cfl-common==8.
|
|
11
|
+
Requires-Dist: cfl-common==8.7.1
|
|
12
12
|
Requires-Dist: chardet==5.2.0; python_version >= "3.7"
|
|
13
13
|
Requires-Dist: charset-normalizer==3.4.2; python_version >= "3.7"
|
|
14
14
|
Requires-Dist: diff-match-patch==20241021; python_version >= "3.7"
|
|
@@ -60,7 +60,7 @@ Requires-Dist: asttokens==3.0.0; python_version >= "3.8" and extra == "dev"
|
|
|
60
60
|
Requires-Dist: attrs==25.3.0; python_version >= "3.8" and extra == "dev"
|
|
61
61
|
Requires-Dist: black==25.1.0; python_version >= "3.9" and extra == "dev"
|
|
62
62
|
Requires-Dist: certifi==2025.4.26; python_version >= "3.6" and extra == "dev"
|
|
63
|
-
Requires-Dist: cfl-common==8.
|
|
63
|
+
Requires-Dist: cfl-common==8.7.1; extra == "dev"
|
|
64
64
|
Requires-Dist: charset-normalizer==3.4.2; python_version >= "3.7" and extra == "dev"
|
|
65
65
|
Requires-Dist: click==8.1.8; python_version >= "3.7" and extra == "dev"
|
|
66
66
|
Requires-Dist: coverage[toml]==7.8.0; python_version >= "3.9" and extra == "dev"
|
|
@@ -87,7 +87,7 @@ cfl_common/common/tests/utils/organisation.py,sha256=vNgKFtU3VPcWRnZfh82yCS90PLA
|
|
|
87
87
|
cfl_common/common/tests/utils/student.py,sha256=PLd980iSlxmMoB8J3C2pVjNC5xHdVxfAkJXzhv_dRhg,3814
|
|
88
88
|
cfl_common/common/tests/utils/teacher.py,sha256=KQ_NAl4yQqiX_zwcULQjkovc29JPhnkLR5Nk3Ljzbpg,2661
|
|
89
89
|
cfl_common/common/tests/utils/user.py,sha256=NvLzZLVP4jy5Hn1iztOYF_BTQ9WsbSmuWMEzGzhAsRU,919
|
|
90
|
-
codeforlife_portal-8.
|
|
90
|
+
codeforlife_portal-8.7.1.dist-info/licenses/LICENSE.md,sha256=9AbRlCDqD2D1tPibimysFv3zg3AIc49-eyv9aEsyq9w,115
|
|
91
91
|
deploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
92
|
deploy/captcha.py,sha256=MbOBuGnbT_SOIltSjP1XMOLrfo1DldCilaVAEim0vM4,23
|
|
93
93
|
deploy/views.py,sha256=7mY2zNPkaBFrlWGByJHp2zFqHIHSMc0YArjE_sFWMMU,131
|
|
@@ -111,7 +111,7 @@ example_project/portal_test_settings.py,sha256=AaXwSSB2A6qu6My_7cBciQa_jONy084uq
|
|
|
111
111
|
example_project/settings.py,sha256=RRSHhAgJVDn4uNZG395V6_Td7jwsSaBGNwQBZ_KJm0Y,5973
|
|
112
112
|
example_project/urls.py,sha256=FUTzHPlUS1O5kqMHjL5V4L552N2ln7uTDXcw9wjKUto,422
|
|
113
113
|
example_project/wsgi.py,sha256=U1W6WzZxZaIdYZ5tks7w9fqp5WS5qvn2iThsVcskrWw,829
|
|
114
|
-
portal/__init__.py,sha256=
|
|
114
|
+
portal/__init__.py,sha256=a3FQRJoDSFET9iDvdpENKACBRF5lwOJHqpR4Y9QjQY4,22
|
|
115
115
|
portal/admin.py,sha256=RKJizTF6dPJKmGPZw7nZUM0X8jkiTjgyKhLQxtvHJ0I,6148
|
|
116
116
|
portal/app_settings.py,sha256=DhWLQOwM0zVOXE3O5TNKbMM9K6agfLuCsHOdr1J7xEI,651
|
|
117
117
|
portal/backends.py,sha256=2Dss6_WoQwPuDzJUF1yEaTQTNG4eUrD12ujJQ5cp5Tc,812
|
|
@@ -190,7 +190,7 @@ portal/static/portal/img/dee.png,sha256=GQHNpaEiQt9M3qjb3wy6KOzlpKB1fNkESPe6yFZ7
|
|
|
190
190
|
portal/static/portal/img/facebook.png,sha256=qaQdtT9dOg-7-XWnRxz8dW5Wwm6CEWq-S0hQDiLCWus,6543
|
|
191
191
|
portal/static/portal/img/favicon.ico,sha256=vZMUaL5b47WxGRxlRMzp1fRlZQXevGHxBq5vOTV_tdg,481
|
|
192
192
|
portal/static/portal/img/get_involved_hero.png,sha256=TD52SemoUt0xILs_WMxtoaKaSaXQVEQhSr2MgMNW8NY,176315
|
|
193
|
-
portal/static/portal/img/gitbook.png,sha256=
|
|
193
|
+
portal/static/portal/img/gitbook.png,sha256=98fufaDz8WkaxvUM0xw6zR2qjtp_9Gk8fPBt0SXWBKY,181846
|
|
194
194
|
portal/static/portal/img/gitbook_space.png,sha256=d1krFtJM36UgBu_aXW0LmGKG7QyIvp4zC6kOXBTjon0,62646
|
|
195
195
|
portal/static/portal/img/github.png,sha256=NaZ4dyTsDgbTaBLJQW9hRo49uuGw6KOXZ4p7BInrVsI,63596
|
|
196
196
|
portal/static/portal/img/github_hero.png,sha256=HQHyoT5IRnCGzCklqc6fJM6BOoN9ksrBN0WJD7k50ss,47014
|
|
@@ -439,7 +439,7 @@ portal/templates/portal/about.html,sha256=_iD0GCP6q3-XuZ2LC-9O0KYY-mKL6c9qk3O-NR
|
|
|
439
439
|
portal/templates/portal/base.html,sha256=pEcfyYQjWtZk-de4NuQBcZwu9VMQFangbrpSnz3pJqg,11655
|
|
440
440
|
portal/templates/portal/base_no_userprofile.html,sha256=PlRufyYmUUGWBZ6CvbYhJWOMTqKqdcee4xnO5--AogA,447
|
|
441
441
|
portal/templates/portal/coding_club.html,sha256=DhPSIhxxzo6w0v80jXKVo_fjZ_TKnv7_u5cQq0woW8Q,5324
|
|
442
|
-
portal/templates/portal/contribute.html,sha256=
|
|
442
|
+
portal/templates/portal/contribute.html,sha256=bhIMaUh9ucQfiudomsZJerYRuhA7963ELO7ukH7vgdw,4269
|
|
443
443
|
portal/templates/portal/dotmailer_consent_form.html,sha256=UDdizPoKYZGybr6z9nzDV4WPhJPa-S3bIKywvVgFrxg,918
|
|
444
444
|
portal/templates/portal/email_invitation_sent.html,sha256=hAMzQXE3NFGnOsQlCGuo3Aps-vazSJb5BhAN7bWT48M,641
|
|
445
445
|
portal/templates/portal/email_style_template.html,sha256=lIlpiEC7-mO9tx3Ra9jtBjdXYxh5HKGZU8uqbvKdEbk,11893
|
|
@@ -471,7 +471,7 @@ portal/templates/portal/partials/card_list.html,sha256=yHcp4oc0WRG9H6Ek871VCHbwY
|
|
|
471
471
|
portal/templates/portal/partials/character_list.html,sha256=SnNFHzbIkOSaiRPIvSdplEf_B87UYgJ1pOvMltNDxLE,415
|
|
472
472
|
portal/templates/portal/partials/cookie_list.html,sha256=j815wyA9nAe74tzuKtp7Rxnm9oclORmipImfbI33BRI,6538
|
|
473
473
|
portal/templates/portal/partials/delete_popup.html,sha256=nEhnwgnmKoFzb6LlCwdX973Em9bVOFh6MON36RPteYs,1275
|
|
474
|
-
portal/templates/portal/partials/footer.html,sha256=
|
|
474
|
+
portal/templates/portal/partials/footer.html,sha256=dC3eKqZ2e2gCnRsZwsFNWVEOwgwQtXrO7yfwBuW63hk,5628
|
|
475
475
|
portal/templates/portal/partials/header.html,sha256=LJ8L_BV3DPAfpxAZlhH35MHNd5_bC3NhSVW2zAeonGU,20834
|
|
476
476
|
portal/templates/portal/partials/headline.html,sha256=xKb-WtkT0FyQqT0smyNSKkXFvU7KQ5Czad8ca6YKQg4,89
|
|
477
477
|
portal/templates/portal/partials/hero_card.html,sha256=UN5hyxrw-McuXFsDzPBIECB9yGrv9VjsFf8SEBowwXc,786
|
|
@@ -541,7 +541,7 @@ portal/tests/test_emails.py,sha256=pLr06j3uMBxP1raoZQWzUTBVFvsEDFtUh85J8OnqCwE,9
|
|
|
541
541
|
portal/tests/test_global_forms.py,sha256=GIm_oSN4VsfaO--E2SMRu8CwVraan0UBj-_LE_tu8w0,833
|
|
542
542
|
portal/tests/test_helper_methods.py,sha256=-SQCDZm2XUtyXGEp0CHIb_SSC9CPD-XOSnpnY8QclHk,890
|
|
543
543
|
portal/tests/test_independent_student.py,sha256=NrRjTEr6V4WXpCE74N8LYNVocvLSvddkjuo3dYpfAZc,27245
|
|
544
|
-
portal/tests/test_invite_teacher.py,sha256=
|
|
544
|
+
portal/tests/test_invite_teacher.py,sha256=hynk2p63URQocCS8N6-IQnQWkdzggQ-1jGDuWcRSY_Y,14902
|
|
545
545
|
portal/tests/test_middleware.py,sha256=HV7AdgWyvgrtPyDMw3np5YDbUstFpKif7Qqw-e8gs5s,8258
|
|
546
546
|
portal/tests/test_organisation.py,sha256=kCMUNzLN6EzaMUBcFkqXwnqLGgOuQxQWIHHt63nhqBs,7574
|
|
547
547
|
portal/tests/test_partials.py,sha256=cSLNLjdsriROjPxkZlM3HKD3CSKDuKgpaDIdL3fPyBs,1794
|
|
@@ -628,13 +628,13 @@ portal/views/student/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
628
628
|
portal/views/student/edit_account_details.py,sha256=Ba-3D_zzKbX5N01NG5qqBS0ud10B8D5SN8hOpLiGa9U,8468
|
|
629
629
|
portal/views/student/play.py,sha256=GMxk65bxWOe1Ds2kb6rvuOd1GoAtt5v_9AihLNXoUL0,8768
|
|
630
630
|
portal/views/teacher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
631
|
-
portal/views/teacher/dashboard.py,sha256=
|
|
631
|
+
portal/views/teacher/dashboard.py,sha256=Z5PGmyEmNSltCPMzADq2tUN66LHPVrdrtgdMgvPlrkk,28119
|
|
632
632
|
portal/views/teacher/teach.py,sha256=IXlvsJ_1pX7oSr2SMWdWWC7aCFDA38IgxAoHzy_HHp4,36850
|
|
633
633
|
portal/views/two_factor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
634
634
|
portal/views/two_factor/core.py,sha256=Lk32z2SN2Pg0rRkK-N-LXMvXC1kKKsH3l692kiSDQ4E,964
|
|
635
635
|
portal/views/two_factor/form.py,sha256=lnHNKI-BMlpncTuW3zUzjPaJJNuEra2I_nOam0eOKFY,257
|
|
636
636
|
portal/views/two_factor/profile.py,sha256=SHSg_xHccE5PtD-OfuOkYhREYz_er4bj5ro1RjJ88Yw,393
|
|
637
|
-
codeforlife_portal-8.
|
|
638
|
-
codeforlife_portal-8.
|
|
639
|
-
codeforlife_portal-8.
|
|
640
|
-
codeforlife_portal-8.
|
|
637
|
+
codeforlife_portal-8.7.1.dist-info/METADATA,sha256=obJATb8BqSwTeVWDexVXJ5bM8Rid-sCKkyCIjbRAwC0,12318
|
|
638
|
+
codeforlife_portal-8.7.1.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
|
|
639
|
+
codeforlife_portal-8.7.1.dist-info/top_level.txt,sha256=8e5pdsuIoTqEAMqpelHBjGjLbffcBtgOoggmd2q7nMw,41
|
|
640
|
+
codeforlife_portal-8.7.1.dist-info/RECORD,,
|
portal/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "8.
|
|
1
|
+
__version__ = "8.7.1"
|
|
Binary file
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
</div>
|
|
78
78
|
<div class="col-sm-6">
|
|
79
79
|
<h4>How you can contribute</h4>
|
|
80
|
-
<p>Today, there is a small dedicated team working full
|
|
80
|
+
<p>Today, there is a small dedicated team working full-time on Code
|
|
81
81
|
for Life. Our community, made up of dedicated teachers, tutors,
|
|
82
82
|
students, and volunteers, has played a vital part in Code For
|
|
83
83
|
Life's growth. The resources and games are aligned to the UK
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
<p><a href="{% url 'privacy_notice' %}">Privacy notice</a></p>
|
|
19
19
|
<p><a href="{% url 'terms' %}">Terms of use</a></p>
|
|
20
20
|
<p><a class="optanon-toggle-display">Cookie settings</a></p>
|
|
21
|
+
<p><a href="https://docs.codeforlife.education/">CFL community</a></p>
|
|
21
22
|
</div>
|
|
22
23
|
<div class="col-sm-4 col-xs-12">
|
|
23
24
|
<p><a href="{% url 'home-learning' %}">Home learning</a></p>
|
|
@@ -5,7 +5,7 @@ from uuid import uuid4
|
|
|
5
5
|
import pytest
|
|
6
6
|
from common.models import SchoolTeacherInvitation, Teacher
|
|
7
7
|
from common.tests.utils.classes import create_class_directly
|
|
8
|
-
from common.tests.utils.organisation import create_organisation_directly
|
|
8
|
+
from common.tests.utils.organisation import create_organisation_directly, join_teacher_to_organisation
|
|
9
9
|
from common.tests.utils.teacher import signup_teacher_directly
|
|
10
10
|
from django.contrib.messages import get_messages
|
|
11
11
|
from django.core import mail
|
|
@@ -149,8 +149,66 @@ class TestInviteTeacher(TestCase):
|
|
|
149
149
|
"access this page."
|
|
150
150
|
)
|
|
151
151
|
|
|
152
|
+
def test_invite_permissions(self):
|
|
153
|
+
# Create an admin and a standard teacher in the same school
|
|
154
|
+
admin_email, admin_password = signup_teacher_directly()
|
|
155
|
+
school = create_organisation_directly(admin_email)
|
|
156
|
+
create_class_directly(admin_email)
|
|
157
|
+
|
|
158
|
+
standard_email, standard_password = signup_teacher_directly()
|
|
159
|
+
join_teacher_to_organisation(standard_email, school.name)
|
|
160
|
+
create_class_directly(standard_email)
|
|
161
|
+
|
|
162
|
+
# Log in as standard teacher, try inviting a teacher, no invitation should be created
|
|
163
|
+
client = Client()
|
|
164
|
+
client.login(username=standard_email, password=standard_password)
|
|
165
|
+
|
|
166
|
+
dashboard_url = reverse("dashboard")
|
|
167
|
+
data = {
|
|
168
|
+
"teacher_first_name": "Valid",
|
|
169
|
+
"teacher_last_name": "Name",
|
|
170
|
+
"teacher_email": "new@teacher.com",
|
|
171
|
+
"invite_teacher": "",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
response = client.post(dashboard_url, data)
|
|
175
|
+
|
|
176
|
+
assert response.status_code == 200
|
|
177
|
+
messages = list(response.context["messages"])
|
|
178
|
+
assert len(messages) == 0
|
|
179
|
+
assert len(mail.outbox) == 0
|
|
180
|
+
client.logout()
|
|
181
|
+
|
|
182
|
+
assert not SchoolTeacherInvitation.objects.filter(invited_teacher_email="new@teacher.com").exists()
|
|
183
|
+
|
|
184
|
+
# Log in as admin teacher to invite a teacher
|
|
185
|
+
client.login(username=admin_email, password=admin_password)
|
|
186
|
+
|
|
187
|
+
dashboard_url = reverse("dashboard")
|
|
188
|
+
data = {
|
|
189
|
+
"teacher_first_name": "Valid",
|
|
190
|
+
"teacher_last_name": "Name",
|
|
191
|
+
"teacher_email": "new@teacher.com",
|
|
192
|
+
"invite_teacher": "",
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
client.post(dashboard_url, data)
|
|
196
|
+
client.logout()
|
|
197
|
+
|
|
198
|
+
assert SchoolTeacherInvitation.objects.filter(invited_teacher_email="new@teacher.com").exists()
|
|
199
|
+
invite = SchoolTeacherInvitation.objects.get(invited_teacher_email="new@teacher.com")
|
|
200
|
+
|
|
201
|
+
# Log in as standard teacher, try resending and deleting the invitation, both should fail
|
|
202
|
+
client.login(username=standard_email, password=standard_password)
|
|
203
|
+
|
|
204
|
+
response = client.post(reverse("resend_invite_teacher", kwargs={"token": invite.token}))
|
|
205
|
+
message = list(response.wsgi_request._messages)[0].message
|
|
206
|
+
assert message == "You do not have permission to perform this action or the invite does not exist"
|
|
207
|
+
|
|
208
|
+
response = client.post(reverse("delete_teacher_invite", kwargs={"token": invite.token}))
|
|
209
|
+
message = list(response.wsgi_request._messages)[0].message
|
|
210
|
+
assert message == "You do not have permission to perform this action or the invite does not exist"
|
|
152
211
|
|
|
153
|
-
class TestTeacherInviteAPI(TestCase):
|
|
154
212
|
def test_delete_exception(self):
|
|
155
213
|
email, password = signup_teacher_directly()
|
|
156
214
|
create_organisation_directly(email)
|
|
@@ -29,6 +29,7 @@ from django.utils import timezone
|
|
|
29
29
|
from django.views.decorators.http import require_POST
|
|
30
30
|
from game.level_management import levels_shared_with, unshare_level
|
|
31
31
|
from game.models import Level
|
|
32
|
+
from game.views.level_selection import is_admin_teacher
|
|
32
33
|
from two_factor.utils import devices_for_user
|
|
33
34
|
|
|
34
35
|
from portal.forms.invite_teacher import InviteTeacherForm
|
|
@@ -160,7 +161,7 @@ def dashboard_teacher_view(request, is_admin):
|
|
|
160
161
|
elif request.POST.get("show_onboarding_complete") == "1":
|
|
161
162
|
show_onboarding_complete = True
|
|
162
163
|
|
|
163
|
-
elif "invite_teacher" in request.POST:
|
|
164
|
+
elif "invite_teacher" in request.POST and is_admin:
|
|
164
165
|
invite_teacher_form = InviteTeacherForm(request.POST)
|
|
165
166
|
if invite_teacher_form.is_valid():
|
|
166
167
|
data = invite_teacher_form.cleaned_data
|
|
@@ -674,7 +675,7 @@ def delete_teacher_invite(request, token):
|
|
|
674
675
|
teacher = request.user.new_teacher
|
|
675
676
|
|
|
676
677
|
# auth the user before deletion
|
|
677
|
-
if invite is None or teacher.school != invite.school:
|
|
678
|
+
if invite is None or teacher.school != invite.school or not is_admin_teacher(request.user):
|
|
678
679
|
messages.error(
|
|
679
680
|
request,
|
|
680
681
|
"You do not have permission to perform this action or the invite does not exist",
|
|
@@ -698,7 +699,7 @@ def resend_invite_teacher(request, token):
|
|
|
698
699
|
teacher = request.user.new_teacher
|
|
699
700
|
|
|
700
701
|
# auth the user before deletion
|
|
701
|
-
if invite is None or teacher.school != invite.school:
|
|
702
|
+
if invite is None or teacher.school != invite.school or not is_admin_teacher(request.user):
|
|
702
703
|
messages.error(
|
|
703
704
|
request,
|
|
704
705
|
"You do not have permission to perform this action or the invite does not exist",
|
|
@@ -706,7 +707,7 @@ def resend_invite_teacher(request, token):
|
|
|
706
707
|
else:
|
|
707
708
|
invite.expiry = timezone.now() + timedelta(days=30)
|
|
708
709
|
invite.save()
|
|
709
|
-
teacher = Teacher.objects.filter(id=invite.from_teacher.id)
|
|
710
|
+
teacher = Teacher.objects.filter(id=invite.from_teacher.id)
|
|
710
711
|
|
|
711
712
|
messages.success(request, "Teacher re-invited!")
|
|
712
713
|
|
|
File without changes
|
{codeforlife_portal-8.6.12.dist-info → codeforlife_portal-8.7.1.dist-info}/licenses/LICENSE.md
RENAMED
|
File without changes
|
|
File without changes
|