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
@@ -1,6 +1,7 @@
1
1
  import json
2
+ from unittest.mock import patch
2
3
 
3
- from common.models import Teacher
4
+ from common.models import Teacher, User
4
5
  from common.tests.utils.classes import create_class_directly
5
6
  from common.tests.utils.organisation import create_organisation_directly
6
7
  from common.tests.utils.student import create_school_student_directly
@@ -10,22 +11,21 @@ from django.core import mail
10
11
  from django.test.client import Client
11
12
  from django.test.testcases import TestCase
12
13
  from django.urls import reverse
13
- from hamcrest import assert_that, equal_to
14
14
 
15
15
  from game.models import Level
16
- from game.tests.utils.level import create_save_level
16
+ from game.tests.utils.level import create_save_level, create_save_level_with_multiple_houses, multiple_house_data
17
17
  from game.tests.utils.teacher import add_teacher_to_school, create_school
18
18
 
19
19
 
20
20
  class LevelEditorTestCase(TestCase):
21
21
  LEVEL_DATA1 = {
22
22
  "origin": '{"coordinate":[3,5],"direction":"S"}',
23
- "pythonEnabled": False,
23
+ "python_enabled": False,
24
24
  "decor": [],
25
- "blocklyEnabled": True,
25
+ "blockly_enabled": True,
26
26
  "blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
27
27
  "max_fuel": "50",
28
- "pythonViewEnabled": False,
28
+ "python_view_enabled": False,
29
29
  "character": "3",
30
30
  "name": "abc",
31
31
  "theme": 1,
@@ -87,10 +87,21 @@ class LevelEditorTestCase(TestCase):
87
87
  url = reverse("save_level_for_editor")
88
88
  response = self.client.post(url, {"data": json.dumps(self.LEVEL_DATA1)})
89
89
 
90
- assert_that(response.status_code, equal_to(200))
90
+ assert response.status_code == 200
91
+
92
+ level = Level.objects.all().last()
93
+ teacher_user = User.objects.get(email=email)
94
+
95
+ assert level.needs_approval == True
96
+ assert level.shared_with.count() == 1
97
+ assert level.shared_with.filter(id=teacher_user.id).exists()
98
+ assert len(mail.outbox) == 1
99
+
100
+ level.needs_approval = False
101
+ level.save()
102
+
91
103
  sharing_info1 = json.loads(self.get_sharing_information(json.loads(response.content)["id"]).getvalue())
92
- assert_that(sharing_info1["teacher"]["shared"], equal_to(True))
93
- assert_that(len(mail.outbox), equal_to(1))
104
+ assert sharing_info1["teacher"]["shared"]
94
105
 
95
106
  def test_anonymous_level_saving_school_student(self):
96
107
  email, password = signup_teacher_directly()
@@ -102,12 +113,12 @@ class LevelEditorTestCase(TestCase):
102
113
  url = reverse("save_level_for_editor")
103
114
  data1 = {
104
115
  "origin": '{"coordinate":[3,5],"direction":"S"}',
105
- "pythonEnabled": False,
116
+ "python_enabled": False,
106
117
  "decor": [],
107
- "blocklyEnabled": True,
118
+ "blockly_enabled": True,
108
119
  "blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
109
120
  "max_fuel": "50",
110
- "pythonViewEnabled": False,
121
+ "python_view_enabled": False,
111
122
  "character": "3",
112
123
  "name": "abc",
113
124
  "theme": 1,
@@ -119,10 +130,14 @@ class LevelEditorTestCase(TestCase):
119
130
  }
120
131
  response = self.client.post(url, {"data": json.dumps(data1)})
121
132
 
122
- assert_that(response.status_code, equal_to(200))
133
+ level = Level.objects.all().last()
134
+ level.needs_approval = False
135
+ level.save()
136
+
137
+ assert response.status_code == 200
123
138
  sharing_info1 = json.loads(self.get_sharing_information(json.loads(response.content)["id"]).getvalue())
124
- assert_that(sharing_info1["teacher"]["shared"], equal_to(True))
125
- assert_that(len(mail.outbox), equal_to(0))
139
+ assert sharing_info1["teacher"]["shared"]
140
+ assert len(mail.outbox) == 0
126
141
 
127
142
  def test_level_sharing_with_no_school(self):
128
143
  email1, password1 = signup_teacher_directly()
@@ -131,7 +146,7 @@ class LevelEditorTestCase(TestCase):
131
146
  level = create_save_level(teacher1)
132
147
 
133
148
  sharing_info1 = json.loads(self.get_sharing_information(level.id).getvalue())
134
- assert_that(len(sharing_info1["teachers"]), equal_to(0))
149
+ assert len(sharing_info1["teachers"]) == 0
135
150
 
136
151
  def test_level_sharing_with_school(self):
137
152
  email1, password1 = signup_teacher_directly()
@@ -148,7 +163,7 @@ class LevelEditorTestCase(TestCase):
148
163
  add_teacher_to_school(teacher2, school1)
149
164
 
150
165
  sharing_info1 = json.loads(self.get_sharing_information(level.id).getvalue())
151
- assert_that(len(sharing_info1["teachers"]), equal_to(1))
166
+ assert len(sharing_info1["teachers"]) == 1
152
167
 
153
168
  def test_level_sharing_with_empty_school(self):
154
169
  email1, password1 = signup_teacher_directly()
@@ -161,7 +176,7 @@ class LevelEditorTestCase(TestCase):
161
176
  add_teacher_to_school(teacher1, school1)
162
177
 
163
178
  sharing_info1 = json.loads(self.get_sharing_information(level.id).getvalue())
164
- assert_that(len(sharing_info1["teachers"]), equal_to(0))
179
+ assert len(sharing_info1["teachers"]) == 0
165
180
 
166
181
  def test_level_sharing_permissions(self):
167
182
  email1, password1 = signup_teacher_directly()
@@ -175,18 +190,19 @@ class LevelEditorTestCase(TestCase):
175
190
  share_url = reverse("share_level_for_editor", args=[level.id])
176
191
 
177
192
  school1 = create_school()
178
- add_teacher_to_school(teacher1, school1)
193
+ add_teacher_to_school(teacher1, school1, is_admin=True)
179
194
  add_teacher_to_school(teacher2, school1)
180
195
 
181
- # Create a class and student for the second teacher
196
+ # Create a class and 2 students for the second teacher
182
197
  _, class_name2, access_code2 = create_class_directly(email2)
198
+ student_name1, student_password1, student1 = create_school_student_directly(access_code2)
183
199
  student_name2, student_password2, student2 = create_school_student_directly(access_code2)
184
200
 
185
201
  self.logout()
186
202
  self.login(email2, password2)
187
203
 
188
204
  # Second teacher can't share the level as it's not been shared with them yet
189
- response = self.client.post(share_url, {"recipientIDs[]": [student2.new_user.id], "action": ["share"]})
205
+ response = self.client.post(share_url, {"recipientIDs[]": [student1.new_user.id], "action": ["share"]})
190
206
  assert response.status_code == 403
191
207
 
192
208
  # Log in as the first teacher again
@@ -202,14 +218,34 @@ class LevelEditorTestCase(TestCase):
202
218
  self.login(email2, password2)
203
219
 
204
220
  # Now the second teacher should be able to share the level
205
- response = self.client.post(share_url, {"recipientIDs[]": [student2.new_user.id], "action": ["share"]})
221
+ response = self.client.post(share_url, {"recipientIDs[]": [student1.new_user.id], "action": ["share"]})
206
222
  assert response.status_code == 200
207
223
  # and load it
208
224
  load_level_url = reverse("load_level_for_editor", kwargs={"levelID": level.id})
209
225
  response = self.client.get(load_level_url)
210
226
  assert response.status_code == 200
211
227
 
212
- # Login as a student
228
+ # Log in as the first student
229
+ self.logout()
230
+ self.student_login(student_name1, access_code2, student_password1)
231
+
232
+ # Check that the student cannot share the level
233
+ sharing_info = json.loads(self.get_sharing_information(level.id).getvalue())
234
+ assert sharing_info["detail"] == "You do not have permission to perform this action."
235
+
236
+ # Check the student can view the level
237
+ response = self.client.get(reverse("play_custom_level", args=[level.id]))
238
+ assert response.status_code == 200
239
+
240
+ # Log in as first teacher again
241
+ self.logout()
242
+ self.login(email1, password1)
243
+
244
+ # Share the level with the second student of teacher 2
245
+ response = self.client.post(share_url, {"recipientIDs[]": [student2.new_user.id], "action": ["share"]})
246
+ assert response.status_code == 200
247
+
248
+ # Log in as the second student
213
249
  self.logout()
214
250
  self.student_login(student_name2, access_code2, student_password2)
215
251
 
@@ -221,6 +257,8 @@ class LevelEditorTestCase(TestCase):
221
257
  response = self.client.get(reverse("play_custom_level", args=[level.id]))
222
258
  assert response.status_code == 200
223
259
 
260
+ self.logout()
261
+
224
262
  def test_level_can_only_be_edited_by_owner(self):
225
263
  email, password = signup_teacher_directly()
226
264
  create_organisation_directly(email)
@@ -233,14 +271,14 @@ class LevelEditorTestCase(TestCase):
233
271
  self.student_login(hacker_name, access_code, hacker_password)
234
272
  resp = self.client.get(reverse("level_editor_chosen_level", kwargs={"levelId": new_level.id}))
235
273
 
236
- self.assertNotIn("level", resp.context)
274
+ assert "level" not in resp.context
237
275
 
238
276
  self.logout()
239
277
 
240
278
  self.student_login(student_name, access_code, student_password)
241
279
  resp = self.client.get(reverse("level_editor_chosen_level", kwargs={"levelId": new_level.id}))
242
280
 
243
- self.assertIn("level", resp.context)
281
+ assert "level" in resp.context
244
282
 
245
283
  def test_no_character_set_defaults_to_van(self):
246
284
 
@@ -253,12 +291,12 @@ class LevelEditorTestCase(TestCase):
253
291
  url = reverse("save_level_for_editor")
254
292
  data_with_no_character = {
255
293
  "origin": '{"coordinate":[3,5],"direction":"S"}',
256
- "pythonEnabled": False,
294
+ "python_enabled": False,
257
295
  "decor": [],
258
- "blocklyEnabled": True,
296
+ "blockly_enabled": True,
259
297
  "blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
260
298
  "max_fuel": "50",
261
- "pythonViewEnabled": False,
299
+ "python_view_enabled": False,
262
300
  "name": "abc",
263
301
  "theme": 1,
264
302
  "anonymous": True,
@@ -269,9 +307,48 @@ class LevelEditorTestCase(TestCase):
269
307
  }
270
308
  response = self.client.post(url, {"data": json.dumps(data_with_no_character)})
271
309
 
272
- assert_that(response.status_code, equal_to(200))
310
+ assert response.status_code == 200
273
311
  new_level = Level.objects.get(name="abc")
274
- assert_that(new_level.character.name, equal_to("Van"))
312
+ assert new_level.character.name == "Van"
313
+
314
+ def test_language_set_appropriately(self):
315
+
316
+ email, password = signup_teacher_directly()
317
+ create_organisation_directly(email)
318
+ _, class_name, access_code = create_class_directly(email)
319
+ student_name, student_password, _ = create_school_student_directly(access_code)
320
+
321
+ self.student_login(student_name, access_code, student_password)
322
+ url = reverse("save_level_for_editor")
323
+ data_with_split_language = {
324
+ "origin": '{"coordinate":[3,5],"direction":"S"}',
325
+ "python_enabled": False,
326
+ "decor": [],
327
+ "blockly_enabled": True,
328
+ "blocks": [
329
+ {"type": "move_forwards"},
330
+ {"type": "turn_left"},
331
+ {"type": "turn_right"},
332
+ ],
333
+ "max_fuel": "50",
334
+ "python_view_enabled": True,
335
+ "name": "abc",
336
+ "theme": 1,
337
+ "anonymous": True,
338
+ "cows": "[]",
339
+ "path": '[{"coordinate":[3,5],"connectedNodes":[1]},{"coordinate":[3,4],'
340
+ '"connectedNodes":[0]}]',
341
+ "traffic_lights": "[]",
342
+ "destinations": "[[3,4]]",
343
+ }
344
+ response = self.client.post(url, {"data": json.dumps(data_with_split_language)})
345
+
346
+ assert response.status_code == 200
347
+ new_level = Level.objects.get(name="abc")
348
+
349
+ assert new_level.python_view_enabled
350
+ assert not new_level.python_enabled
351
+ assert new_level.blockly_enabled
275
352
 
276
353
  def test_level_loading(self):
277
354
  email1, password1 = signup_teacher_directly()
@@ -283,7 +360,70 @@ class LevelEditorTestCase(TestCase):
283
360
  url = reverse("load_level_for_editor", kwargs={"levelID": level.id})
284
361
  response = self.client.get(url)
285
362
 
286
- assert_that(response.status_code, equal_to(200))
363
+ assert response.status_code == 200
364
+
365
+ def test_level_saving_with_multiple_houses(self):
366
+ email, password = signup_teacher_directly()
367
+ create_organisation_directly(email)
368
+ _, class_name, access_code = create_class_directly(email)
369
+ student_name, student_password, _ = create_school_student_directly(access_code)
370
+
371
+ self.student_login(student_name, access_code, student_password)
372
+ url = reverse("save_level_for_editor")
373
+ data_with_multiple_houses = {
374
+ "origin": '{"coordinate":[3,5],"direction":"S"}',
375
+ "python_enabled": False,
376
+ "decor": [],
377
+ "blockly_enabled": True,
378
+ "blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
379
+ "max_fuel": "50",
380
+ "python_view_enabled": False,
381
+ "name": "multiple_houses",
382
+ "theme": 1,
383
+ "anonymous": True,
384
+ "cows": "[]",
385
+ "path": '[{"coordinate":[3,5],"connectedNodes":[1]},{"coordinate":[3,4],"connectedNodes":[0,2]}, {"coordinate":[3,3],"connectedNodes":[1]}]',
386
+ "traffic_lights": "[]",
387
+ "destinations": "[[3,4],[3,3]]",
388
+ }
389
+ response = self.client.post(url, {"data": json.dumps(data_with_multiple_houses)})
390
+
391
+ assert response.status_code == 200
392
+ new_level = Level.objects.get(name="multiple_houses")
393
+ assert new_level.destinations == "[[3,4],[3,3]]"
394
+
395
+ def test_level_loading_with_multiple_houses(self):
396
+
397
+ email1, password1 = signup_teacher_directly()
398
+
399
+ teacher1 = Teacher.objects.get(new_user__email=email1)
400
+
401
+ self.login(email1, password1)
402
+ level = create_save_level_with_multiple_houses(teacher1)
403
+ url = reverse("load_level_for_editor", kwargs={"levelID": level.id})
404
+ response = self.client.get(url)
405
+ response_data = response.json()
406
+
407
+ assert response.status_code == 200
408
+ assert response_data["level"]["destinations"] == "[[3,4], [3,3]]"
409
+
410
+ @patch("game.level_management.save_level")
411
+ def test_custom_level_scoring(self, mock_save_level):
412
+ email1, password1 = signup_teacher_directly()
413
+
414
+ teacher1 = Teacher.objects.get(new_user__email=email1)
415
+
416
+ self.login(email1, password1)
417
+
418
+ level = create_save_level_with_multiple_houses(teacher1)
419
+
420
+ save_url = reverse("save_level_for_editor", kwargs={"levelId": level.id})
421
+ response = self.client.post(save_url, {"data": json.dumps(multiple_house_data)})
422
+
423
+ disable_algorithm_score = mock_save_level.call_args.args[1]["disable_algorithm_score"]
424
+
425
+ assert response.status_code == 200
426
+ assert disable_algorithm_score
287
427
 
288
428
  def test_level_of_anonymised_teacher_is_hidden(self):
289
429
  # Create 2 teacher accounts
@@ -309,7 +449,7 @@ class LevelEditorTestCase(TestCase):
309
449
  # Check `teacher1` can see `teacher2`'s shared level
310
450
  levels_url = reverse("levels")
311
451
  response = self.client.get(levels_url)
312
- assert len(response.context["shared_levels"]) == 1
452
+ assert len(response.context["directly_shared_levels"]) == 1
313
453
 
314
454
  # Make teacher2 inactive
315
455
  teacher2.new_user.is_active = 0
@@ -317,7 +457,7 @@ class LevelEditorTestCase(TestCase):
317
457
 
318
458
  # `teacher1` shouldn't see any shared levels now
319
459
  response = self.client.get(levels_url)
320
- assert len(response.context["shared_levels"]) == 0
460
+ assert len(response.context["directly_shared_levels"]) == 0
321
461
 
322
462
  def test_level_of_anonymised_student_is_hidden(self):
323
463
  # Create a teacher and a student
@@ -339,7 +479,7 @@ class LevelEditorTestCase(TestCase):
339
479
  # Check teacher can see student's shared level
340
480
  levels_url = reverse("levels")
341
481
  response = self.client.get(levels_url)
342
- assert len(response.context["shared_levels"]) == 1
482
+ assert len(response.context["directly_shared_levels"]) == 1
343
483
 
344
484
  # Make student inactive
345
485
  student.new_user.is_active = 0
@@ -347,4 +487,39 @@ class LevelEditorTestCase(TestCase):
347
487
 
348
488
  # Teacher shouldn't see any shared levels now
349
489
  response = self.client.get(levels_url)
350
- assert len(response.context["shared_levels"]) == 0
490
+ assert len(response.context["directly_shared_levels"]) == 0
491
+
492
+ def test_level_cannot_be_created_with_invalid_fields(self):
493
+ email, password = signup_teacher_directly()
494
+ create_organisation_directly(email)
495
+ _, _, access_code = create_class_directly(email)
496
+ create_school_student_directly(access_code)
497
+
498
+ level_data = self.LEVEL_DATA1
499
+ level_data["subtitle"] = "<a>invalid subtitle</a>"
500
+
501
+ self.login(email, password)
502
+ url = reverse("save_level_for_editor")
503
+ response = self.client.post(url, {"data": json.dumps(self.LEVEL_DATA1)})
504
+
505
+ assert response.status_code == 401
506
+
507
+ level_data["subtitle"] = "valid subtitle"
508
+ level_data["lesson"] = "<a>invalid lesson</a>"
509
+
510
+ response = self.client.post(url, {"data": json.dumps(self.LEVEL_DATA1)})
511
+
512
+ assert response.status_code == 401
513
+
514
+ level_data["lesson"] = "valid lesson"
515
+ level_data["hint"] = "<a>invalid hint</a>"
516
+
517
+ response = self.client.post(url, {"data": json.dumps(self.LEVEL_DATA1)})
518
+
519
+ assert response.status_code == 401
520
+
521
+ level_data["hint"] = "valid hint"
522
+
523
+ response = self.client.post(url, {"data": json.dumps(self.LEVEL_DATA1)})
524
+
525
+ assert response.status_code == 200
@@ -1,17 +1,11 @@
1
- import json
2
-
3
1
  from common.tests.utils.classes import create_class_directly
2
+ from common.tests.utils.organisation import create_organisation_directly, join_teacher_to_organisation
4
3
  from common.tests.utils.student import create_school_student_directly
5
4
  from common.tests.utils.teacher import signup_teacher_directly
6
- from common.tests.utils.organisation import (
7
- create_organisation_directly,
8
- join_teacher_to_organisation,
9
- )
10
5
  from deploy import captcha
11
6
  from django.test.client import Client
12
7
  from django.test.testcases import TestCase
13
8
  from django.urls import reverse
14
- from hamcrest import *
15
9
 
16
10
  from .utils.level import create_save_level
17
11
 
@@ -114,8 +108,8 @@ class LevelModerationTestCase(TestCase):
114
108
  # Create 2 teachers in the same school, one admin, one standard
115
109
  email1, password1 = signup_teacher_directly()
116
110
  email2, password2 = signup_teacher_directly()
117
- name, postcode = create_organisation_directly(email1)
118
- join_teacher_to_organisation(email2, name, postcode, is_admin=False)
111
+ school = create_organisation_directly(email1)
112
+ join_teacher_to_organisation(email2, school.name)
119
113
 
120
114
  # Create one class and student for each teacher
121
115
  _, class_name1, access_code1 = create_class_directly(email1)
@@ -137,9 +131,7 @@ class LevelModerationTestCase(TestCase):
137
131
  assert student1.new_user.first_name not in response.content.decode()
138
132
  assert student2.new_user.first_name in response.content.decode()
139
133
  # Try to delete level1, it shouldn't work
140
- delete_level1_url = reverse(
141
- "delete_level_for_editor", kwargs={"levelId": level1.id}
142
- )
134
+ delete_level1_url = reverse("delete_level_for_editor", kwargs={"levelId": level1.id})
143
135
  response = self.client.get(delete_level1_url)
144
136
  assert response.status_code == 401
145
137
  # Check level2 is still there
@@ -157,9 +149,7 @@ class LevelModerationTestCase(TestCase):
157
149
  assert student1.new_user.first_name in response.content.decode()
158
150
  assert student2.new_user.first_name in response.content.decode()
159
151
  # Delete level2
160
- delete_level2_url = reverse(
161
- "delete_level_for_editor", kwargs={"levelId": level2.id}
162
- )
152
+ delete_level2_url = reverse("delete_level_for_editor", kwargs={"levelId": level2.id})
163
153
  response = self.client.get(delete_level2_url)
164
154
  assert response.status_code == 200
165
155
  # Check level1 is still there and level2 is not there anymore
@@ -171,10 +161,6 @@ class LevelModerationTestCase(TestCase):
171
161
  def teacher_login(self, email, password):
172
162
  self.client.post(
173
163
  reverse("teacher_login"),
174
- {
175
- "auth-username": email,
176
- "auth-password": password,
177
- "teacher_login_view-current_step": "auth",
178
- },
164
+ {"auth-username": email, "auth-password": password, "teacher_login_view-current_step": "auth"},
179
165
  follow=True,
180
166
  )