rapid-router 5.4.1__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 (354) hide show
  1. example_project/rapid_router_test_settings.py +164 -0
  2. example_project/settings.py +152 -0
  3. example_project/urls.py +15 -0
  4. example_project/{example_project/wsgi.py → wsgi.py} +2 -1
  5. game/__init__.py +1 -1
  6. game/admin.py +43 -4
  7. game/app_settings.py +3 -7
  8. game/character.py +26 -18
  9. game/decor.py +172 -97
  10. game/end_to_end_tests/base_game_test.py +44 -33
  11. game/end_to_end_tests/editor_page.py +17 -2
  12. game/end_to_end_tests/game_page.py +127 -45
  13. game/end_to_end_tests/selenium_test_case.py +1 -20
  14. game/end_to_end_tests/test_cow_crashes.py +3 -5
  15. game/end_to_end_tests/test_language_dropdown.py +14 -0
  16. game/end_to_end_tests/test_level_editor.py +290 -0
  17. game/end_to_end_tests/test_level_failures.py +1 -1
  18. game/end_to_end_tests/test_level_selection.py +79 -0
  19. game/end_to_end_tests/test_play_through.py +240 -102
  20. game/end_to_end_tests/test_python_levels.py +44 -13
  21. game/end_to_end_tests/test_saving_workspace.py +22 -0
  22. game/forms.py +9 -2
  23. game/level_management.py +38 -29
  24. game/messages.py +1218 -203
  25. game/migrations/0001_squashed_0025_levels_ordering_pt1.py +19 -1
  26. game/migrations/0026_levels_pt2.py +13 -2
  27. game/migrations/0032_cannot_turn_left_level.py +13 -2
  28. game/migrations/0033_recursion_level.py +13 -2
  29. game/migrations/0034_joes_level.py +13 -2
  30. game/migrations/0035_disable_route_score_level_70.py +0 -2
  31. game/migrations/0036_level_score_73.py +0 -2
  32. game/migrations/0037_level_score_79.py +0 -2
  33. game/migrations/0038_level_score_40.py +0 -1
  34. game/migrations/0042_level_score_73.py +0 -2
  35. game/migrations/0048_add_cow_field_and_blocks.py +0 -2
  36. game/migrations/0049_level_score_34.py +0 -2
  37. game/migrations/0050_level_score_40.py +0 -2
  38. game/migrations/0051_level_score_49.py +0 -1
  39. game/migrations/0076_level_locked_for_class.py +19 -0
  40. game/migrations/0077_alter_level_next_level.py +52 -0
  41. game/migrations/0078_add_block_types.py +23 -0
  42. game/migrations/0079_populate_block_type_add_cow_blocks.py +60 -0
  43. game/migrations/0080_level_disable_algorithm_score.py +18 -0
  44. game/migrations/0081_first_12_levels_no_algo_score.py +29 -0
  45. game/migrations/0082_level_43_solution.py +16 -0
  46. game/migrations/0083_add_cows_to_existing_levels.py +195 -0
  47. game/migrations/0084_alter_block_block_type.py +18 -0
  48. game/migrations/0085_add_new_blocks.py +53 -0
  49. game/migrations/0086_loop_levels.py +482 -0
  50. game/migrations/0087_workspace_python_view_enabled.py +18 -0
  51. game/migrations/0088_rename_episodes.py +35 -0
  52. game/migrations/0089_episodes_in_development.py +30 -0
  53. game/migrations/0090_add_missing_model_solutions.py +144 -0
  54. game/migrations/0091_disable_algo_score_if_no_model_solution.py +46 -0
  55. game/migrations/0092_disable_algo_score_in_custom_levels.py +28 -0
  56. game/migrations/0093_alter_level_character_name.py +18 -0
  57. game/migrations/0094_add_hint_lesson_subtitle_to_levels.py +28 -0
  58. game/migrations/0095_level_commands.py +18 -0
  59. game/migrations/0096_alter_level_commands.py +18 -0
  60. game/migrations/0097_add_python_den_levels.py +1515 -0
  61. game/migrations/0098_add_episode_link_fields.py +44 -0
  62. game/migrations/0099_python_episodes_links.py +103 -0
  63. game/migrations/0100_reorder_python_levels.py +179 -0
  64. game/migrations/0101_rename_episodes.py +45 -0
  65. game/migrations/0102_reoder_episodes_13_14.py +136 -0
  66. game/migrations/0103_level_1015_solution.py +26 -0
  67. game/migrations/0104_remove_level_direct_drive.py +17 -0
  68. game/migrations/0105_delete_invalid_attempts.py +18 -0
  69. game/migrations/0106_fields_to_snake_case.py +48 -0
  70. game/migrations/0107_rename_worksheet_link_episode_student_worksheet_link.py +18 -0
  71. game/migrations/0108_episode_indy_worksheet_link.py +18 -0
  72. game/migrations/0109_create_episodes_23_and_24.py +99 -0
  73. game/migrations/0110_remove_episode_indy_worksheet_link_and_more.py +100 -0
  74. game/migrations/0111_create_worksheets.py +149 -0
  75. game/migrations/0112_worksheet_locked_classes.py +21 -0
  76. game/migrations/0113_level_needs_approval.py +18 -0
  77. game/migrations/0114_default_and_non_student_levels_no_approval.py +31 -0
  78. game/migrations/0115_level_level__default_does_not_need_approval.py +22 -0
  79. game/migrations/0116_update_worksheet_video_links.py +68 -0
  80. game/migrations/0117_update_solutions_to_if_else.py +61 -0
  81. game/models.py +157 -16
  82. game/permissions.py +34 -19
  83. game/python_den_urls.py +26 -0
  84. game/random_road.py +43 -127
  85. game/serializers.py +12 -17
  86. game/static/django_reverse_js/js/reverse.js +171 -0
  87. game/static/game/css/LilitaOne-Regular.ttf +0 -0
  88. game/static/game/css/backgrounds.css +14 -10
  89. game/static/game/css/dataTables.custom.css +4 -2
  90. game/static/game/css/dataTables.jqueryui.css +561 -320
  91. game/static/game/css/editor.css +47 -0
  92. game/static/game/css/game.css +43 -49
  93. game/static/game/css/game_screen.css +116 -53
  94. game/static/game/css/jquery.dataTables.css +455 -251
  95. game/static/game/css/level_editor.css +10 -1
  96. game/static/game/css/level_selection.css +32 -3
  97. game/static/game/css/level_share.css +6 -5
  98. game/static/game/css/skulpt/codemirror.css +1 -0
  99. game/static/game/image/Python_Den_hero_student.png +0 -0
  100. game/static/game/image/Python_levels_page.svg +1954 -0
  101. game/static/game/image/characters/front_view/Electric_van.svg +448 -0
  102. game/static/game/image/characters/top_view/Electric_van.svg +448 -0
  103. game/static/game/image/characters/top_view/Sleigh.svg +436 -0
  104. game/static/game/image/decor/city/solar_panel.svg +1200 -0
  105. game/static/game/image/decor/farm/solar_panel.svg +86 -0
  106. game/static/game/image/decor/grass/solar_panel.svg +86 -0
  107. game/static/game/image/decor/snow/barn.svg +1788 -0
  108. game/static/game/image/decor/snow/cfc.svg +1050 -147
  109. game/static/game/image/decor/snow/crops.svg +7370 -0
  110. game/static/game/image/decor/snow/hospital.svg +1220 -0
  111. game/static/game/image/decor/snow/house1.svg +971 -0
  112. game/static/game/image/decor/snow/house2.svg +1574 -0
  113. game/static/game/image/decor/snow/school.svg +1071 -0
  114. game/static/game/image/decor/snow/shop.svg +3211 -0
  115. game/static/game/image/decor/snow/solar_panel.svg +173 -0
  116. game/static/game/image/electric_van.svg +448 -0
  117. game/static/game/image/icons/add_house.svg +26 -0
  118. game/static/game/image/icons/delete_house.svg +26 -0
  119. game/static/game/image/icons/description.svg +1 -0
  120. game/static/game/image/icons/hint.svg +1 -0
  121. game/static/game/image/icons/if_else.svg +3 -0
  122. game/static/game/image/icons/python.svg +1 -1
  123. game/static/game/image/if_else_example.png +0 -0
  124. game/static/game/image/pigeon.svg +684 -0
  125. game/static/game/image/python_den_header.svg +19 -0
  126. game/static/game/js/animation.js +84 -28
  127. game/static/game/js/blockly/msg/js/bg.js +52 -1
  128. game/static/game/js/blockly/msg/js/ca.js +52 -1
  129. game/static/game/js/blockly/msg/js/en-gb.js +52 -1
  130. game/static/game/js/blockly/msg/js/en.js +52 -1
  131. game/static/game/js/blockly/msg/js/es.js +52 -1
  132. game/static/game/js/blockly/msg/js/fr.js +52 -1
  133. game/static/game/js/blockly/msg/js/hi.js +52 -1
  134. game/static/game/js/blockly/msg/js/it.js +52 -1
  135. game/static/game/js/blockly/msg/js/pl.js +52 -1
  136. game/static/game/js/blockly/msg/js/pt-br.js +52 -1
  137. game/static/game/js/blockly/msg/js/ru.js +52 -1
  138. game/static/game/js/blockly/msg/js/ur.js +52 -1
  139. game/static/game/js/blocklyCompiler.js +550 -392
  140. game/static/game/js/blocklyControl.js +335 -302
  141. game/static/game/js/blocklyCustomBlocks.js +691 -458
  142. game/static/game/js/blocklyCustomisations.js +3 -1
  143. game/static/game/js/button.js +12 -0
  144. game/static/game/js/cow.js +15 -130
  145. game/static/game/js/drawing.js +313 -201
  146. game/static/game/js/editor.js +23 -0
  147. game/static/game/js/game.js +148 -139
  148. game/static/game/js/jquery.dataTables.min.js +3 -159
  149. game/static/game/js/level_editor.js +823 -448
  150. game/static/game/js/level_moderation.js +33 -2
  151. game/static/game/js/level_selection.js +62 -25
  152. game/static/game/js/loadLanguages.js +21 -0
  153. game/static/game/js/map.js +106 -36
  154. game/static/game/js/model.js +55 -107
  155. game/static/game/js/pathFinder.js +73 -72
  156. game/static/game/js/program.js +184 -193
  157. game/static/game/js/pythonControl.js +14 -1
  158. game/static/game/js/scoreboard.js +0 -37
  159. game/static/game/js/scoreboardSharedLevels.js +48 -0
  160. game/static/game/js/sharing.js +22 -10
  161. game/static/game/js/skulpt/codemirror.js +5 -4
  162. game/static/game/js/skulpt/skulpt-stdlib.js +1 -1
  163. game/static/game/js/sound.js +52 -5
  164. game/static/game/js/van.js +0 -7
  165. game/static/game/raphael_image/characters/top_view/Electric_van.svg +448 -0
  166. game/static/game/raphael_image/characters/top_view/Sleigh.svg +436 -0
  167. game/static/game/raphael_image/decor/city/solar_panel.svg +1200 -0
  168. game/static/game/raphael_image/decor/farm/solar_panel.svg +86 -0
  169. game/static/game/raphael_image/decor/grass/solar_panel.svg +86 -0
  170. game/static/game/raphael_image/decor/snow/barn.svg +1788 -0
  171. game/static/game/raphael_image/decor/snow/cfc.svg +1050 -147
  172. game/static/game/raphael_image/decor/snow/crops.svg +7370 -0
  173. game/static/game/raphael_image/decor/snow/hospital.svg +1220 -0
  174. game/static/game/raphael_image/decor/snow/house1.svg +971 -0
  175. game/static/game/raphael_image/decor/snow/house2.svg +1574 -0
  176. game/static/game/raphael_image/decor/snow/school.svg +1071 -0
  177. game/static/game/raphael_image/decor/snow/shop.svg +3211 -0
  178. game/static/game/raphael_image/decor/snow/solar_panel.svg +173 -0
  179. game/static/game/raphael_image/pigeon.svg +685 -0
  180. game/static/game/raphael_image/sleigh_wreckage.svg +430 -0
  181. game/static/game/sass/game.scss +22 -6
  182. game/static/game/sound/clown_horn.mp3 +0 -0
  183. game/static/game/sound/clown_horn.ogg +0 -0
  184. game/static/game/sound/electric_van_starting.mp3 +0 -0
  185. game/static/game/sound/electric_van_starting.ogg +0 -0
  186. game/static/game/sound/pigeon.mp3 +0 -0
  187. game/static/game/sound/pigeon.ogg +0 -0
  188. game/static/game/sound/sleigh_bells.mp3 +0 -0
  189. game/static/game/sound/sleigh_bells.ogg +0 -0
  190. game/static/game/sound/sleigh_crash.mp3 +0 -0
  191. game/static/game/sound/sleigh_crash.ogg +0 -0
  192. game/templates/game/base.html +35 -15
  193. game/templates/game/basenonav.html +23 -17
  194. game/templates/game/game.html +236 -111
  195. game/templates/game/level_editor.html +353 -275
  196. game/templates/game/level_moderation.html +19 -6
  197. game/templates/game/level_selection.html +75 -62
  198. game/templates/game/python_den_level_selection.html +291 -0
  199. game/templates/game/python_den_worksheet.html +101 -0
  200. game/templates/game/scoreboard.html +88 -65
  201. game/tests/test_level_editor.py +210 -35
  202. game/tests/test_level_moderation.py +6 -20
  203. game/tests/test_level_selection.py +332 -11
  204. game/tests/test_python_den_worksheet.py +85 -0
  205. game/tests/test_scoreboard.py +258 -66
  206. game/tests/utils/level.py +43 -3
  207. game/tests/utils/teacher.py +2 -2
  208. game/theme.py +21 -21
  209. game/urls.py +125 -78
  210. game/views/language_code_conversions.py +90 -0
  211. game/views/level.py +201 -63
  212. game/views/level_editor.py +109 -48
  213. game/views/level_moderation.py +29 -6
  214. game/views/level_selection.py +179 -56
  215. game/views/level_solutions.py +600 -106
  216. game/views/scoreboard.py +181 -66
  217. game/views/worksheet.py +25 -0
  218. rapid_router-7.6.8.dist-info/METADATA +174 -0
  219. {rapid_router-5.4.1.dist-info → rapid_router-7.6.8.dist-info}/RECORD +222 -242
  220. {rapid_router-5.4.1.dist-info → rapid_router-7.6.8.dist-info}/WHEEL +1 -1
  221. rapid_router-7.6.8.dist-info/licenses/LICENSE.md +3 -0
  222. example_project/example_project/__init__.py +0 -1
  223. example_project/example_project/settings.py +0 -54
  224. example_project/example_project/urls.py +0 -16
  225. example_project/manage.py +0 -10
  226. game/autoconfig.py +0 -59
  227. game/csp_config.py +0 -23
  228. game/locale/ar_SA/LC_MESSAGES/django.mo +0 -0
  229. game/locale/ar_SA/LC_MESSAGES/django.po +0 -405
  230. game/locale/ar_SA/LC_MESSAGES/djangojs.mo +0 -0
  231. game/locale/ar_SA/LC_MESSAGES/djangojs.po +0 -743
  232. game/locale/bg_BG/LC_MESSAGES/django.mo +0 -0
  233. game/locale/bg_BG/LC_MESSAGES/django.po +0 -405
  234. game/locale/bg_BG/LC_MESSAGES/djangojs.mo +0 -0
  235. game/locale/bg_BG/LC_MESSAGES/djangojs.po +0 -739
  236. game/locale/ca_ES/LC_MESSAGES/django.mo +0 -0
  237. game/locale/ca_ES/LC_MESSAGES/django.po +0 -405
  238. game/locale/ca_ES/LC_MESSAGES/djangojs.mo +0 -0
  239. game/locale/ca_ES/LC_MESSAGES/djangojs.po +0 -740
  240. game/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  241. game/locale/cs_CZ/LC_MESSAGES/django.po +0 -405
  242. game/locale/cs_CZ/LC_MESSAGES/djangojs.mo +0 -0
  243. game/locale/cs_CZ/LC_MESSAGES/djangojs.po +0 -741
  244. game/locale/cy_GB/LC_MESSAGES/django.mo +0 -0
  245. game/locale/cy_GB/LC_MESSAGES/django.po +0 -405
  246. game/locale/cy_GB/LC_MESSAGES/djangojs.mo +0 -0
  247. game/locale/cy_GB/LC_MESSAGES/djangojs.po +0 -743
  248. game/locale/de_DE/LC_MESSAGES/django.mo +0 -0
  249. game/locale/de_DE/LC_MESSAGES/django.po +0 -405
  250. game/locale/de_DE/LC_MESSAGES/djangojs.mo +0 -0
  251. game/locale/de_DE/LC_MESSAGES/djangojs.po +0 -739
  252. game/locale/el_GR/LC_MESSAGES/django.mo +0 -0
  253. game/locale/el_GR/LC_MESSAGES/django.po +0 -405
  254. game/locale/el_GR/LC_MESSAGES/djangojs.mo +0 -0
  255. game/locale/el_GR/LC_MESSAGES/djangojs.po +0 -739
  256. game/locale/en_GB/LC_MESSAGES/django.mo +0 -0
  257. game/locale/en_GB/LC_MESSAGES/django.po +0 -405
  258. game/locale/en_GB/LC_MESSAGES/djangojs.mo +0 -0
  259. game/locale/en_GB/LC_MESSAGES/djangojs.po +0 -739
  260. game/locale/es_ES/LC_MESSAGES/django.mo +0 -0
  261. game/locale/es_ES/LC_MESSAGES/django.po +0 -405
  262. game/locale/es_ES/LC_MESSAGES/djangojs.mo +0 -0
  263. game/locale/es_ES/LC_MESSAGES/djangojs.po +0 -739
  264. game/locale/fi_FI/LC_MESSAGES/django.mo +0 -0
  265. game/locale/fi_FI/LC_MESSAGES/django.po +0 -405
  266. game/locale/fi_FI/LC_MESSAGES/djangojs.mo +0 -0
  267. game/locale/fi_FI/LC_MESSAGES/djangojs.po +0 -739
  268. game/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  269. game/locale/fr_FR/LC_MESSAGES/django.po +0 -405
  270. game/locale/fr_FR/LC_MESSAGES/djangojs.mo +0 -0
  271. game/locale/fr_FR/LC_MESSAGES/djangojs.po +0 -739
  272. game/locale/gu_IN/LC_MESSAGES/django.mo +0 -0
  273. game/locale/gu_IN/LC_MESSAGES/django.po +0 -405
  274. game/locale/gu_IN/LC_MESSAGES/djangojs.mo +0 -0
  275. game/locale/gu_IN/LC_MESSAGES/djangojs.po +0 -739
  276. game/locale/hi_IN/LC_MESSAGES/django.mo +0 -0
  277. game/locale/hi_IN/LC_MESSAGES/django.po +0 -405
  278. game/locale/hi_IN/LC_MESSAGES/djangojs.mo +0 -0
  279. game/locale/hi_IN/LC_MESSAGES/djangojs.po +0 -739
  280. game/locale/id_ID/LC_MESSAGES/django.mo +0 -0
  281. game/locale/id_ID/LC_MESSAGES/django.po +0 -405
  282. game/locale/id_ID/LC_MESSAGES/djangojs.mo +0 -0
  283. game/locale/id_ID/LC_MESSAGES/djangojs.po +0 -738
  284. game/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  285. game/locale/it_IT/LC_MESSAGES/django.po +0 -405
  286. game/locale/it_IT/LC_MESSAGES/djangojs.mo +0 -0
  287. game/locale/it_IT/LC_MESSAGES/djangojs.po +0 -739
  288. game/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
  289. game/locale/ja_JP/LC_MESSAGES/django.po +0 -405
  290. game/locale/ja_JP/LC_MESSAGES/djangojs.mo +0 -0
  291. game/locale/ja_JP/LC_MESSAGES/djangojs.po +0 -738
  292. game/locale/lol_US/LC_MESSAGES/django.mo +0 -0
  293. game/locale/lol_US/LC_MESSAGES/django.po +0 -405
  294. game/locale/lol_US/LC_MESSAGES/djangojs.mo +0 -0
  295. game/locale/lol_US/LC_MESSAGES/djangojs.po +0 -739
  296. game/locale/nb_NO/LC_MESSAGES/django.mo +0 -0
  297. game/locale/nb_NO/LC_MESSAGES/django.po +0 -405
  298. game/locale/nb_NO/LC_MESSAGES/djangojs.mo +0 -0
  299. game/locale/nb_NO/LC_MESSAGES/djangojs.po +0 -739
  300. game/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  301. game/locale/nl_NL/LC_MESSAGES/django.po +0 -405
  302. game/locale/nl_NL/LC_MESSAGES/djangojs.mo +0 -0
  303. game/locale/nl_NL/LC_MESSAGES/djangojs.po +0 -739
  304. game/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  305. game/locale/pl_PL/LC_MESSAGES/django.po +0 -405
  306. game/locale/pl_PL/LC_MESSAGES/djangojs.mo +0 -0
  307. game/locale/pl_PL/LC_MESSAGES/djangojs.po +0 -741
  308. game/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  309. game/locale/pt_BR/LC_MESSAGES/django.po +0 -405
  310. game/locale/pt_BR/LC_MESSAGES/djangojs.mo +0 -0
  311. game/locale/pt_BR/LC_MESSAGES/djangojs.po +0 -739
  312. game/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  313. game/locale/pt_PT/LC_MESSAGES/django.po +0 -405
  314. game/locale/pt_PT/LC_MESSAGES/djangojs.mo +0 -0
  315. game/locale/pt_PT/LC_MESSAGES/djangojs.po +0 -739
  316. game/locale/ro_RO/LC_MESSAGES/django.mo +0 -0
  317. game/locale/ro_RO/LC_MESSAGES/django.po +0 -405
  318. game/locale/ro_RO/LC_MESSAGES/djangojs.mo +0 -0
  319. game/locale/ro_RO/LC_MESSAGES/djangojs.po +0 -740
  320. game/locale/ru_RU/LC_MESSAGES/django.mo +0 -0
  321. game/locale/ru_RU/LC_MESSAGES/django.po +0 -405
  322. game/locale/ru_RU/LC_MESSAGES/djangojs.mo +0 -0
  323. game/locale/ru_RU/LC_MESSAGES/djangojs.po +0 -741
  324. game/locale/sv_SE/LC_MESSAGES/django.mo +0 -0
  325. game/locale/sv_SE/LC_MESSAGES/django.po +0 -405
  326. game/locale/sv_SE/LC_MESSAGES/djangojs.mo +0 -0
  327. game/locale/sv_SE/LC_MESSAGES/djangojs.po +0 -739
  328. game/locale/tl_PH/LC_MESSAGES/django.mo +0 -0
  329. game/locale/tl_PH/LC_MESSAGES/django.po +0 -405
  330. game/locale/tl_PH/LC_MESSAGES/djangojs.mo +0 -0
  331. game/locale/tl_PH/LC_MESSAGES/djangojs.po +0 -739
  332. game/locale/tlh_AA/LC_MESSAGES/django.mo +0 -0
  333. game/locale/tlh_AA/LC_MESSAGES/django.po +0 -405
  334. game/locale/tlh_AA/LC_MESSAGES/djangojs.mo +0 -0
  335. game/locale/tlh_AA/LC_MESSAGES/djangojs.po +0 -739
  336. game/locale/tr_TR/LC_MESSAGES/django.mo +0 -0
  337. game/locale/tr_TR/LC_MESSAGES/django.po +0 -405
  338. game/locale/tr_TR/LC_MESSAGES/djangojs.mo +0 -0
  339. game/locale/tr_TR/LC_MESSAGES/djangojs.po +0 -740
  340. game/locale/ur_IN/LC_MESSAGES/django.mo +0 -0
  341. game/locale/ur_IN/LC_MESSAGES/django.po +0 -405
  342. game/locale/ur_IN/LC_MESSAGES/djangojs.mo +0 -0
  343. game/locale/ur_IN/LC_MESSAGES/djangojs.po +0 -739
  344. game/locale/ur_PK/LC_MESSAGES/django.mo +0 -0
  345. game/locale/ur_PK/LC_MESSAGES/django.po +0 -405
  346. game/locale/ur_PK/LC_MESSAGES/djangojs.mo +0 -0
  347. game/locale/ur_PK/LC_MESSAGES/djangojs.po +0 -739
  348. game/static/game/image/actions/go.svg +0 -18
  349. game/static/game/image/icons/destination.svg +0 -9
  350. game/static/game/js/pqselect.min.js +0 -9
  351. game/static/game/js/widget-scroller.js +0 -906
  352. rapid_router-5.4.1.dist-info/LICENSE.md +0 -577
  353. rapid_router-5.4.1.dist-info/METADATA +0 -24
  354. {rapid_router-5.4.1.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
@@ -34,7 +31,7 @@ class StudentRow:
34
31
  self.total_score = kwargs.get("total_score", 0)
35
32
  self.level_scores = kwargs.get("level_scores", {})
36
33
  self.completed = kwargs.get("completed", 0)
37
- self.percentage_complete = kwargs.get("percentage_complete", 0)
34
+ self.success_rate = kwargs.get("success_rate", 0)
38
35
 
39
36
 
40
37
  # Returns row of student object with values and scores of each selected level
@@ -51,20 +48,42 @@ def student_row(levels_sorted, student, best_attempts):
51
48
  level_scores[level.id] = {}
52
49
  level_scores[level.id]["score"] = ""
53
50
 
54
- if level.episode is None and student.new_user not in level.shared_with.all():
51
+ if (
52
+ level.episode is None
53
+ and student.new_user not in level.shared_with.all()
54
+ ):
55
55
  level_scores[level.id]["score"] = "Not shared"
56
56
 
57
57
  if level.owner == student.user:
58
58
  level_scores[level.id]["score"] = "Owner"
59
59
 
60
60
  if best_attempts:
61
- attempts_dict = {best_attempt.level.id: best_attempt for best_attempt in best_attempts}
61
+ attempts_dict = {
62
+ best_attempt.level.id: best_attempt
63
+ for best_attempt in best_attempts
64
+ }
62
65
  for level in levels_sorted:
63
66
 
64
67
  attempt = attempts_dict.get(level.id)
68
+
65
69
  if attempt:
70
+ max_score = 0
71
+
72
+ # Max score logic as follows:
73
+ # - All custom levels have a max score of 10
74
+ # - All official levels have a max score of 20 if both route score and algorithm scores are enabled
75
+ # except for levels 1-12 which have a max score of 20 even though the algorithm score is disabled
76
+ if attempt.level.episode is None:
77
+ max_score = 10
78
+ else:
79
+ if not attempt.level.disable_route_score:
80
+ max_score += 10
81
+ if not attempt.level.disable_algorithm_score:
82
+ max_score += 10
83
+ if int(attempt.level.name) < 13:
84
+ max_score = 20
85
+
66
86
  num_all += 1
67
- max_score = 10 if attempt.level.disable_route_score or attempt.level.episode is None else 20
68
87
  if attempt.score:
69
88
  if attempt.score / max_score >= threshold:
70
89
  num_finished += 1
@@ -77,17 +96,28 @@ def student_row(levels_sorted, student, best_attempts):
77
96
  total_possible_score += max_score
78
97
 
79
98
  elapsed_time = attempt.elapsed_time()
80
- times.append(chop_miliseconds(elapsed_time))
99
+ times.append(chop_milliseconds(elapsed_time))
81
100
  # '-' is used to show that the student has started the level but has not submitted any attempts
82
101
 
83
- level_scores[level.id]["score"] = int(attempt.score) if attempt.score is not None else "-"
84
- level_scores[level.id]["full_score"] = attempt.score == max_score
85
- level_scores[level.id]["is_low_attempt"] = attempt.score == 0 or max_score / attempt.score < threshold
102
+ level_scores[level.id]["score"] = (
103
+ int(attempt.score) if attempt.score is not None else "-"
104
+ )
105
+ level_scores[level.id]["full_score"] = (
106
+ attempt.score == max_score
107
+ )
108
+ level_scores[level.id]["is_low_attempt"] = (
109
+ attempt.score == 0 or max_score / attempt.score < threshold
110
+ )
86
111
  else:
87
112
  times.append(timedelta(0))
88
113
 
89
114
  total_time = sum(times, timedelta())
90
- percentage_complete = total_score / total_possible_score * 100 if total_possible_score > 0 else 0
115
+
116
+ success_rate = (
117
+ total_score / total_possible_score * 100
118
+ if total_possible_score > 0
119
+ else 0
120
+ )
91
121
 
92
122
  row = StudentRow(
93
123
  student=student,
@@ -95,7 +125,7 @@ def student_row(levels_sorted, student, best_attempts):
95
125
  total_score=int(total_score),
96
126
  level_scores=level_scores,
97
127
  completed=num_finished,
98
- percentage_complete=percentage_complete,
128
+ success_rate=success_rate,
99
129
  )
100
130
  return row
101
131
 
@@ -104,29 +134,43 @@ def to_name(level):
104
134
  return f"L{level.name}"
105
135
 
106
136
 
137
+ def to_python_name(level):
138
+ return f"L{int(level.name) - 1000}"
139
+
140
+
107
141
  def shared_level_to_name(level, user):
108
- return f"{level.name} (you)" if user == level.owner else f"{level.name} ({level.owner})"
142
+ return (
143
+ f"{level.name} (you)"
144
+ if user == level.owner
145
+ else f"{level.name} ({level.owner})"
146
+ )
109
147
 
110
148
 
111
- def scoreboard_data(episode_ids, attempts_per_students):
149
+ def scoreboard_data(episode_ids, attempts_per_students, language):
112
150
  # Show the total score, total time and score of each level
113
151
  levels_sorted = []
114
152
  for episode_id in episode_ids:
115
153
  episode = Episode.objects.get(id=episode_id)
116
154
  levels_sorted += episode.levels
117
155
 
118
- 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))
119
159
  student_data = [
120
- student_row(levels_sorted, student, best_attempts) for student, best_attempts in attempts_per_students.items()
160
+ student_row(levels_sorted, student, best_attempts)
161
+ for student, best_attempts in attempts_per_students.items()
121
162
  ]
