codeforlife-portal 8.0.0__py2.py3-none-any.whl → 8.0.2__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of codeforlife-portal might be problematic. Click here for more details.

@@ -30,9 +30,7 @@ class TestClass(TestCase):
30
30
 
31
31
  c = Client()
32
32
 
33
- url = reverse(
34
- "teacher_delete_class", kwargs={"access_code": access_code}
35
- )
33
+ url = reverse("teacher_delete_class", kwargs={"access_code": access_code})
36
34
 
37
35
  # Login as another teacher, try to delete the class and check for 404
38
36
  c.login(username=email2, password=password2)
@@ -136,112 +134,138 @@ class TestClass(TestCase):
136
134
  old_daily_activity = DailyActivity(date=old_date)
137
135
  old_daily_activity.save()
138
136
 
139
- url = reverse(
140
- "teacher_edit_class", kwargs={"access_code": access_code1}
141
- )
137
+ url = reverse("teacher_edit_class", kwargs={"access_code": access_code1})
142
138
  # POST request data for locking only the first level
143
139
  data = {
144
140
  "Getting Started": [
145
- "2",
146
- "3",
147
- "4",
148
- "5",
149
- "6",
150
- "7",
151
- "8",
152
- "9",
153
- "10",
154
- "11",
155
- "12",
141
+ "level:2",
142
+ "level:3",
143
+ "level:4",
144
+ "level:5",
145
+ "level:6",
146
+ "level:7",
147
+ "level:8",
148
+ "level:9",
149
+ "level:10",
150
+ "level:11",
151
+ "level:12",
152
+ ],
153
+ "Shortest Route": [
154
+ "level:13",
155
+ "level:14",
156
+ "level:15",
157
+ "level:16",
158
+ "level:17",
159
+ "level:18",
156
160
  ],
157
- "Shortest Route": ["13", "14", "15", "16", "17", "18"],
158
161
  "Loops and Repetitions": [
159
- "19",
160
- "20",
161
- "21",
162
- "22",
163
- "23",
164
- "24",
165
- "25",
166
- "26",
167
- "27",
168
- "28",
162
+ "level:19",
163
+ "level:20",
164
+ "level:21",
165
+ "level:22",
166
+ "level:23",
167
+ "level:24",
168
+ "level:25",
169
+ "level:26",
170
+ "level:27",
171
+ "level:28",
172
+ ],
173
+ "Loops with Conditions": [
174
+ "level:29",
175
+ "level:30",
176
+ "level:31",
177
+ "level:32",
169
178
  ],
170
- "Loops with Conditions": ["29", "30", "31", "32"],
171
179
  "If... Only": [
172
- "33",
173
- "34",
174
- "35",
175
- "36",
176
- "37",
177
- "38",
178
- "39",
179
- "40",
180
- "41",
181
- "42",
182
- "43",
180
+ "level:33",
181
+ "level:34",
182
+ "level:35",
183
+ "level:36",
184
+ "level:37",
185
+ "level:38",
186
+ "level:39",
187
+ "level:40",
188
+ "level:41",
189
+ "level:42",
190
+ "level:43",
191
+ ],
192
+ "Traffic Lights": [
193
+ "level:44",
194
+ "level:45",
195
+ "level:46",
196
+ "level:47",
197
+ "level:48",
198
+ "level:49",
199
+ "level:50",
183
200
  ],
184
- "Traffic Lights": ["44", "45", "46", "47", "48", "49", "50"],
185
201
  "Limited Blocks": [
186
- "53",
187
- "78",
188
- "79",
189
- "80",
190
- "81",
191
- "82",
192
- "83",
193
- "84",
194
- "54",
195
- "55",
202
+ "level:53",
203
+ "level:78",
204
+ "level:79",
205
+ "level:80",
206
+ "level:81",
207
+ "level:82",
208
+ "level:83",
209
+ "level:84",
210
+ "level:54",
211
+ "level:55",
212
+ ],
213
+ "Procedures": [
214
+ # "level:85",
215
+ # "level:52",
216
+ # "level:60",
217
+ # "level:86",
218
+ # "level:62",
219
+ # "level:87",
220
+ # "level:61",
196
221
  ],
197
- "Procedures": ["85", "52", "60", "86", "62", "87", "61"],
198
222
  "Blockly Brain Teasers": [
199
- "56",
200
- "57",
201
- "58",
202
- "59",
203
- "88",
204
- "91",
205
- "90",
206
- "89",
207
- "110",
208
- "111",
209
- "112",
210
- "92",
223
+ "level:56",
224
+ "level:57",
225
+ "level:58",
226
+ "level:59",
227
+ "level:88",
228
+ "level:91",
229
+ "level:90",
230
+ "level:89",
231
+ "level:110",
232
+ "level:111",
233
+ "level:112",
234
+ "level:92",
211
235
  ],
212
236
  "Introduction to Python": [
213
- "93",
214
- "63",
215
- "64",
216
- "65",
217
- "94",
218
- "66",
219
- "67",
220
- "68",
221
- "95",
222
- "69",
223
- "96",
224
- "97",
237
+ "level:93",
238
+ "level:63",
239
+ "level:64",
240
+ "level:65",
241
+ "level:94",
242
+ "level:66",
243
+ "level:67",
244
+ "level:68",
245
+ "level:95",
246
+ "level:69",
247
+ "level:96",
248
+ "level:97",
225
249
  ],
226
250
  "Python": [
227
- "98",
228
- "70",
229
- "71",
230
- "73",
231
- "72",
232
- "99",
233
- "74",
234
- "75",
235
- "100",
236
- "101",
237
- "102",
238
- "103",
239
- "104",
240
- "105",
241
- "106",
242
- "107",
243
- "108",
244
- "109",
251
+ "level:98",
252
+ "level:70",
253
+ "level:71",
254
+ "level:73",
255
+ "level:72",
256
+ "level:99",
257
+ "level:74",
258
+ "level:75",
259
+ "level:100",
260
+ "level:101",
261
+ "level:102",
262
+ "level:103",
263
+ "level:104",
264
+ "level:105",
265
+ "level:106",
266
+ "level:107",
267
+ "level:108",
268
+ "level:109",
245
269
  ],
246
270
  "level_control_submit": "",
247
271
  }
