codeforlife-portal 7.4.9__py2.py3-none-any.whl → 8.0.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.
- cfl_common/setup.py +10 -5
- {codeforlife_portal-7.4.9.dist-info → codeforlife_portal-8.0.1.dist-info}/METADATA +7 -14
- {codeforlife_portal-7.4.9.dist-info → codeforlife_portal-8.0.1.dist-info}/RECORD +29 -29
- {codeforlife_portal-7.4.9.dist-info → codeforlife_portal-8.0.1.dist-info}/WHEEL +1 -1
- 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/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.9.dist-info → codeforlife_portal-8.0.1.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-7.4.9.dist-info → codeforlife_portal-8.0.1.dist-info}/top_level.txt +0 -0
portal/urls.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from common.permissions import teacher_verified
|
|
2
|
-
from django.conf.urls import include, url
|
|
3
2
|
from django.http import HttpResponse
|
|
4
|
-
from django.urls import path
|
|
3
|
+
from django.urls import include, path, re_path
|
|
5
4
|
from django.views.generic import RedirectView
|
|
6
5
|
from django.views.generic.base import TemplateView
|
|
7
6
|
from django.views.i18n import JavaScriptCatalog
|
|
8
7
|
from game.views.level import play_default_level
|
|
8
|
+
from two_factor.urls import urlpatterns as tf_urls
|
|
9
9
|
from two_factor.views import (
|
|
10
10
|
BackupTokensView,
|
|
11
11
|
ProfileView,
|
|
@@ -114,26 +114,28 @@ from portal.views.two_factor.profile import CustomDisableView
|
|
|
114
114
|
js_info_dict = {"packages": ("conf.locale",)}
|
|
115
115
|
|
|
116
116
|
two_factor_patterns = [
|
|
117
|
-
|
|
117
|
+
re_path(
|
|
118
118
|
r"^account/two_factor/setup/$", CustomSetupView.as_view(), name="setup"
|
|
119
119
|
),
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
re_path(
|
|
121
|
+
r"^account/two_factor/qrcode/$", QRGeneratorView.as_view(), name="qr"
|
|
122
|
+
),
|
|
123
|
+
re_path(
|
|
122
124
|
r"^account/two_factor/setup/complete/$",
|
|
123
125
|
SetupCompleteView.as_view(),
|
|
124
126
|
name="setup_complete",
|
|
125
127
|
),
|
|
126
|
-
|
|
128
|
+
re_path(
|
|
127
129
|
r"^account/two_factor/backup/tokens/$",
|
|
128
130
|
teacher_verified(BackupTokensView.as_view()),
|
|
129
131
|
name="backup_tokens",
|
|
130
132
|
),
|
|
131
|
-
|
|
133
|
+
re_path(
|
|
132
134
|
r"^account/two_factor/$",
|
|
133
135
|
teacher_verified(ProfileView.as_view()),
|
|
134
136
|
name="profile",
|
|
135
137
|
),
|
|
136
|
-
|
|
138
|
+
re_path(
|
|
137
139
|
r"^account/two_factor/disable/$",
|
|
138
140
|
teacher_verified(CustomDisableView.as_view()),
|
|
139
141
|
name="disable",
|
|
@@ -186,45 +188,45 @@ urlpatterns = [
|
|
|
186
188
|
]
|
|
187
189
|
),
|
|
188
190
|
),
|
|
189
|
-
|
|
191
|
+
re_path(
|
|
190
192
|
r"^favicon\.ico$",
|
|
191
193
|
RedirectView.as_view(
|
|
192
194
|
url="/static/portal/img/favicon.ico", permanent=True
|
|
193
195
|
),
|
|
194
196
|
),
|
|
195
|
-
|
|
197
|
+
re_path(
|
|
196
198
|
r"^administration/password_change/$",
|
|
197
199
|
AdminChangePasswordView.as_view(),
|
|
198
200
|
name="administration_password_change",
|
|
199
201
|
),
|
|
200
|
-
|
|
202
|
+
re_path(
|
|
201
203
|
r"^administration/password_change_done/$",
|
|
202
204
|
AdminChangePasswordDoneView.as_view(),
|
|
203
205
|
name="administration_password_change_done",
|
|
204
206
|
),
|
|
205
|
-
|
|
207
|
+
re_path(
|
|
206
208
|
r"^users/inactive/", InactiveUsersView.as_view(), name="inactive_users"
|
|
207
209
|
),
|
|
208
|
-
|
|
210
|
+
re_path(
|
|
209
211
|
r"^locked_out/$",
|
|
210
212
|
TemplateView.as_view(template_name="portal/locked_out.html"),
|
|
211
213
|
name="locked_out",
|
|
212
214
|
),
|
|
213
|
-
|
|
215
|
+
re_path(
|
|
214
216
|
r"^",
|
|
215
217
|
include((two_factor_patterns, "two_factor"), namespace="two_factor"),
|
|
216
218
|
),
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
re_path(r"^i18n/", include("django.conf.urls.i18n")),
|
|
220
|
+
re_path(r"^jsi18n/$", JavaScriptCatalog.as_view(), js_info_dict),
|
|
221
|
+
re_path(
|
|
220
222
|
r"^(?P<level_name>[A-Z0-9]+)/$",
|
|
221
223
|
play_default_level,
|
|
222
224
|
name="play_default_level",
|
|
223
225
|
),
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
re_path(r"^$", home, name="home"),
|
|
227
|
+
re_path(r"^home-learning", home_learning, name="home-learning"),
|
|
228
|
+
re_path(r"^register_form", register_view, name="register"),
|
|
229
|
+
re_path(
|
|
228
230
|
r"^login/teacher/$",
|
|
229
231
|
# The ratelimit decorator checks how often a POST request is performed on that view.
|
|
230
232
|
# It checks against the username value specifically. If the number of requests
|
|
@@ -238,7 +240,7 @@ urlpatterns = [
|
|
|
238
240
|
)(TeacherLoginView.as_view()),
|
|
239
241
|
name="teacher_login",
|
|
240
242
|
),
|
|
241
|
-
|
|
243
|
+
re_path(
|
|
242
244
|
rf"^login/student/(?P<access_code>{ACCESS_CODE_REGEX})/(?:(?P<login_type>classform)/)?$",
|
|
243
245
|
ratelimit(
|
|
244
246
|
group=RATELIMIT_LOGIN_GROUP,
|
|
@@ -250,17 +252,17 @@ urlpatterns = [
|
|
|
250
252
|
)(StudentLoginView.as_view()),
|
|
251
253
|
name="student_login",
|
|
252
254
|
),
|
|
253
|
-
|
|
255
|
+
re_path(
|
|
254
256
|
r"^login/student/$",
|
|
255
257
|
StudentClassCodeView.as_view(),
|
|
256
258
|
name="student_login_access_code",
|
|
257
259
|
),
|
|
258
|
-
|
|
260
|
+
re_path(
|
|
259
261
|
r"^u/(?P<user_id>[0-9]+)/(?P<login_id>[a-z0-9]+)/$",
|
|
260
262
|
student_direct_login,
|
|
261
263
|
name="student_direct_login",
|
|
262
264
|
),
|
|
263
|
-
|
|
265
|
+
re_path(
|
|
264
266
|
r"^login/independent/$",
|
|
265
267
|
ratelimit(
|
|
266
268
|
group=RATELIMIT_LOGIN_GROUP,
|
|
@@ -272,106 +274,110 @@ urlpatterns = [
|
|
|
272
274
|
)(IndependentStudentLoginView.as_view()),
|
|
273
275
|
name="independent_student_login",
|
|
274
276
|
),
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
277
|
+
re_path(r"^login_form", old_login_form_redirect, name="old_login_form"),
|
|
278
|
+
re_path(r"^logout/$", logout_view, name="logout_view"),
|
|
279
|
+
re_path(
|
|
278
280
|
r"^news_signup/$",
|
|
279
281
|
process_newsletter_form,
|
|
280
282
|
name="process_newsletter_form",
|
|
281
283
|
),
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
284
|
+
re_path(
|
|
285
|
+
r"^donate_signup/$", process_donate_form, name="process_donate_form"
|
|
286
|
+
),
|
|
287
|
+
re_path(r"^consent_form/$", dotmailer_consent_form, name="consent_form"),
|
|
288
|
+
re_path(
|
|
285
289
|
r"^verify_email/$",
|
|
286
290
|
TemplateView.as_view(
|
|
287
291
|
template_name="portal/email_verification_needed.html"
|
|
288
292
|
),
|
|
289
293
|
name="email_verification",
|
|
290
294
|
),
|
|
291
|
-
|
|
295
|
+
re_path(
|
|
292
296
|
rf"^verify_email/(?P<token>{JWT_REGEX})/$",
|
|
293
297
|
verify_email,
|
|
294
298
|
name="verify_email",
|
|
295
299
|
),
|
|
296
|
-
|
|
300
|
+
re_path(
|
|
297
301
|
r"^user/password/reset/student/$",
|
|
298
302
|
student_password_reset,
|
|
299
303
|
name="student_password_reset",
|
|
300
304
|
),
|
|
301
|
-
|
|
305
|
+
re_path(
|
|
302
306
|
r"^user/password/reset/teacher/$",
|
|
303
307
|
teacher_password_reset,
|
|
304
308
|
name="teacher_password_reset",
|
|
305
309
|
),
|
|
306
|
-
|
|
310
|
+
re_path(
|
|
307
311
|
r"^user/password/reset/done/$",
|
|
308
312
|
password_reset_done,
|
|
309
313
|
name="reset_password_email_sent",
|
|
310
314
|
),
|
|
311
|
-
|
|
315
|
+
re_path(
|
|
312
316
|
r"^user/password/reset/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$",
|
|
313
317
|
password_reset_check_and_confirm,
|
|
314
318
|
name="password_reset_check_and_confirm",
|
|
315
319
|
),
|
|
316
|
-
|
|
320
|
+
re_path(
|
|
317
321
|
r"^user/reset_screentime_warning/$",
|
|
318
322
|
reset_screentime_warning,
|
|
319
323
|
name="reset_screentime_warning",
|
|
320
324
|
),
|
|
321
|
-
|
|
325
|
+
re_path(
|
|
322
326
|
r"^user/reset_session_time/$",
|
|
323
327
|
lambda _: HttpResponse(status=204),
|
|
324
328
|
name="reset_session_time",
|
|
325
329
|
),
|
|
326
|
-
|
|
330
|
+
re_path(
|
|
327
331
|
r"^teacher/password/reset/complete/$",
|
|
328
332
|
TemplateView.as_view(template_name="portal/reset_password_done.html"),
|
|
329
333
|
name="password_reset_complete",
|
|
330
334
|
),
|
|
331
|
-
|
|
332
|
-
|
|
335
|
+
re_path(r"^teach/$", teach, name="teach"),
|
|
336
|
+
re_path(
|
|
333
337
|
r"^teach/onboarding-organisation/$",
|
|
334
338
|
organisation_manage,
|
|
335
339
|
name="onboarding-organisation",
|
|
336
340
|
),
|
|
337
|
-
|
|
341
|
+
re_path(
|
|
338
342
|
r"^teach/onboarding-classes",
|
|
339
343
|
teacher_onboarding_create_class,
|
|
340
344
|
name="onboarding-classes",
|
|
341
345
|
),
|
|
342
|
-
|
|
346
|
+
re_path(
|
|
343
347
|
rf"^teach/onboarding-class/(?P<access_code>{ACCESS_CODE_REGEX})$",
|
|
344
348
|
teacher_onboarding_edit_class,
|
|
345
349
|
name="onboarding-class",
|
|
346
350
|
),
|
|
347
|
-
|
|
351
|
+
re_path(
|
|
348
352
|
rf"^teach/onboarding-class/(?P<access_code>{ACCESS_CODE_REGEX})/print_reminder_cards/$",
|
|
349
353
|
teacher_print_reminder_cards,
|
|
350
354
|
name="teacher_print_reminder_cards",
|
|
351
355
|
),
|
|
352
|
-
|
|
356
|
+
re_path(
|
|
353
357
|
rf"^teach/onboarding-class/(?P<access_code>{ACCESS_CODE_REGEX})/download_csv/$",
|
|
354
358
|
teacher_download_csv,
|
|
355
359
|
name="teacher_download_csv",
|
|
356
360
|
),
|
|
357
|
-
|
|
361
|
+
re_path(
|
|
358
362
|
r"^invited_teacher/(?P<token>[0-9a-f]+)/$",
|
|
359
363
|
invited_teacher,
|
|
360
364
|
name="invited_teacher",
|
|
361
365
|
),
|
|
362
|
-
|
|
363
|
-
|
|
366
|
+
re_path(r"^play/$", play_landing_page, name="play"),
|
|
367
|
+
re_path(
|
|
364
368
|
r"^play/details/$",
|
|
365
369
|
SchoolStudentDashboard.as_view(),
|
|
366
370
|
name="student_details",
|
|
367
371
|
),
|
|
368
|
-
|
|
372
|
+
re_path(
|
|
369
373
|
r"^play/details/independent$",
|
|
370
374
|
IndependentStudentDashboard.as_view(),
|
|
371
375
|
name="independent_student_details",
|
|
372
376
|
),
|
|
373
|
-
|
|
374
|
-
|
|
377
|
+
re_path(
|
|
378
|
+
r"^play/account/$", student_edit_account, name="student_edit_account"
|
|
379
|
+
),
|
|
380
|
+
re_path(
|
|
375
381
|
r"^play/account/independent/$",
|
|
376
382
|
ratelimit(
|
|
377
383
|
group=RATELIMIT_LOGIN_GROUP,
|
|
@@ -383,136 +389,136 @@ urlpatterns = [
|
|
|
383
389
|
)(independentStudentEditAccountView),
|
|
384
390
|
name="independent_edit_account",
|
|
385
391
|
),
|
|
386
|
-
|
|
392
|
+
re_path(
|
|
387
393
|
r"^play/account/school_student/$",
|
|
388
394
|
SchoolStudentEditAccountView.as_view(),
|
|
389
395
|
name="school_student_edit_account",
|
|
390
396
|
),
|
|
391
|
-
|
|
397
|
+
re_path(
|
|
392
398
|
r"^play/join/$",
|
|
393
399
|
student_join_organisation,
|
|
394
400
|
name="student_join_organisation",
|
|
395
401
|
),
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
+
re_path(r"^about", about, name="about"),
|
|
403
|
+
re_path(r"^getinvolved", getinvolved, name="getinvolved"),
|
|
404
|
+
re_path(r"^contribute", contribute, name="contribute"),
|
|
405
|
+
re_path(r"^terms", terms, name="terms"),
|
|
406
|
+
re_path(r"^privacy-notice/$", privacy_notice, name="privacy_notice"),
|
|
407
|
+
re_path(
|
|
402
408
|
r"^privacy-policy/$", privacy_notice, name="privacy_policy"
|
|
403
409
|
), # Keeping this to route from old URL
|
|
404
|
-
|
|
405
|
-
|
|
410
|
+
re_path(r"^teach/dashboard/$", dashboard_manage, name="dashboard"),
|
|
411
|
+
re_path(
|
|
406
412
|
r"^teach/dashboard/kick/(?P<pk>[0-9]+)/$",
|
|
407
413
|
organisation_kick,
|
|
408
414
|
name="organisation_kick",
|
|
409
415
|
),
|
|
410
|
-
|
|
416
|
+
re_path(
|
|
411
417
|
r"^teach/dashboard/toggle_admin/(?P<pk>[0-9]+)/$",
|
|
412
418
|
organisation_toggle_admin,
|
|
413
419
|
name="organisation_toggle_admin",
|
|
414
420
|
),
|
|
415
|
-
|
|
421
|
+
re_path(
|
|
416
422
|
r"^teach/dashboard/disable_2FA/(?P<pk>[0-9]+)/$",
|
|
417
423
|
teacher_disable_2FA,
|
|
418
424
|
name="teacher_disable_2FA",
|
|
419
425
|
),
|
|
420
|
-
|
|
426
|
+
re_path(
|
|
421
427
|
r"^teach/dashboard/school/leave/$",
|
|
422
428
|
organisation_leave,
|
|
423
429
|
name="organisation_leave",
|
|
424
430
|
),
|
|
425
|
-
|
|
431
|
+
re_path(
|
|
426
432
|
r"^teach/dashboard/student/accept/(?P<pk>[0-9]+)/$",
|
|
427
433
|
teacher_accept_student_request,
|
|
428
434
|
name="teacher_accept_student_request",
|
|
429
435
|
),
|
|
430
|
-
|
|
436
|
+
re_path(
|
|
431
437
|
r"^teach/dashboard/student/reject/(?P<pk>[0-9]+)/$",
|
|
432
438
|
teacher_reject_student_request,
|
|
433
439
|
name="teacher_reject_student_request",
|
|
434
440
|
),
|
|
435
|
-
|
|
441
|
+
re_path(
|
|
436
442
|
rf"^teach/class/(?P<access_code>{ACCESS_CODE_REGEX})$",
|
|
437
443
|
teacher_view_class,
|
|
438
444
|
name="view_class",
|
|
439
445
|
),
|
|
440
|
-
|
|
446
|
+
re_path(
|
|
441
447
|
rf"^teach/class/delete/(?P<access_code>{ACCESS_CODE_REGEX})$",
|
|
442
448
|
teacher_delete_class,
|
|
443
449
|
name="teacher_delete_class",
|
|
444
450
|
),
|
|
445
|
-
|
|
451
|
+
re_path(
|
|
446
452
|
rf"^teach/class/(?P<access_code>{ACCESS_CODE_REGEX})/students/delete/$",
|
|
447
453
|
teacher_delete_students,
|
|
448
454
|
name="teacher_delete_students",
|
|
449
455
|
),
|
|
450
|
-
|
|
456
|
+
re_path(
|
|
451
457
|
rf"^teach/class/edit/(?P<access_code>{ACCESS_CODE_REGEX})$",
|
|
452
458
|
teacher_edit_class,
|
|
453
459
|
name="teacher_edit_class",
|
|
454
460
|
),
|
|
455
|
-
|
|
461
|
+
re_path(
|
|
456
462
|
r"^teach/class/student/edit/(?P<pk>[0-9]+)/$",
|
|
457
463
|
teacher_edit_student,
|
|
458
464
|
name="teacher_edit_student",
|
|
459
465
|
),
|
|
460
|
-
|
|
466
|
+
re_path(
|
|
461
467
|
rf"^teach/class/(?P<access_code>{ACCESS_CODE_REGEX})/password_reset/$",
|
|
462
468
|
teacher_class_password_reset,
|
|
463
469
|
name="teacher_class_password_reset",
|
|
464
470
|
),
|
|
465
|
-
|
|
471
|
+
re_path(
|
|
466
472
|
rf"^teach/class/(?P<access_code>{ACCESS_CODE_REGEX})/students/dismiss/$",
|
|
467
473
|
teacher_dismiss_students,
|
|
468
474
|
name="teacher_dismiss_students",
|
|
469
475
|
),
|
|
470
|
-
|
|
476
|
+
re_path(
|
|
471
477
|
rf"^teach/class/(?P<access_code>{ACCESS_CODE_REGEX})/students/move/$",
|
|
472
478
|
teacher_move_students,
|
|
473
479
|
name="teacher_move_students",
|
|
474
480
|
),
|
|
475
|
-
|
|
481
|
+
re_path(
|
|
476
482
|
r"^teach/dashboard/resend_invite/(?P<token>[0-9a-f]+)/$",
|
|
477
483
|
resend_invite_teacher,
|
|
478
484
|
name="resend_invite_teacher",
|
|
479
485
|
),
|
|
480
|
-
|
|
486
|
+
re_path(
|
|
481
487
|
r"^teach/dashboard/toggle_admin_invite/(?P<invite_id>[0-9]+)/$",
|
|
482
488
|
invite_toggle_admin,
|
|
483
489
|
name="invite_toggle_admin",
|
|
484
490
|
),
|
|
485
|
-
|
|
491
|
+
re_path(
|
|
486
492
|
r"^teach/dashboard/delete_teacher_invite/(?P<token>[0-9a-f]+)$",
|
|
487
493
|
delete_teacher_invite,
|
|
488
494
|
name="delete_teacher_invite",
|
|
489
495
|
),
|
|
490
|
-
|
|
496
|
+
re_path(
|
|
491
497
|
rf"^teach/class/(?P<access_code>{ACCESS_CODE_REGEX})/students/move/disambiguate/$",
|
|
492
498
|
teacher_move_students_to_class,
|
|
493
499
|
name="teacher_move_students_to_class",
|
|
494
500
|
),
|
|
495
|
-
|
|
496
|
-
|
|
501
|
+
re_path(r"^delete/account/$", delete_account, name="delete_account"),
|
|
502
|
+
re_path(
|
|
497
503
|
r"^schools/anonymise/(?P<start_id>\d+)/",
|
|
498
504
|
AnonymiseOrphanSchoolsView.as_view(),
|
|
499
505
|
name="anonymise_orphan_schools",
|
|
500
506
|
),
|
|
501
|
-
|
|
507
|
+
re_path(
|
|
502
508
|
r"^api/",
|
|
503
509
|
include(
|
|
504
510
|
[
|
|
505
|
-
|
|
511
|
+
re_path(
|
|
506
512
|
r"^registered/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/$",
|
|
507
513
|
registered_users,
|
|
508
514
|
name="registered-users",
|
|
509
515
|
),
|
|
510
|
-
|
|
516
|
+
re_path(
|
|
511
517
|
r"^lastconnectedsince/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/$",
|
|
512
518
|
last_connected_since,
|
|
513
519
|
name="last-connected-since",
|
|
514
520
|
),
|
|
515
|
-
|
|
521
|
+
re_path(
|
|
516
522
|
r"^userspercountry/(?P<country>(AF|AX|AL|DZ|AS|AD|AO|AI|AQ|AG|AR|AM|AW|AU|AT|AZ|BS|BH|BD|BB|BY|BE|BZ|BJ|BM|BT|BO|BQ|BA|BW|BV|BR|IO|BN|BG|BF|BI|KH|CM|CA|CV|KY|CF|TD|CL|CN|CX|CC|CO|KM|CG|CD|CK|CR|CI|HR|CU|CW|CY|CZ|DK|DJ|DM|DO|EC|EG|SV|GQ|ER|EE|ET|FK|FO|FJ|FI|FR|GF|PF|TF|GA|GM|GE|DE|GH|GI|GR|GL|GD|GP|GU|GT|GG|GN|GW|GY|HT|HM|VA|HN|HK|HU|IS|IN|ID|IR|IQ|IE|IM|IL|IT|JM|JP|JE|JO|KZ|KE|KI|KP|KR|KW|KG|LA|LV|LB|LS|LR|LY|LI|LT|LU|MO|MK|MG|MW|MY|MV|ML|MT|MH|MQ|MR|MU|YT|MX|FM|MD|MC|MN|ME|MS|MA|MZ|MM|NA|NR|NP|NL|NC|NZ|NI|NE|NG|NU|NF|MP|NO|OM|PK|PW|PS|PA|PG|PY|PE|PH|PN|PL|PT|PR|QA|RE|RO|RU|RW|BL|SH|KN|LC|MF|PM|VC|WS|SM|ST|SA|SN|RS|SC|SL|SG|SX|SK|SI|SB|SO|ZA|GS|SS|ES|LK|SD|SR|SJ|SZ|SE|CH|SY|TW|TJ|TZ|TH|TL|TG|TK|TO|TT|TN|TR|TM|TC|TV|UG|UA|AE|GB|US|UM|UY|UZ|VU|VE|VN|VG|VI|WF|EH|YE|ZM|ZW))/$",
|
|
517
523
|
number_users_per_country,
|
|
518
524
|
name="number_users_per_country",
|
|
@@ -520,16 +526,16 @@ urlpatterns = [
|
|
|
520
526
|
]
|
|
521
527
|
),
|
|
522
528
|
),
|
|
523
|
-
|
|
524
|
-
|
|
529
|
+
re_path(r"^codingClub/$", coding_club, name="codingClub"),
|
|
530
|
+
re_path(
|
|
525
531
|
r"^codingClub/(?P<student_pack_type>[3-4])/",
|
|
526
532
|
download_student_pack,
|
|
527
533
|
name="download_student_pack",
|
|
528
534
|
),
|
|
529
|
-
|
|
535
|
+
re_path(
|
|
530
536
|
r"^removeFakeAccounts/",
|
|
531
537
|
RemoveFakeAccounts.as_view(),
|
|
532
538
|
name="remove_fake_accounts",
|
|
533
539
|
),
|
|
534
|
-
|
|
540
|
+
re_path(r"^celebrate/", ten_year_map_page, name="celebrate"),
|
|
535
541
|
]
|
portal/views/api.py
CHANGED
|
@@ -5,16 +5,16 @@ import uuid
|
|
|
5
5
|
from common.models import Class, School, Student, Teacher, UserProfile
|
|
6
6
|
from django.contrib.auth.decorators import login_required
|
|
7
7
|
from django.contrib.auth.models import User
|
|
8
|
-
from django.db.models import Exists, OuterRef
|
|
9
8
|
from django.http import HttpRequest, HttpResponse
|
|
10
9
|
from django.utils import timezone
|
|
11
|
-
from portal.app_settings import IS_CLOUD_SCHEDULER_FUNCTION
|
|
12
10
|
from rest_framework import generics, permissions, serializers, status
|
|
13
11
|
from rest_framework.authentication import SessionAuthentication
|
|
14
12
|
from rest_framework.decorators import api_view
|
|
15
13
|
from rest_framework.response import Response
|
|
16
14
|
from rest_framework.reverse import reverse_lazy
|
|
17
15
|
|
|
16
|
+
from portal.app_settings import IS_CLOUD_SCHEDULER_FUNCTION
|
|
17
|
+
|
|
18
18
|
LOGGER = logging.getLogger(__name__)
|
|
19
19
|
THREE_YEARS_IN_DAYS = 1095
|
|
20
20
|
|
|
@@ -23,7 +23,11 @@ THREE_YEARS_IN_DAYS = 1095
|
|
|
23
23
|
@login_required(login_url=reverse_lazy("administration_login"))
|
|
24
24
|
def registered_users(request, year, month, day):
|
|
25
25
|
try:
|
|
26
|
-
nbr_reg = User.objects.filter(
|
|
26
|
+
nbr_reg = User.objects.filter(
|
|
27
|
+
date_joined__startswith=datetime.date(
|
|
28
|
+
int(year), int(month), int(day)
|
|
29
|
+
)
|
|
30
|
+
).count()
|
|
27
31
|
return Response(nbr_reg)
|
|
28
32
|
except ValueError:
|
|
29
33
|
return HttpResponse(status=404)
|
|
@@ -33,7 +37,9 @@ def registered_users(request, year, month, day):
|
|
|
33
37
|
@login_required(login_url=reverse_lazy("administration_login"))
|
|
34
38
|
def last_connected_since(request, year, month, day):
|
|
35
39
|
try:
|
|
36
|
-
nbr_active_users = User.objects.filter(
|
|
40
|
+
nbr_active_users = User.objects.filter(
|
|
41
|
+
last_login__gte=datetime.date(int(year), int(month), int(day))
|
|
42
|
+
).count()
|
|
37
43
|
return Response(nbr_active_users)
|
|
38
44
|
except ValueError:
|
|
39
45
|
return HttpResponse(status=404)
|
|
@@ -45,7 +51,9 @@ def number_users_per_country(request, country):
|
|
|
45
51
|
try:
|
|
46
52
|
nbr_reg = (
|
|
47
53
|
Teacher.objects.filter(school__country__exact=country).count()
|
|
48
|
-
+ Student.objects.filter(
|
|
54
|
+
+ Student.objects.filter(
|
|
55
|
+
class_field__teacher__school__country__exact=country
|
|
56
|
+
).count()
|
|
49
57
|
)
|
|
50
58
|
return Response(nbr_reg)
|
|
51
59
|
except ValueError:
|
|
@@ -67,7 +75,9 @@ class IsAdminOrGoogleAppEngine(permissions.IsAdminUser):
|
|
|
67
75
|
"""Checks whether the request is from a Google App Engine cron job."""
|
|
68
76
|
|
|
69
77
|
def has_permission(self, request: HttpRequest, view):
|
|
70
|
-
is_admin = super(IsAdminOrGoogleAppEngine, self).has_permission(
|
|
78
|
+
is_admin = super(IsAdminOrGoogleAppEngine, self).has_permission(
|
|
79
|
+
request, view
|
|
80
|
+
)
|
|
71
81
|
return IS_CLOUD_SCHEDULER_FUNCTION(request) or is_admin
|
|
72
82
|
|
|
73
83
|
|
|
@@ -107,7 +117,9 @@ def anonymise(user):
|
|
|
107
117
|
|
|
108
118
|
# if user is admin and the school does not have another admin, appoint another teacher as admin
|
|
109
119
|
if is_admin:
|
|
110
|
-
teachers = Teacher.objects.filter(school=school).order_by(
|
|
120
|
+
teachers = Teacher.objects.filter(school=school).order_by(
|
|
121
|
+
"new_user__last_name", "new_user__first_name"
|
|
122
|
+
)
|
|
111
123
|
if not teachers:
|
|
112
124
|
# no other teacher, anonymise the school
|
|
113
125
|
school.anonymise()
|
|
@@ -134,10 +146,14 @@ class InactiveUsersView(generics.ListAPIView):
|
|
|
134
146
|
"""
|
|
135
147
|
|
|
136
148
|
queryset = User.objects.filter(is_active=True) & (
|
|
137
|
-
User.objects.filter(
|
|
149
|
+
User.objects.filter(
|
|
150
|
+
last_login__lte=timezone.now()
|
|
151
|
+
- timezone.timedelta(days=THREE_YEARS_IN_DAYS)
|
|
152
|
+
)
|
|
138
153
|
| User.objects.filter(
|
|
139
154
|
last_login__isnull=True,
|
|
140
|
-
date_joined__lte=timezone.now()
|
|
155
|
+
date_joined__lte=timezone.now()
|
|
156
|
+
- timezone.timedelta(days=THREE_YEARS_IN_DAYS),
|
|
141
157
|
)
|
|
142
158
|
)
|
|
143
159
|
authentication_classes = (SessionAuthentication,)
|
|
@@ -156,6 +172,7 @@ class RemoveFakeAccounts(generics.ListAPIView):
|
|
|
156
172
|
"""
|
|
157
173
|
This API endpoint will delete suspicious accounts that have the same first and last name and who are not verified
|
|
158
174
|
"""
|
|
175
|
+
|
|
159
176
|
authentication_classes = (SessionAuthentication,)
|
|
160
177
|
serializer_class = InactiveUserSerializer
|
|
161
178
|
permission_classes = (IsAdminOrGoogleAppEngine,)
|
|
@@ -178,17 +195,14 @@ class AnonymiseOrphanSchoolsView(generics.ListAPIView):
|
|
|
178
195
|
|
|
179
196
|
def get(self, request: HttpRequest, start_id):
|
|
180
197
|
# Re-anonymise all inactive teachers so their schools (if necessary) and classes/students are anonymised
|
|
181
|
-
for teacher in Teacher._base_manager.filter(
|
|
198
|
+
for teacher in Teacher._base_manager.filter(
|
|
199
|
+
pk__gte=start_id, new_user__is_active=False
|
|
200
|
+
):
|
|
182
201
|
anonymise(teacher.new_user)
|
|
183
202
|
LOGGER.info(f"Anonymised teacher ID {teacher.pk}")
|
|
184
203
|
|
|
185
204
|
# Anonymise schools without any teachers
|
|
186
|
-
for school in School.objects.filter(
|
|
187
|
-
Exists(
|
|
188
|
-
Teacher._base_manager.filter(school=OuterRef("pk")),
|
|
189
|
-
negated=True,
|
|
190
|
-
)
|
|
191
|
-
):
|
|
205
|
+
for school in School.objects.filter(teacher_school__isnull=True):
|
|
192
206
|
school.anonymise()
|
|
193
207
|
|
|
194
208
|
return Response(status=status.HTTP_204_NO_CONTENT)
|