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
@@ -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 %}Code for Life - Rapid Router - Scoreboard{% endblock %}
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
- <section class="banner banner--rapid-router">
35
- <img title="Rapid Router logo" alt="Rapid Router logo" src="{% static 'common/img/RR_logo.svg' %}">
36
- </section>
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
- {% ifnotequal student.name user.first_name %}
170
+ {% if student.name != user.first_name %}
154
171
  <tr>
155
172
  {% else %}
156
173
  <tr class="current-student">
157
- {% endifnotequal %}
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
- {% ifnotequal student.name user.first_name %}
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
- {% endifnotequal %}
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
- <div class="container">
221
+ {% if language == "blockly" %}
222
+ <div class="container">
205
223
 
206
- <h5>Shared levels</h5>
207
- {% if user|is_logged_in_as_teacher %}
208
- <p>The shared levels table displays levels that have been shared and then played by others.
209
- You can moderate which levels are shared on the <a href="{% url 'level_moderation' %}">moderation page</a>.</p>
210
- {% endif %}
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
- <div class="background tableWrapper" id="tableWrapper">
213
- {% if shared_student_data %}
214
- <div class="dataTables_info" role="status" aria-live="polite"></div>
215
- <table id="sharedLevelsTable" class="display cell-border wide header-primary data-primary">
216
- <thead>
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
- {% else %}
230
- <tr class="current-student">
231
- {% endifnotequal %}
232
- <td class="fixed-width">{{ student.class_field }}</td>
233
- <td class="fixed-width">{{ student.name }}</td>
234
- {% for level_id, level_score in student.level_scores.items %}
235
- {% if level_score.full_score %}
236
- {% ifnotequal student.name user.first_name %}
237
- <td class="text-center scoreboard--completed">
238
- {% else %}
239
- <td class="text-center scoreboard--completed-secondary">
240
- {% endifnotequal %}
241
- <div title="{{ level_score.score }}" class="d-flex justify-content-center">
242
- <span class="iconify" data-icon="mdi:star"></span>
243
- </div>
244
- </td>
245
- {% elif level_score.is_low_attempt %}
246
- {% if user|is_logged_in_as_teacher %}
247
- <td class="text-center scoreboard--started">{{ level_score.score }}</td>
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
- {% else %}
252
- <td class="text-center">{{ level_score.score }}</td>
253
- {% endif %}
254
- {% endfor %}
255
- </tr>
256
- {% endfor %}
257
- </table>
258
- {% else %}
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
- </div>
281
+ {% endif %}
263
282
 
264
283
  {% if user|is_logged_in_as_teacher %}
265
284
  <div class="container">
@@ -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
- "pythonEnabled": False,
23
+ "python_enabled": False,
24
24
  "decor": [],
25
- "blocklyEnabled": True,
25
+ "blockly_enabled": True,
26
26
  "blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
27
27
  "max_fuel": "50",
28
- "pythonViewEnabled": False,
28
+ "python_view_enabled": False,
29
29
  "character": "3",
30
30
  "name": "abc",
31
31
  "theme": 1,
@@ -87,10 +87,21 @@ class LevelEditorTestCase(TestCase):
87
87
  url = reverse("save_level_for_editor")
88
88
  response = self.client.post(url, {"data": json.dumps(self.LEVEL_DATA1)})
89
89
 
90
- assert_that(response.status_code, equal_to(200))
90
+ assert response.status_code == 200
91
+
92
+ level = Level.objects.all().last()
93
+ teacher_user = User.objects.get(email=email)
94
+
95
+ assert level.needs_approval == True
96
+ assert level.shared_with.count() == 1
97
+ assert level.shared_with.filter(id=teacher_user.id).exists()
98
+ assert len(mail.outbox) == 1
99
+
100
+ level.needs_approval = False
101
+ level.save()
102
+
91
103
  sharing_info1 = json.loads(self.get_sharing_information(json.loads(response.content)["id"]).getvalue())
92
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
- "pythonEnabled": False,
116
+ "python_enabled": False,
106
117
  "decor": [],
107
- "blocklyEnabled": True,
118
+ "blockly_enabled": True,
108
119
  "blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
109
120
  "max_fuel": "50",
110
- "pythonViewEnabled": False,
121
+ "python_view_enabled": False,
111
122
  "character": "3",
112
123
  "name": "abc",
113
124
  "theme": 1,
@@ -119,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
- # Login as the first student
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
- # Login as first teacher again
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
- # Login as the second student
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
- "pythonEnabled": False,
294
+ "python_enabled": False,
280
295
  "decor": [],
281
- "blocklyEnabled": True,
296
+ "blockly_enabled": True,
282
297
  "blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
283
298
  "max_fuel": "50",
284
- "pythonViewEnabled": False,
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
- "pythonEnabled": False,
325
+ "python_enabled": False,
311
326
  "decor": [],
312
- "blocklyEnabled": True,
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
- "pythonViewEnabled": True,
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.pythonViewEnabled
335
- assert not new_level.pythonEnabled
336
- assert new_level.blocklyEnabled
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
- "pythonEnabled": False,
375
+ "python_enabled": False,
361
376
  "decor": [],
362
- "blocklyEnabled": True,
377
+ "blockly_enabled": True,
363
378
  "blocks": [{"type": "move_forwards"}, {"type": "turn_left"}, {"type": "turn_right"}],
364
379
  "max_fuel": "50",
365
- "pythonViewEnabled": False,
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