codeforlife-portal 8.0.1__py2.py3-none-any.whl → 8.0.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.

@@ -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
@@ -305,6 +303,8 @@ def teacher_edit_class(request, access_code):
305
303
  locked_levels = klass.locked_levels.all()
306
304
  locked_levels_ids = [locked_level.id for locked_level in locked_levels]
307
305
 
306
+ locked_worksheet_ids = [worksheet.id for worksheet in klass.locked_worksheets.all()]
307
+
308
308
  form = ClassEditForm(
309
309
  initial={
310
310
  "name": klass.name,
@@ -338,6 +338,7 @@ def teacher_edit_class(request, access_code):
338
338
  "blockly_episodes": blockly_episodes,
339
339
  "python_episodes": python_episodes,
340
340
  "locked_levels": locked_levels_ids,
341
+ "locked_worksheet_ids": locked_worksheet_ids,
341
342
  "class": klass,
342
343
  "external_requests_message": external_requests_message,
343
344
  },
@@ -391,7 +392,7 @@ def process_edit_class_form(request, klass, form):
391
392
  return HttpResponseRedirect(reverse_lazy("view_class", kwargs={"access_code": klass.access_code}))
392
393
 
393
394
 
394
- def process_level_control_form(request, klass, blockly_episodes, python_episodes):
395
+ def process_level_control_form(request, klass: Class, blockly_episodes, python_episodes):
395
396
  """
396
397
  Find the levels that the user wants to lock and lock them for the specific class.
397
398
  :param request: The request sent by the user submitting the form.
@@ -401,12 +402,16 @@ def process_level_control_form(request, klass, blockly_episodes, python_episodes
401
402
  :return: A redirect to the teacher dashboard with a success message.
402
403
  """
403
404
  levels_to_lock_ids = []
405
+ locked_worksheet_ids = []
404
406
 
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)
407
+ mark_levels_to_lock_in_episodes(request, blockly_episodes, levels_to_lock_ids, locked_worksheet_ids)
408
+ mark_levels_to_lock_in_episodes(request, python_episodes, levels_to_lock_ids, locked_worksheet_ids)
407
409
 
408
410
  klass.locked_levels.clear()
409
411
  [klass.locked_levels.add(levels_to_lock_id) for levels_to_lock_id in levels_to_lock_ids]
412
+ klass.locked_worksheets.clear()
413
+ for locked_worksheet_id in locked_worksheet_ids:
414
+ klass.locked_worksheets.add(locked_worksheet_id)
410
415
 
411
416
  messages.success(request, "Your level preferences have been saved.")
412
417
  activity_today = DailyActivity.objects.get_or_create(date=datetime.now().date())[0]
@@ -416,7 +421,7 @@ def process_level_control_form(request, klass, blockly_episodes, python_episodes
416
421
  return HttpResponseRedirect(reverse_lazy("dashboard"))
417
422
 
418
423
 
419
- def mark_levels_to_lock_in_episodes(request, episodes, levels_to_lock_ids):
424
+ def mark_levels_to_lock_in_episodes(request, episodes, levels_to_lock_ids, locked_worksheet_ids: list):
420
425
  """
421
426
  For a given set of Episodes, find which Levels are to be locked. This is done by checking the POST request data.
422
427
  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 +433,21 @@ def mark_levels_to_lock_in_episodes(request, episodes, levels_to_lock_ids):
428
433
  for episode in episodes:
429
434
  episode_name = episode["name"]
430
435
  episode_levels = episode["levels"]
436
+ episode_worksheets = episode["worksheets"]
431
437
  if episode_name in request.POST:
432
438
  [
433
439
  levels_to_lock_ids.append(episode_level["id"])
434
440
  for episode_level in episode_levels
435
- if str(episode_level["id"]) not in request.POST.getlist(episode_name)
441
+ if f'level:{episode_level["id"]}' not in request.POST.getlist(episode_name)
436
442
  ]
443
+ for episode_worksheet in episode_worksheets:
444
+ worksheet_id = episode_worksheet["id"]
445
+ if f"worksheet:{worksheet_id}" not in request.POST.getlist(episode_name):
446
+ locked_worksheet_ids.append(worksheet_id)
437
447
  else:
438
448
  [levels_to_lock_ids.append(episode_level["id"]) for episode_level in episode_levels]
449
+ for episode_worksheet in episode_worksheets:
450
+ locked_worksheet_ids.append(episode_worksheet["id"])
439
451
 
440
452
 
441
453
  def process_move_class_form(request, klass, form):