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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{% extends 'game/base.html' %}
|
|
2
|
+
|
|
3
|
+
{% load static %}
|
|
4
|
+
{% load i18n %}
|
|
5
|
+
{% load app_tags %}
|
|
6
|
+
|
|
7
|
+
{% block scripts %}
|
|
8
|
+
{{block.super}}
|
|
9
|
+
<script type="text/javascript" src="https://editor-static.raspberrypi.org/releases/v0.29.1/web-component.js"></script>
|
|
10
|
+
<script type="text/javascript" src="{% static 'game/js/editor.js' %}"></script>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
var EPISODE = "{{ worksheet.episode.name|escapejs }}"
|
|
14
|
+
</script>
|
|
15
|
+
{% endblock %}
|
|
16
|
+
|
|
17
|
+
{% block css %}
|
|
18
|
+
{{block.super}}
|
|
19
|
+
<link href="{% static 'game/css/level_selection.css' %}" rel="stylesheet" type="text/css">
|
|
20
|
+
<link href="{% static 'game/css/editor.css' %}" rel="stylesheet" type="text/css">
|
|
21
|
+
{% endblock %}
|
|
22
|
+
|
|
23
|
+
{% block header %}
|
|
24
|
+
<section class="banner">
|
|
25
|
+
{% if user|is_logged_in_as_student or user|is_independent_student %}
|
|
26
|
+
<img class="banner--python-den--image" title="Python Den logo" alt="Python Den logo" src="{% static 'game/image/Python_Den_hero_student.png' %}">
|
|
27
|
+
{% else %}
|
|
28
|
+
<img class="banner--python-den--image" title="Python Den logo" alt="Python Den logo" src="{% static 'game/image/Python_levels_page.svg' %}">
|
|
29
|
+
{% endif %}
|
|
30
|
+
</section>
|
|
31
|
+
{% endblock header %}
|
|
32
|
+
|
|
33
|
+
{% block content %}
|
|
34
|
+
<div class="container">
|
|
35
|
+
<h4 class="text-center">{{worksheet.episode.name}}</h4>
|
|
36
|
+
<p>You can write your code and see its output using the code editor below.</p>
|
|
37
|
+
<p>Next to it, you can view your worksheet - you can't write anything in it, but you can copy and paste from it.</p>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div>
|
|
41
|
+
<div class="d-flex align-items-stretch justify-content-between m-5 flex-lg-row flex-md-column flex-sm-column">
|
|
42
|
+
<div class="d-flex align-items-stretch justify-content-center">
|
|
43
|
+
<a href="{% if user|is_independent_student %} {{worksheet.indy_worksheet_link}} {% else %} {{worksheet.student_worksheet_link}} {% endif %}"
|
|
44
|
+
class="button button--level button--icon hidden-md hidden-lg mb-5" target="_blank">
|
|
45
|
+
Worksheet<span class="iconify" data-icon="mdi:open-in-new"></span></a>
|
|
46
|
+
{% if not user|is_logged_in_as_student %}
|
|
47
|
+
<a href="{{worksheet.video_link}}" class="button button--level button--icon hidden-md hidden-lg mb-5 ml-5" target="_blank">
|
|
48
|
+
Video<span class="iconify" data-icon="mdi:open-in-new"></span></a>
|
|
49
|
+
{% endif %}
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<editor-wc
|
|
53
|
+
code="{{starter_code}}"
|
|
54
|
+
use_editor_styles="true">
|
|
55
|
+
</editor-wc>
|
|
56
|
+
|
|
57
|
+
<div class="hidden-sm hidden-xs">
|
|
58
|
+
<div class="d-flex flex-column">
|
|
59
|
+
{% if user|is_logged_in_as_student %}
|
|
60
|
+
<iframe height="500" width="600" src="{{worksheet.student_worksheet_link}}"></iframe>
|
|
61
|
+
{% else %}
|
|
62
|
+
<ul class="nav nav-tabs" role="tablist">
|
|
63
|
+
<li role="presentation" class="active">
|
|
64
|
+
{% if user|is_independent_student %}
|
|
65
|
+
<a href="#worksheet" aria-controls="worksheet" role="tab" data-toggle="tab">Worksheet</a>
|
|
66
|
+
{% else %}
|
|
67
|
+
<a href="#student-worksheet" aria-controls="student_worksheet" role="tab" data-toggle="tab">Student Worksheet</a>
|
|
68
|
+
{% endif %}
|
|
69
|
+
</li>
|
|
70
|
+
{% if user|is_logged_in_as_teacher %}
|
|
71
|
+
<li role="presentation">
|
|
72
|
+
<a href="#worksheet" aria-controls="worksheet" role="tab" data-toggle="tab">Lesson plan</a>
|
|
73
|
+
</li>
|
|
74
|
+
{% endif %}
|
|
75
|
+
<li role="presentation">
|
|
76
|
+
<a href="#video" aria-controls="video" role="tab" data-toggle="tab">Video</a>
|
|
77
|
+
</li>
|
|
78
|
+
</ul>
|
|
79
|
+
|
|
80
|
+
<div class="tab-content">
|
|
81
|
+
{% if user|is_logged_in_as_teacher %}
|
|
82
|
+
<div role="tabpanel" class="tab-pane active" id="student-worksheet">
|
|
83
|
+
<iframe height="500" width="600" src="{{worksheet.student_worksheet_link}}"></iframe>
|
|
84
|
+
</div>
|
|
85
|
+
{% endif %}
|
|
86
|
+
<div role="tabpanel" class="tab-pane {% if user|is_independent_student %} active {% endif %}" id="worksheet">
|
|
87
|
+
<iframe height="500" width="600" src="{% if user|is_independent_student %} {{worksheet.indy_worksheet_link}} {% else %} {{worksheet.lesson_plan_link}} {% endif %}"></iframe>
|
|
88
|
+
</div>
|
|
89
|
+
<div role="tabpanel" class="tab-pane" id="video">
|
|
90
|
+
<video height="500" width="600"
|
|
91
|
+
src="{{worksheet.video_link}}" type="video/mp4" controls></video>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
{% endif %}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{% endblock content %}
|
|
@@ -4,24 +4,29 @@
|
|
|
4
4
|
{% load game.utils %}
|
|
5
5
|
{% load app_tags %}
|
|
6
6
|
|
|
7
|
-
{% block title %}
|
|
7
|
+
{% block title %}
|
|
8
|
+
{% if language == "blockly" %}
|
|
9
|
+
Code for Life - Rapid Router - Scoreboard
|
|
10
|
+
{% else %}
|
|
11
|
+
Code for Life - Python Den - Scoreboard
|
|
12
|
+
{% endif %}
|
|
13
|
+
{% endblock %}
|
|
8
14
|
|
|
9
15
|
{% block scripts %}
|
|
10
16
|
{{block.super}}
|
|
11
17
|
<script src="{% static 'game/js/jquery.dataTables.min.js' %}"></script>
|
|
12
18
|
<script src="{% static 'game/js/dataTables.fixedColumns.js' %}"></script>
|
|
13
|
-
<script src="{% static 'game/js/widget-scroller.js' %}"></script>
|
|
14
|
-
<script src="{% static 'game/js/pqselect.min.js' %}"></script>
|
|
15
19
|
<script src="{% static 'game/js/jquery.outerhtml.js' %}"></script>
|
|
16
20
|
<script src="{% static 'game/js/scoreboard.js' %}"></script>
|
|
21
|
+
{% if language == "blockly" %}
|
|
22
|
+
<script src="{% static 'game/js/scoreboardSharedLevels.js' %}"></script>
|
|
23
|
+
{% endif %}
|
|
17
24
|
{% endblock %}
|
|
18
25
|
|
|
19
26
|
{% block css %}
|
|
20
27
|
{{block.super}}
|
|
21
28
|
<link href="https://code.jquery.com/ui/1.13.1/themes/base/jquery-ui.css" rel="stylesheet" type="text/css">
|
|
22
29
|
<link href="{% static 'game/css/scoreboard.css' %}" rel="stylesheet" type="text/css">
|
|
23
|
-
<link href="{% static 'game/css/pqselect.min.css' %}" rel="stylesheet" type="text/css">
|
|
24
|
-
<link href="{% static 'game/css/pqselect.multiselect.css' %}" rel="stylesheet" type="text/css">
|
|
25
30
|
<link href="{% static 'game/css/jquery.dataTables.css' %}" rel="stylesheet" type="text/css">
|
|
26
31
|
<link href="{% static 'game/css/dataTables.fixedColumns.css' %}" rel="stylesheet" type="text/css">
|
|
27
32
|
<link href="{% static 'game/css/dataTables.jqueryui.css' %}" rel="stylesheet" type="text/css">
|
|
@@ -31,9 +36,21 @@
|
|
|
31
36
|
{% endblock %}
|
|
32
37
|
|
|
33
38
|
{% block header %}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
{% if language == "blockly" %}
|
|
40
|
+
<section class="banner banner--rapid-router">
|
|
41
|
+
<img title="Rapid Router logo" alt="Rapid Router logo" src="{% static 'common/img/RR_logo.svg' %}">
|
|
42
|
+
</section>
|
|
43
|
+
{% else %}
|
|
44
|
+
<section class="banner">
|
|
45
|
+
{% if user|is_logged_in_as_student or user|is_independent_student %}
|
|
46
|
+
<img class="banner--python-den--image" title="Python Den logo"
|
|
47
|
+
alt="Python Den logo" src="{% static 'game/image/Python_Den_hero_student.png' %}">
|
|
48
|
+
{% else %}
|
|
49
|
+
<img class="banner--python-den--image" title="Python Den logo"
|
|
50
|
+
alt="Python Den logo" src="{% static 'game/image/Python_levels_page.svg' %}">
|
|
51
|
+
{% endif %}
|
|
52
|
+
</section>
|
|
53
|
+
{% endif %}
|
|
37
54
|
{% endblock header %}
|
|
38
55
|
|
|
39
56
|
{% block nav_ocargo_scoreboard %}
|
|
@@ -150,22 +167,22 @@
|
|
|
150
167
|
</tr>
|
|
151
168
|
</thead>
|
|
152
169
|
{% for student in student_data %}
|
|
153
|
-
{%
|
|
170
|
+
{% if student.name != user.first_name %}
|
|
154
171
|
<tr>
|
|
155
172
|
{% else %}
|
|
156
173
|
<tr class="current-student">
|
|
157
|
-
{%
|
|
174
|
+
{% endif %}
|
|
158
175
|
<td class="fixed-width">{{ student.class_field }}</td>
|
|
159
176
|
<td class="fixed-width">{{ student.name }}</td>
|
|
160
177
|
<td>{{ student.completed }}</td>
|
|
161
178
|
<td>{{ student.total_time }}</td>
|
|
162
179
|
{% for level_id, level_score in student.level_scores.items %}
|
|
163
180
|
{% if level_score.full_score %}
|
|
164
|
-
{%
|
|
181
|
+
{% if student.name != user.first_name %}
|
|
165
182
|
<td class="text-center scoreboard--completed">
|
|
166
183
|
{% else %}
|
|
167
184
|
<td class="text-center scoreboard--completed-secondary">
|
|
168
|
-
{%
|
|
185
|
+
{% endif %}
|
|
169
186
|
<div title="{{ level_score.score }}" class="d-flex justify-content-center">
|
|
170
187
|
<span class="iconify" data-icon="mdi:star"></span>
|
|
171
188
|
</div>
|
|
@@ -201,65 +218,67 @@
|
|
|
201
218
|
</div>
|
|
202
219
|
</div>
|
|
203
220
|
|
|
204
|
-
|
|
221
|
+
{% if language == "blockly" %}
|
|
222
|
+
<div class="container">
|
|
205
223
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
<h5>Shared levels</h5>
|
|
225
|
+
{% if user|is_logged_in_as_teacher %}
|
|
226
|
+
<p>The shared levels table displays levels that have been shared and then played by others.
|
|
227
|
+
You can moderate which levels are shared on the <a href="{% url 'level_moderation' %}">moderation page</a>.</p>
|
|
228
|
+
{% endif %}
|
|
211
229
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
<tr>
|
|
218
|
-
{% for shared_header in shared_headers %}
|
|
219
|
-
<th class="fixed-width">{{ shared_header }}</th>
|
|
220
|
-
{% endfor %}
|
|
221
|
-
{% for shared_level_header in shared_level_headers %}
|
|
222
|
-
<th class="no-sort text-center">{{ shared_level_header }}</th>
|
|
223
|
-
{% endfor %}
|
|
224
|
-
</tr>
|
|
225
|
-
</thead>
|
|
226
|
-
{% for student in shared_student_data %}
|
|
227
|
-
{% ifnotequal student.name user.first_name %}
|
|
230
|
+
<div class="background tableWrapper" id="tableWrapper">
|
|
231
|
+
{% if shared_student_data %}
|
|
232
|
+
<div class="dataTables_info" role="status" aria-live="polite"></div>
|
|
233
|
+
<table id="sharedLevelsTable" class="display cell-border wide header-primary data-primary">
|
|
234
|
+
<thead>
|
|
228
235
|
<tr>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
{%
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
{%
|
|
246
|
-
{% if
|
|
247
|
-
|
|
236
|
+
{% for shared_header in shared_headers %}
|
|
237
|
+
<th class="fixed-width">{{ shared_header }}</th>
|
|
238
|
+
{% endfor %}
|
|
239
|
+
{% for shared_level_header in shared_level_headers %}
|
|
240
|
+
<th class="no-sort text-center">{{ shared_level_header }}</th>
|
|
241
|
+
{% endfor %}
|
|
242
|
+
</tr>
|
|
243
|
+
</thead>
|
|
244
|
+
{% for student in shared_student_data %}
|
|
245
|
+
{% if student.name != user.first_name %}
|
|
246
|
+
<tr>
|
|
247
|
+
{% else %}
|
|
248
|
+
<tr class="current-student">
|
|
249
|
+
{% endif %}
|
|
250
|
+
<td class="fixed-width">{{ student.class_field }}</td>
|
|
251
|
+
<td class="fixed-width">{{ student.name }}</td>
|
|
252
|
+
{% for level_id, level_score in student.level_scores.items %}
|
|
253
|
+
{% if level_score.full_score %}
|
|
254
|
+
{% if student.name != user.first_name %}
|
|
255
|
+
<td class="text-center scoreboard--completed">
|
|
256
|
+
{% else %}
|
|
257
|
+
<td class="text-center scoreboard--completed-secondary">
|
|
258
|
+
{% endif %}
|
|
259
|
+
<div title="{{ level_score.score }}" class="d-flex justify-content-center">
|
|
260
|
+
<span class="iconify" data-icon="mdi:star"></span>
|
|
261
|
+
</div>
|
|
262
|
+
</td>
|
|
263
|
+
{% elif level_score.is_low_attempt %}
|
|
264
|
+
{% if user|is_logged_in_as_teacher %}
|
|
265
|
+
<td class="text-center scoreboard--started">{{ level_score.score }}</td>
|
|
266
|
+
{% else %}
|
|
267
|
+
<td class="text-center">{{ level_score.score }}</td>
|
|
268
|
+
{% endif %}
|
|
248
269
|
{% else %}
|
|
249
270
|
<td class="text-center">{{ level_score.score }}</td>
|
|
250
271
|
{% endif %}
|
|
251
|
-
{%
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
<p>No data.</p>
|
|
260
|
-
{% endif %}
|
|
272
|
+
{% endfor %}
|
|
273
|
+
</tr>
|
|
274
|
+
{% endfor %}
|
|
275
|
+
</table>
|
|
276
|
+
{% else %}
|
|
277
|
+
<p>No data.</p>
|
|
278
|
+
{% endif %}
|
|
279
|
+
</div>
|
|
261
280
|
</div>
|
|
262
|
-
|
|
281
|
+
{% endif %}
|
|
263
282
|
|
|
264
283
|
{% if user|is_logged_in_as_teacher %}
|
|
265
284
|
<div class="container">
|
game/tests/test_level_editor.py
CHANGED
|
@@ -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, create_save_level_with_multiple_houses
|
|
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
|
-
"
|
|
23
|
+
"python_enabled": False,
|
|
24
24
|
"decor": [],
|
|
25
|
-
"
|
|
25
|
+
"blockly_enabled": True,
|
|
26
26
|
"blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
|
|
27
27
|
"max_fuel": "50",
|
|
28
|
-
"
|
|
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
|
-
|
|
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
104
|
assert sharing_info1["teacher"]["shared"]
|
|
93
|
-
assert len(mail.outbox) == 1
|
|
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
|
-
"
|
|
116
|
+
"python_enabled": False,
|
|
106
117
|
"decor": [],
|
|
107
|
-
"
|
|
118
|
+
"blockly_enabled": True,
|
|
108
119
|
"blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
|
|
109
120
|
"max_fuel": "50",
|
|
110
|
-
"
|
|
121
|
+
"python_view_enabled": False,
|
|
111
122
|
"character": "3",
|
|
112
123
|
"name": "abc",
|
|
113
124
|
"theme": 1,
|
|
@@ -119,6 +130,10 @@ class LevelEditorTestCase(TestCase):
|
|
|
119
130
|
}
|
|
120
131
|
response = self.client.post(url, {"data": json.dumps(data1)})
|
|
121
132
|
|
|
133
|
+
level = Level.objects.all().last()
|
|
134
|
+
level.needs_approval = False
|
|
135
|
+
level.save()
|
|
136
|
+
|
|
122
137
|
assert response.status_code == 200
|
|
123
138
|
sharing_info1 = json.loads(self.get_sharing_information(json.loads(response.content)["id"]).getvalue())
|
|
124
139
|
assert sharing_info1["teacher"]["shared"]
|
|
@@ -210,7 +225,7 @@ class LevelEditorTestCase(TestCase):
|
|
|
210
225
|
response = self.client.get(load_level_url)
|
|
211
226
|
assert response.status_code == 200
|
|
212
227
|
|
|
213
|
-
#
|
|
228
|
+
# Log in as the first student
|
|
214
229
|
self.logout()
|
|
215
230
|
self.student_login(student_name1, access_code2, student_password1)
|
|
216
231
|
|
|
@@ -222,7 +237,7 @@ class LevelEditorTestCase(TestCase):
|
|
|
222
237
|
response = self.client.get(reverse("play_custom_level", args=[level.id]))
|
|
223
238
|
assert response.status_code == 200
|
|
224
239
|
|
|
225
|
-
#
|
|
240
|
+
# Log in as first teacher again
|
|
226
241
|
self.logout()
|
|
227
242
|
self.login(email1, password1)
|
|
228
243
|
|
|
@@ -230,7 +245,7 @@ class LevelEditorTestCase(TestCase):
|
|
|
230
245
|
response = self.client.post(share_url, {"recipientIDs[]": [student2.new_user.id], "action": ["share"]})
|
|
231
246
|
assert response.status_code == 200
|
|
232
247
|
|
|
233
|
-
#
|
|
248
|
+
# Log in as the second student
|
|
234
249
|
self.logout()
|
|
235
250
|
self.student_login(student_name2, access_code2, student_password2)
|
|
236
251
|
|
|
@@ -276,12 +291,12 @@ class LevelEditorTestCase(TestCase):
|
|
|
276
291
|
url = reverse("save_level_for_editor")
|
|
277
292
|
data_with_no_character = {
|
|
278
293
|
"origin": '{"coordinate":[3,5],"direction":"S"}',
|
|
279
|
-
"
|
|
294
|
+
"python_enabled": False,
|
|
280
295
|
"decor": [],
|
|
281
|
-
"
|
|
296
|
+
"blockly_enabled": True,
|
|
282
297
|
"blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
|
|
283
298
|
"max_fuel": "50",
|
|
284
|
-
"
|
|
299
|
+
"python_view_enabled": False,
|
|
285
300
|
"name": "abc",
|
|
286
301
|
"theme": 1,
|
|
287
302
|
"anonymous": True,
|
|
@@ -307,16 +322,16 @@ class LevelEditorTestCase(TestCase):
|
|
|
307
322
|
url = reverse("save_level_for_editor")
|
|
308
323
|
data_with_split_language = {
|
|
309
324
|
"origin": '{"coordinate":[3,5],"direction":"S"}',
|
|
310
|
-
"
|
|
325
|
+
"python_enabled": False,
|
|
311
326
|
"decor": [],
|
|
312
|
-
"
|
|
327
|
+
"blockly_enabled": True,
|
|
313
328
|
"blocks": [
|
|
314
329
|
{"type": "move_forwards"},
|
|
315
330
|
{"type": "turn_left"},
|
|
316
331
|
{"type": "turn_right"},
|
|
317
332
|
],
|
|
318
333
|
"max_fuel": "50",
|
|
319
|
-
"
|
|
334
|
+
"python_view_enabled": True,
|
|
320
335
|
"name": "abc",
|
|
321
336
|
"theme": 1,
|
|
322
337
|
"anonymous": True,
|
|
@@ -331,9 +346,9 @@ class LevelEditorTestCase(TestCase):
|
|
|
331
346
|
assert response.status_code == 200
|
|
332
347
|
new_level = Level.objects.get(name="abc")
|
|
333
348
|
|
|
334
|
-
assert new_level.
|
|
335
|
-
assert not new_level.
|
|
336
|
-
assert new_level.
|
|
349
|
+
assert new_level.python_view_enabled
|
|
350
|
+
assert not new_level.python_enabled
|
|
351
|
+
assert new_level.blockly_enabled
|
|
337
352
|
|
|
338
353
|
def test_level_loading(self):
|
|
339
354
|
email1, password1 = signup_teacher_directly()
|
|
@@ -357,12 +372,12 @@ class LevelEditorTestCase(TestCase):
|
|
|
357
372
|
url = reverse("save_level_for_editor")
|
|
358
373
|
data_with_multiple_houses = {
|
|
359
374
|
"origin": '{"coordinate":[3,5],"direction":"S"}',
|
|
360
|
-
"
|
|
375
|
+
"python_enabled": False,
|
|
361
376
|
"decor": [],
|
|
362
|
-
"
|
|
377
|
+
"blockly_enabled": True,
|
|
363
378
|
"blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
|
|
364
379
|
"max_fuel": "50",
|
|
365
|
-
"
|
|
380
|
+
"python_view_enabled": False,
|
|
366
381
|
"name": "multiple_houses",
|
|
367
382
|
"theme": 1,
|
|
368
383
|
"anonymous": True,
|
|
@@ -392,6 +407,24 @@ class LevelEditorTestCase(TestCase):
|
|
|
392
407
|
assert response.status_code == 200
|
|
393
408
|
assert response_data["level"]["destinations"] == "[[3,4], [3,3]]"
|
|
394
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
|
|
427
|
+
|
|
395
428
|
def test_level_of_anonymised_teacher_is_hidden(self):
|
|
396
429
|
# Create 2 teacher accounts
|
|
397
430
|
email1, password1 = signup_teacher_directly()
|
|
@@ -455,3 +488,38 @@ class LevelEditorTestCase(TestCase):
|
|
|
455
488
|
# Teacher shouldn't see any shared levels now
|
|
456
489
|
response = self.client.get(levels_url)
|
|
457
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
|