rapid-router 5.18.0__py2.py3-none-any.whl → 7.6.8__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.
Files changed (170) hide show
  1. example_project/rapid_router_test_settings.py +19 -7
  2. example_project/settings.py +21 -8
  3. example_project/urls.py +5 -6
  4. game/__init__.py +1 -1
  5. game/admin.py +7 -2
  6. game/character.py +8 -0
  7. game/decor.py +40 -0
  8. game/end_to_end_tests/base_game_test.py +34 -27
  9. game/end_to_end_tests/editor_page.py +15 -0
  10. game/end_to_end_tests/game_page.py +88 -20
  11. game/end_to_end_tests/selenium_test_case.py +1 -20
  12. game/end_to_end_tests/test_cow_crashes.py +3 -5
  13. game/end_to_end_tests/test_level_editor.py +273 -10
  14. game/end_to_end_tests/test_level_selection.py +25 -3
  15. game/end_to_end_tests/test_play_through.py +222 -127
  16. game/end_to_end_tests/test_python_levels.py +41 -7
  17. game/end_to_end_tests/test_saving_workspace.py +2 -1
  18. game/forms.py +7 -1
  19. game/level_management.py +26 -11
  20. game/messages.py +899 -337
  21. game/migrations/0001_squashed_0025_levels_ordering_pt1.py +19 -1
  22. game/migrations/0026_levels_pt2.py +13 -2
  23. game/migrations/0032_cannot_turn_left_level.py +13 -2
  24. game/migrations/0033_recursion_level.py +13 -2
  25. game/migrations/0034_joes_level.py +13 -2
  26. game/migrations/0035_disable_route_score_level_70.py +0 -2
  27. game/migrations/0036_level_score_73.py +0 -2
  28. game/migrations/0037_level_score_79.py +0 -2
  29. game/migrations/0038_level_score_40.py +0 -1
  30. game/migrations/0042_level_score_73.py +0 -2
  31. game/migrations/0048_add_cow_field_and_blocks.py +0 -2
  32. game/migrations/0049_level_score_34.py +0 -2
  33. game/migrations/0050_level_score_40.py +0 -2
  34. game/migrations/0051_level_score_49.py +0 -1
  35. game/migrations/0086_loop_levels.py +13 -2
  36. game/migrations/0092_disable_algo_score_in_custom_levels.py +28 -0
  37. game/migrations/0093_alter_level_character_name.py +18 -0
  38. game/migrations/0094_add_hint_lesson_subtitle_to_levels.py +28 -0
  39. game/migrations/0095_level_commands.py +18 -0
  40. game/migrations/0096_alter_level_commands.py +18 -0
  41. game/migrations/0097_add_python_den_levels.py +1515 -0
  42. game/migrations/0098_add_episode_link_fields.py +44 -0
  43. game/migrations/0099_python_episodes_links.py +103 -0
  44. game/migrations/0100_reorder_python_levels.py +179 -0
  45. game/migrations/0101_rename_episodes.py +45 -0
  46. game/migrations/0102_reoder_episodes_13_14.py +136 -0
  47. game/migrations/0103_level_1015_solution.py +26 -0
  48. game/migrations/0104_remove_level_direct_drive.py +17 -0
  49. game/migrations/0105_delete_invalid_attempts.py +18 -0
  50. game/migrations/0106_fields_to_snake_case.py +48 -0
  51. game/migrations/0107_rename_worksheet_link_episode_student_worksheet_link.py +18 -0
  52. game/migrations/0108_episode_indy_worksheet_link.py +18 -0
  53. game/migrations/0109_create_episodes_23_and_24.py +99 -0
  54. game/migrations/0110_remove_episode_indy_worksheet_link_and_more.py +100 -0
  55. game/migrations/0111_create_worksheets.py +149 -0
  56. game/migrations/0112_worksheet_locked_classes.py +21 -0
  57. game/migrations/0113_level_needs_approval.py +18 -0
  58. game/migrations/0114_default_and_non_student_levels_no_approval.py +31 -0
  59. game/migrations/0115_level_level__default_does_not_need_approval.py +22 -0
  60. game/migrations/0116_update_worksheet_video_links.py +68 -0
  61. game/migrations/0117_update_solutions_to_if_else.py +61 -0
  62. game/models.py +127 -17
  63. game/permissions.py +51 -19
  64. game/python_den_urls.py +26 -0
  65. game/random_road.py +9 -9
  66. game/serializers.py +12 -17
  67. game/static/django_reverse_js/js/reverse.js +171 -0
  68. game/static/game/css/LilitaOne-Regular.ttf +0 -0
  69. game/static/game/css/backgrounds.css +8 -12
  70. game/static/game/css/dataTables.custom.css +3 -2
  71. game/static/game/css/editor.css +47 -0
  72. game/static/game/css/game.css +37 -43
  73. game/static/game/css/game_screen.css +16 -0
  74. game/static/game/css/level_editor.css +5 -0
  75. game/static/game/css/level_selection.css +17 -2
  76. game/static/game/image/Python_Den_hero_student.png +0 -0
  77. game/static/game/image/Python_levels_page.svg +1954 -0
  78. game/static/game/image/characters/front_view/Electric_van.svg +448 -0
  79. game/static/game/image/characters/top_view/Electric_van.svg +448 -0
  80. game/static/game/image/decor/city/solar_panel.svg +1200 -0
  81. game/static/game/image/decor/farm/solar_panel.svg +86 -0
  82. game/static/game/image/decor/grass/solar_panel.svg +86 -0
  83. game/static/game/image/decor/snow/solar_panel.svg +173 -0
  84. game/static/game/image/electric_van.svg +448 -0
  85. game/static/game/image/icons/description.svg +1 -0
  86. game/static/game/image/icons/hint.svg +1 -0
  87. game/static/game/image/icons/python.svg +1 -1
  88. game/static/game/image/pigeon.svg +684 -0
  89. game/static/game/image/python_den_header.svg +19 -0
  90. game/static/game/js/animation.js +65 -24
  91. game/static/game/js/blockly/msg/js/bg.js +52 -1
  92. game/static/game/js/blockly/msg/js/ca.js +52 -1
  93. game/static/game/js/blockly/msg/js/en-gb.js +2 -0
  94. game/static/game/js/blockly/msg/js/en.js +2 -0
  95. game/static/game/js/blockly/msg/js/es.js +52 -1
  96. game/static/game/js/blockly/msg/js/fr.js +2 -0
  97. game/static/game/js/blockly/msg/js/hi.js +2 -0
  98. game/static/game/js/blockly/msg/js/it.js +52 -1
  99. game/static/game/js/blockly/msg/js/pl.js +52 -1
  100. game/static/game/js/blockly/msg/js/pt-br.js +52 -1
  101. game/static/game/js/blockly/msg/js/ru.js +52 -1
  102. game/static/game/js/blockly/msg/js/ur.js +52 -1
  103. game/static/game/js/blocklyCustomBlocks.js +93 -52
  104. game/static/game/js/button.js +12 -0
  105. game/static/game/js/cow.js +11 -7
  106. game/static/game/js/drawing.js +68 -29
  107. game/static/game/js/editor.js +23 -0
  108. game/static/game/js/game.js +74 -110
  109. game/static/game/js/level_editor.js +646 -274
  110. game/static/game/js/level_moderation.js +33 -2
  111. game/static/game/js/level_selection.js +1 -1
  112. game/static/game/js/loadLanguages.js +2 -2
  113. game/static/game/js/model.js +32 -2
  114. game/static/game/js/pythonControl.js +14 -1
  115. game/static/game/js/scoreboard.js +0 -37
  116. game/static/game/js/scoreboardSharedLevels.js +48 -0
  117. game/static/game/js/skulpt/skulpt-stdlib.js +1 -1
  118. game/static/game/js/sound.js +52 -5
  119. game/static/game/raphael_image/characters/top_view/Electric_van.svg +448 -0
  120. game/static/game/raphael_image/decor/city/solar_panel.svg +1200 -0
  121. game/static/game/raphael_image/decor/farm/solar_panel.svg +86 -0
  122. game/static/game/raphael_image/decor/grass/solar_panel.svg +86 -0
  123. game/static/game/raphael_image/decor/snow/solar_panel.svg +173 -0
  124. game/static/game/raphael_image/pigeon.svg +685 -0
  125. game/static/game/sass/game.scss +2 -2
  126. game/static/game/sound/clown_horn.mp3 +0 -0
  127. game/static/game/sound/clown_horn.ogg +0 -0
  128. game/static/game/sound/electric_van_starting.mp3 +0 -0
  129. game/static/game/sound/electric_van_starting.ogg +0 -0
  130. game/static/game/sound/pigeon.mp3 +0 -0
  131. game/static/game/sound/pigeon.ogg +0 -0
  132. game/static/game/sound/sleigh_bells.mp3 +0 -0
  133. game/static/game/sound/sleigh_bells.ogg +0 -0
  134. game/static/game/sound/sleigh_crash.mp3 +0 -0
  135. game/static/game/sound/sleigh_crash.ogg +0 -0
  136. game/templates/game/base.html +34 -14
  137. game/templates/game/basenonav.html +11 -5
  138. game/templates/game/game.html +142 -38
  139. game/templates/game/level_editor.html +340 -236
  140. game/templates/game/level_moderation.html +19 -6
  141. game/templates/game/level_selection.html +18 -110
  142. game/templates/game/python_den_level_selection.html +291 -0
  143. game/templates/game/python_den_worksheet.html +101 -0
  144. game/templates/game/scoreboard.html +83 -64
  145. game/tests/test_level_editor.py +94 -26
  146. game/tests/test_level_selection.py +149 -46
  147. game/tests/test_python_den_worksheet.py +85 -0
  148. game/tests/test_scoreboard.py +34 -7
  149. game/tests/utils/level.py +32 -26
  150. game/theme.py +5 -5
  151. game/urls.py +199 -61
  152. game/views/language_code_conversions.py +86 -86
  153. game/views/level.py +155 -63
  154. game/views/level_editor.py +88 -55
  155. game/views/level_moderation.py +23 -0
  156. game/views/level_selection.py +116 -47
  157. game/views/level_solutions.py +491 -106
  158. game/views/scoreboard.py +76 -51
  159. game/views/worksheet.py +25 -0
  160. rapid_router-7.6.8.dist-info/METADATA +174 -0
  161. {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/RECORD +164 -104
  162. {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/WHEEL +1 -1
  163. example_project/manage.py +0 -10
  164. game/static/game/image/actions/go.svg +0 -18
  165. game/static/game/js/js-reverse.js +0 -14
  166. game/static/game/js/pqselect.min.js +0 -9
  167. game/static/game/js/widget-scroller.js +0 -906
  168. rapid_router-5.18.0.dist-info/METADATA +0 -17
  169. {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info/licenses}/LICENSE.md +0 -0
  170. {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/top_level.txt +0 -0
game/views/scoreboard.py CHANGED
@@ -1,9 +1,6 @@
1
- from __future__ import absolute_import
2
- from __future__ import division
1
+ from __future__ import absolute_import, division
3
2
 
4
- from builtins import map
5
- from builtins import next
6
- from builtins import object
3
+ from builtins import map, next, object
7
4
  from datetime import timedelta
8
5
 
9
6
  from common.models import Class, Teacher, Student
@@ -99,7 +96,7 @@ def student_row(levels_sorted, student, best_attempts):
99
96
  total_possible_score += max_score
100
97
 
101
98
  elapsed_time = attempt.elapsed_time()
102
- times.append(chop_miliseconds(elapsed_time))
99
+ times.append(chop_milliseconds(elapsed_time))
103
100
  # '-' is used to show that the student has started the level but has not submitted any attempts
104
101
 
105
102
  level_scores[level.id]["score"] = (
@@ -137,6 +134,10 @@ def to_name(level):
137
134
  return f"L{level.name}"
138
135
 
139
136
 
137
+ def to_python_name(level):
138
+ return f"L{int(level.name) - 1000}"
139
+
140
+
140
141
  def shared_level_to_name(level, user):
141
142
  return (
142
143
  f"{level.name} (you)"
@@ -145,14 +146,16 @@ def shared_level_to_name(level, user):
145
146
  )
146
147
 
147
148
 
148
- def scoreboard_data(episode_ids, attempts_per_students):
149
+ def scoreboard_data(episode_ids, attempts_per_students, language):
149
150
  # Show the total score, total time and score of each level
150
151
  levels_sorted = []
151
152
  for episode_id in episode_ids:
152
153
  episode = Episode.objects.get(id=episode_id)
153
154
  levels_sorted += episode.levels
154
155
 
155
- level_headers = list(map(to_name, levels_sorted))
156
+ to_name_function = to_name if language == "blockly" else to_python_name
157
+
158
+ level_headers = list(map(to_name_function, levels_sorted))
156
159
  student_data = [
157
160
  student_row(levels_sorted, student, best_attempts)
158
161
  for student, best_attempts in attempts_per_students.items()
@@ -238,6 +241,7 @@ def scoreboard_view(
238
241
  shared_headers,
239
242
  shared_level_headers,
240
243
  shared_student_data,
244
+ language,
241
245
  ):
242
246
  database_episodes = level_selection.fetch_episode_data(False)
243
247
 
@@ -256,11 +260,20 @@ def scoreboard_view(
256
260
  "shared_headers": shared_headers,
257
261
  "shared_level_headers": shared_level_headers,
258
262
  "shared_student_data": shared_student_data,
263
+ "language": language,
259
264
  },
260
265
  )
261
266
 
262
267
 
263
- def scoreboard(request):
268
+ def blockly_scoreboard(request):
269
+ return scoreboard(request, "blockly")
270
+
271
+
272
+ def python_scoreboard(request):
273
+ return scoreboard(request, "python")
274
+
275
+
276
+ def scoreboard(request, language):
264
277
  """
265
278
  Renders a page with students' scores. A teacher can see the visible classes in
266
279
  their school. Student's view is restricted to their class if their teacher enabled
@@ -271,8 +284,11 @@ def scoreboard(request):
271
284
 
272
285
  user = User(request.user.userprofile)
273
286
  users_classes = classes_for(user)
287
+
288
+ episodes_range = range(1, 10) if language == "blockly" else range(12, 16)
289
+
274
290
  all_episode_ids = [
275
- episode.id for episode in Episode.objects.filter(in_development=False)
291
+ episode.id for episode in Episode.objects.filter(pk__in=episodes_range)
276
292
  ]
277
293
 
278
294
  if user.is_independent_student():
@@ -305,6 +321,7 @@ def scoreboard(request):
305
321
  form = ScoreboardForm(
306
322
  request.POST or None,
307
323
  classes=users_classes,
324
+ language=language,
308
325
  initial={
309
326
  "classes": class_ids,
310
327
  "episodes": episode_ids,
@@ -322,56 +339,63 @@ def scoreboard(request):
322
339
  all_levels += episode.levels
323
340
 
324
341
  attempts_per_student = {}
325
- attempts_per_student_shared_levels = {}
326
-
327
- if user.is_teacher():
328
- if user.teacher.is_admin:
329
- # Get all custom levels owned by non-admin teachers
330
- standard_teachers = Teacher.objects.filter(
331
- school=user.teacher.school, is_admin=False
332
- )
333
- for standard_teacher in standard_teachers:
334
- shared_levels += levels_owned_by(standard_teacher.new_user)
335
- else:
336
- # Get logged in teacher's custom levels
337
- shared_levels += levels_owned_by(request.user)
338
-
339
- # In all cases, get all admins' custom levels
340
- school_admins = Teacher.objects.filter(
341
- school=user.teacher.school, is_admin=True
342
- )
343
- for school_admin in school_admins:
344
- shared_levels += levels_owned_by(school_admin.new_user)
345
-
346
- elif user.is_student():
347
- shared_levels += levels_shared_with(request.user)
348
342
 
349
343
  for student in students:
350
344
  best_attempts = Attempt.objects.filter(
351
345
  level__in=all_levels, student=student, is_best_attempt=True
352
346
  ).select_related("level")
353
347
  attempts_per_student[student] = best_attempts
354
- shared_levels += levels_owned_by(student.new_user)
355
- best_attempts_shared_levels = Attempt.objects.filter(
356
- level__in=shared_levels, student=student, is_best_attempt=True
357
- ).select_related("level")
358
- attempts_per_student_shared_levels[
359
- student
360
- ] = best_attempts_shared_levels
361
348
 
362
349
  (student_data, headers, level_headers, levels_sorted) = scoreboard_data(
363
- episode_ids, attempts_per_student
350
+ episode_ids, attempts_per_student, language
364
351
  )
365
352
  improvement_data = get_improvement_data(attempts_per_student)
366
- (
367
- shared_headers,
368
- shared_level_headers,
369
- shared_student_data,
370
- ) = shared_levels_data(
371
- request.user.userprofile,
372
- shared_levels,
373
- attempts_per_student_shared_levels,
374
- )
353
+
354
+ shared_headers = shared_level_headers = shared_student_data = []
355
+
356
+ if language == "blockly":
357
+ attempts_per_student_shared_levels = {}
358
+
359
+ if user.is_teacher():
360
+ if user.teacher.is_admin:
361
+ # Get all custom levels owned by non-admin teachers
362
+ standard_teachers = Teacher.objects.filter(
363
+ school=user.teacher.school, is_admin=False
364
+ )
365
+ for standard_teacher in standard_teachers:
366
+ shared_levels += levels_owned_by(standard_teacher.new_user)
367
+ else:
368
+ # Get logged in teacher's custom levels
369
+ shared_levels += levels_owned_by(request.user)
370
+
371
+ # In all cases, get all admins' custom levels
372
+ school_admins = Teacher.objects.filter(
373
+ school=user.teacher.school, is_admin=True
374
+ )
375
+ for school_admin in school_admins:
376
+ shared_levels += levels_owned_by(school_admin.new_user)
377
+
378
+ elif user.is_student():
379
+ shared_levels += levels_shared_with(request.user)
380
+
381
+ for student in students:
382
+ shared_levels += levels_owned_by(student.new_user)
383
+ best_attempts_shared_levels = Attempt.objects.filter(
384
+ level__in=shared_levels, student=student, is_best_attempt=True
385
+ ).select_related("level")
386
+ attempts_per_student_shared_levels[
387
+ student
388
+ ] = best_attempts_shared_levels
389
+
390
+ (
391
+ shared_headers,
392
+ shared_level_headers,
393
+ shared_student_data,
394
+ ) = shared_levels_data(
395
+ request.user.userprofile,
396
+ shared_levels,
397
+ attempts_per_student_shared_levels,
398
+ )
375
399
 
376
400
  csv_export = "export" in request.POST
377
401
 
@@ -394,6 +418,7 @@ def scoreboard(request):
394
418
  shared_headers,
395
419
  shared_level_headers,
396
420
  shared_student_data,
421
+ language,
397
422
  )
398
423
 
399
424
 
@@ -485,7 +510,7 @@ def is_viewable(class_):
485
510
  return class_.classmates_data_viewable
486
511
 
487
512
 
488
- def chop_miliseconds(delta):
513
+ def chop_milliseconds(delta):
489
514
  return delta - timedelta(microseconds=delta.microseconds)
490
515
 
491
516
 
@@ -0,0 +1,25 @@
1
+ from __future__ import absolute_import, division
2
+
3
+ from django.shortcuts import render
4
+
5
+ from game import messages
6
+ from game.models import Worksheet
7
+ from .helper import renderError
8
+
9
+
10
+ def worksheet(request, worksheetId):
11
+ if request.user.is_anonymous:
12
+ return renderError(
13
+ request,
14
+ messages.no_permission_python_den_worksheet_title(),
15
+ messages.no_permission_python_den_worksheet_page(),
16
+ )
17
+
18
+ worksheet = Worksheet.objects.get(pk=worksheetId)
19
+ starter_code = messages.worksheet_starter_code()
20
+
21
+ return render(
22
+ request,
23
+ "game/python_den_worksheet.html",
24
+ context={"worksheet": worksheet, "starter_code": starter_code},
25
+ )
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: rapid-router
3
+ Version: 7.6.8
4
+ Classifier: Programming Language :: Python
5
+ Classifier: Programming Language :: Python :: 3.12
6
+ Classifier: Framework :: Django
7
+ License-File: LICENSE.md
8
+ Requires-Dist: asgiref==3.10.0; python_version >= "3.9"
9
+ Requires-Dist: asttokens==3.0.0; python_version >= "3.8"
10
+ Requires-Dist: certifi==2025.10.5; python_version >= "3.7"
11
+ Requires-Dist: cffi==2.0.0; platform_python_implementation != "PyPy"
12
+ Requires-Dist: cfl-common==8.9.8
13
+ Requires-Dist: charset-normalizer==3.4.4; python_version >= "3.7"
14
+ Requires-Dist: cryptography==44.0.1; python_version >= "3.7" and python_full_version not in "3.9.0, 3.9.1"
15
+ Requires-Dist: decorator==5.2.1; python_version >= "3.8"
16
+ Requires-Dist: diff-match-patch==20241021; python_version >= "3.7"
17
+ Requires-Dist: django==5.1.14; python_version >= "3.10"
18
+ Requires-Dist: django-countries==7.6.1
19
+ Requires-Dist: django-csp==3.8
20
+ Requires-Dist: django-formtools==2.5.1; python_version >= "3.8"
21
+ Requires-Dist: django-import-export==4.2.0; python_version >= "3.9"
22
+ Requires-Dist: django-otp==1.6.3; python_version >= "3.7"
23
+ Requires-Dist: django-phonenumber-field==8.3.0; python_version >= "3.9"
24
+ Requires-Dist: django-pipeline==4.0.0; python_version >= "3.9"
25
+ Requires-Dist: django-reverse-js==0.1.8; python_version >= "3.10"
26
+ Requires-Dist: django-two-factor-auth==1.17.0; python_version >= "3.8"
27
+ Requires-Dist: djangorestframework==3.16.0; python_version >= "3.9"
28
+ Requires-Dist: executing==2.2.1; python_version >= "3.8"
29
+ Requires-Dist: idna==3.11; python_version >= "3.8"
30
+ Requires-Dist: ipython==9.7.0; python_version >= "3.11"
31
+ Requires-Dist: ipython-pygments-lexers==1.1.1; python_version >= "3.8"
32
+ Requires-Dist: jedi==0.19.2; python_version >= "3.6"
33
+ Requires-Dist: libsass==0.23.0; python_version >= "3.8"
34
+ Requires-Dist: matplotlib-inline==0.2.1; python_version >= "3.9"
35
+ Requires-Dist: more-itertools==8.7.0; python_version >= "3.5"
36
+ Requires-Dist: numpy==2.3.4; python_version >= "3.11"
37
+ Requires-Dist: pandas==2.3.3; python_version >= "3.9"
38
+ Requires-Dist: parso==0.8.5; python_version >= "3.6"
39
+ Requires-Dist: pexpect==4.9.0; sys_platform != "win32" and sys_platform != "emscripten"
40
+ Requires-Dist: pgeocode==0.4.0; python_version >= "3.8"
41
+ Requires-Dist: prompt-toolkit==3.0.52; python_version >= "3.8"
42
+ Requires-Dist: ptyprocess==0.7.0
43
+ Requires-Dist: pure-eval==0.2.3
44
+ Requires-Dist: pycparser==2.23; implementation_name != "PyPy"
45
+ Requires-Dist: pygments==2.19.2; python_version >= "3.8"
46
+ Requires-Dist: pyhamcrest==2.0.2; python_version >= "3.5"
47
+ Requires-Dist: pyjwt==2.6.0; python_version >= "3.7"
48
+ Requires-Dist: pypng==0.20220715.0
49
+ Requires-Dist: python-dateutil==2.9.0.post0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
50
+ Requires-Dist: pytz==2025.2
51
+ Requires-Dist: qrcode==7.4.2; python_version >= "3.7"
52
+ Requires-Dist: requests==2.32.5; python_version >= "3.9"
53
+ Requires-Dist: setuptools==80.9.0; python_version >= "3.9"
54
+ Requires-Dist: six==1.17.0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
55
+ Requires-Dist: sqlparse==0.5.3; python_version >= "3.8"
56
+ Requires-Dist: stack-data==0.6.3
57
+ Requires-Dist: tablib==3.7.0; python_version >= "3.9"
58
+ Requires-Dist: traitlets==5.14.3; python_version >= "3.8"
59
+ Requires-Dist: typing-extensions==4.15.0; python_version >= "3.9"
60
+ Requires-Dist: tzdata==2025.2; python_version >= "2"
61
+ Requires-Dist: urllib3==2.5.0; python_version >= "3.9"
62
+ Requires-Dist: wcwidth==0.2.14; python_version >= "3.6"
63
+ Requires-Dist: wheel==0.45.1; python_version >= "3.8"
64
+ Provides-Extra: dev
65
+ Requires-Dist: amqp==5.3.1; python_version >= "3.6" and extra == "dev"
66
+ Requires-Dist: asgiref==3.10.0; python_version >= "3.9" and extra == "dev"
67
+ Requires-Dist: attrs==25.4.0; python_version >= "3.9" and extra == "dev"
68
+ Requires-Dist: billiard==4.2.2; python_version >= "3.7" and extra == "dev"
69
+ Requires-Dist: black==25.11.0; python_version >= "3.9" and extra == "dev"
70
+ Requires-Dist: boto3==1.36.14; python_version >= "3.8" and extra == "dev"
71
+ Requires-Dist: botocore==1.36.26; python_version >= "3.8" and extra == "dev"
72
+ Requires-Dist: celery[sqs]==5.4.0; python_version >= "3.8" and extra == "dev"
73
+ Requires-Dist: certifi==2025.10.5; python_version >= "3.7" and extra == "dev"
74
+ Requires-Dist: cffi==2.0.0; platform_python_implementation != "PyPy" and extra == "dev"
75
+ Requires-Dist: cfl-common==8.9.8; extra == "dev"
76
+ Requires-Dist: charset-normalizer==3.4.4; python_version >= "3.7" and extra == "dev"
77
+ Requires-Dist: click==8.3.0; python_version >= "3.10" and extra == "dev"
78
+ Requires-Dist: click-didyoumean==0.3.1; python_full_version >= "3.6.2" and extra == "dev"
79
+ Requires-Dist: click-plugins==1.1.1.2; extra == "dev"
80
+ Requires-Dist: click-repl==0.3.0; python_version >= "3.6" and extra == "dev"
81
+ Requires-Dist: codeforlife-portal==8.9.8; extra == "dev"
82
+ Requires-Dist: cryptography==44.0.1; (python_version >= "3.7" and python_full_version not in "3.9.0, 3.9.1") and extra == "dev"
83
+ Requires-Dist: diff-match-patch==20241021; python_version >= "3.7" and extra == "dev"
84
+ Requires-Dist: django==5.1.14; python_version >= "3.10" and extra == "dev"
85
+ Requires-Dist: django-classy-tags==4.1.0; python_version >= "3.8" and extra == "dev"
86
+ Requires-Dist: django-countries==7.6.1; extra == "dev"
87
+ Requires-Dist: django-csp==3.8; extra == "dev"
88
+ Requires-Dist: django-extensions==4.1; python_version >= "3.9" and extra == "dev"
89
+ Requires-Dist: django-formtools==2.5.1; python_version >= "3.8" and extra == "dev"
90
+ Requires-Dist: django-import-export==4.2.0; python_version >= "3.9" and extra == "dev"
91
+ Requires-Dist: django-otp==1.6.3; python_version >= "3.7" and extra == "dev"
92
+ Requires-Dist: django-phonenumber-field==8.3.0; python_version >= "3.9" and extra == "dev"
93
+ Requires-Dist: django-pipeline==4.0.0; python_version >= "3.9" and extra == "dev"
94
+ Requires-Dist: django-preventconcurrentlogins==0.8.2; extra == "dev"
95
+ Requires-Dist: django-ratelimit==3.0.1; python_version >= "3.4" and extra == "dev"
96
+ Requires-Dist: django-recaptcha==4.0.0; extra == "dev"
97
+ Requires-Dist: django-sekizai==4.1.0; python_version >= "3.8" and extra == "dev"
98
+ Requires-Dist: django-selenium-clean==1.0.1; extra == "dev"
99
+ Requires-Dist: django-test-migrations==1.4.0; (python_version >= "3.9" and python_version < "4.0") and extra == "dev"
100
+ Requires-Dist: django-treebeard==4.7.1; python_version >= "3.8" and extra == "dev"
101
+ Requires-Dist: django-two-factor-auth==1.17.0; python_version >= "3.8" and extra == "dev"
102
+ Requires-Dist: djangorestframework==3.16.0; python_version >= "3.9" and extra == "dev"
103
+ Requires-Dist: execnet==2.1.1; python_version >= "3.8" and extra == "dev"
104
+ Requires-Dist: gunicorn==23.0.0; python_version >= "3.7" and extra == "dev"
105
+ Requires-Dist: h11==0.16.0; python_version >= "3.8" and extra == "dev"
106
+ Requires-Dist: idna==3.11; python_version >= "3.8" and extra == "dev"
107
+ Requires-Dist: importlib-metadata==4.13.0; python_version >= "3.7" and extra == "dev"
108
+ Requires-Dist: iniconfig==2.3.0; python_version >= "3.10" and extra == "dev"
109
+ Requires-Dist: isort==7.0.0; python_full_version >= "3.10.0" and extra == "dev"
110
+ Requires-Dist: jmespath==1.0.1; python_version >= "3.7" and extra == "dev"
111
+ Requires-Dist: kombu[sqs]==5.6.0; python_version >= "3.9" and extra == "dev"
112
+ Requires-Dist: libsass==0.23.0; python_version >= "3.8" and extra == "dev"
113
+ Requires-Dist: markupsafe==3.0.3; python_version >= "3.9" and extra == "dev"
114
+ Requires-Dist: more-itertools==8.7.0; python_version >= "3.5" and extra == "dev"
115
+ Requires-Dist: mypy-extensions==1.1.0; python_version >= "3.8" and extra == "dev"
116
+ Requires-Dist: numpy==2.3.4; python_version >= "3.11" and extra == "dev"
117
+ Requires-Dist: outcome==1.3.0.post0; python_version >= "3.7" and extra == "dev"
118
+ Requires-Dist: packaging==25.0; python_version >= "3.8" and extra == "dev"
119
+ Requires-Dist: pandas==2.3.3; python_version >= "3.9" and extra == "dev"
120
+ Requires-Dist: pathspec==0.12.1; python_version >= "3.8" and extra == "dev"
121
+ Requires-Dist: pgeocode==0.4.0; python_version >= "3.8" and extra == "dev"
122
+ Requires-Dist: phonenumbers==8.12.12; extra == "dev"
123
+ Requires-Dist: pillow==12.0.0; python_version >= "3.10" and extra == "dev"
124
+ Requires-Dist: platformdirs==4.5.0; python_version >= "3.10" and extra == "dev"
125
+ Requires-Dist: pluggy==1.6.0; python_version >= "3.9" and extra == "dev"
126
+ Requires-Dist: prompt-toolkit==3.0.52; python_version >= "3.8" and extra == "dev"
127
+ Requires-Dist: psycopg2-binary==2.9.9; python_version >= "3.7" and extra == "dev"
128
+ Requires-Dist: pycparser==2.23; implementation_name != "PyPy" and extra == "dev"
129
+ Requires-Dist: pycurl==7.45.7; python_version >= "3.5" and extra == "dev"
130
+ Requires-Dist: pygments==2.19.2; python_version >= "3.8" and extra == "dev"
131
+ Requires-Dist: pyjwt==2.6.0; python_version >= "3.7" and extra == "dev"
132
+ Requires-Dist: pyopenssl==25.1.0; python_version >= "3.7" and extra == "dev"
133
+ Requires-Dist: pypng==0.20220715.0; extra == "dev"
134
+ Requires-Dist: pysocks==1.7.1; extra == "dev"
135
+ Requires-Dist: pytest==8.4.2; python_version >= "3.9" and extra == "dev"
136
+ Requires-Dist: pytest-django==4.8.0; python_version >= "3.8" and extra == "dev"
137
+ Requires-Dist: pytest-order==1.3.0; python_version >= "3.7" and extra == "dev"
138
+ Requires-Dist: pytest-xdist==3.8.0; python_version >= "3.9" and extra == "dev"
139
+ Requires-Dist: python-dateutil==2.9.0.post0; (python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3") and extra == "dev"
140
+ Requires-Dist: python-dotenv==1.0.1; python_version >= "3.8" and extra == "dev"
141
+ Requires-Dist: pytokens==0.3.0; python_version >= "3.8" and extra == "dev"
142
+ Requires-Dist: pytz==2025.2; extra == "dev"
143
+ Requires-Dist: pyyaml==6.0.2; python_version >= "3.8" and extra == "dev"
144
+ Requires-Dist: qrcode==7.4.2; python_version >= "3.7" and extra == "dev"
145
+ Requires-Dist: reportlab==4.4.2; (python_version >= "3.7" and python_version < "4") and extra == "dev"
146
+ Requires-Dist: requests==2.32.5; python_version >= "3.9" and extra == "dev"
147
+ Requires-Dist: requests-toolbelt==1.0.0; (python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3") and extra == "dev"
148
+ Requires-Dist: s3transfer==0.11.3; python_version >= "3.8" and extra == "dev"
149
+ Requires-Dist: selenium==4.29.0; python_version >= "3.9" and extra == "dev"
150
+ Requires-Dist: setuptools==80.9.0; python_version >= "3.9" and extra == "dev"
151
+ Requires-Dist: six==1.17.0; (python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3") and extra == "dev"
152
+ Requires-Dist: sniffio==1.3.1; python_version >= "3.7" and extra == "dev"
153
+ Requires-Dist: sortedcontainers==2.4.0; extra == "dev"
154
+ Requires-Dist: sqlparse==0.5.3; python_version >= "3.8" and extra == "dev"
155
+ Requires-Dist: tablib==3.7.0; python_version >= "3.9" and extra == "dev"
156
+ Requires-Dist: trio==0.32.0; python_version >= "3.10" and extra == "dev"
157
+ Requires-Dist: trio-websocket==0.12.2; python_version >= "3.8" and extra == "dev"
158
+ Requires-Dist: typing-extensions==4.15.0; python_version >= "3.9" and extra == "dev"
159
+ Requires-Dist: tzdata==2025.2; python_version >= "2" and extra == "dev"
160
+ Requires-Dist: urllib3==2.5.0; python_version >= "3.9" and extra == "dev"
161
+ Requires-Dist: uvicorn==0.38.0; python_version >= "3.9" and extra == "dev"
162
+ Requires-Dist: uvicorn-worker==0.2.0; python_version >= "3.8" and extra == "dev"
163
+ Requires-Dist: vine==5.1.0; python_version >= "3.6" and extra == "dev"
164
+ Requires-Dist: wcwidth==0.2.14; python_version >= "3.6" and extra == "dev"
165
+ Requires-Dist: websocket-client==1.9.0; python_version >= "3.9" and extra == "dev"
166
+ Requires-Dist: werkzeug==3.1.3; python_version >= "3.9" and extra == "dev"
167
+ Requires-Dist: wheel==0.45.1; python_version >= "3.8" and extra == "dev"
168
+ Requires-Dist: whitenoise==6.9.0; python_version >= "3.9" and extra == "dev"
169
+ Requires-Dist: wsproto==1.2.0; python_full_version >= "3.7.0" and extra == "dev"
170
+ Requires-Dist: zipp==3.23.0; python_version >= "3.9" and extra == "dev"
171
+ Dynamic: classifier
172
+ Dynamic: license-file
173
+ Dynamic: provides-extra
174
+ Dynamic: requires-dist