@@ -261,118 +285,141 @@ class TestClass(TestCase):
261
285
  assert str(messages[0]) == "Your level preferences have been saved."
262
286
 
263
287
  # test the old analytic stays the same and the new one is incremented
264
- assert (
265
- DailyActivity.objects.get(date=old_date).level_control_submits == 0
266
- )
267
- assert (
268
- DailyActivity.objects.get(date=datetime.now()).level_control_submits
269
- == 1
270
- )
288
+ assert DailyActivity.objects.get(date=old_date).level_control_submits == 0
289
+ assert DailyActivity.objects.get(date=datetime.now()).level_control_submits == 1
271
290
 
272
291
  # Resubmitting to unlock level 1
273
292
  data = {
274
293
  "Getting Started": [
275
- "1",
276
- "2",
277
- "3",
278
- "4",
279
- "5",
280
- "6",
281
- "7",
282
- "8",
283
- "9",
284
- "10",
285
- "11",
286
- "12",
294
+ "level:1",
295
+ "level:2",
296
+ "level:3",
297
+ "level:4",
298
+ "level:5",
299
+ "level:6",
300
+ "level:7",
301
+ "level:8",
302
+ "level:9",
303
+ "level:10",
304
+ "level:11",
305
+ "level:12",
306
+ ],
307
+ "Shortest Route": [
308
+ "level:13",
309
+ "level:14",
310
+ "level:15",
311
+ "level:16",
312
+ "level:17",
313
+ "level:18",
287
314
  ],
288
- "Shortest Route": ["13", "14", "15", "16", "17", "18"],
289
315
  "Loops and Repetitions": [
290
- "19",
291
- "20",
292
- "21",
293
- "22",
294
- "23",
295
- "24",
296
- "25",
297
- "26",
298
- "27",
299
- "28",
316
+ "level:19",
317
+ "level:20",
318
+ "level:21",
319
+ "level:22",
320
+ "level:23",
321
+ "level:24",
322
+ "level:25",
323
+ "level:26",
324
+ "level:27",
325
+ "level:28",
326
+ ],
327
+ "Loops with Conditions": [
328
+ "level:29",
329
+ "level:30",
330
+ "level:31",
331
+ "level:32",
300
332
  ],
301
- "Loops with Conditions": ["29", "30", "31", "32"],
302
333
  "If... Only": [
303
- "33",
304
- "34",
305
- "35",
306
- "36",
307
- "37",
308
- "38",
309
- "39",
310
- "40",
311
- "41",
312
- "42",
313
- "43",
334
+ "level:33",
335
+ "level:34",
336
+ "level:35",
337
+ "level:36",
338
+ "level:37",
339
+ "level:38",
340
+ "level:39",
341
+ "level:40",
342
+ "level:41",
343
+ "level:42",
344
+ "level:43",
345
+ ],
346
+ "Traffic Lights": [
347
+ "level:44",
348
+ "level:45",
349
+ "level:46",
350
+ "level:47",
351
+ "level:48",
352
+ "level:49",
353
+ "level:50",
314
354
  ],
315
- "Traffic Lights": ["44", "45", "46", "47", "48", "49", "50"],
316
355
  "Limited Blocks": [
317
- "53",
318
- "78",
319
- "79",
320
- "80",
321
- "81",
322
- "82",
323
- "83",
324
- "84",
325
- "54",
326
- "55",
356
+ "level:53",
357
+ "level:78",
358
+ "level:79",
359
+ "level:80",
360
+ "level:81",
361
+ "level:82",
362
+ "level:83",
363
+ "level:84",
364
+ "level:54",
365
+ "level:55",
366
+ ],
367
+ "Procedures": [
368
+ # "level:85",
369
+ # "level:52",
370
+ # "level:60",
371
+ # "level:86",
372
+ # "level:62",
373
+ # "level:87",
374
+ # "level:61",
327
375
  ],
328
- "Procedures": ["85", "52", "60", "86", "62", "87", "61"],
329
376
  "Blockly Brain Teasers": [
330
- "56",
331
- "57",
332
- "58",
333
- "59",
334
- "88",
335
- "91",
336
- "90",
337
- "89",
338
- "110",
339
- "111",
340
- "112",
341
- "92",
377
+ "level:56",
378
+ "level:57",
379
+ "level:58",
380
+ "level:59",
381
+ "level:88",
382
+ "level:91",
383
+ "level:90",
384
+ "level:89",
385
+ "level:110",
386
+ "level:111",
387
+ "level:112",
388
+ "level:92",
342
389
  ],
343
390
  "Introduction to Python": [
344
- "93",
345
- "63",
346
- "64",
347
- "65",
348
- "94",
349
- "66",
350
- "67",
351
- "68",
352
- "95",
353
- "69",
354
- "96",
355
- "97",
391
+ "level:93",
392
+ "level:63",
393
+ "level:64",
394
+ "level:65",
395
+ "level:94",
396
+ "level:66",
397
+ "level:67",
398
+ "level:68",
399
+ "level:95",
400
+ "level:69",
401
+ "level:96",
402
+ "level:97",
356
403
  ],
357
404
  "Python": [
358
- "98",
359
- "70",
360
- "71",
361
- "73",
362
- "72",
363
- "99",
364
- "74",
365
- "75",
366
- "100",
367
- "101",
368
- "102",
369
- "103",
370
- "104",
371
- "105",
372
- "106",
373
- "107",
374
- "108",
375
- "109",
405
+ "level:98",
406
+ "level:70",
407
+ "level:71",
408
+ "level:73",
409
+ "level:72",
410
+ "level:99",
411
+ "level:74",
412
+ "level:75",
413
+ "level:100",
414
+ "level:101",
415
+ "level:102",
416
+ "level:103",
417
+ "level:104",
418
+ "level:105",
419
+ "level:106",
420
+ "level:107",
421
+ "level:108",
422
+ "level:109",
376
423
  ],
377
424
  "level_control_submit": "",
378
425
  }
