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/models.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import typing as t
|
|
1
2
|
from builtins import str
|
|
2
3
|
|
|
3
|
-
from common.models import
|
|
4
|
+
from common.models import Class, Student, UserProfile
|
|
4
5
|
from django.contrib.auth.models import User
|
|
5
6
|
from django.db import models
|
|
7
|
+
from django.db.models.query import QuerySet
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
def theme_choices():
|
|
@@ -19,17 +21,33 @@ def character_choices():
|
|
|
19
21
|
|
|
20
22
|
class Block(models.Model):
|
|
21
23
|
type = models.CharField(max_length=200)
|
|
24
|
+
block_type = models.IntegerField(
|
|
25
|
+
choices=[
|
|
26
|
+
(0, "Start"),
|
|
27
|
+
(1, "Action"),
|
|
28
|
+
(2, "Condition"),
|
|
29
|
+
(3, "Procedure"),
|
|
30
|
+
(4, "ControlFlow"),
|
|
31
|
+
(5, "Variable"),
|
|
32
|
+
(6, "Math"),
|
|
33
|
+
]
|
|
34
|
+
)
|
|
22
35
|
|
|
23
36
|
def __str__(self):
|
|
24
37
|
return self.type
|
|
25
38
|
|
|
39
|
+
class Meta:
|
|
40
|
+
ordering = ["block_type", "pk"]
|
|
41
|
+
|
|
26
42
|
|
|
27
43
|
class Episode(models.Model):
|
|
28
44
|
"""Variables prefixed with r_ signify they are parameters for random level generation"""
|
|
29
45
|
|
|
46
|
+
worksheets: QuerySet["Worksheet"]
|
|
47
|
+
|
|
30
48
|
name = models.CharField(max_length=200)
|
|
31
49
|
next_episode = models.ForeignKey(
|
|
32
|
-
"self", null=True, default=None, on_delete=models.SET_NULL
|
|
50
|
+
"self", null=True, blank=True, default=None, on_delete=models.SET_NULL
|
|
33
51
|
)
|
|
34
52
|
in_development = models.BooleanField(default=False)
|
|
35
53
|
|
|
@@ -39,9 +57,9 @@ class Episode(models.Model):
|
|
|
39
57
|
r_curviness = models.FloatField(default=0, null=True)
|
|
40
58
|
r_num_tiles = models.IntegerField(default=5, null=True)
|
|
41
59
|
r_blocks = models.ManyToManyField(Block, related_name="episodes")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
r_blockly_enabled = models.BooleanField(default=True)
|
|
61
|
+
r_python_enabled = models.BooleanField(default=False)
|
|
62
|
+
r_traffic_lights = models.BooleanField(default=False)
|
|
45
63
|
r_cows = models.BooleanField(default=False)
|
|
46
64
|
|
|
47
65
|
@property
|
|
@@ -70,8 +88,23 @@ class Episode(models.Model):
|
|
|
70
88
|
7: "medium-hard",
|
|
71
89
|
8: "medium-hard",
|
|
72
90
|
9: "brainteasers",
|
|
73
|
-
10: "
|
|
74
|
-
11: "
|
|
91
|
+
10: "early-python",
|
|
92
|
+
11: "early-python",
|
|
93
|
+
12: "late-python",
|
|
94
|
+
13: "late-python",
|
|
95
|
+
14: "late-python",
|
|
96
|
+
15: "late-python",
|
|
97
|
+
16: "early-python",
|
|
98
|
+
17: "early-python",
|
|
99
|
+
18: "early-python",
|
|
100
|
+
19: "early-python",
|
|
101
|
+
20: "late-python",
|
|
102
|
+
21: "late-python",
|
|
103
|
+
22: "late-python",
|
|
104
|
+
23: "late-python",
|
|
105
|
+
24: "late-python",
|
|
106
|
+
25: "late-python",
|
|
107
|
+
26: "late-python",
|
|
75
108
|
}
|
|
76
109
|
|
|
77
110
|
return difficulty_map.get(self.id, "easy")
|
|
@@ -85,7 +118,11 @@ class LevelManager(models.Manager):
|
|
|
85
118
|
# Sorts all the levels by integer conversion of "name" which should equate to the correct play order
|
|
86
119
|
# Custom levels do not have an episode
|
|
87
120
|
|
|
88
|
-
return sort_levels(
|
|
121
|
+
return sort_levels(
|
|
122
|
+
self.model.objects.filter(episode__isnull=False).exclude(
|
|
123
|
+
episode__name__icontains="coming soon"
|
|
124
|
+
)
|
|
125
|
+
)
|
|
89
126
|
|
|
90
127
|
|
|
91
128
|
def sort_levels(levels):
|
|
@@ -93,6 +130,8 @@ def sort_levels(levels):
|
|
|
93
130
|
|
|
94
131
|
|
|
95
132
|
class Level(models.Model):
|
|
133
|
+
after_worksheet: t.Optional["Worksheet"]
|
|
134
|
+
|
|
96
135
|
name = models.CharField(max_length=100)
|
|
97
136
|
episode = models.ForeignKey(
|
|
98
137
|
Episode, blank=True, null=True, default=None, on_delete=models.PROTECT
|
|
@@ -112,26 +151,94 @@ class Level(models.Model):
|
|
|
112
151
|
)
|
|
113
152
|
fuel_gauge = models.BooleanField(default=True)
|
|
114
153
|
max_fuel = models.IntegerField(default=50)
|
|
115
|
-
direct_drive = models.BooleanField(default=False)
|
|
116
154
|
next_level = models.ForeignKey(
|
|
117
|
-
"self",
|
|
155
|
+
"self",
|
|
156
|
+
null=True,
|
|
157
|
+
blank=True,
|
|
158
|
+
default=None,
|
|
159
|
+
on_delete=models.SET_NULL,
|
|
160
|
+
related_name="prev_level",
|
|
118
161
|
)
|
|
119
162
|
shared_with = models.ManyToManyField(User, related_name="shared", blank=True)
|
|
120
163
|
model_solution = models.CharField(blank=True, max_length=20, default="[]")
|
|
121
164
|
disable_route_score = models.BooleanField(default=False)
|
|
165
|
+
disable_algorithm_score = models.BooleanField(default=False)
|
|
122
166
|
threads = models.IntegerField(blank=False, default=1)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
167
|
+
blockly_enabled = models.BooleanField(default=True)
|
|
168
|
+
python_enabled = models.BooleanField(default=True)
|
|
169
|
+
python_view_enabled = models.BooleanField(default=False)
|
|
126
170
|
theme_name = models.CharField(
|
|
127
|
-
max_length=10,
|
|
171
|
+
max_length=10,
|
|
172
|
+
choices=theme_choices(),
|
|
173
|
+
blank=True,
|
|
174
|
+
null=True,
|
|
175
|
+
default=None,
|
|
128
176
|
)
|
|
129
177
|
character_name = models.CharField(
|
|
130
|
-
max_length=20,
|
|
178
|
+
max_length=20,
|
|
179
|
+
choices=character_choices(),
|
|
180
|
+
blank=True,
|
|
181
|
+
null=True,
|
|
182
|
+
default=None,
|
|
183
|
+
)
|
|
184
|
+
subtitle = models.TextField(max_length=100, blank=True, null=True)
|
|
185
|
+
lesson = models.TextField(
|
|
186
|
+
max_length=10000, default="Can you find the shortest route?"
|
|
187
|
+
)
|
|
188
|
+
hint = models.TextField(
|
|
189
|
+
max_length=10000,
|
|
190
|
+
default="Think back to earlier levels. What did you learn?",
|
|
191
|
+
)
|
|
192
|
+
commands = models.TextField(
|
|
193
|
+
max_length=10000,
|
|
194
|
+
default='<div class="row">'
|
|
195
|
+
+ '<div class="large-4 columns">'
|
|
196
|
+
+ "<p><b>Movement</b>"
|
|
197
|
+
+ "<br>my_van.move_forwards()"
|
|
198
|
+
+ "<br>my_van.turn_around()"
|
|
199
|
+
+ "<br>my_van.turn_left()"
|
|
200
|
+
+ "<br>my_van.turn_right()"
|
|
201
|
+
+ "<br>my_van.wait()<p></div>"
|
|
202
|
+
+ '<div class="large-4 columns">'
|
|
203
|
+
+ "<p><b>Position</b>"
|
|
204
|
+
+ "<br>my_van.at_dead_end()"
|
|
205
|
+
+ "<br>my_van.at_destination()"
|
|
206
|
+
+ "<br>my_van.at_red_traffic_light()"
|
|
207
|
+
+ "<br>my_van.at_green_traffic_light()"
|
|
208
|
+
+ "<br>my_van.at_traffic_light(c)"
|
|
209
|
+
+ "<br><i>where c is 'RED' or 'GREEN'</i></p></div>"
|
|
210
|
+
+ '<div class="large-4 columns">'
|
|
211
|
+
+ "<p><br>my_van.is_road_right()"
|
|
212
|
+
+ "<br>my_van.is_road_left()"
|
|
213
|
+
+ "<br>my_van.is_road_forward()"
|
|
214
|
+
+ "<br>my_van.is_road(d)"
|
|
215
|
+
+ "<br><i>where d is 'FORWARD', 'LEFT', or 'RIGHT'</i></p></div>"
|
|
216
|
+
+ "</div>"
|
|
217
|
+
+ '<div class="row">'
|
|
218
|
+
+ '<div class="large-4 columns">'
|
|
219
|
+
+ "<p><b>Animals</b>"
|
|
220
|
+
+ "<br>my_van.is_animal_crossing()"
|
|
221
|
+
+ "<br>my_van.sound_horn()</div>"
|
|
222
|
+
+ "</div>",
|
|
131
223
|
)
|
|
132
224
|
anonymous = models.BooleanField(default=False)
|
|
225
|
+
locked_for_class = models.ManyToManyField(
|
|
226
|
+
Class, blank=True, related_name="locked_levels"
|
|
227
|
+
)
|
|
228
|
+
needs_approval = models.BooleanField(default=True)
|
|
133
229
|
objects = LevelManager()
|
|
134
230
|
|
|
231
|
+
class Meta:
|
|
232
|
+
constraints = [
|
|
233
|
+
models.CheckConstraint(
|
|
234
|
+
check=~models.Q(
|
|
235
|
+
default=True,
|
|
236
|
+
needs_approval=True,
|
|
237
|
+
),
|
|
238
|
+
name="level__default_does_not_need_approval",
|
|
239
|
+
),
|
|
240
|
+
]
|
|
241
|
+
|
|
135
242
|
def __str__(self):
|
|
136
243
|
return f"Level {self.name}"
|
|
137
244
|
|
|
@@ -180,7 +287,7 @@ class LevelDecor(models.Model):
|
|
|
180
287
|
x = models.IntegerField()
|
|
181
288
|
y = models.IntegerField()
|
|
182
289
|
level = models.ForeignKey(Level, on_delete=models.CASCADE)
|
|
183
|
-
|
|
290
|
+
decor_name = models.CharField(max_length=100, default="tree1")
|
|
184
291
|
|
|
185
292
|
|
|
186
293
|
class Workspace(models.Model):
|
|
@@ -196,6 +303,7 @@ class Workspace(models.Model):
|
|
|
196
303
|
python_contents = models.TextField(default="")
|
|
197
304
|
blockly_enabled = models.BooleanField(default=False)
|
|
198
305
|
python_enabled = models.BooleanField(default=False)
|
|
306
|
+
python_view_enabled = models.BooleanField(default=False)
|
|
199
307
|
|
|
200
308
|
def __str__(self):
|
|
201
309
|
return str(self.name)
|
|
@@ -220,3 +328,36 @@ class Attempt(models.Model):
|
|
|
220
328
|
|
|
221
329
|
def elapsed_time(self):
|
|
222
330
|
return self.finish_time - self.start_time
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class Worksheet(models.Model):
|
|
334
|
+
episode = models.ForeignKey(
|
|
335
|
+
Episode,
|
|
336
|
+
blank=True,
|
|
337
|
+
null=True,
|
|
338
|
+
default=None,
|
|
339
|
+
on_delete=models.PROTECT,
|
|
340
|
+
related_name="worksheets",
|
|
341
|
+
)
|
|
342
|
+
before_level = models.OneToOneField(
|
|
343
|
+
Level,
|
|
344
|
+
blank=True,
|
|
345
|
+
null=True,
|
|
346
|
+
default=None,
|
|
347
|
+
on_delete=models.PROTECT,
|
|
348
|
+
related_name="after_worksheet",
|
|
349
|
+
)
|
|
350
|
+
lesson_plan_link = models.CharField(
|
|
351
|
+
max_length=500, null=True, blank=True, default=None
|
|
352
|
+
)
|
|
353
|
+
slides_link = models.CharField(max_length=500, null=True, blank=True, default=None)
|
|
354
|
+
student_worksheet_link = models.CharField(
|
|
355
|
+
max_length=500, null=True, blank=True, default=None
|
|
356
|
+
)
|
|
357
|
+
indy_worksheet_link = models.CharField(
|
|
358
|
+
max_length=500, null=True, blank=True, default=None
|
|
359
|
+
)
|
|
360
|
+
video_link = models.CharField(max_length=500, null=True, blank=True, default=None)
|
|
361
|
+
locked_classes = models.ManyToManyField(
|
|
362
|
+
Class, blank=True, related_name="locked_worksheets"
|
|
363
|
+
)
|
game/permissions.py
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
1
|
from rest_framework import permissions
|
|
4
2
|
|
|
5
|
-
LOGGER = logging.getLogger(__name__)
|
|
6
|
-
|
|
7
3
|
|
|
8
4
|
def _get_userprofile_school(userprofile):
|
|
9
5
|
if hasattr(userprofile, "teacher"):
|
|
@@ -11,9 +7,6 @@ def _get_userprofile_school(userprofile):
|
|
|
11
7
|
elif hasattr(userprofile, "student"):
|
|
12
8
|
return userprofile.student.class_field.teacher.school
|
|
13
9
|
else:
|
|
14
|
-
LOGGER.error(
|
|
15
|
-
f"Userprofile ID {userprofile.id} has no teacher or student attribute"
|
|
16
|
-
)
|
|
17
10
|
return None
|
|
18
11
|
|
|
19
12
|
|
|
@@ -37,6 +30,7 @@ def can_save_workspace(user, workspace):
|
|
|
37
30
|
def can_delete_workspace(user, workspace):
|
|
38
31
|
return not user.is_anonymous and workspace.owner == user.userprofile
|
|
39
32
|
|
|
33
|
+
|
|
40
34
|
#####################
|
|
41
35
|
# Level permissions #
|
|
42
36
|
#####################
|
|
@@ -51,15 +45,32 @@ def can_play_or_delete_level(user, level):
|
|
|
51
45
|
# from their own classes
|
|
52
46
|
if user.userprofile.teacher.is_admin and hasattr(level.owner, "student"):
|
|
53
47
|
return (
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
user.userprofile.teacher.school
|
|
49
|
+
== level.owner.student.class_field.teacher.school
|
|
56
50
|
)
|
|
57
51
|
else:
|
|
58
52
|
return user.userprofile.teacher.teaches(level.owner)
|
|
59
53
|
|
|
60
54
|
|
|
55
|
+
def can_approve_level(user, level):
|
|
56
|
+
return (
|
|
57
|
+
hasattr(user.userprofile, "teacher")
|
|
58
|
+
and level.shared_with.filter(id=user.id).exists()
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
61
62
|
def can_play_level(user, level, early_access):
|
|
62
|
-
if
|
|
63
|
+
if (
|
|
64
|
+
not user.is_anonymous
|
|
65
|
+
and hasattr(user.userprofile, "student")
|
|
66
|
+
and user.userprofile.student.class_field
|
|
67
|
+
):
|
|
68
|
+
# If the user is a student, check that the level isn't locked for their class
|
|
69
|
+
return user.userprofile.id == level.owner_id or (
|
|
70
|
+
user.userprofile.student.class_field not in level.locked_for_class.all()
|
|
71
|
+
and not level.needs_approval
|
|
72
|
+
)
|
|
73
|
+
elif level.default and not level.episode.in_development:
|
|
63
74
|
return True
|
|
64
75
|
elif level.anonymous:
|
|
65
76
|
return False
|
|
@@ -115,8 +126,8 @@ def can_delete_level(user, level):
|
|
|
115
126
|
class CanShareLevel(permissions.BasePermission):
|
|
116
127
|
"""
|
|
117
128
|
Used to verify that an incoming request is made by a user who is authorised to share
|
|
118
|
-
the level - that is, that they are the owner of the level as a student
|
|
119
|
-
shared with them.
|
|
129
|
+
the level - that is, that they are the owner of the level as a student and the level has been approved by a teacher,
|
|
130
|
+
or if they're a teacher that the level was shared with them.
|
|
120
131
|
"""
|
|
121
132
|
|
|
122
133
|
def has_permission(self, request, view):
|
|
@@ -137,20 +148,19 @@ class CanShareLevel(permissions.BasePermission):
|
|
|
137
148
|
):
|
|
138
149
|
return True
|
|
139
150
|
else:
|
|
140
|
-
return obj.owner == request.user.userprofile
|
|
151
|
+
return obj.owner == request.user.userprofile and not obj.needs_approval
|
|
141
152
|
|
|
142
153
|
|
|
143
154
|
class CanShareLevelWith(permissions.BasePermission):
|
|
144
155
|
"""
|
|
145
|
-
Used to verify that
|
|
146
|
-
|
|
156
|
+
Used to verify that the user who is requesting to share their level is authorised to share the level with a specific
|
|
157
|
+
recipient.
|
|
147
158
|
The user is authorised if:
|
|
148
159
|
- neither they nor the recipient are anonymous,
|
|
149
160
|
- neither they nor the recipient are independent students,
|
|
150
|
-
- they are a student and the recipient is a student in the same class, or their
|
|
151
|
-
teacher
|
|
152
|
-
- they are
|
|
153
|
-
student
|
|
161
|
+
- they are a student and the recipient is a student in the same class, or their teacher
|
|
162
|
+
- they are a teacher and the recipient is a teacher in the same school, or their student
|
|
163
|
+
- they are an admin teacher and the recipient is a student in the same school
|
|
154
164
|
"""
|
|
155
165
|
|
|
156
166
|
def has_permission(self, request, view):
|
|
@@ -193,6 +203,11 @@ class CanShareLevelWith(permissions.BasePermission):
|
|
|
193
203
|
):
|
|
194
204
|
# Are they in the same organisation?
|
|
195
205
|
return recipient_profile.teacher.school == sharer_profile.teacher.school
|
|
206
|
+
elif hasattr(sharer_profile, "teacher") and sharer_profile.teacher.is_admin:
|
|
207
|
+
return (
|
|
208
|
+
recipient_profile.student.class_field.teacher.school
|
|
209
|
+
== sharer_profile.teacher.school
|
|
210
|
+
)
|
|
196
211
|
else:
|
|
197
212
|
return False
|
|
198
213
|
|
game/python_den_urls.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from django.urls import re_path
|
|
2
|
+
|
|
3
|
+
from game.views.level import play_default_python_level, start_python_episode
|
|
4
|
+
from game.views.level_selection import python_levels
|
|
5
|
+
from game.views.scoreboard import python_scoreboard
|
|
6
|
+
from game.views.worksheet import worksheet
|
|
7
|
+
|
|
8
|
+
urlpatterns = [
|
|
9
|
+
re_path(r"^$", python_levels, name="python_levels"),
|
|
10
|
+
re_path(
|
|
11
|
+
r"^(?P<level_name>[A-Z0-9]+)/$",
|
|
12
|
+
play_default_python_level,
|
|
13
|
+
name="play_python_default_level",
|
|
14
|
+
),
|
|
15
|
+
re_path(
|
|
16
|
+
r"^episode/(?P<episodeId>[0-9]+)/$",
|
|
17
|
+
start_python_episode,
|
|
18
|
+
name="start_python_episode",
|
|
19
|
+
),
|
|
20
|
+
re_path(
|
|
21
|
+
r"^worksheet/(?P<worksheetId>[0-9]+)/$",
|
|
22
|
+
worksheet,
|
|
23
|
+
name="worksheet",
|
|
24
|
+
),
|
|
25
|
+
re_path(r"^scoreboard/$", python_scoreboard, name="python_scoreboard"),
|
|
26
|
+
]
|