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.
- example_project/rapid_router_test_settings.py +19 -7
- example_project/settings.py +21 -8
- example_project/urls.py +5 -6
- game/__init__.py +1 -1
- game/admin.py +7 -2
- game/character.py +8 -0
- game/decor.py +40 -0
- game/end_to_end_tests/base_game_test.py +34 -27
- game/end_to_end_tests/editor_page.py +15 -0
- game/end_to_end_tests/game_page.py +88 -20
- game/end_to_end_tests/selenium_test_case.py +1 -20
- game/end_to_end_tests/test_cow_crashes.py +3 -5
- game/end_to_end_tests/test_level_editor.py +273 -10
- game/end_to_end_tests/test_level_selection.py +25 -3
- game/end_to_end_tests/test_play_through.py +222 -127
- game/end_to_end_tests/test_python_levels.py +41 -7
- game/end_to_end_tests/test_saving_workspace.py +2 -1
- game/forms.py +7 -1
- game/level_management.py +26 -11
- game/messages.py +899 -337
- game/migrations/0001_squashed_0025_levels_ordering_pt1.py +19 -1
- game/migrations/0026_levels_pt2.py +13 -2
- game/migrations/0032_cannot_turn_left_level.py +13 -2
- game/migrations/0033_recursion_level.py +13 -2
- game/migrations/0034_joes_level.py +13 -2
- game/migrations/0035_disable_route_score_level_70.py +0 -2
- game/migrations/0036_level_score_73.py +0 -2
- game/migrations/0037_level_score_79.py +0 -2
- game/migrations/0038_level_score_40.py +0 -1
- game/migrations/0042_level_score_73.py +0 -2
- game/migrations/0048_add_cow_field_and_blocks.py +0 -2
- game/migrations/0049_level_score_34.py +0 -2
- game/migrations/0050_level_score_40.py +0 -2
- game/migrations/0051_level_score_49.py +0 -1
- game/migrations/0086_loop_levels.py +13 -2
- game/migrations/0092_disable_algo_score_in_custom_levels.py +28 -0
- game/migrations/0093_alter_level_character_name.py +18 -0
- game/migrations/0094_add_hint_lesson_subtitle_to_levels.py +28 -0
- game/migrations/0095_level_commands.py +18 -0
- game/migrations/0096_alter_level_commands.py +18 -0
- game/migrations/0097_add_python_den_levels.py +1515 -0
- game/migrations/0098_add_episode_link_fields.py +44 -0
- game/migrations/0099_python_episodes_links.py +103 -0
- game/migrations/0100_reorder_python_levels.py +179 -0
- game/migrations/0101_rename_episodes.py +45 -0
- game/migrations/0102_reoder_episodes_13_14.py +136 -0
- game/migrations/0103_level_1015_solution.py +26 -0
- game/migrations/0104_remove_level_direct_drive.py +17 -0
- game/migrations/0105_delete_invalid_attempts.py +18 -0
- game/migrations/0106_fields_to_snake_case.py +48 -0
- game/migrations/0107_rename_worksheet_link_episode_student_worksheet_link.py +18 -0
- game/migrations/0108_episode_indy_worksheet_link.py +18 -0
- game/migrations/0109_create_episodes_23_and_24.py +99 -0
- game/migrations/0110_remove_episode_indy_worksheet_link_and_more.py +100 -0
- game/migrations/0111_create_worksheets.py +149 -0
- game/migrations/0112_worksheet_locked_classes.py +21 -0
- game/migrations/0113_level_needs_approval.py +18 -0
- game/migrations/0114_default_and_non_student_levels_no_approval.py +31 -0
- game/migrations/0115_level_level__default_does_not_need_approval.py +22 -0
- game/migrations/0116_update_worksheet_video_links.py +68 -0
- game/migrations/0117_update_solutions_to_if_else.py +61 -0
- game/models.py +127 -17
- game/permissions.py +51 -19
- game/python_den_urls.py +26 -0
- game/random_road.py +9 -9
- game/serializers.py +12 -17
- game/static/django_reverse_js/js/reverse.js +171 -0
- game/static/game/css/LilitaOne-Regular.ttf +0 -0
- game/static/game/css/backgrounds.css +8 -12
- game/static/game/css/dataTables.custom.css +3 -2
- game/static/game/css/editor.css +47 -0
- game/static/game/css/game.css +37 -43
- game/static/game/css/game_screen.css +16 -0
- game/static/game/css/level_editor.css +5 -0
- game/static/game/css/level_selection.css +17 -2
- game/static/game/image/Python_Den_hero_student.png +0 -0
- game/static/game/image/Python_levels_page.svg +1954 -0
- game/static/game/image/characters/front_view/Electric_van.svg +448 -0
- game/static/game/image/characters/top_view/Electric_van.svg +448 -0
- game/static/game/image/decor/city/solar_panel.svg +1200 -0
- game/static/game/image/decor/farm/solar_panel.svg +86 -0
- game/static/game/image/decor/grass/solar_panel.svg +86 -0
- game/static/game/image/decor/snow/solar_panel.svg +173 -0
- game/static/game/image/electric_van.svg +448 -0
- game/static/game/image/icons/description.svg +1 -0
- game/static/game/image/icons/hint.svg +1 -0
- game/static/game/image/icons/python.svg +1 -1
- game/static/game/image/pigeon.svg +684 -0
- game/static/game/image/python_den_header.svg +19 -0
- game/static/game/js/animation.js +65 -24
- game/static/game/js/blockly/msg/js/bg.js +52 -1
- game/static/game/js/blockly/msg/js/ca.js +52 -1
- game/static/game/js/blockly/msg/js/en-gb.js +2 -0
- game/static/game/js/blockly/msg/js/en.js +2 -0
- game/static/game/js/blockly/msg/js/es.js +52 -1
- game/static/game/js/blockly/msg/js/fr.js +2 -0
- game/static/game/js/blockly/msg/js/hi.js +2 -0
- game/static/game/js/blockly/msg/js/it.js +52 -1
- game/static/game/js/blockly/msg/js/pl.js +52 -1
- game/static/game/js/blockly/msg/js/pt-br.js +52 -1
- game/static/game/js/blockly/msg/js/ru.js +52 -1
- game/static/game/js/blockly/msg/js/ur.js +52 -1
- game/static/game/js/blocklyCustomBlocks.js +93 -52
- game/static/game/js/button.js +12 -0
- game/static/game/js/cow.js +11 -7
- game/static/game/js/drawing.js +68 -29
- game/static/game/js/editor.js +23 -0
- game/static/game/js/game.js +74 -110
- game/static/game/js/level_editor.js +646 -274
- game/static/game/js/level_moderation.js +33 -2
- game/static/game/js/level_selection.js +1 -1
- game/static/game/js/loadLanguages.js +2 -2
- game/static/game/js/model.js +32 -2
- game/static/game/js/pythonControl.js +14 -1
- game/static/game/js/scoreboard.js +0 -37
- game/static/game/js/scoreboardSharedLevels.js +48 -0
- game/static/game/js/skulpt/skulpt-stdlib.js +1 -1
- game/static/game/js/sound.js +52 -5
- game/static/game/raphael_image/characters/top_view/Electric_van.svg +448 -0
- game/static/game/raphael_image/decor/city/solar_panel.svg +1200 -0
- game/static/game/raphael_image/decor/farm/solar_panel.svg +86 -0
- game/static/game/raphael_image/decor/grass/solar_panel.svg +86 -0
- game/static/game/raphael_image/decor/snow/solar_panel.svg +173 -0
- game/static/game/raphael_image/pigeon.svg +685 -0
- game/static/game/sass/game.scss +2 -2
- game/static/game/sound/clown_horn.mp3 +0 -0
- game/static/game/sound/clown_horn.ogg +0 -0
- game/static/game/sound/electric_van_starting.mp3 +0 -0
- game/static/game/sound/electric_van_starting.ogg +0 -0
- game/static/game/sound/pigeon.mp3 +0 -0
- game/static/game/sound/pigeon.ogg +0 -0
- game/static/game/sound/sleigh_bells.mp3 +0 -0
- game/static/game/sound/sleigh_bells.ogg +0 -0
- game/static/game/sound/sleigh_crash.mp3 +0 -0
- game/static/game/sound/sleigh_crash.ogg +0 -0
- game/templates/game/base.html +34 -14
- game/templates/game/basenonav.html +11 -5
- game/templates/game/game.html +142 -38
- game/templates/game/level_editor.html +340 -236
- game/templates/game/level_moderation.html +19 -6
- game/templates/game/level_selection.html +18 -110
- game/templates/game/python_den_level_selection.html +291 -0
- game/templates/game/python_den_worksheet.html +101 -0
- game/templates/game/scoreboard.html +83 -64
- game/tests/test_level_editor.py +94 -26
- game/tests/test_level_selection.py +149 -46
- game/tests/test_python_den_worksheet.py +85 -0
- game/tests/test_scoreboard.py +34 -7
- game/tests/utils/level.py +32 -26
- game/theme.py +5 -5
- game/urls.py +199 -61
- game/views/language_code_conversions.py +86 -86
- game/views/level.py +155 -63
- game/views/level_editor.py +88 -55
- game/views/level_moderation.py +23 -0
- game/views/level_selection.py +116 -47
- game/views/level_solutions.py +491 -106
- game/views/scoreboard.py +76 -51
- game/views/worksheet.py +25 -0
- rapid_router-7.6.8.dist-info/METADATA +174 -0
- {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/RECORD +164 -104
- {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/WHEEL +1 -1
- example_project/manage.py +0 -10
- game/static/game/image/actions/go.svg +0 -18
- game/static/game/js/js-reverse.js +0 -14
- game/static/game/js/pqselect.min.js +0 -9
- game/static/game/js/widget-scroller.js +0 -906
- rapid_router-5.18.0.dist-info/METADATA +0 -17
- {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info/licenses}/LICENSE.md +0 -0
- {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
|
-
"
|
|
60
|
+
"python_enabled": False,
|
|
61
61
|
"decor": [],
|
|
62
|
-
"
|
|
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
|
-
"
|
|
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
|
|
87
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
#
|
|
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(
|
|
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
|
-
#
|
|
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(
|
|
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
|
-
#
|
|
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(
|
|
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
|
-
#
|
|
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
|
|
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
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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(
|
|
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(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
assert
|
|
234
|
-
|
|
235
|
-
|
|
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(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
assert
|
|
245
|
-
|
|
246
|
-
|
|
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()
|
game/tests/test_scoreboard.py
CHANGED
|
@@ -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(
|
|
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",
|
|
217
|
+
"Normal", "Teacher", teacher_email, "secretpa$sword"
|
|
191
218
|
)
|
|
192
219
|
admin_teacher = Teacher.objects.factory(
|
|
193
|
-
"Admin", "Admin",
|
|
220
|
+
"Admin", "Admin", admin_email, "secretpa$sword2"
|
|
194
221
|
)
|
|
195
222
|
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
"
|
|
31
|
+
"python_enabled": False,
|
|
9
32
|
"decor": [],
|
|
10
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
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,
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
47
|
+
text=gettext("City"),
|
|
48
48
|
selected="#C1C1C1",
|
|
49
49
|
background="#969696",
|
|
50
50
|
border="#686868",
|