rapid-router 5.4.1__py2.py3-none-any.whl → 7.6.8__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- example_project/rapid_router_test_settings.py +164 -0
- example_project/settings.py +152 -0
- example_project/urls.py +15 -0
- example_project/{example_project/wsgi.py → wsgi.py} +2 -1
- game/__init__.py +1 -1
- game/admin.py +43 -4
- game/app_settings.py +3 -7
- game/character.py +26 -18
- game/decor.py +172 -97
- game/end_to_end_tests/base_game_test.py +44 -33
- game/end_to_end_tests/editor_page.py +17 -2
- game/end_to_end_tests/game_page.py +127 -45
- 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_language_dropdown.py +14 -0
- game/end_to_end_tests/test_level_editor.py +290 -0
- game/end_to_end_tests/test_level_failures.py +1 -1
- game/end_to_end_tests/test_level_selection.py +79 -0
- game/end_to_end_tests/test_play_through.py +240 -102
- game/end_to_end_tests/test_python_levels.py +44 -13
- game/end_to_end_tests/test_saving_workspace.py +22 -0
- game/forms.py +9 -2
- game/level_management.py +38 -29
- game/messages.py +1218 -203
- 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/0076_level_locked_for_class.py +19 -0
- game/migrations/0077_alter_level_next_level.py +52 -0
- game/migrations/0078_add_block_types.py +23 -0
- game/migrations/0079_populate_block_type_add_cow_blocks.py +60 -0
- game/migrations/0080_level_disable_algorithm_score.py +18 -0
- game/migrations/0081_first_12_levels_no_algo_score.py +29 -0
- game/migrations/0082_level_43_solution.py +16 -0
- game/migrations/0083_add_cows_to_existing_levels.py +195 -0
- game/migrations/0084_alter_block_block_type.py +18 -0
- game/migrations/0085_add_new_blocks.py +53 -0
- game/migrations/0086_loop_levels.py +482 -0
- game/migrations/0087_workspace_python_view_enabled.py +18 -0
- game/migrations/0088_rename_episodes.py +35 -0
- game/migrations/0089_episodes_in_development.py +30 -0
- game/migrations/0090_add_missing_model_solutions.py +144 -0
- game/migrations/0091_disable_algo_score_if_no_model_solution.py +46 -0
- 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 +157 -16
- game/permissions.py +34 -19
- game/python_den_urls.py +26 -0
- game/random_road.py +43 -127
- 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 +14 -10
- game/static/game/css/dataTables.custom.css +4 -2
- game/static/game/css/dataTables.jqueryui.css +561 -320
- game/static/game/css/editor.css +47 -0
- game/static/game/css/game.css +43 -49
- game/static/game/css/game_screen.css +116 -53
- game/static/game/css/jquery.dataTables.css +455 -251
- game/static/game/css/level_editor.css +10 -1
- game/static/game/css/level_selection.css +32 -3
- game/static/game/css/level_share.css +6 -5
- game/static/game/css/skulpt/codemirror.css +1 -0
- 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/characters/top_view/Sleigh.svg +436 -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/barn.svg +1788 -0
- game/static/game/image/decor/snow/cfc.svg +1050 -147
- game/static/game/image/decor/snow/crops.svg +7370 -0
- game/static/game/image/decor/snow/hospital.svg +1220 -0
- game/static/game/image/decor/snow/house1.svg +971 -0
- game/static/game/image/decor/snow/house2.svg +1574 -0
- game/static/game/image/decor/snow/school.svg +1071 -0
- game/static/game/image/decor/snow/shop.svg +3211 -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/add_house.svg +26 -0
- game/static/game/image/icons/delete_house.svg +26 -0
- game/static/game/image/icons/description.svg +1 -0
- game/static/game/image/icons/hint.svg +1 -0
- game/static/game/image/icons/if_else.svg +3 -0
- game/static/game/image/icons/python.svg +1 -1
- game/static/game/image/if_else_example.png +0 -0
- game/static/game/image/pigeon.svg +684 -0
- game/static/game/image/python_den_header.svg +19 -0
- game/static/game/js/animation.js +84 -28
- 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 +52 -1
- game/static/game/js/blockly/msg/js/en.js +52 -1
- game/static/game/js/blockly/msg/js/es.js +52 -1
- game/static/game/js/blockly/msg/js/fr.js +52 -1
- game/static/game/js/blockly/msg/js/hi.js +52 -1
- 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/blocklyCompiler.js +550 -392
- game/static/game/js/blocklyControl.js +335 -302
- game/static/game/js/blocklyCustomBlocks.js +691 -458
- game/static/game/js/blocklyCustomisations.js +3 -1
- game/static/game/js/button.js +12 -0
- game/static/game/js/cow.js +15 -130
- game/static/game/js/drawing.js +313 -201
- game/static/game/js/editor.js +23 -0
- game/static/game/js/game.js +148 -139
- game/static/game/js/jquery.dataTables.min.js +3 -159
- game/static/game/js/level_editor.js +823 -448
- game/static/game/js/level_moderation.js +33 -2
- game/static/game/js/level_selection.js +62 -25
- game/static/game/js/loadLanguages.js +21 -0
- game/static/game/js/map.js +106 -36
- game/static/game/js/model.js +55 -107
- game/static/game/js/pathFinder.js +73 -72
- game/static/game/js/program.js +184 -193
- 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/sharing.js +22 -10
- game/static/game/js/skulpt/codemirror.js +5 -4
- game/static/game/js/skulpt/skulpt-stdlib.js +1 -1
- game/static/game/js/sound.js +52 -5
- game/static/game/js/van.js +0 -7
- game/static/game/raphael_image/characters/top_view/Electric_van.svg +448 -0
- game/static/game/raphael_image/characters/top_view/Sleigh.svg +436 -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/barn.svg +1788 -0
- game/static/game/raphael_image/decor/snow/cfc.svg +1050 -147
- game/static/game/raphael_image/decor/snow/crops.svg +7370 -0
- game/static/game/raphael_image/decor/snow/hospital.svg +1220 -0
- game/static/game/raphael_image/decor/snow/house1.svg +971 -0
- game/static/game/raphael_image/decor/snow/house2.svg +1574 -0
- game/static/game/raphael_image/decor/snow/school.svg +1071 -0
- game/static/game/raphael_image/decor/snow/shop.svg +3211 -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/raphael_image/sleigh_wreckage.svg +430 -0
- game/static/game/sass/game.scss +22 -6
- 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 +35 -15
- game/templates/game/basenonav.html +23 -17
- game/templates/game/game.html +236 -111
- game/templates/game/level_editor.html +353 -275
- game/templates/game/level_moderation.html +19 -6
- game/templates/game/level_selection.html +75 -62
- game/templates/game/python_den_level_selection.html +291 -0
- game/templates/game/python_den_worksheet.html +101 -0
- game/templates/game/scoreboard.html +88 -65
- game/tests/test_level_editor.py +210 -35
- game/tests/test_level_moderation.py +6 -20
- game/tests/test_level_selection.py +332 -11
- game/tests/test_python_den_worksheet.py +85 -0
- game/tests/test_scoreboard.py +258 -66
- game/tests/utils/level.py +43 -3
- game/tests/utils/teacher.py +2 -2
- game/theme.py +21 -21
- game/urls.py +125 -78
- game/views/language_code_conversions.py +90 -0
- game/views/level.py +201 -63
- game/views/level_editor.py +109 -48
- game/views/level_moderation.py +29 -6
- game/views/level_selection.py +179 -56
- game/views/level_solutions.py +600 -106
- game/views/scoreboard.py +181 -66
- game/views/worksheet.py +25 -0
- rapid_router-7.6.8.dist-info/METADATA +174 -0
- {rapid_router-5.4.1.dist-info → rapid_router-7.6.8.dist-info}/RECORD +222 -242
- {rapid_router-5.4.1.dist-info → rapid_router-7.6.8.dist-info}/WHEEL +1 -1
- rapid_router-7.6.8.dist-info/licenses/LICENSE.md +3 -0
- example_project/example_project/__init__.py +0 -1
- example_project/example_project/settings.py +0 -54
- example_project/example_project/urls.py +0 -16
- example_project/manage.py +0 -10
- game/autoconfig.py +0 -59
- game/csp_config.py +0 -23
- game/locale/ar_SA/LC_MESSAGES/django.mo +0 -0
- game/locale/ar_SA/LC_MESSAGES/django.po +0 -405
- game/locale/ar_SA/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/ar_SA/LC_MESSAGES/djangojs.po +0 -743
- game/locale/bg_BG/LC_MESSAGES/django.mo +0 -0
- game/locale/bg_BG/LC_MESSAGES/django.po +0 -405
- game/locale/bg_BG/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/bg_BG/LC_MESSAGES/djangojs.po +0 -739
- game/locale/ca_ES/LC_MESSAGES/django.mo +0 -0
- game/locale/ca_ES/LC_MESSAGES/django.po +0 -405
- game/locale/ca_ES/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/ca_ES/LC_MESSAGES/djangojs.po +0 -740
- game/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- game/locale/cs_CZ/LC_MESSAGES/django.po +0 -405
- game/locale/cs_CZ/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/cs_CZ/LC_MESSAGES/djangojs.po +0 -741
- game/locale/cy_GB/LC_MESSAGES/django.mo +0 -0
- game/locale/cy_GB/LC_MESSAGES/django.po +0 -405
- game/locale/cy_GB/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/cy_GB/LC_MESSAGES/djangojs.po +0 -743
- game/locale/de_DE/LC_MESSAGES/django.mo +0 -0
- game/locale/de_DE/LC_MESSAGES/django.po +0 -405
- game/locale/de_DE/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/de_DE/LC_MESSAGES/djangojs.po +0 -739
- game/locale/el_GR/LC_MESSAGES/django.mo +0 -0
- game/locale/el_GR/LC_MESSAGES/django.po +0 -405
- game/locale/el_GR/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/el_GR/LC_MESSAGES/djangojs.po +0 -739
- game/locale/en_GB/LC_MESSAGES/django.mo +0 -0
- game/locale/en_GB/LC_MESSAGES/django.po +0 -405
- game/locale/en_GB/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/en_GB/LC_MESSAGES/djangojs.po +0 -739
- game/locale/es_ES/LC_MESSAGES/django.mo +0 -0
- game/locale/es_ES/LC_MESSAGES/django.po +0 -405
- game/locale/es_ES/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/es_ES/LC_MESSAGES/djangojs.po +0 -739
- game/locale/fi_FI/LC_MESSAGES/django.mo +0 -0
- game/locale/fi_FI/LC_MESSAGES/django.po +0 -405
- game/locale/fi_FI/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/fi_FI/LC_MESSAGES/djangojs.po +0 -739
- game/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
- game/locale/fr_FR/LC_MESSAGES/django.po +0 -405
- game/locale/fr_FR/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/fr_FR/LC_MESSAGES/djangojs.po +0 -739
- game/locale/gu_IN/LC_MESSAGES/django.mo +0 -0
- game/locale/gu_IN/LC_MESSAGES/django.po +0 -405
- game/locale/gu_IN/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/gu_IN/LC_MESSAGES/djangojs.po +0 -739
- game/locale/hi_IN/LC_MESSAGES/django.mo +0 -0
- game/locale/hi_IN/LC_MESSAGES/django.po +0 -405
- game/locale/hi_IN/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/hi_IN/LC_MESSAGES/djangojs.po +0 -739
- game/locale/id_ID/LC_MESSAGES/django.mo +0 -0
- game/locale/id_ID/LC_MESSAGES/django.po +0 -405
- game/locale/id_ID/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/id_ID/LC_MESSAGES/djangojs.po +0 -738
- game/locale/it_IT/LC_MESSAGES/django.mo +0 -0
- game/locale/it_IT/LC_MESSAGES/django.po +0 -405
- game/locale/it_IT/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/it_IT/LC_MESSAGES/djangojs.po +0 -739
- game/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
- game/locale/ja_JP/LC_MESSAGES/django.po +0 -405
- game/locale/ja_JP/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/ja_JP/LC_MESSAGES/djangojs.po +0 -738
- game/locale/lol_US/LC_MESSAGES/django.mo +0 -0
- game/locale/lol_US/LC_MESSAGES/django.po +0 -405
- game/locale/lol_US/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/lol_US/LC_MESSAGES/djangojs.po +0 -739
- game/locale/nb_NO/LC_MESSAGES/django.mo +0 -0
- game/locale/nb_NO/LC_MESSAGES/django.po +0 -405
- game/locale/nb_NO/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/nb_NO/LC_MESSAGES/djangojs.po +0 -739
- game/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
- game/locale/nl_NL/LC_MESSAGES/django.po +0 -405
- game/locale/nl_NL/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/nl_NL/LC_MESSAGES/djangojs.po +0 -739
- game/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
- game/locale/pl_PL/LC_MESSAGES/django.po +0 -405
- game/locale/pl_PL/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/pl_PL/LC_MESSAGES/djangojs.po +0 -741
- game/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
- game/locale/pt_BR/LC_MESSAGES/django.po +0 -405
- game/locale/pt_BR/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/pt_BR/LC_MESSAGES/djangojs.po +0 -739
- game/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
- game/locale/pt_PT/LC_MESSAGES/django.po +0 -405
- game/locale/pt_PT/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/pt_PT/LC_MESSAGES/djangojs.po +0 -739
- game/locale/ro_RO/LC_MESSAGES/django.mo +0 -0
- game/locale/ro_RO/LC_MESSAGES/django.po +0 -405
- game/locale/ro_RO/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/ro_RO/LC_MESSAGES/djangojs.po +0 -740
- game/locale/ru_RU/LC_MESSAGES/django.mo +0 -0
- game/locale/ru_RU/LC_MESSAGES/django.po +0 -405
- game/locale/ru_RU/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/ru_RU/LC_MESSAGES/djangojs.po +0 -741
- game/locale/sv_SE/LC_MESSAGES/django.mo +0 -0
- game/locale/sv_SE/LC_MESSAGES/django.po +0 -405
- game/locale/sv_SE/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/sv_SE/LC_MESSAGES/djangojs.po +0 -739
- game/locale/tl_PH/LC_MESSAGES/django.mo +0 -0
- game/locale/tl_PH/LC_MESSAGES/django.po +0 -405
- game/locale/tl_PH/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/tl_PH/LC_MESSAGES/djangojs.po +0 -739
- game/locale/tlh_AA/LC_MESSAGES/django.mo +0 -0
- game/locale/tlh_AA/LC_MESSAGES/django.po +0 -405
- game/locale/tlh_AA/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/tlh_AA/LC_MESSAGES/djangojs.po +0 -739
- game/locale/tr_TR/LC_MESSAGES/django.mo +0 -0
- game/locale/tr_TR/LC_MESSAGES/django.po +0 -405
- game/locale/tr_TR/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/tr_TR/LC_MESSAGES/djangojs.po +0 -740
- game/locale/ur_IN/LC_MESSAGES/django.mo +0 -0
- game/locale/ur_IN/LC_MESSAGES/django.po +0 -405
- game/locale/ur_IN/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/ur_IN/LC_MESSAGES/djangojs.po +0 -739
- game/locale/ur_PK/LC_MESSAGES/django.mo +0 -0
- game/locale/ur_PK/LC_MESSAGES/django.po +0 -405
- game/locale/ur_PK/LC_MESSAGES/djangojs.mo +0 -0
- game/locale/ur_PK/LC_MESSAGES/djangojs.po +0 -739
- game/static/game/image/actions/go.svg +0 -18
- game/static/game/image/icons/destination.svg +0 -9
- game/static/game/js/pqselect.min.js +0 -9
- game/static/game/js/widget-scroller.js +0 -906
- rapid_router-5.4.1.dist-info/LICENSE.md +0 -577
- rapid_router-5.4.1.dist-info/METADATA +0 -24
- {rapid_router-5.4.1.dist-info → rapid_router-7.6.8.dist-info}/top_level.txt +0 -0
game/tests/test_scoreboard.py
CHANGED
|
@@ -5,45 +5,92 @@ 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
|
|
9
|
-
from common.tests.utils.student import
|
|
8
|
+
from common.tests.utils.organisation import create_organisation_directly, join_teacher_to_organisation
|
|
9
|
+
from common.tests.utils.student import (
|
|
10
|
+
create_school_student_directly,
|
|
11
|
+
create_independent_student_directly,
|
|
12
|
+
)
|
|
10
13
|
from common.tests.utils.teacher import signup_teacher_directly
|
|
11
14
|
from django.test import Client, TestCase
|
|
12
15
|
from django.urls import reverse
|
|
13
16
|
|
|
14
17
|
from game.models import Attempt, Level, Episode
|
|
15
18
|
from game.tests.utils.level import create_save_level
|
|
16
|
-
from game.views.scoreboard import
|
|
17
|
-
|
|
19
|
+
from game.views.scoreboard import (
|
|
20
|
+
StudentRow,
|
|
21
|
+
scoreboard_data,
|
|
22
|
+
Headers,
|
|
23
|
+
StudentInTrouble,
|
|
24
|
+
shared_level_to_name,
|
|
25
|
+
shared_levels_data,
|
|
26
|
+
SharedHeaders,
|
|
27
|
+
)
|
|
28
|
+
from game.views.scoreboard_csv import (
|
|
29
|
+
scoreboard_csv,
|
|
30
|
+
Headers as CSVHeaders,
|
|
31
|
+
SharedHeaders as CSVSharedHeaders,
|
|
32
|
+
)
|
|
18
33
|
|
|
19
34
|
|
|
20
35
|
class ScoreboardTestCase(TestCase):
|
|
21
36
|
def test_teacher_multiple_students_multiple_levels(self):
|
|
22
|
-
|
|
37
|
+
# Setup official levels data
|
|
38
|
+
episode_ids = [1, 2]
|
|
23
39
|
episode1 = Episode.objects.get(id=1)
|
|
24
|
-
|
|
40
|
+
episode2 = Episode.objects.get(id=2)
|
|
41
|
+
level_ids = [
|
|
42
|
+
f"{x}"
|
|
43
|
+
for x in range(1, len(episode1.levels) + len(episode2.levels) + 1)
|
|
44
|
+
]
|
|
25
45
|
level1 = Level.objects.get(name="1")
|
|
26
|
-
|
|
46
|
+
level13 = Level.objects.get(name="13")
|
|
27
47
|
|
|
28
48
|
clas, student, student2 = set_up_data()
|
|
29
49
|
|
|
30
|
-
create_attempt(student, level1,
|
|
50
|
+
create_attempt(student, level1, 20)
|
|
31
51
|
create_attempt(student2, level1, 2)
|
|
32
|
-
create_attempt(student2,
|
|
52
|
+
create_attempt(student2, level13, 16)
|
|
33
53
|
|
|
34
|
-
|
|
54
|
+
# Setup custom levels data
|
|
55
|
+
shared_level = create_save_level(
|
|
56
|
+
student, "custom_level1", shared_with=[student2.new_user]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
create_attempt(student2, shared_level, 10)
|
|
60
|
+
|
|
61
|
+
all_levels = [level1, level13]
|
|
62
|
+
all_shared_levels = [shared_level]
|
|
35
63
|
|
|
36
64
|
attempts_per_student = {
|
|
37
|
-
student: Attempt.objects.filter(
|
|
38
|
-
|
|
39
|
-
),
|
|
65
|
+
student: Attempt.objects.filter(
|
|
66
|
+
level__in=all_levels, student=student, is_best_attempt=True
|
|
67
|
+
).select_related("level"),
|
|
40
68
|
student2: Attempt.objects.filter(
|
|
41
69
|
level__in=all_levels, student=student2, is_best_attempt=True
|
|
42
70
|
).select_related("level"),
|
|
43
71
|
}
|
|
44
72
|
|
|
45
|
-
|
|
73
|
+
shared_attempts_per_student = {
|
|
74
|
+
student2: Attempt.objects.filter(
|
|
75
|
+
level__in=all_shared_levels,
|
|
76
|
+
student=student2,
|
|
77
|
+
is_best_attempt=True,
|
|
78
|
+
).select_related("level"),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Generate results
|
|
82
|
+
student_data, headers, level_headers, levels_sorted = scoreboard_data(
|
|
83
|
+
episode_ids, attempts_per_student, "blockly"
|
|
84
|
+
)
|
|
85
|
+
(
|
|
86
|
+
shared_headers,
|
|
87
|
+
shared_level_headers,
|
|
88
|
+
shared_student_data,
|
|
89
|
+
) = shared_levels_data(
|
|
90
|
+
student.new_user, all_shared_levels, shared_attempts_per_student
|
|
91
|
+
)
|
|
46
92
|
|
|
93
|
+
# Check data for official levels matches
|
|
47
94
|
assert headers == Headers
|
|
48
95
|
assert level_headers == [f"L{id}" for id in level_ids]
|
|
49
96
|
|
|
@@ -52,21 +99,40 @@ class ScoreboardTestCase(TestCase):
|
|
|
52
99
|
student_row = student_data[0]
|
|
53
100
|
assert student_row.class_field.name == clas.name
|
|
54
101
|
assert student_row.name == student.user.user.first_name
|
|
55
|
-
assert student_row.total_score ==
|
|
102
|
+
assert student_row.total_score == 20
|
|
56
103
|
assert student_row.total_time == timedelta(0)
|
|
57
|
-
assert student_row.level_scores[
|
|
104
|
+
assert student_row.level_scores[all_levels[0].id]["score"] == 20
|
|
58
105
|
assert student_row.completed == 1
|
|
59
|
-
assert student_row.
|
|
106
|
+
assert student_row.success_rate == 100.0
|
|
60
107
|
|
|
61
108
|
student_row = student_data[1]
|
|
62
109
|
assert student_row.class_field.name == clas.name
|
|
63
110
|
assert student_row.name == student2.user.user.first_name
|
|
64
111
|
assert student_row.total_score == 18
|
|
65
112
|
assert student_row.total_time == timedelta(0)
|
|
66
|
-
assert student_row.level_scores[
|
|
67
|
-
assert student_row.level_scores[
|
|
113
|
+
assert student_row.level_scores[all_levels[0].id]["score"] == 2
|
|
114
|
+
assert student_row.level_scores[all_levels[1].id]["score"] == 16
|
|
68
115
|
assert student_row.completed == 1
|
|
69
|
-
assert
|
|
116
|
+
assert (
|
|
117
|
+
student_row.success_rate == 45.0
|
|
118
|
+
) ## the scores, (2 + 16 = 18), divided by the total possible, (2 * 20 = 40), 18/40 = 45%
|
|
119
|
+
|
|
120
|
+
# Check data for custom levels matches
|
|
121
|
+
assert shared_headers == SharedHeaders
|
|
122
|
+
assert shared_level_headers == [
|
|
123
|
+
f"{shared_level.name} ({shared_level.owner})"
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
assert len(shared_student_data) == 1
|
|
127
|
+
|
|
128
|
+
student_row = shared_student_data[0]
|
|
129
|
+
assert student_row.class_field.name == clas.name
|
|
130
|
+
assert student_row.name == student2.user.user.first_name
|
|
131
|
+
assert student_row.total_score == 10
|
|
132
|
+
assert student_row.total_time == timedelta(0)
|
|
133
|
+
assert student_row.level_scores[shared_level.id]["score"] == 10
|
|
134
|
+
assert student_row.completed == 1
|
|
135
|
+
assert student_row.success_rate == 100.0
|
|
70
136
|
|
|
71
137
|
def test_scoreboard_loads(self):
|
|
72
138
|
email, password = signup_teacher_directly()
|
|
@@ -86,14 +152,47 @@ class ScoreboardTestCase(TestCase):
|
|
|
86
152
|
data = {"classes": [klass.id], "view": [""]}
|
|
87
153
|
|
|
88
154
|
response = c.post(url, data)
|
|
155
|
+
|
|
156
|
+
active_levels = Level.objects.filter(episode__pk__in=range(1, 10))
|
|
157
|
+
|
|
89
158
|
assert response.status_code == 200
|
|
90
|
-
assert len(response.context["level_headers"]) ==
|
|
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))
|
|
181
|
+
|
|
182
|
+
assert response.status_code == 200
|
|
183
|
+
assert len(response.context["level_headers"]) == active_levels.count()
|
|
91
184
|
|
|
92
185
|
def test_student_can_see_classes(self):
|
|
93
186
|
"""A student should be able to see the classes they are in"""
|
|
94
|
-
mr_teacher = Teacher.objects.factory(
|
|
95
|
-
|
|
96
|
-
|
|
187
|
+
mr_teacher = Teacher.objects.factory(
|
|
188
|
+
"Normal", "Teacher", "normal@school.edu", "secretpa$sword"
|
|
189
|
+
)
|
|
190
|
+
klass, name1, _ = create_class_directly(
|
|
191
|
+
mr_teacher.user.user.email, class_name="Class 1"
|
|
192
|
+
)
|
|
193
|
+
_, name2, _ = create_class_directly(
|
|
194
|
+
mr_teacher.user.user.email, class_name="Class 2"
|
|
195
|
+
)
|
|
97
196
|
student = Student.objects.schoolFactory(klass, "some student", "secret")
|
|
98
197
|
|
|
99
198
|
c = Client()
|
|
@@ -102,30 +201,49 @@ class ScoreboardTestCase(TestCase):
|
|
|
102
201
|
url = reverse("scoreboard")
|
|
103
202
|
response = c.get(url)
|
|
104
203
|
|
|
105
|
-
choices_in_form = [
|
|
204
|
+
choices_in_form = [
|
|
205
|
+
v for (k, v) in response.context["form"]["classes"].field.choices
|
|
206
|
+
]
|
|
106
207
|
assert name1 in choices_in_form
|
|
107
208
|
assert name2 not in choices_in_form
|
|
108
209
|
assert len(choices_in_form) == 1
|
|
109
210
|
|
|
110
211
|
def test_admin_teacher_can_see_all_classes(self):
|
|
111
212
|
"""An admin should be able to see all classes, not just the ones they teach"""
|
|
112
|
-
|
|
113
|
-
|
|
213
|
+
teacher_email = "normal@school.edu"
|
|
214
|
+
admin_email = "admin@school.edu"
|
|
114
215
|
|
|
115
|
-
|
|
116
|
-
|
|
216
|
+
normal_teacher = Teacher.objects.factory(
|
|
217
|
+
"Normal", "Teacher", teacher_email, "secretpa$sword"
|
|
218
|
+
)
|
|
219
|
+
admin_teacher = Teacher.objects.factory(
|
|
220
|
+
"Admin", "Admin", admin_email, "secretpa$sword2"
|
|
221
|
+
)
|
|
117
222
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
223
|
+
school = create_organisation_directly(admin_email)
|
|
224
|
+
join_teacher_to_organisation(teacher_email, school.name)
|
|
225
|
+
|
|
226
|
+
_, name1, _ = create_class_directly(
|
|
227
|
+
admin_teacher.user.user.email, class_name="Class 1"
|
|
228
|
+
)
|
|
229
|
+
_, name2, _ = create_class_directly(
|
|
230
|
+
admin_teacher.user.user.email, class_name="Class 2"
|
|
231
|
+
)
|
|
232
|
+
_, name3, _ = create_class_directly(
|
|
233
|
+
normal_teacher.user.user.email, class_name="Class 3"
|
|
234
|
+
)
|
|
121
235
|
|
|
122
236
|
c = Client()
|
|
123
|
-
c.login(
|
|
237
|
+
c.login(
|
|
238
|
+
username=admin_teacher.user.user.email, password="secretpa$sword2"
|
|
239
|
+
)
|
|
124
240
|
|
|
125
241
|
url = reverse("scoreboard")
|
|
126
242
|
response = c.get(url)
|
|
127
243
|
|
|
128
|
-
choices_in_form = [
|
|
244
|
+
choices_in_form = [
|
|
245
|
+
v for (k, v) in response.context["form"]["classes"].field.choices
|
|
246
|
+
]
|
|
129
247
|
|
|
130
248
|
assert name1 in choices_in_form
|
|
131
249
|
assert name2 in choices_in_form
|
|
@@ -133,12 +251,22 @@ class ScoreboardTestCase(TestCase):
|
|
|
133
251
|
|
|
134
252
|
def test_non_admin_teacher_can_only_see_their_own_classes(self):
|
|
135
253
|
"""A teacher who is not an admin should only be able to see their classes, not ones taught by others"""
|
|
136
|
-
teacher1 = Teacher.objects.factory(
|
|
137
|
-
|
|
254
|
+
teacher1 = Teacher.objects.factory(
|
|
255
|
+
"First", "Teacher", "normal@school.edu", "secretpa$sword"
|
|
256
|
+
)
|
|
257
|
+
teacher2 = Teacher.objects.factory(
|
|
258
|
+
"Second", "Teacher", "admin@school.edu", "secretpa$sword2"
|
|
259
|
+
)
|
|
138
260
|
|
|
139
|
-
_, name1, _ = create_class_directly(
|
|
140
|
-
|
|
141
|
-
|
|
261
|
+
_, name1, _ = create_class_directly(
|
|
262
|
+
teacher2.user.user.email, class_name="Class 1"
|
|
263
|
+
)
|
|
264
|
+
_, name2, _ = create_class_directly(
|
|
265
|
+
teacher2.user.user.email, class_name="Class 2"
|
|
266
|
+
)
|
|
267
|
+
_, name3, _ = create_class_directly(
|
|
268
|
+
teacher1.user.user.email, class_name="Class 3"
|
|
269
|
+
)
|
|
142
270
|
|
|
143
271
|
c = Client()
|
|
144
272
|
# First teacher logs in. Should see only Class 3
|
|
@@ -147,7 +275,9 @@ class ScoreboardTestCase(TestCase):
|
|
|
147
275
|
url = reverse("scoreboard")
|
|
148
276
|
response = c.get(url)
|
|
149
277
|
|
|
150
|
-
choices_in_form = [
|
|
278
|
+
choices_in_form = [
|
|
279
|
+
v for (k, v) in response.context["form"]["classes"].field.choices
|
|
280
|
+
]
|
|
151
281
|
|
|
152
282
|
assert name3 in choices_in_form
|
|
153
283
|
assert name1 not in choices_in_form
|
|
@@ -158,7 +288,9 @@ class ScoreboardTestCase(TestCase):
|
|
|
158
288
|
c.login(username="admin@school.edu", password="secretpa$sword2")
|
|
159
289
|
|
|
160
290
|
response = c.get(url)
|
|
161
|
-
choices_in_form = [
|
|
291
|
+
choices_in_form = [
|
|
292
|
+
v for (k, v) in response.context["form"]["classes"].field.choices
|
|
293
|
+
]
|
|
162
294
|
|
|
163
295
|
assert name3 not in choices_in_form
|
|
164
296
|
assert name1 in choices_in_form
|
|
@@ -174,7 +306,10 @@ class ScoreboardTestCase(TestCase):
|
|
|
174
306
|
url = reverse("scoreboard")
|
|
175
307
|
response = c.get(url)
|
|
176
308
|
|
|
177
|
-
assert
|
|
309
|
+
assert (
|
|
310
|
+
"Scoreboard is only visible to school students and teachers"
|
|
311
|
+
in str(response.content)
|
|
312
|
+
)
|
|
178
313
|
|
|
179
314
|
|
|
180
315
|
class ScoreboardCsvTestCase(TestCase):
|
|
@@ -190,14 +325,25 @@ class ScoreboardCsvTestCase(TestCase):
|
|
|
190
325
|
|
|
191
326
|
# Create 2 custom levels and create the associated student data
|
|
192
327
|
shared_level_rows = [None, None]
|
|
193
|
-
shared_level1 = create_save_level(
|
|
328
|
+
shared_level1 = create_save_level(
|
|
329
|
+
students[0], "level1", shared_with=[students[1].new_user]
|
|
330
|
+
)
|
|
194
331
|
shared_level2 = create_save_level(students[1], "level2")
|
|
195
332
|
shared_levels = [shared_level1, shared_level2]
|
|
196
333
|
|
|
197
|
-
shared_levels_headers = list(
|
|
334
|
+
shared_levels_headers = list(
|
|
335
|
+
[
|
|
336
|
+
shared_level_to_name(level, level.owner)
|
|
337
|
+
for level in shared_levels
|
|
338
|
+
]
|
|
339
|
+
)
|
|
198
340
|
|
|
199
|
-
shared_level_rows[0] = self.shared_student_row(
|
|
200
|
-
|
|
341
|
+
shared_level_rows[0] = self.shared_student_row(
|
|
342
|
+
students[0], shared_levels
|
|
343
|
+
)
|
|
344
|
+
shared_level_rows[1] = self.shared_student_row(
|
|
345
|
+
students[1], shared_levels
|
|
346
|
+
)
|
|
201
347
|
|
|
202
348
|
# Create students' improvement table data
|
|
203
349
|
improvement_data = []
|
|
@@ -206,23 +352,44 @@ class ScoreboardCsvTestCase(TestCase):
|
|
|
206
352
|
improvement_data.append(stud)
|
|
207
353
|
|
|
208
354
|
# Generate the CSV
|
|
209
|
-
response = scoreboard_csv(
|
|
355
|
+
response = scoreboard_csv(
|
|
356
|
+
student_rows,
|
|
357
|
+
levels,
|
|
358
|
+
improvement_data,
|
|
359
|
+
shared_levels_headers,
|
|
360
|
+
shared_level_rows,
|
|
361
|
+
)
|
|
210
362
|
|
|
211
363
|
# Gather the data from the CSV
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
364
|
+
(
|
|
365
|
+
actual_scoreboard_header,
|
|
366
|
+
actual_scoreboard_rows,
|
|
367
|
+
actual_shared_levels_header,
|
|
368
|
+
actual_shared_levels_rows,
|
|
369
|
+
) = self.actual_data(response.content.decode("utf-8"), len(students))
|
|
215
370
|
|
|
216
371
|
# Check the headers and the number or rows match expectations
|
|
217
|
-
assert actual_scoreboard_header == self.expected_scoreboard_header(
|
|
372
|
+
assert actual_scoreboard_header == self.expected_scoreboard_header(
|
|
373
|
+
levels
|
|
374
|
+
)
|
|
218
375
|
assert len(actual_scoreboard_rows) == len(student_rows)
|
|
219
|
-
assert
|
|
376
|
+
assert (
|
|
377
|
+
actual_shared_levels_header
|
|
378
|
+
== self.expected_shared_levels_header(shared_levels)
|
|
379
|
+
)
|
|
220
380
|
assert len(actual_shared_levels_rows) == len(shared_level_rows)
|
|
221
381
|
|
|
222
382
|
# check first scoreboard row
|
|
223
|
-
(
|
|
224
|
-
|
|
225
|
-
|
|
383
|
+
(
|
|
384
|
+
class_name,
|
|
385
|
+
name,
|
|
386
|
+
completed_levels,
|
|
387
|
+
total_time,
|
|
388
|
+
total_scores,
|
|
389
|
+
l1,
|
|
390
|
+
l2,
|
|
391
|
+
improvement,
|
|
392
|
+
) = actual_scoreboard_rows[0].split(",")
|
|
226
393
|
assert student_rows[0].class_field.name == class_name
|
|
227
394
|
assert student_rows[0].name == name
|
|
228
395
|
assert student_rows[0].level_scores[0]["score"] == int(l1)
|
|
@@ -231,9 +398,16 @@ class ScoreboardCsvTestCase(TestCase):
|
|
|
231
398
|
|
|
232
399
|
# check last scoreboard row
|
|
233
400
|
last = len(actual_scoreboard_rows) - 1
|
|
234
|
-
(
|
|
235
|
-
|
|
236
|
-
|
|
401
|
+
(
|
|
402
|
+
class_name,
|
|
403
|
+
name,
|
|
404
|
+
completed_levels,
|
|
405
|
+
total_time,
|
|
406
|
+
total_scores,
|
|
407
|
+
l1,
|
|
408
|
+
l2,
|
|
409
|
+
improvement,
|
|
410
|
+
) = actual_scoreboard_rows[last].split(",")
|
|
237
411
|
assert student_rows[last].class_field.name == class_name
|
|
238
412
|
assert student_rows[last].name == name
|
|
239
413
|
assert str(student_rows[last].total_time) == total_time
|
|
@@ -279,7 +453,7 @@ class ScoreboardCsvTestCase(TestCase):
|
|
|
279
453
|
total_score=total_score,
|
|
280
454
|
level_scores=level_scores,
|
|
281
455
|
completed=2,
|
|
282
|
-
|
|
456
|
+
success_rate=45,
|
|
283
457
|
)
|
|
284
458
|
|
|
285
459
|
return row, student
|
|
@@ -310,19 +484,26 @@ class ScoreboardCsvTestCase(TestCase):
|
|
|
310
484
|
total_score=0,
|
|
311
485
|
level_scores=level_scores,
|
|
312
486
|
completed=0,
|
|
313
|
-
|
|
487
|
+
success_rate=0,
|
|
314
488
|
)
|
|
315
489
|
|
|
316
490
|
return row
|
|
317
491
|
|
|
318
492
|
def expected_scoreboard_header(self, levels):
|
|
319
493
|
level_strings = list(map(str, levels))
|
|
320
|
-
all_header_strings =
|
|
494
|
+
all_header_strings = (
|
|
495
|
+
CSVHeaders + level_strings + ["Areas for improvement"]
|
|
496
|
+
)
|
|
321
497
|
joined = ",".join(all_header_strings)
|
|
322
498
|
return joined
|
|
323
499
|
|
|
324
500
|
def expected_shared_levels_header(self, shared_levels):
|
|
325
|
-
level_strings = list(
|
|
501
|
+
level_strings = list(
|
|
502
|
+
[
|
|
503
|
+
shared_level_to_name(level, level.owner)
|
|
504
|
+
for level in shared_levels
|
|
505
|
+
]
|
|
506
|
+
)
|
|
326
507
|
all_header_strings = CSVSharedHeaders + level_strings
|
|
327
508
|
joined = ",".join(all_header_strings)
|
|
328
509
|
return joined
|
|
@@ -343,14 +524,25 @@ class ScoreboardCsvTestCase(TestCase):
|
|
|
343
524
|
scoreboard_header = split[scoreboard_header_row]
|
|
344
525
|
scoreboard_rows = split[scoreboard_rows_start:scoreboard_rows_end]
|
|
345
526
|
shared_levels_header = split[shared_levels_header_row]
|
|
346
|
-
shared_levels_rows = split[
|
|
347
|
-
|
|
348
|
-
|
|
527
|
+
shared_levels_rows = split[
|
|
528
|
+
shared_levels_rows_start:shared_levels_rows_end
|
|
529
|
+
]
|
|
530
|
+
|
|
531
|
+
return (
|
|
532
|
+
scoreboard_header,
|
|
533
|
+
scoreboard_rows,
|
|
534
|
+
shared_levels_header,
|
|
535
|
+
shared_levels_rows,
|
|
536
|
+
)
|
|
349
537
|
|
|
350
538
|
|
|
351
539
|
def create_attempt(student, level, score):
|
|
352
540
|
attempt = Attempt.objects.create(
|
|
353
|
-
finish_time=datetime.fromtimestamp(1435305072),
|
|
541
|
+
finish_time=datetime.fromtimestamp(1435305072),
|
|
542
|
+
level=level,
|
|
543
|
+
student=student,
|
|
544
|
+
score=score,
|
|
545
|
+
is_best_attempt=True,
|
|
354
546
|
)
|
|
355
547
|
attempt.start_time = datetime.fromtimestamp(1435305072)
|
|
356
548
|
attempt.save()
|
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,23 @@ 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
|
+
|
|
57
|
+
level.save()
|
|
58
|
+
|
|
59
|
+
if shared_with is not None:
|
|
60
|
+
for user in shared_with:
|
|
61
|
+
level.shared_with.add(user)
|
|
62
|
+
level.save()
|
|
63
|
+
|
|
64
|
+
return level
|
|
65
|
+
|
|
66
|
+
def create_save_level_with_multiple_houses(teacher_or_student, level_name="2", shared_with=None):
|
|
67
|
+
level = Level(default=False, anonymous=multiple_house_data["anonymous"])
|
|
68
|
+
level.owner = teacher_or_student.user
|
|
69
|
+
level_management.save_level(level, multiple_house_data)
|
|
30
70
|
level.save()
|
|
31
71
|
|
|
32
72
|
if shared_with is not None:
|
game/tests/utils/teacher.py
CHANGED
|
@@ -7,13 +7,13 @@ from common.models import School, Teacher
|
|
|
7
7
|
def create_school() -> School:
|
|
8
8
|
school = School()
|
|
9
9
|
school.name = "".join(random.choice(string.ascii_uppercase) for _ in range(10))
|
|
10
|
-
school.postcode = "".join(random.choice(string.ascii_uppercase) for _ in range(7))
|
|
11
10
|
school.country = "United Kingdom"
|
|
12
11
|
school.save()
|
|
13
12
|
|
|
14
13
|
return school
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
def add_teacher_to_school(teacher: Teacher, school: School):
|
|
16
|
+
def add_teacher_to_school(teacher: Teacher, school: School, is_admin=False):
|
|
18
17
|
teacher.user.teacher.school = school
|
|
18
|
+
teacher.is_admin = is_admin
|
|
19
19
|
teacher.user.teacher.save()
|
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):
|
|
@@ -19,35 +19,35 @@ class Theme(object):
|
|
|
19
19
|
|
|
20
20
|
THEME_DATA = {
|
|
21
21
|
"grass": Theme(
|
|
22
|
-
name=
|
|
23
|
-
text=
|
|
24
|
-
selected=
|
|
25
|
-
background=
|
|
26
|
-
border=
|
|
22
|
+
name="grass",
|
|
23
|
+
text=gettext("Grass"),
|
|
24
|
+
selected="#bce369",
|
|
25
|
+
background="#a0c53a",
|
|
26
|
+
border="#70961f",
|
|
27
27
|
pk=1,
|
|
28
28
|
),
|
|
29
29
|
"snow": Theme(
|
|
30
|
-
name=
|
|
31
|
-
text=
|
|
32
|
-
selected=
|
|
33
|
-
background=
|
|
34
|
-
border=
|
|
30
|
+
name="snow",
|
|
31
|
+
text=gettext("Snow"),
|
|
32
|
+
selected="#b3deff",
|
|
33
|
+
background="#eef7ff",
|
|
34
|
+
border="#83c9fe",
|
|
35
35
|
pk=2,
|
|
36
36
|
),
|
|
37
37
|
"farm": Theme(
|
|
38
|
-
name=
|
|
39
|
-
text=
|
|
40
|
-
selected=
|
|
41
|
-
background=
|
|
42
|
-
border=
|
|
38
|
+
name="farm",
|
|
39
|
+
text=gettext("Farm"),
|
|
40
|
+
selected="#bce369",
|
|
41
|
+
background="#a0c53a",
|
|
42
|
+
border="#70961f",
|
|
43
43
|
pk=3,
|
|
44
44
|
),
|
|
45
45
|
"city": Theme(
|
|
46
|
-
name=
|
|
47
|
-
text=
|
|
48
|
-
selected=
|
|
49
|
-
background=
|
|
50
|
-
border=
|
|
46
|
+
name="city",
|
|
47
|
+
text=gettext("City"),
|
|
48
|
+
selected="#C1C1C1",
|
|
49
|
+
background="#969696",
|
|
50
|
+
border="#686868",
|
|
51
51
|
pk=4,
|
|
52
52
|
),
|
|
53
53
|
}
|