codeforlife-portal 7.4.8__py2.py3-none-any.whl → 8.0.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.
- cfl_common/setup.py +10 -5
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/METADATA +7 -14
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/RECORD +30 -30
- deploy/middleware/security.py +4 -5
- example_project/portal_test_settings.py +1 -1
- example_project/settings.py +2 -2
- example_project/urls.py +4 -5
- portal/__init__.py +1 -1
- portal/admin.py +26 -24
- portal/forms/play.py +2 -2
- portal/forms/registration.py +2 -2
- portal/forms/teach.py +2 -2
- portal/helpers/decorators.py +18 -18
- portal/helpers/password.py +52 -54
- portal/templates/{captcha → django_recaptcha}/includes/js_v2_invisible.html +3 -3
- portal/templates/{captcha → django_recaptcha}/widget_v2_invisible.html +2 -2
- portal/templates/two_factor/core/login.html +66 -59
- portal/templates/two_factor/core/setup.html +58 -49
- portal/templates/two_factor/profile/profile.html +35 -17
- portal/tests/test_captcha_forms.py +2 -2
- portal/tests/test_middleware.py +32 -10
- portal/urls.py +94 -88
- portal/views/api.py +30 -16
- portal/views/home.py +45 -15
- portal/views/registration.py +3 -3
- portal/views/two_factor/core.py +22 -19
- portal/views/two_factor/profile.py +2 -2
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/WHEEL +0 -0
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/top_level.txt +0 -0
portal/views/home.py
CHANGED
|
@@ -33,9 +33,12 @@ 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
|
|
36
|
+
from portal.strings.ten_year_map import (
|
|
37
|
+
TEN_YEAR_MAP_BANNER,
|
|
38
|
+
TEN_YEAR_MAP_HEADLINE,
|
|
39
|
+
)
|
|
37
40
|
from portal.templatetags.app_tags import cloud_storage
|
|
38
|
-
from portal.views.teacher.teach import
|
|
41
|
+
from portal.views.teacher.teach import count_student_pack_downloads_click
|
|
39
42
|
|
|
40
43
|
LOGGER = logging.getLogger(__name__)
|
|
41
44
|
|
|
@@ -65,11 +68,15 @@ def render_signup_form(request):
|
|
|
65
68
|
invalid_form = False
|
|
66
69
|
|
|
67
70
|
teacher_signup_form = TeacherSignupForm(prefix="teacher_signup")
|
|
68
|
-
independent_student_signup_form = IndependentStudentSignupForm(
|
|
71
|
+
independent_student_signup_form = IndependentStudentSignupForm(
|
|
72
|
+
prefix="independent_student_signup"
|
|
73
|
+
)
|
|
69
74
|
|
|
70
75
|
if request.method == "POST":
|
|
71
76
|
if "teacher_signup-teacher_email" in request.POST:
|
|
72
|
-
teacher_signup_form = TeacherSignupForm(
|
|
77
|
+
teacher_signup_form = TeacherSignupForm(
|
|
78
|
+
request.POST, prefix="teacher_signup"
|
|
79
|
+
)
|
|
73
80
|
|
|
74
81
|
if not captcha.CAPTCHA_ENABLED:
|
|
75
82
|
remove_captcha_from_forms(teacher_signup_form)
|
|
@@ -123,11 +130,15 @@ def process_signup_form(request, data):
|
|
|
123
130
|
[email],
|
|
124
131
|
personalization_values={
|
|
125
132
|
"EMAIL": email,
|
|
126
|
-
"LOGIN_URL": request.build_absolute_uri(
|
|
133
|
+
"LOGIN_URL": request.build_absolute_uri(
|
|
134
|
+
reverse("teacher_login")
|
|
135
|
+
),
|
|
127
136
|
},
|
|
128
137
|
)
|
|
129
138
|
else:
|
|
130
|
-
LOGGER.warn(
|
|
139
|
+
LOGGER.warn(
|
|
140
|
+
f"Ratelimit teacher {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}"
|
|
141
|
+
)
|
|
131
142
|
else:
|
|
132
143
|
teacher = Teacher.objects.factory(
|
|
133
144
|
first_name=data["teacher_first_name"],
|
|
@@ -138,7 +149,9 @@ def process_signup_form(request, data):
|
|
|
138
149
|
|
|
139
150
|
send_verification_email(request, teacher.user.user, data)
|
|
140
151
|
|
|
141
|
-
TotalActivity.objects.update(
|
|
152
|
+
TotalActivity.objects.update(
|
|
153
|
+
teacher_registrations=F("teacher_registrations") + 1
|
|
154
|
+
)
|
|
142
155
|
|
|
143
156
|
return render(
|
|
144
157
|
request,
|
|
@@ -166,11 +179,15 @@ def process_independent_student_signup_form(request, data):
|
|
|
166
179
|
[email],
|
|
167
180
|
personalization_values={
|
|
168
181
|
"EMAIL": email,
|
|
169
|
-
"LOGIN_URL": request.build_absolute_uri(
|
|
182
|
+
"LOGIN_URL": request.build_absolute_uri(
|
|
183
|
+
reverse("independent_student_login")
|
|
184
|
+
),
|
|
170
185
|
},
|
|
171
186
|
)
|
|
172
187
|
else:
|
|
173
|
-
LOGGER.warning(
|
|
188
|
+
LOGGER.warning(
|
|
189
|
+
f"Ratelimit independent {RATELIMIT_USER_ALREADY_REGISTERED_EMAIL_GROUP}: {email}"
|
|
190
|
+
)
|
|
174
191
|
return render(
|
|
175
192
|
request,
|
|
176
193
|
"portal/email_verification_needed.html",
|
|
@@ -188,7 +205,9 @@ def process_independent_student_signup_form(request, data):
|
|
|
188
205
|
|
|
189
206
|
send_verification_email(request, student.new_user, data, age=age)
|
|
190
207
|
|
|
191
|
-
TotalActivity.objects.update(
|
|
208
|
+
TotalActivity.objects.update(
|
|
209
|
+
independent_registrations=F("independent_registrations") + 1
|
|
210
|
+
)
|
|
192
211
|
|
|
193
212
|
return render(
|
|
194
213
|
request,
|
|
@@ -199,7 +218,10 @@ def process_independent_student_signup_form(request, data):
|
|
|
199
218
|
|
|
200
219
|
|
|
201
220
|
def is_developer(request):
|
|
202
|
-
return
|
|
221
|
+
return (
|
|
222
|
+
hasattr(request.user, "userprofile")
|
|
223
|
+
and request.user.userprofile.developer
|
|
224
|
+
)
|
|
203
225
|
|
|
204
226
|
|
|
205
227
|
def redirect_teacher_to_correct_page(request, teacher):
|
|
@@ -229,10 +251,14 @@ def home(request):
|
|
|
229
251
|
# tests where the first Selenium test passes, but any following test
|
|
230
252
|
# fails because it cannot find the Maintenance banner instance.
|
|
231
253
|
try:
|
|
232
|
-
maintenance_banner = DynamicElement.objects.get(
|
|
254
|
+
maintenance_banner = DynamicElement.objects.get(
|
|
255
|
+
name="Maintenance banner"
|
|
256
|
+
)
|
|
233
257
|
|
|
234
258
|
if maintenance_banner.active:
|
|
235
|
-
messages.info(
|
|
259
|
+
messages.info(
|
|
260
|
+
request, format_html(maintenance_banner.text), extra_tags="safe"
|
|
261
|
+
)
|
|
236
262
|
except ObjectDoesNotExist:
|
|
237
263
|
pass
|
|
238
264
|
|
|
@@ -250,7 +276,9 @@ def home(request):
|
|
|
250
276
|
|
|
251
277
|
|
|
252
278
|
def coding_club(request):
|
|
253
|
-
return render(
|
|
279
|
+
return render(
|
|
280
|
+
request, "portal/coding_club.html", {"BANNER": CODING_CLUB_BANNER}
|
|
281
|
+
)
|
|
254
282
|
|
|
255
283
|
|
|
256
284
|
def download_student_pack(request, student_pack_type):
|
|
@@ -269,7 +297,9 @@ def home_learning(request):
|
|
|
269
297
|
|
|
270
298
|
|
|
271
299
|
def ten_year_map_page(request):
|
|
272
|
-
messages.info(
|
|
300
|
+
messages.info(
|
|
301
|
+
request, "This page is currently under construction.", extra_tags="safe"
|
|
302
|
+
)
|
|
273
303
|
return render(
|
|
274
304
|
request,
|
|
275
305
|
"portal/ten_year_map.html",
|
portal/views/registration.py
CHANGED
|
@@ -21,9 +21,9 @@ from django.http import HttpResponseRedirect
|
|
|
21
21
|
from django.shortcuts import render
|
|
22
22
|
from django.template.response import TemplateResponse
|
|
23
23
|
from django.urls import reverse_lazy
|
|
24
|
-
from django.utils.encoding import
|
|
24
|
+
from django.utils.encoding import force_bytes
|
|
25
25
|
from django.utils.http import urlsafe_base64_decode
|
|
26
|
-
from django.utils.translation import
|
|
26
|
+
from django.utils.translation import gettext as _
|
|
27
27
|
from django.views.decorators.cache import never_cache
|
|
28
28
|
from django.views.decorators.csrf import csrf_protect
|
|
29
29
|
from django.views.decorators.debug import sensitive_post_parameters
|
|
@@ -202,7 +202,7 @@ def password_reset_confirm(
|
|
|
202
202
|
check_uidb64(uidb64, token)
|
|
203
203
|
|
|
204
204
|
try:
|
|
205
|
-
uid =
|
|
205
|
+
uid = force_bytes(urlsafe_base64_decode(uidb64))
|
|
206
206
|
user = UserModel._default_manager.get(pk=uid)
|
|
207
207
|
except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
|
|
208
208
|
user = None
|
portal/views/two_factor/core.py
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import common.permissions as permissions
|
|
2
|
+
from django.contrib.auth.decorators import user_passes_test
|
|
3
|
+
from django.urls import reverse_lazy
|
|
4
|
+
from django.utils.decorators import method_decorator
|
|
5
|
+
from django.views.decorators.cache import never_cache
|
|
6
|
+
from two_factor.forms import MethodForm
|
|
7
7
|
from two_factor.views.core import SetupView
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def login_required(function=None):
|
|
11
|
+
"""
|
|
12
|
+
Decorator for views that checks that the user is logged in, redirecting
|
|
13
|
+
to the log-in page if necessary.
|
|
14
|
+
"""
|
|
15
|
+
actual_decorator = user_passes_test(
|
|
16
|
+
permissions.logged_in_as_teacher,
|
|
17
|
+
login_url=reverse_lazy("teacher_login"),
|
|
18
|
+
)
|
|
19
|
+
if function:
|
|
20
|
+
return actual_decorator(function)
|
|
21
|
+
return actual_decorator
|
|
22
|
+
|
|
23
|
+
|
|
10
24
|
# This custom class gets rid of the 'welcome' step of 2FA
|
|
11
25
|
# which the new design not needs any more
|
|
26
|
+
@method_decorator([never_cache, login_required], name="dispatch")
|
|
12
27
|
class CustomSetupView(SetupView):
|
|
13
|
-
form_list = (
|
|
14
|
-
("generator", TOTPDeviceForm),
|
|
15
|
-
("method", MethodForm),
|
|
16
|
-
("validation", DeviceValidationForm),
|
|
17
|
-
("yubikey", YubiKeyDeviceForm),
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
condition_dict = {
|
|
21
|
-
"call": lambda self: self.get_method() == "call",
|
|
22
|
-
"sms": lambda self: self.get_method() == "sms",
|
|
23
|
-
"validation": lambda self: self.get_method() in ("sms", "call"),
|
|
24
|
-
"yubikey": lambda self: self.get_method() == "yubikey",
|
|
25
|
-
}
|
|
28
|
+
form_list = (("method", MethodForm),)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
from django.utils.decorators import method_decorator
|
|
1
2
|
from django.views.decorators.cache import never_cache
|
|
2
3
|
from two_factor.views.profile import DisableView
|
|
3
|
-
from two_factor.views.utils import class_view_decorator
|
|
4
4
|
|
|
5
5
|
from .form import DisableForm
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
# This is not changed but imports the from form.py so it overwrites the disable 2FA form
|
|
9
|
-
@
|
|
9
|
+
@method_decorator(never_cache, name='dispatch')
|
|
10
10
|
class CustomDisableView(DisableView):
|
|
11
11
|
form_class = DisableForm
|
|
File without changes
|
|
File without changes
|
|
File without changes
|