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
@@ -57,16 +57,16 @@ class LevelSelectionTestCase(TestCase):
57
57
  def level_data(self, levelID):
58
58
  data = {
59
59
  "origin": '{"coordinate":[3,5],"direction":"S"}',
60
- "pythonEnabled": False,
60
+ "python_enabled": False,
61
61
  "decor": [],
62
- "blocklyEnabled": True,
62
+ "blockly_enabled": True,
63
63
  "blocks": [
64
64
  {"type": "move_forwards"},
65
65
  {"type": "turn_left"},
66
66
  {"type": "turn_right"},
67
67
  ],
68
68
  "max_fuel": "50",
69
- "pythonViewEnabled": False,
69
+ "python_view_enabled": False,
70
70
  "character": "3",
71
71
  "name": f"level{levelID}",
72
72
  "theme": 1,
@@ -83,8 +83,23 @@ class LevelSelectionTestCase(TestCase):
83
83
  response = self.client.get(url)
84
84
 
85
85
  assert response.status_code == 200
86
- assert response.context["blocklyEpisodes"][0]["name"] == "Getting Started"
87
- assert response.context["blocklyEpisodes"][0]["levels"][0]["title"] == "Can you help the van get to the house?"
86
+ assert (
87
+ response.context["blocklyEpisodes"][0]["name"] == "Getting Started"
88
+ )
89
+ assert (
90
+ response.context["blocklyEpisodes"][0]["levels"][0]["title"]
91
+ == "Can you help the van get to the house?"
92
+ )
93
+
94
+ def test_list_python_episodes(self):
95
+ url = reverse("python_levels")
96
+ response = self.client.get(url)
97
+
98
+ assert response.status_code == 200
99
+ assert (
100
+ response.context["pythonEpisodes"][0]["name"]
101
+ == "Output, Operators, and Data"
102
+ )
88
103
 
89
104
  def test_custom_levels_access(self):
90
105
  email1, password1 = signup_teacher_directly()
@@ -100,41 +115,55 @@ class LevelSelectionTestCase(TestCase):
100
115
  # Create a class and a student for each teacher
101
116
  _, class_name1, access_code1 = create_class_directly(email1)
102
117
  _, class_name2, access_code2 = create_class_directly(email2)
103
- student_name1, student_password1, student1 = create_school_student_directly(access_code1)
104
- student_name2, student_password2, student2 = create_school_student_directly(access_code2)
118
+ (
119
+ student_name1,
120
+ student_password1,
121
+ student1,
122
+ ) = create_school_student_directly(access_code1)
123
+ (
124
+ student_name2,
125
+ student_password2,
126
+ student2,
127
+ ) = create_school_student_directly(access_code2)
105
128
 
106
129
  save_url = "save_level_for_editor"
107
130
 
108
- # Login as the second teacher
131
+ # Log in as the second teacher
109
132
  self.login(email2, password2)
110
133
  teacher2_level = create_save_level(teacher2)
111
134
  save_level_url = reverse(save_url)
112
135
 
113
- response = self.client.post(save_level_url, {"data": self.level_data(teacher2_level.id)})
136
+ response = self.client.post(
137
+ save_level_url, {"data": self.level_data(teacher2_level.id)}
138
+ )
114
139
 
115
140
  assert response.status_code == 200
116
141
 
117
- # Login as the first student
142
+ # Log in as the first student
118
143
  self.logout()
119
144
  self.student_login(student_name1, access_code1, student_password1)
120
145
 
121
146
  student1_level = create_save_level(student1)
122
147
 
123
- response = self.client.post(save_level_url, {"data": self.level_data(student1_level.id)})
148
+ response = self.client.post(
149
+ save_level_url, {"data": self.level_data(student1_level.id)}
150
+ )
124
151
 
125
152
  assert response.status_code == 200
126
153
 
127
- # Login as the second student
154
+ # Log in as the second student
128
155
  self.logout()
129
156
  self.student_login(student_name2, access_code2, student_password2)
130
157
 
131
158
  student2_level = create_save_level(student2)
132
159
 
133
- response = self.client.post(save_level_url, {"data": self.level_data(student2_level.id)})
160
+ response = self.client.post(
161
+ save_level_url, {"data": self.level_data(student2_level.id)}
162
+ )
134
163
 
135
164
  assert response.status_code == 200
136
165
 
137
- # Login as first teacher again and check they have access to all the levels created above
166
+ # Log in as first teacher again and check they have access to all the levels created above
138
167
  self.logout()
139
168
  self.login(email1, password1)
140
169
 
@@ -143,13 +172,29 @@ class LevelSelectionTestCase(TestCase):
143
172
 
144
173
  assert response.status_code == 200
145
174
  assert len(response.context["directly_shared_levels"]) == 1
146
- assert response.context["directly_shared_levels"][0]["owner"] == student1.new_user
175
+ assert (
176
+ response.context["directly_shared_levels"][0]["owner"]
177
+ == student1.new_user
178
+ )
147
179
  assert response.context["indirectly_shared_levels"][teacher2.new_user]
148
- assert len(response.context["indirectly_shared_levels"][teacher2.new_user]) == 2
149
- assert response.context["indirectly_shared_levels"][teacher2.new_user][0]["owner"] == teacher2.new_user
150
- assert response.context["indirectly_shared_levels"][teacher2.new_user][1]["owner"] == student2.new_user
180
+ assert (
181
+ len(response.context["indirectly_shared_levels"][teacher2.new_user])
182
+ == 2
183
+ )
184
+ assert (
185
+ response.context["indirectly_shared_levels"][teacher2.new_user][0][
186
+ "owner"
187
+ ]
188
+ == teacher2.new_user
189
+ )
190
+ assert (
191
+ response.context["indirectly_shared_levels"][teacher2.new_user][1][
192
+ "owner"
193
+ ]
194
+ == student2.new_user
195
+ )
151
196
 
152
- # Login as second teacher again and check they have access to only their student's level
197
+ # Log in as second teacher again and check they have access to only their student's level
153
198
  self.logout()
154
199
  self.login(email2, password2)
155
200
 
@@ -158,7 +203,10 @@ class LevelSelectionTestCase(TestCase):
158
203
 
159
204
  assert response.status_code == 200
160
205
  assert len(response.context["directly_shared_levels"]) == 1
161
- assert response.context["directly_shared_levels"][0]["owner"] == student2.new_user
206
+ assert (
207
+ response.context["directly_shared_levels"][0]["owner"]
208
+ == student2.new_user
209
+ )
162
210
  assert response.context["indirectly_shared_levels"] == {}
163
211
 
164
212
  def test_cannot_access_locked_level(self):
@@ -196,51 +244,106 @@ class LevelSelectionTestCase(TestCase):
196
244
  level2 = Level.objects.get(name="2")
197
245
  level3 = Level.objects.get(name="3")
198
246
  level4 = Level.objects.get(name="4")
199
- level106 = Level.objects.get(name="106")
200
- level107 = Level.objects.get(name="107")
201
- level108 = Level.objects.get(name="108")
202
- level109 = Level.objects.get(name="109")
247
+ level76 = Level.objects.get(name="76")
248
+ level77 = Level.objects.get(name="77")
249
+ level78 = Level.objects.get(name="78")
250
+ level79 = Level.objects.get(name="79")
251
+ level1014 = Level.objects.get(name="1014")
252
+ level1015 = Level.objects.get(name="1015")
253
+ level1016 = Level.objects.get(name="1016")
203
254
 
204
255
  level2.locked_for_class.add(klass)
205
256
  level3.locked_for_class.add(klass)
206
- level107.locked_for_class.add(klass)
207
- level108.locked_for_class.add(klass)
208
- level109.locked_for_class.add(klass)
257
+ level77.locked_for_class.add(klass)
258
+ level78.locked_for_class.add(klass)
259
+ level1015.locked_for_class.add(klass)
209
260
 
210
- next_level_url = _next_level_url(level1, student.new_user, False)
261
+ next_level_url = _next_level_url(level1, student.new_user, False, False)
211
262
 
212
263
  assert next_level_url == f"/rapidrouter/{level4.name}/"
213
264
 
214
- prev_level_url = _prev_level_url(level4, student.new_user, False)
265
+ prev_level_url = _prev_level_url(level4, student.new_user, False, False)
215
266
  assert prev_level_url == f"/rapidrouter/{level1.name}/"
216
267
 
217
- next_level_url = _next_level_url(level106, student.new_user, False)
268
+ next_level_url = _next_level_url(
269
+ level76, student.new_user, False, False
270
+ )
271
+
272
+ assert next_level_url == f"/rapidrouter/{level79.name}/"
218
273
 
219
- assert next_level_url == f"/rapidrouter/{level109.name}/"
274
+ prev_level_url = _prev_level_url(
275
+ level79, student.new_user, False, False
276
+ )
277
+ assert prev_level_url == f"/rapidrouter/{level76.name}/"
278
+
279
+ next_level_url = _next_level_url(
280
+ level1014, student.new_user, False, True
281
+ )
220
282
 
221
- prev_level_url = _prev_level_url(level109, student.new_user, False)
222
- assert prev_level_url == f"/rapidrouter/{level106.name}/"
283
+ assert next_level_url == f"/pythonden/16/"
284
+
285
+ prev_level_url = _prev_level_url(
286
+ level1016, student.new_user, False, True
287
+ )
288
+ assert prev_level_url == f"/pythonden/14/"
223
289
 
224
- @patch("game.views.level.datetime", side_effect=lambda *args, **kw: datetime(*args, **kw))
290
+ @patch(
291
+ "game.views.level.datetime",
292
+ side_effect=lambda *args, **kw: datetime(*args, **kw),
293
+ )
225
294
  def test_xmas_theme(self, mock_datetime):
226
295
  november = datetime(2023, 11, 1, 0, 0, 0, 0)
227
296
  mock_datetime.now.return_value = november
228
297
 
229
298
  response = self.client.get(f"{reverse('home')}/rapidrouter/1/")
230
- assert """CHARACTER_NAME = "Van"\n""" in response.content.decode("utf-8")
231
- assert """CHARACTER_URL = "characters/top_view/Van.svg"\n""" in response.content.decode("utf-8")
232
- assert """WRECKAGE_URL = "van_wreckage.svg"\n""" in response.content.decode("utf-8")
233
- assert """BACKGROUND_URL = "decor/grass/tile1.svg"\n""" in response.content.decode("utf-8")
234
- assert """HOUSE_URL = "decor/grass/house.svg"\n""" in response.content.decode("utf-8")
235
- assert """CFC_URL = "decor/grass/cfc.svg"\n""" in response.content.decode("utf-8")
299
+ assert """CHARACTER_NAME = "Van"\n""" in response.content.decode(
300
+ "utf-8"
301
+ )
302
+ assert (
303
+ """CHARACTER_URL = "characters/top_view/Van.svg"\n"""
304
+ in response.content.decode("utf-8")
305
+ )
306
+ assert (
307
+ """WRECKAGE_URL = "van_wreckage.svg"\n"""
308
+ in response.content.decode("utf-8")
309
+ )
310
+ assert (
311
+ """BACKGROUND_URL = "decor/grass/tile1.svg"\n"""
312
+ in response.content.decode("utf-8")
313
+ )
314
+ assert (
315
+ """HOUSE_URL = "decor/grass/house.svg"\n"""
316
+ in response.content.decode("utf-8")
317
+ )
318
+ assert (
319
+ """CFC_URL = "decor/grass/cfc.svg"\n"""
320
+ in response.content.decode("utf-8")
321
+ )
236
322
 
237
323
  december = datetime(2023, 12, 1, 0, 0, 0, 0)
238
324
  mock_datetime.now.return_value = december
239
325
 
240
326
  response = self.client.get(f"{reverse('home')}/rapidrouter/1/")
241
- assert """CHARACTER_NAME = "Van"\n""" in response.content.decode("utf-8")
242
- assert """CHARACTER_URL = "characters/top_view/Sleigh.svg"\n""" in response.content.decode("utf-8")
243
- assert """WRECKAGE_URL = "sleigh_wreckage.svg"\n""" in response.content.decode("utf-8")
244
- assert """BACKGROUND_URL = "decor/snow/tile1.svg"\n""" in response.content.decode("utf-8")
245
- assert """HOUSE_URL = "decor/snow/house.svg"\n""" in response.content.decode("utf-8")
246
- assert """CFC_URL = "decor/snow/cfc.svg"\n""" in response.content.decode("utf-8")
327
+ assert """CHARACTER_NAME = "Van"\n""" in response.content.decode(
328
+ "utf-8"
329
+ )
330
+ assert (
331
+ """CHARACTER_URL = "characters/top_view/Sleigh.svg"\n"""
332
+ in response.content.decode("utf-8")
333
+ )
334
+ assert (
335
+ """WRECKAGE_URL = "sleigh_wreckage.svg"\n"""
336
+ in response.content.decode("utf-8")
337
+ )
338
+ assert (
339
+ """BACKGROUND_URL = "decor/snow/tile1.svg"\n"""
340
+ in response.content.decode("utf-8")
341
+ )
342
+ assert (
343
+ """HOUSE_URL = "decor/snow/house.svg"\n"""
344
+ in response.content.decode("utf-8")
345
+ )
346
+ assert (
347
+ """CFC_URL = "decor/snow/cfc.svg"\n"""
348
+ in response.content.decode("utf-8")
349
+ )
@@ -0,0 +1,85 @@
1
+ from deploy import captcha
2
+ from django.test.client import Client
3
+ from django.test.testcases import TestCase
4
+ from django.urls import reverse
5
+
6
+ from game import messages
7
+ from game.models import Worksheet
8
+
9
+
10
+ class PythonDenWorksheetTestCase(TestCase):
11
+ @classmethod
12
+ def setUpClass(cls):
13
+ cls.orig_captcha_enabled = captcha.CAPTCHA_ENABLED
14
+ captcha.CAPTCHA_ENABLED = False
15
+ super(PythonDenWorksheetTestCase, cls).setUpClass()
16
+
17
+ @classmethod
18
+ def tearDownClass(cls):
19
+ captcha.CAPTCHA_ENABLED = cls.orig_captcha_enabled
20
+ super(PythonDenWorksheetTestCase, cls).tearDownClass()
21
+
22
+ def setUp(self):
23
+ self.client = Client()
24
+
25
+ def login(self, email, password):
26
+ self.client.post(
27
+ reverse("teacher_login"),
28
+ {
29
+ "auth-username": email,
30
+ "auth-password": password,
31
+ "teacher_login_view-current_step": "auth",
32
+ },
33
+ follow=True,
34
+ )
35
+
36
+ def logout(self):
37
+ self.client.post(reverse("logout_view"), follow=True)
38
+
39
+ def student_login(self, name, access_code, password):
40
+ self.client.post(
41
+ reverse("student_login", kwargs={"access_code": access_code}),
42
+ {"username": name, "password": password},
43
+ follow=True,
44
+ )
45
+
46
+ def independent_student_login(self, email, password):
47
+ self.client.login(username=email, password=password)
48
+
49
+ def test_page_permissions(self):
50
+ response = self.client.get(reverse("worksheet", args="1"))
51
+
52
+ assert response.status_code == 200
53
+ assert (
54
+ response.context["title"]
55
+ == messages.no_permission_python_den_worksheet_title()
56
+ )
57
+ assert (
58
+ response.context["message"]
59
+ == messages.no_permission_python_den_worksheet_page()
60
+ )
61
+
62
+ self.login("alberteinstein@codeforlife.com", "Password1")
63
+
64
+ response = self.client.get(reverse("worksheet", args="1"))
65
+
66
+ assert response.status_code == 200
67
+ assert response.context["worksheet"] == Worksheet.objects.get(pk=1)
68
+
69
+ self.logout()
70
+ self.student_login("Leonardo", "AB123", "Password1")
71
+
72
+ response = self.client.get(reverse("worksheet", args="1"))
73
+
74
+ assert response.status_code == 200
75
+ assert response.context["worksheet"] == Worksheet.objects.get(pk=1)
76
+
77
+ self.logout()
78
+ self.independent_student_login("indianajones@codeforlife.com", "Password1")
79
+
80
+ response = self.client.get(reverse("worksheet", args="1"))
81
+
82
+ assert response.status_code == 200
83
+ assert response.context["worksheet"] == Worksheet.objects.get(pk=1)
84
+
85
+ self.logout()
@@ -5,7 +5,7 @@ from datetime import datetime, timedelta
5
5
 
6
6
  from common.models import Class, Teacher, Student
7
7
  from common.tests.utils.classes import create_class_directly
8
- from common.tests.utils.organisation import create_organisation_directly
8
+ from common.tests.utils.organisation import create_organisation_directly, join_teacher_to_organisation
9
9
  from common.tests.utils.student import (
10
10
  create_school_student_directly,
11
11
  create_independent_student_directly,
@@ -80,7 +80,7 @@ class ScoreboardTestCase(TestCase):
80
80
 
81
81
  # Generate results
82
82
  student_data, headers, level_headers, levels_sorted = scoreboard_data(
83
- episode_ids, attempts_per_student
83
+ episode_ids, attempts_per_student, "blockly"
84
84
  )
85
85
  (
86
86
  shared_headers,
@@ -153,7 +153,31 @@ class ScoreboardTestCase(TestCase):
153
153
 
154
154
  response = c.post(url, data)
155
155
 
156
- active_levels = Level.objects.filter(episode__in_development=False)
156
+ active_levels = Level.objects.filter(episode__pk__in=range(1, 10))
157
+
158
+ assert response.status_code == 200
159
+ assert len(response.context["level_headers"]) == active_levels.count()
160
+
161
+ def test_python_scoreboard_loads(self):
162
+ email, password = signup_teacher_directly()
163
+ create_organisation_directly(email)
164
+ klass, name, access_code = create_class_directly(email)
165
+ create_school_student_directly(access_code)
166
+
167
+ url = reverse("python_scoreboard")
168
+ c = Client()
169
+ c.login(username=email, password=password)
170
+
171
+ # test scoreboard page loads properly
172
+ response = c.get(url)
173
+ assert response.status_code == 200
174
+
175
+ # test scoreboard shows all episodes if no episodes are manually selected
176
+ data = {"classes": [klass.id], "view": [""]}
177
+
178
+ response = c.post(url, data)
179
+
180
+ active_levels = Level.objects.filter(episode__pk__in=range(12, 16))
157
181
 
158
182
  assert response.status_code == 200
159
183
  assert len(response.context["level_headers"]) == active_levels.count()
@@ -186,15 +210,18 @@ class ScoreboardTestCase(TestCase):
186
210
 
187
211
  def test_admin_teacher_can_see_all_classes(self):
188
212
  """An admin should be able to see all classes, not just the ones they teach"""
213
+ teacher_email = "normal@school.edu"
214
+ admin_email = "admin@school.edu"
215
+
189
216
  normal_teacher = Teacher.objects.factory(
190
- "Normal", "Teacher", "normal@school.edu", "secretpa$sword"
217
+ "Normal", "Teacher", teacher_email, "secretpa$sword"
191
218
  )
192
219
  admin_teacher = Teacher.objects.factory(
193
- "Admin", "Admin", "admin@school.edu", "secretpa$sword2"
220
+ "Admin", "Admin", admin_email, "secretpa$sword2"
194
221
  )
195
222
 
196
- admin_teacher.is_admin = True
197
- admin_teacher.save()
223
+ school = create_organisation_directly(admin_email)
224
+ join_teacher_to_organisation(teacher_email, school.name)
198
225
 
199
226
  _, name1, _ = create_class_directly(
200
227
  admin_teacher.user.user.email, class_name="Class 1"
game/tests/utils/level.py CHANGED
@@ -2,19 +2,42 @@ import game.level_management as level_management
2
2
  from game.models import Level
3
3
 
4
4
 
5
+ multiple_house_data = {
6
+ "origin": '{"coordinate":[3,5],"direction":"S"}',
7
+ "python_enabled": False,
8
+ "decor": [],
9
+ "blockly_enabled": True,
10
+ "blocks": [
11
+ {"type": "move_forwards"},
12
+ {"type": "turn_left"},
13
+ {"type": "turn_right"},
14
+ ],
15
+ "max_fuel": "50",
16
+ "python_view_enabled": False,
17
+ "character": "3",
18
+ "name": "2",
19
+ "theme": 1,
20
+ "anonymous": False,
21
+ "cows": "[]",
22
+ "path": '[{"coordinate":[3,5],"connectedNodes":[1]},{"coordinate":[3,4],"connectedNodes":[0,2]}, {"coordinate": [3,3], "connectedNodes":[1]}]',
23
+ "traffic_lights": "[]",
24
+ "destinations": "[[3,4], [3,3]]",
25
+ }
26
+
27
+
5
28
  def create_save_level(teacher_or_student, level_name="1", shared_with=None):
6
29
  data = {
7
30
  "origin": '{"coordinate":[3,5],"direction":"S"}',
8
- "pythonEnabled": False,
31
+ "python_enabled": False,
9
32
  "decor": [],
10
- "blocklyEnabled": True,
33
+ "blockly_enabled": True,
11
34
  "blocks": [
12
35
  {"type": "move_forwards"},
13
36
  {"type": "turn_left"},
14
37
  {"type": "turn_right"},
15
38
  ],
16
39
  "max_fuel": "50",
17
- "pythonViewEnabled": False,
40
+ "python_view_enabled": False,
18
41
  "character": "3",
19
42
  "name": level_name,
20
43
  "theme": 1,
@@ -27,6 +50,10 @@ def create_save_level(teacher_or_student, level_name="1", shared_with=None):
27
50
  level = Level(default=False, anonymous=data["anonymous"])
28
51
  level.owner = teacher_or_student.user
29
52
  level_management.save_level(level, data)
53
+
54
+ if hasattr(level.owner, "teacher"):
55
+ level.needs_approval = False
56
+
30
57
  level.save()
31
58
 
32
59
  if shared_with is not None:
@@ -37,30 +64,9 @@ def create_save_level(teacher_or_student, level_name="1", shared_with=None):
37
64
  return level
38
65
 
39
66
  def create_save_level_with_multiple_houses(teacher_or_student, level_name="2", shared_with=None):
40
- data = {
41
- "origin": '{"coordinate":[3,5],"direction":"S"}',
42
- "pythonEnabled": False,
43
- "decor": [],
44
- "blocklyEnabled": True,
45
- "blocks": [
46
- {"type": "move_forwards"},
47
- {"type": "turn_left"},
48
- {"type": "turn_right"},
49
- ],
50
- "max_fuel": "50",
51
- "pythonViewEnabled": False,
52
- "character": "3",
53
- "name": level_name,
54
- "theme": 1,
55
- "anonymous": False,
56
- "cows": "[]",
57
- "path": '[{"coordinate":[3,5],"connectedNodes":[1]},{"coordinate":[3,4],"connectedNodes":[0,2]}, {"coordinate": [3,3], "connectedNodes":[1]}]',
58
- "traffic_lights": "[]",
59
- "destinations": "[[3,4], [3,3]]",
60
- }
61
- level = Level(default=False, anonymous=data["anonymous"])
67
+ level = Level(default=False, anonymous=multiple_house_data["anonymous"])
62
68
  level.owner = teacher_or_student.user
63
- level_management.save_level(level, data)
69
+ level_management.save_level(level, multiple_house_data)
64
70
  level.save()
65
71
 
66
72
  if shared_with is not None:
game/theme.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  from builtins import object
6
6
  from rest_framework.reverse import reverse
7
- from django.utils.translation import ugettext
7
+ from django.utils.translation import gettext
8
8
 
9
9
 
10
10
  class Theme(object):
@@ -20,7 +20,7 @@ class Theme(object):
20
20
  THEME_DATA = {
21
21
  "grass": Theme(
22
22
  name="grass",
23
- text=ugettext("Grass"),
23
+ text=gettext("Grass"),
24
24
  selected="#bce369",
25
25
  background="#a0c53a",
26
26
  border="#70961f",
@@ -28,7 +28,7 @@ THEME_DATA = {
28
28
  ),
29
29
  "snow": Theme(
30
30
  name="snow",
31
- text=ugettext("Snow"),
31
+ text=gettext("Snow"),
32
32
  selected="#b3deff",
33
33
  background="#eef7ff",
34
34
  border="#83c9fe",
@@ -36,7 +36,7 @@ THEME_DATA = {
36
36
  ),
37
37
  "farm": Theme(
38
38
  name="farm",
39
- text=ugettext("Farm"),
39
+ text=gettext("Farm"),
40
40
  selected="#bce369",
41
41
  background="#a0c53a",
42
42
  border="#70961f",
@@ -44,7 +44,7 @@ THEME_DATA = {
44
44
  ),
45
45
  "city": Theme(
46
46
  name="city",
47
- text=ugettext("City"),
47
+ text=gettext("City"),
48
48
  selected="#C1C1C1",
49
49
  background="#969696",
50
50
  border="#686868",