122
163
 
123
164
  return student_data, Headers, level_headers, levels_sorted
124
165
 
125
166
 
126
167
  def shared_levels_data(user, shared_levels, attempts_per_students):
127
- shared_level_headers = list(shared_level_to_name(level, user) for level in shared_levels)
168
+ shared_level_headers = list(
169
+ shared_level_to_name(level, user) for level in shared_levels
170
+ )
128
171
  shared_student_data = [
129
- student_row(shared_levels, student, best_attempts) for student, best_attempts in attempts_per_students.items()
172
+ student_row(shared_levels, student, best_attempts)
173
+ for student, best_attempts in attempts_per_students.items()
130
174
  ]
131
175
 
132
176
  return SharedHeaders, shared_level_headers, shared_student_data
@@ -150,14 +194,20 @@ def _check_attempts(best_attempts):
150
194
  total_score = 0
151
195
  total_possible_score = 0
152
196
  # Get the best attempts for the specific Episode
153
- attempts = [best_attempt for best_attempt in best_attempts if best_attempt.level.episode.id == episode_id]
197
+ attempts = [
198
+ best_attempt
199
+ for best_attempt in best_attempts
200
+ if best_attempt.level.episode.id == episode_id
201
+ ]
154
202
  for attempt in attempts:
155
203
  max_score = 10 if attempt.level.disable_route_score else 20