@@ -406,9 +453,7 @@ class TestClass(TestCase):
406
453
 
407
454
  c = Client()
408
455
 
409
- url = reverse(
410
- "teacher_edit_class", kwargs={"access_code": access_code1}
411
- )
456
+ url = reverse("teacher_edit_class", kwargs={"access_code": access_code1})
412
457
  data = {"new_teacher": teacher2.id, "class_move_submit": ""}
413
458
 
414
459
  # Login as first teacher and transfer class to the second teacher
@@ -435,12 +480,7 @@ class TestClassFrontend(BaseTest):
435
480
  def test_create(self):
436
481
  email, password = signup_teacher_directly()
437
482
  create_organisation_directly(email)
438
- page = (
439
- self.go_to_homepage()
440
- .go_to_teacher_login_page()
441
- .login_no_class(email, password)
442
- .open_classes_tab()
443
- )
483
+ page = self.go_to_homepage().go_to_teacher_login_page().login_no_class(email, password).open_classes_tab()
444
484
 
445
485
  assert page.does_not_have_classes()
446
486
 
@@ -455,34 +495,19 @@ class TestClassFrontend(BaseTest):
455
495
  join_teacher_to_organisation(email2, school.name)
456
496
 
457
497
  # Check teacher 2 doesn't have any classes
458
- page = (
459
- self.go_to_homepage()
460
- .go_to_teacher_login_page()
461
- .login(email2, password2)
462
- .open_classes_tab()
463
- )
498
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email2, password2).open_classes_tab()
464
499
  assert page.does_not_have_classes()
465
500
  page.logout()
466
501
 
467
502
  # Log in as the first teacher and create a class for the second one
468
- page = (
469
- self.go_to_homepage()
470
- .go_to_teacher_login_page()
471
- .login(email1, password1)
472
- .open_classes_tab()
473
- )
503
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email1, password1).open_classes_tab()
474
504
  page, class_name = create_class(page, teacher_id=teacher2.id)
475
505
  page = TeachClassPage(page.browser)
476
506
  assert is_class_created_message_showing(self.selenium, class_name)
477
507
  page.logout()
478
508
 
479
509
  # Check teacher 2 now has the class
480
- page = (
481
- self.go_to_homepage()
482
- .go_to_teacher_login_page()
483
- .login(email2, password2)
484
- .open_classes_tab()
485
- )
510
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email2, password2).open_classes_tab()
486
511
  assert page.has_classes()
487
512
 
488
513
  def test_create_dashboard(self):
@@ -491,12 +516,7 @@ class TestClassFrontend(BaseTest):
491
516
  klass, name, access_code = create_class_directly(email)
492
517
  create_school_student_directly(access_code)
493
518
 
494
- page = (
495
- self.go_to_homepage()
496
- .go_to_teacher_login_page()
497
- .login(email, password)
498
- .open_classes_tab()
499
- )
519
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email, password).open_classes_tab()
500
520
 
501
521
  page, class_name = create_class(page)
502
522
 
@@ -512,12 +532,7 @@ class TestClassFrontend(BaseTest):
512
532
  klass_2, class_name_2, access_code_2 = create_class_directly(email_2)
513
533
  create_school_student_directly(access_code_2)
514
534
 
515
- page = (
516
- self.go_to_homepage()
517
- .go_to_teacher_login_page()
518
- .login(email_2, password_2)
519
- .open_classes_tab()
520
- )
535
+ page = self.go_to_homepage().go_to_teacher_login_page().login(email_2, password_2).open_classes_tab()
521
536
 
522
537
  page, class_name_3 = create_class(page)
523
538
 
@@ -33,14 +33,13 @@ from django.shortcuts import get_object_or_404, render
33
33
  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
+ from game.models import Level
36
37
  from game.views.level_selection import get_blockly_episodes, get_python_episodes
37
38
  from reportlab.lib.colors import black, red
38
39
  from reportlab.lib.pagesizes import A4
39
40
  from reportlab.lib.utils import ImageReader
40
41
  from reportlab.pdfgen import canvas
41
42
 