156
204
 
157
205
  total_score += attempt.score if attempt.score is not None else 0
158
206
  total_possible_score += max_score
159
207
 
160
- is_low_attempt = attempt.score == 0 or max_score / attempt.score < threshold
208
+ is_low_attempt = (
209
+ attempt.score == 0 or max_score / attempt.score < threshold
210
+ )
161
211
  if is_low_attempt:
162
212
  low_episode_ids.add(episode_id)
163
213
 
@@ -170,9 +220,14 @@ def get_improvement_data(attempts_per_student):
170
220
  for student, best_attempts in attempts_per_student.items():
171
221
  episodes_of_concern = _check_attempts(best_attempts)
172
222
  if episodes_of_concern:
173
- areas = [messages.get_episode_title(ep_id) for ep_id in episodes_of_concern]
223
+ areas = [
224
+ messages.get_episode_title(ep_id)
225
+ for ep_id in episodes_of_concern
226
+ ]
174
227
  areas_summary = ", ".join(areas)
175
- the_students.append(StudentInTrouble(student=student, areas=areas_summary))
228
+ the_students.append(
229
+ StudentInTrouble(student=student, areas=areas_summary)
230
+ )
176
231
  return the_students
177
232
 
178
233
 
@@ -186,6 +241,7 @@ def scoreboard_view(
186
241
  shared_headers,
187
242
  shared_level_headers,
188
243
  shared_student_data,
244
+ language,
189
245
  ):