42
- from game.models import Level
43
-
44
43
  from portal.forms.teach import (
45
44
  BaseTeacherDismissStudentsFormSet,
46
45
  BaseTeacherMoveStudentsDisambiguationFormSet,
@@ -58,7 +57,6 @@ from portal.forms.teach import (
58
57
  from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
59
58
  from portal.views.registration import handle_reset_password_tracking
60
59
 
61
-
62
60
  STUDENT_PASSWORD_LENGTH = 6
63
61
  REMINDER_CARDS_PDF_ROWS = 8
64
62
  REMINDER_CARDS_PDF_COLUMNS = 1
@@ -391,7 +389,7 @@ def process_edit_class_form(request, klass, form):
391
389
  return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": klass.access_code}))
392
390
 
393
391
 
394
- def process_level_control_form(request, klass, blockly_episodes, python_episodes):
392
+ def process_level_control_form(request, klass: Class, blockly_episodes, python_episodes):
395
393
  """
396
394
  Find the levels that the user wants to lock and lock them for the specific class.
397
395
  :param request: The request sent by the user submitting the form.
@@ -401,12 +399,16 @@ def process_level_control_form(request, klass, blockly_episodes, python_episodes
401
399
  :return: A redirect to the teacher dashboard with a success message.
402
400
  """
403
401
  levels_to_lock_ids = []
402
+ locked_worksheet_ids = []
404
403
 
405
- mark_levels_to_lock_in_episodes(request, blockly_episodes, levels_to_lock_ids)
406
- mark_levels_to_lock_in_episodes(request, python_episodes, levels_to_lock_ids)
404
+ mark_levels_to_lock_in_episodes(request, blockly_episodes, levels_to_lock_ids, locked_worksheet_ids)
405
+ mark_levels_to_lock_in_episodes(request, python_episodes, levels_to_lock_ids, locked_worksheet_ids)
407
406
 
408
407
  klass.locked_levels.clear()
409
408
  [klass.locked_levels.add(levels_to_lock_id) for levels_to_lock_id in levels_to_lock_ids]
409
+ klass.locked_worksheets.clear()
410
+ for locked_worksheet_id in locked_worksheet_ids:
411
+ klass.locked_worksheets.add(locked_worksheet_id)
410
412
 
411
413
  messages.success(request, "Your level preferences have been saved.")
412
414
  activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
@@ -416,7 +418,7 @@ def process_level_control_form(request, klass, blockly_episodes, python_episodes
416
418
  return HttpResponseRedirect(reverse_lazy("dashboard"))
417
419
 
418
420
 
419
- def mark_levels_to_lock_in_episodes(request, episodes, levels_to_lock_ids):
421
+ def mark_levels_to_lock_in_episodes(request, episodes, levels_to_lock_ids, locked_worksheet_ids: list):
420
422
  """
421
423
  For a given set of Episodes, find which Levels are to be locked. This is done by checking the POST request data.
422
424
  If a Level ID is missing from the request.POST, it means it needs to be locked, and if the entire Episode is missing
@@ -428,14 +430,21 @@ def mark_levels_to_lock_in_episodes(request, episodes, levels_to_lock_ids):
428
430
  for episode in episodes:
429
431
  episode_name = episode["name"]
430
432
  episode_levels = episode["levels"]
433
+ episode_worksheets = episode["worksheets"]
431
434
  if episode_name in request.POST:
432
435
  [
433
436
  levels_to_lock_ids.append(episode_level["id"])
434
437
  for episode_level in episode_levels
435
- if str(episode_level["id"]) not in request.POST.getlist(episode_name)
438
+ if f'level:{episode_level["id"]}' not in request.POST.getlist(episode_name)
436
439
  ]
440
+ for episode_worksheet in episode_worksheets:
441
+ worksheet_id = episode_worksheet["id"]
442
+ if f"worksheet:{worksheet_id}" not in request.POST.getlist(episode_name):
443
+ locked_worksheet_ids.append(worksheet_id)
437
444
  else:
438
445
  [levels_to_lock_ids.append(episode_level["id"]) for episode_level in episode_levels]
446
+ for episode_worksheet in episode_worksheets:
447
+ locked_worksheet_ids.append(episode_worksheet["id"])
439
448
 
440
449
 
441
450
  def process_move_class_form(request, klass, form):