190
246
  database_episodes = level_selection.fetch_episode_data(False)
191
247
 
@@ -204,11 +260,20 @@ def scoreboard_view(
204
260
  "shared_headers": shared_headers,
205
261
  "shared_level_headers": shared_level_headers,
206
262
  "shared_student_data": shared_student_data,
263
+ "language": language,
207
264
  },
208
265
  )
209
266
 
210
267
 
211
- 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):
212
277
  """
213
278
  Renders a page with students' scores. A teacher can see the visible classes in
214
279
  their school. Student's view is restricted to their class if their teacher enabled
@@ -219,7 +284,12 @@ def scoreboard(request):
219
284
 
220
285
  user = User(request.user.userprofile)
221
286
  users_classes = classes_for(user)
222
- all_episode_ids = list(range(1, 12))
287
+
288
+ episodes_range = range(1, 10) if language == "blockly" else range(12, 16)
289
+
290
+ all_episode_ids = [
291
+ episode.id for episode in Episode.objects.filter(pk__in=episodes_range)
292
+ ]
223
293
 
224
294
  if user.is_independent_student():
225
295
  return render_no_permission_error(request)
@@ -228,7 +298,9 @@ def scoreboard(request):
228
298
  class_ids = set(map(int, request.POST.getlist("classes")))
229
299
  # Show all levels if the teacher doesn't select any
230
300
  episode_ids = (
231
- set(all_episode_ids) if "episodes" not in request.POST else set(map(int, request.POST.getlist("episodes")))
301
+ set(all_episode_ids)
302
+ if "episodes" not in request.POST
303
+ else set(map(int, request.POST.getlist("episodes")))
232
304
  )
233
305
  else:
234
306
  # Show no data on page load by default (if teacher)
@@ -249,6 +321,7 @@ def scoreboard(request):
249
321
  form = ScoreboardForm(
250
322
  request.POST or None,
251
323
  classes=users_classes,
324
+ language=language,
252
325
  initial={
253
326
  "classes": class_ids,
254
327
  "episodes": episode_ids,
@@ -266,47 +339,74 @@ def scoreboard(request):
266
339
  all_levels += episode.levels
267
340
 
268
341
  attempts_per_student = {}
269
- attempts_per_student_shared_levels = {}
270
-
271
- if user.is_teacher():
272
- if user.teacher.is_admin:
273
- # Get all custom levels owned by non-admin teachers
274
- standard_teachers = Teacher.objects.filter(school=user.teacher.school, is_admin=False)
275
- for standard_teacher in standard_teachers:
276
- shared_levels += levels_owned_by(standard_teacher.new_user)
277
- else:
278
- # Get logged in teacher's custom levels
279
- shared_levels += levels_owned_by(request.user)
280
-
281
- # In all cases, get all admins' custom levels
282
- school_admins = Teacher.objects.filter(school=user.teacher.school, is_admin=True)
283
- for school_admin in school_admins:
284
- shared_levels += levels_owned_by(school_admin.new_user)
285
-
286
- elif user.is_student():
287
- shared_levels += levels_shared_with(request.user)
288
342
 
289
343
  for student in students:
290
344
  best_attempts = Attempt.objects.filter(
291
345
  level__in=all_levels, student=student, is_best_attempt=True
292
346
  ).select_related("level")
293
347
  attempts_per_student[student] = best_attempts
294
- shared_levels += levels_owned_by(student.new_user)
295
- best_attempts_shared_levels = Attempt.objects.filter(
296
- level__in=shared_levels, student=student, is_best_attempt=True
297
- ).select_related("level")
298
- attempts_per_student_shared_levels[student] = best_attempts_shared_levels
299
348
 
300
- (student_data, headers, level_headers, levels_sorted) = scoreboard_data(episode_ids, attempts_per_student)
301
- improvement_data = get_improvement_data(attempts_per_student)
302
- shared_headers, shared_level_headers, shared_student_data = shared_levels_data(
303
- request.user.userprofile, shared_levels, attempts_per_student_shared_levels
349
+ (student_data, headers, level_headers, levels_sorted) = scoreboard_data(
350
+ episode_ids, attempts_per_student, language
304
351
  )
352
+ improvement_data = get_improvement_data(attempts_per_student)
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
+ )
305
399
 
306
400
  csv_export = "export" in request.POST
307
401
 
308
402
  if csv_export:
309
- return scoreboard_csv(student_data, levels_sorted, improvement_data, shared_level_headers, shared_student_data)
403
+ return scoreboard_csv(
404
+ student_data,
405
+ levels_sorted,
406
+ improvement_data,
407
+ shared_level_headers,
408
+ shared_student_data,
409
+ )
310
410
  else:
311
411
  return scoreboard_view(
312
412
  request,
@@ -318,11 +418,16 @@ def scoreboard(request):
318
418
  shared_headers,
319
419
  shared_level_headers,
320
420
  shared_student_data,
421
+ language,
321
422
  )
322
423
 
323
424
 
324
425
  def render_no_permission_error(request):
325
- return renderError(request, messages.noPermissionTitle(), messages.noPermissionScoreboard())
426
+ return renderError(
427
+ request,
428
+ messages.no_permission_title(),
429
+ messages.no_permission_scoreboard(),
430
+ )
326
431
 
327
432
 
328
433
  def is_teacher_with_no_classes_assigned(user, users_classes):
@@ -352,7 +457,9 @@ def sorted_levels_by(level_ids):
352
457
 
353
458
  def are_classes_viewable_by_teacher(class_ids, user):
354
459
  teachers = Teacher.objects.filter(school=user.teacher.school)
355
- classes_in_teachers_school = Class.objects.filter(teacher__in=teachers).values_list("id", flat=True)
460
+ classes_in_teachers_school = Class.objects.filter(
461
+ teacher__in=teachers
462
+ ).values_list("id", flat=True)
356
463
  for class_id in class_ids:
357
464
  is_authorised = class_id in classes_in_teachers_school
358
465
  if not is_authorised:
@@ -367,7 +474,9 @@ def authorised_student_access(class_, class_ids):
367
474
  def students_visible_to_student(student):
368
475
  class_ = student.class_field
369
476
  if is_viewable(class_):
370
- return class_.students.filter(new_user__is_active=True).select_related("class_field", "user__user")
477
+ return class_.students.filter(new_user__is_active=True).select_related(
478
+ "class_field", "user__user"
479
+ )
371
480
  else:
372
481
  return [student]
373
482
 
@@ -381,9 +490,9 @@ def students_visible_to_user(user, classes):
381
490
 
382
491
 
383
492
  def students_of_classes(classes):
384
- return Student.objects.filter(class_field__in=classes, new_user__is_active=True).select_related(
385
- "class_field", "user__user"
386
- )
493
+ return Student.objects.filter(
494
+ class_field__in=classes, new_user__is_active=True
495
+ ).select_related("class_field", "user__user")
387
496
 
388
497
 
389
498
  def is_valid_request(user, class_ids):
@@ -401,7 +510,7 @@ def is_viewable(class_):
401
510
  return class_.classmates_data_viewable
402
511
 
403
512
 
404
- def chop_miliseconds(delta):
513
+ def chop_milliseconds(delta):
405
514
  return delta - timedelta(microseconds=delta.microseconds)
406
515
 
407
516
 
@@ -415,10 +524,16 @@ class User(object):
415
524
  self.student = profile.student
416
525
 
417
526
  def is_student(self):
418
- return hasattr(self.profile, "student") and not self.profile.student.is_independent()
527
+ return (
528
+ hasattr(self.profile, "student")
529
+ and not self.profile.student.is_independent()
530
+ )
419
531
 
420
532
  def is_teacher(self):
421
533
  return hasattr(self.profile, "teacher")
422
534
 
423
535
  def is_independent_student(self):
424
- return hasattr(self.profile, "student") and self.profile.student.is_independent()
536
+ return (
537
+ hasattr(self.profile, "student")
538
+ and self.profile.student.is_independent()
539
+ )
@@ -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