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/views/level.py
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
from __future__ import division
|
|
2
|
-
from __future__ import absolute_import
|
|
1
|
+
from __future__ import absolute_import, division
|
|
3
2
|
|
|
4
|
-
from builtins import str
|
|
5
|
-
from builtins import object
|
|
6
3
|
import json
|
|
4
|
+
from builtins import object, str
|
|
5
|
+
from datetime import datetime
|
|
7
6
|
|
|
8
|
-
from django.urls import reverse
|
|
9
7
|
from django.http import Http404, HttpResponse
|
|
10
|
-
from django.shortcuts import
|
|
8
|
+
from django.shortcuts import get_object_or_404, redirect, render
|
|
9
|
+
from django.urls import reverse
|
|
11
10
|
from django.utils import timezone
|
|
12
|
-
from django.utils.safestring import mark_safe
|
|
13
11
|
from django.views.decorators.http import require_POST
|
|
14
12
|
from rest_framework import serializers
|
|
15
13
|
|
|
@@ -18,15 +16,19 @@ import game.messages as messages
|
|
|
18
16
|
import game.permissions as permissions
|
|
19
17
|
from game import app_settings
|
|
20
18
|
from game.cache import (
|
|
19
|
+
cached_custom_level,
|
|
21
20
|
cached_default_level,
|
|
22
21
|
cached_episode,
|
|
23
|
-
cached_custom_level,
|
|
24
|
-
cached_level_decor,
|
|
25
22
|
cached_level_blocks,
|
|
23
|
+
cached_level_decor,
|
|
26
24
|
)
|
|
25
|
+
from game.character import get_character
|
|
27
26
|
from game.decor import get_decor_element
|
|
28
|
-
from game.models import
|
|
27
|
+
from game.models import Attempt, Level, Workspace
|
|
28
|
+
from game.theme import get_theme
|
|
29
|
+
from game.views.language_code_conversions import language_code_dict
|
|
29
30
|
from game.views.level_solutions import solutions
|
|
31
|
+
|
|
30
32
|
from .helper import renderError
|
|
31
33
|
|
|
32
34
|
|
|
@@ -42,16 +44,94 @@ def play_custom_level(request, levelId, from_editor=False):
|
|
|
42
44
|
return play_level(request, level, from_editor)
|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
def play_default_level(request,
|
|
46
|
-
|
|
47
|
+
def play_default_level(request, level_name):
|
|
48
|
+
level_index = int(level_name)
|
|
49
|
+
if level_index > 79:
|
|
50
|
+
raise Http404
|
|
51
|
+
if (
|
|
52
|
+
level_index > 19
|
|
53
|
+
and not level_index in [29, 33, 44, 51, 61, 68]
|
|
54
|
+
and not request.user.is_authenticated
|
|
55
|
+
):
|
|
56
|
+
return redirect(reverse("levels"))
|
|
57
|
+
|
|
58
|
+
level = cached_default_level(level_name)
|
|
47
59
|
return play_level(request, level)
|
|
48
60
|
|
|
49
61
|
|
|
50
|
-
def
|
|
62
|
+
def play_default_python_level(request, level_name):
|
|
63
|
+
level_index = int(level_name)
|
|
64
|
+
if level_index > 49:
|
|
65
|
+
raise Http404
|
|
66
|
+
if (
|
|
67
|
+
level_index > 26
|
|
68
|
+
and not level_index in [41]
|
|
69
|
+
and not request.user.is_authenticated
|
|
70
|
+
):
|
|
71
|
+
return redirect(reverse("python_levels"))
|
|
72
|
+
|
|
73
|
+
levelId = int(level_name) + 1000
|
|
74
|
+
|
|
75
|
+
level = cached_default_level(levelId)
|
|
76
|
+
return play_level(request, level, from_python_den=True)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _prev_level_url(level, user, night_mode, from_python_den):
|
|
80
|
+
"""
|
|
81
|
+
Find the previous available level. Check if level is available if so, go to
|
|
82
|
+
it.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
if not level.prev_level.all():
|
|
86
|
+
return ""
|
|
87
|
+
|
|
88
|
+
prev_level = level.prev_level.all()[0]
|
|
89
|
+
if not user.is_anonymous and hasattr(user.userprofile, "student"):
|
|
90
|
+
student = user.userprofile.student
|
|
91
|
+
klass = student.class_field
|
|
92
|
+
|
|
93
|
+
is_prev_level_locked = klass in prev_level.locked_for_class.all()
|
|
94
|
+
if is_prev_level_locked:
|
|
95
|
+
while is_prev_level_locked and int(prev_level.name) > 1:
|
|
96
|
+
prev_level = prev_level.prev_level.all()[0]
|
|
97
|
+
is_prev_level_locked = klass in prev_level.locked_for_class.all()
|
|
98
|
+
|
|
99
|
+
return _level_url(prev_level, night_mode, from_python_den)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _next_level_url(level, user, night_mode, from_python_den):
|
|
103
|
+
"""
|
|
104
|
+
Find the next available level. By default, this will be the `next_level`
|
|
105
|
+
field in the Level model, but in the case where the user is a student and
|
|
106
|
+
the teacher has locked certain levels, then loop until we find the next
|
|
107
|
+
unlocked level (or we run out of levels).
|
|
108
|
+
"""
|
|
109
|
+
|
|
51
110
|
if not level.next_level:
|
|
111
|
+
if (
|
|
112
|
+
level.episode
|
|
113
|
+
and level.episode.next_episode
|
|
114
|
+
and len(level.episode.next_episode.levels) == 0
|
|
115
|
+
):
|
|
116
|
+
return reverse("python_levels")
|
|
52
117
|
return ""
|
|
53
118
|
|
|
54
|
-
|
|
119
|
+
next_level = level.next_level
|
|
120
|
+
|
|
121
|
+
if not user.is_anonymous and hasattr(user.userprofile, "student"):
|
|
122
|
+
student = user.userprofile.student
|
|
123
|
+
klass = student.class_field
|
|
124
|
+
|
|
125
|
+
is_next_level_locked = klass in next_level.locked_for_class.all()
|
|
126
|
+
|
|
127
|
+
if is_next_level_locked:
|
|
128
|
+
while is_next_level_locked and (
|
|
129
|
+
int(next_level.name) < 1050 if from_python_den else 80
|
|
130
|
+
):
|
|
131
|
+
next_level = next_level.next_level
|
|
132
|
+
is_next_level_locked = klass in next_level.locked_for_class.all()
|
|
133
|
+
|
|
134
|
+
return _level_url(next_level, night_mode, from_python_den)
|
|
55
135
|
|
|
56
136
|
|
|
57
137
|
def add_night(url, night_mode):
|
|
@@ -60,24 +140,28 @@ def add_night(url, night_mode):
|
|
|
60
140
|
return url
|
|
61
141
|
|
|
62
142
|
|
|
63
|
-
def _level_url(level, night_mode):
|
|
143
|
+
def _level_url(level, night_mode, from_python_den):
|
|
64
144
|
if level.default:
|
|
65
|
-
result = _default_level_url(level)
|
|
145
|
+
result = _default_level_url(level, from_python_den)
|
|
66
146
|
else:
|
|
67
147
|
result = _custom_level_url(level)
|
|
68
148
|
|
|
69
149
|
return add_night(result, night_mode)
|
|
70
150
|
|
|
71
151
|
|
|
72
|
-
def _default_level_url(level):
|
|
73
|
-
|
|
152
|
+
def _default_level_url(level, from_python_den):
|
|
153
|
+
viewname = "play_python_default_level" if from_python_den else "play_default_level"
|
|
154
|
+
|
|
155
|
+
level_name = int(level.name) - 1000 if from_python_den else level.name
|
|
156
|
+
|
|
157
|
+
return reverse(viewname, args=[level_name])
|
|
74
158
|
|
|
75
159
|
|
|
76
160
|
def _custom_level_url(level):
|
|
77
161
|
return reverse("play_custom_level", args=[level.id])
|
|
78
162
|
|
|
79
163
|
|
|
80
|
-
def play_level(request, level, from_editor=False):
|
|
164
|
+
def play_level(request, level, from_editor=False, from_python_den=False):
|
|
81
165
|
"""Loads a level for rendering in the game.
|
|
82
166
|
|
|
83
167
|
**Context**
|
|
@@ -105,34 +189,43 @@ def play_level(request, level, from_editor=False):
|
|
|
105
189
|
request.user, level, app_settings.EARLY_ACCESS_FUNCTION(request)
|
|
106
190
|
):
|
|
107
191
|
return renderError(
|
|
108
|
-
request, messages.
|
|
192
|
+
request, messages.no_permission_title(), messages.not_shared_level()
|
|
109
193
|
)
|
|
110
194
|
|
|
111
|
-
|
|
112
|
-
lesson =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
lessonCall = messages.description_level_default
|
|
127
|
-
hintCall = messages.hint_level_default
|
|
128
|
-
|
|
129
|
-
lesson = mark_safe(lessonCall())
|
|
130
|
-
hint = mark_safe(hintCall())
|
|
131
|
-
|
|
132
|
-
house = get_decor_element("house", level.theme).url
|
|
133
|
-
cfc = get_decor_element("cfc", level.theme).url
|
|
134
|
-
background = get_decor_element("tile1", level.theme).url
|
|
195
|
+
subtitle = level.subtitle
|
|
196
|
+
lesson = (
|
|
197
|
+
getattr(messages, "description_level" + str(level.name))
|
|
198
|
+
if level.default
|
|
199
|
+
else level.lesson
|
|
200
|
+
)
|
|
201
|
+
hint = (
|
|
202
|
+
getattr(messages, "hint_level" + str(level.name))
|
|
203
|
+
if level.default
|
|
204
|
+
else level.hint
|
|
205
|
+
)
|
|
206
|
+
commands_attr = "commands_level" + str(level.name)
|
|
207
|
+
commands = (
|
|
208
|
+
getattr(messages, commands_attr, None) if level.default else level.commands
|
|
209
|
+
)
|
|
135
210
|
character = level.character
|
|
211
|
+
character_url = character.top_down
|
|
212
|
+
wreckage_url = "van_wreckage.svg"
|
|
213
|
+
|
|
214
|
+
if datetime.now().month == 12:
|
|
215
|
+
house = get_decor_element("house", get_theme("snow")).url
|
|
216
|
+
cfc = get_decor_element("cfc", get_theme("snow")).url
|
|
217
|
+
background = get_decor_element("tile1", get_theme("snow")).url
|
|
218
|
+
|
|
219
|
+
if character == get_character("Van"):
|
|
220
|
+
character_url = "characters/top_view/Sleigh.svg"
|
|
221
|
+
wreckage_url = "sleigh_wreckage.svg"
|
|
222
|
+
else:
|
|
223
|
+
house = get_decor_element("house", level.theme).url
|
|
224
|
+
cfc = get_decor_element("cfc", level.theme).url
|
|
225
|
+
background = get_decor_element("tile1", level.theme).url
|
|
226
|
+
|
|
227
|
+
character_width = character.width
|
|
228
|
+
character_height = character.height
|
|
136
229
|
|
|
137
230
|
workspace = None
|
|
138
231
|
python_workspace = None
|
|
@@ -162,11 +255,6 @@ def play_level(request, level, from_editor=False):
|
|
|
162
255
|
|
|
163
256
|
decor_data = cached_level_decor(level)
|
|
164
257
|
|
|
165
|
-
character_url = character.top_down
|
|
166
|
-
character_width = character.width
|
|
167
|
-
character_height = character.height
|
|
168
|
-
wreckage_url = "van_wreckage.svg"
|
|
169
|
-
|
|
170
258
|
if night_mode:
|
|
171
259
|
block_data = level_management.get_night_blocks(level)
|
|
172
260
|
night_mode_javascript = "true"
|
|
@@ -177,21 +265,36 @@ def play_level(request, level, from_editor=False):
|
|
|
177
265
|
night_mode_javascript = "false"
|
|
178
266
|
model_solution = level.model_solution
|
|
179
267
|
|
|
180
|
-
return_view =
|
|
268
|
+
return_view = (
|
|
269
|
+
"level_editor"
|
|
270
|
+
if from_editor
|
|
271
|
+
else "python_levels" if from_python_den else "levels"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
temp_block_data = []
|
|
275
|
+
[
|
|
276
|
+
temp_block_data.append(block)
|
|
277
|
+
for block in block_data
|
|
278
|
+
if block not in temp_block_data
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
block_data = temp_block_data
|
|
181
282
|
|
|
182
283
|
return render(
|
|
183
284
|
request,
|
|
184
285
|
"game/game.html",
|
|
185
286
|
context={
|
|
186
287
|
"level": level,
|
|
288
|
+
"subtitle": subtitle,
|
|
187
289
|
"lesson": lesson,
|
|
290
|
+
"hint": hint,
|
|
291
|
+
"commands": commands if commands is not None else level.commands,
|
|
188
292
|
"blocks": block_data,
|
|
189
293
|
"decor": decor_data,
|
|
190
294
|
"character": character,
|
|
191
295
|
"background": background,
|
|
192
296
|
"house": house,
|
|
193
297
|
"cfc": cfc,
|
|
194
|
-
"hint": hint,
|
|
195
298
|
"workspace": workspace,
|
|
196
299
|
"python_workspace": python_workspace,
|
|
197
300
|
"return_url": reverse(return_view),
|
|
@@ -204,8 +307,14 @@ def play_level(request, level, from_editor=False):
|
|
|
204
307
|
app_settings.NIGHT_MODE_FEATURE_ENABLED
|
|
205
308
|
).lower(),
|
|
206
309
|
"model_solution": model_solution,
|
|
207
|
-
"
|
|
208
|
-
|
|
310
|
+
"prev_level_url": _prev_level_url(
|
|
311
|
+
level, request.user, night_mode, from_python_den
|
|
312
|
+
),
|
|
313
|
+
"next_level_url": _next_level_url(
|
|
314
|
+
level, request.user, night_mode, from_python_den
|
|
315
|
+
),
|
|
316
|
+
"flip_night_mode_url": _level_url(level, not night_mode, from_python_den),
|
|
317
|
+
"available_language_dict": language_code_dict,
|
|
209
318
|
},
|
|
210
319
|
)
|
|
211
320
|
|
|
@@ -213,7 +322,9 @@ def play_level(request, level, from_editor=False):
|
|
|
213
322
|
def fetch_workspace_from_last_attempt(attempt):
|
|
214
323
|
latest_attempt = (
|
|
215
324
|
Attempt.objects.filter(
|
|
216
|
-
level=attempt.level,
|
|
325
|
+
level=attempt.level,
|
|
326
|
+
student=attempt.student,
|
|
327
|
+
night_mode=attempt.night_mode,
|
|
217
328
|
)
|
|
218
329
|
.order_by("-start_time")
|
|
219
330
|
.first()
|
|
@@ -323,11 +434,25 @@ def load_workspace(request, workspaceID):
|
|
|
323
434
|
|
|
324
435
|
|
|
325
436
|
def save_workspace(request, workspaceID=None):
|
|
437
|
+
request_params = [
|
|
438
|
+
"name",
|
|
439
|
+
"contents",
|
|
440
|
+
"python_contents",
|
|
441
|
+
"blockly_enabled",
|
|
442
|
+
"python_enabled",
|
|
443
|
+
"python_view_enabled",
|
|
444
|
+
]
|
|
445
|
+
missing_params = [param for param in request_params if param not in request.POST]
|
|
446
|
+
if missing_params != []:
|
|
447
|
+
raise Exception(
|
|
448
|
+
"Request missing the following required parameters", missing_params
|
|
449
|
+
)
|
|
326
450
|
name = request.POST.get("name")
|
|
327
451
|
contents = request.POST.get("contents")
|
|
328
452
|
python_contents = request.POST.get("python_contents")
|
|
329
453
|
blockly_enabled = json.loads(request.POST.get("blockly_enabled"))
|
|
330
454
|
python_enabled = json.loads(request.POST.get("python_enabled"))
|
|
455
|
+
python_view_enabled = json.loads(request.POST.get("python_view_enabled"))
|
|
331
456
|
|
|
332
457
|
workspace = None
|
|
333
458
|
if workspaceID:
|
|
@@ -341,26 +466,28 @@ def save_workspace(request, workspaceID=None):
|
|
|
341
466
|
workspace.python_contents = python_contents
|
|
342
467
|
workspace.blockly_enabled = blockly_enabled
|
|
343
468
|
workspace.python_enabled = python_enabled
|
|
469
|
+
workspace.python_view_enabled = python_view_enabled
|
|
344
470
|
workspace.save()
|
|
345
471
|
|
|
346
472
|
return load_list_of_workspaces(request)
|
|
347
473
|
|
|
348
474
|
|
|
349
|
-
def load_workspace_solution(request,
|
|
350
|
-
|
|
351
|
-
if levelName in solutions:
|
|
475
|
+
def load_workspace_solution(request, level_name):
|
|
476
|
+
if level_name in solutions:
|
|
352
477
|
workspace = Workspace(owner=request.user.userprofile)
|
|
353
|
-
workspace.id =
|
|
354
|
-
workspace.name =
|
|
478
|
+
workspace.id = level_name
|
|
479
|
+
workspace.name = level_name
|
|
355
480
|
workspace.contents = solutions["blockly_default"]
|
|
356
481
|
workspace.python_contents = solutions["python_default"]
|
|
357
482
|
|
|
358
|
-
|
|
359
|
-
|
|
483
|
+
level = Level.objects.get(name=level_name, default=True)
|
|
484
|
+
|
|
485
|
+
if level.blockly_enabled:
|
|
486
|
+
workspace.contents = solutions[level_name]
|
|
360
487
|
workspace.blockly_enabled = True
|
|
361
488
|
workspace.python_enabled = False
|
|
362
489
|
else:
|
|
363
|
-
workspace.python_contents = solutions[
|
|
490
|
+
workspace.python_contents = solutions[level_name]
|
|
364
491
|
workspace.blockly_enabled = False
|
|
365
492
|
workspace.python_enabled = True
|
|
366
493
|
|
|
@@ -378,8 +505,19 @@ def load_workspace_solution(request, levelName):
|
|
|
378
505
|
|
|
379
506
|
|
|
380
507
|
def start_episode(request, episodeId):
|
|
508
|
+
if int(episodeId) > 9:
|
|
509
|
+
raise Http404
|
|
510
|
+
|
|
511
|
+
episode = cached_episode(episodeId)
|
|
512
|
+
return play_level(request, episode.first_level)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def start_python_episode(request, episodeId):
|
|
516
|
+
if int(episodeId) < 12:
|
|
517
|
+
raise Http404
|
|
518
|
+
|
|
381
519
|
episode = cached_episode(episodeId)
|
|
382
|
-
return play_level(request, episode.first_level,
|
|
520
|
+
return play_level(request, episode.first_level, from_python_den=True)
|
|
383
521
|
|
|
384
522
|
|
|
385
523
|
@require_POST
|
game/views/level_editor.py
CHANGED
|
@@ -2,16 +2,14 @@ from __future__ import division
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import re
|
|
5
|
-
from builtins import map
|
|
6
|
-
from builtins import str
|
|
5
|
+
from builtins import map, str
|
|
7
6
|
|
|
8
|
-
from common.models import Student,
|
|
7
|
+
from common.models import Student, Teacher
|
|
9
8
|
from django.contrib.auth.models import User
|
|
10
|
-
from django.urls import reverse
|
|
11
9
|
from django.db import transaction
|
|
12
10
|
from django.http import HttpResponse
|
|
13
11
|
from django.shortcuts import get_object_or_404, render, redirect
|
|
14
|
-
from django.
|
|
12
|
+
from django.urls import reverse
|
|
15
13
|
from django.views.decorators.http import require_POST
|
|
16
14
|
from portal.templatetags import app_tags
|
|
17
15
|
from rest_framework.authentication import SessionAuthentication
|
|
@@ -20,8 +18,7 @@ from rest_framework.views import APIView
|
|
|
20
18
|
import game.level_management as level_management
|
|
21
19
|
import game.messages as messages
|
|
22
20
|
import game.permissions as permissions
|
|
23
|
-
from game import app_settings
|
|
24
|
-
from game import random_road
|
|
21
|
+
from game import app_settings, random_road
|
|
25
22
|
from game.cache import cached_level_decor, cached_level_blocks
|
|
26
23
|
from game.character import get_all_character
|
|
27
24
|
from game.decor import get_all_decor, get_decor_element
|
|
@@ -69,9 +66,7 @@ def available_blocks():
|
|
|
69
66
|
if app_settings.COW_FEATURE_ENABLED:
|
|
70
67
|
return Block.objects.all()
|
|
71
68
|
else:
|
|
72
|
-
return Block.objects.all().exclude(
|
|
73
|
-
type__in=["declare_event", "puff_up", "sound_horn"]
|
|
74
|
-
)
|
|
69
|
+
return Block.objects.all().exclude(type__in=["cow_crossing", "sound_horn"])
|
|
75
70
|
|
|
76
71
|
|
|
77
72
|
def play_anonymous_level(request, levelId, from_level_editor=True, random_level=False):
|
|
@@ -88,8 +83,9 @@ def play_anonymous_level(request, levelId, from_level_editor=True, random_level=
|
|
|
88
83
|
if not level.anonymous:
|
|
89
84
|
return redirect(reverse("level_editor"), permanent=True)
|
|
90
85
|
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
subtitle = level.subtitle
|
|
87
|
+
lesson = level.lesson
|
|
88
|
+
hint = level.hint
|
|
93
89
|
|
|
94
90
|
attempt = None
|
|
95
91
|
house = get_decor_element("house", level.theme).url
|
|
@@ -125,12 +121,13 @@ def play_anonymous_level(request, levelId, from_level_editor=True, random_level=
|
|
|
125
121
|
"level": level,
|
|
126
122
|
"decor": decor_data,
|
|
127
123
|
"blocks": block_data,
|
|
124
|
+
"subtitle": subtitle,
|
|
128
125
|
"lesson": lesson,
|
|
126
|
+
"hint": hint,
|
|
129
127
|
"character": character,
|
|
130
128
|
"background": background,
|
|
131
129
|
"house": house,
|
|
132
130
|
"cfc": cfc,
|
|
133
|
-
"hint": hint,
|
|
134
131
|
"attempt": attempt,
|
|
135
132
|
"random_level": random_level,
|
|
136
133
|
"return_url": reverse(return_view_name),
|
|
@@ -197,12 +194,12 @@ def load_level_for_editor(request, levelID):
|
|
|
197
194
|
|
|
198
195
|
level_dict = LevelSerializer(level).data
|
|
199
196
|
level_dict["theme"] = level.theme.id
|
|
200
|
-
level_dict["decor"] =
|
|
197
|
+
level_dict["decor"] = level_management.get_decor(level)
|
|
201
198
|
level_dict["blocks"] = cached_level_blocks(level)
|
|
202
199
|
|
|
203
200
|
response = {"owned": level.owner == request.user.userprofile, "level": level_dict}
|
|
204
201
|
|
|
205
|
-
return HttpResponse(json.dumps(response), content_type="application/
|
|
202
|
+
return HttpResponse(json.dumps(response), content_type="application/json")
|
|
206
203
|
|
|
207
204
|
|
|
208
205
|
@transaction.atomic
|
|
@@ -210,6 +207,8 @@ def load_level_for_editor(request, levelID):
|
|
|
210
207
|
def save_level_for_editor(request, levelId=None):
|
|
211
208
|
"""Processes a request on creation of the map in the level editor"""
|
|
212
209
|
data = json.loads(request.POST["data"])
|
|
210
|
+
data["disable_algorithm_score"] = True
|
|
211
|
+
|
|
213
212
|
if ("character" not in data) or (not data["character"]):
|
|
214
213
|
# Set a default, to deal with issue #1158 "Cannot save custom level"
|
|
215
214
|
data["character"] = 1
|
|
@@ -222,19 +221,74 @@ def save_level_for_editor(request, levelId=None):
|
|
|
222
221
|
if not permissions.can_save_level(request.user, level):
|
|
223
222
|
return HttpResponseUnauthorized()
|
|
224
223
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
224
|
+
name_pattern = re.compile("^(\w?[ ]?)*$")
|
|
225
|
+
fields_pattern = re.compile("^[\w.?!', ]*$")
|
|
226
|
+
|
|
227
|
+
name_is_safe = name_pattern.match(data["name"])
|
|
228
|
+
fields_are_safe = all(
|
|
229
|
+
[
|
|
230
|
+
field not in data or fields_pattern.match(data[field])
|
|
231
|
+
for field in ["subtitle", "lesson", "hint"]
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if not (name_is_safe and fields_are_safe):
|
|
236
|
+
return HttpResponseUnauthorized()
|
|
237
|
+
|
|
238
|
+
level_management.save_level(level, data)
|
|
239
|
+
|
|
240
|
+
is_user_school_student = (
|
|
241
|
+
hasattr(level.owner, "student") and not level.owner.student.is_independent()
|
|
242
|
+
)
|
|
243
|
+
is_user_independent = (
|
|
244
|
+
hasattr(level.owner, "student") and level.owner.student.is_independent()
|
|
245
|
+
)
|
|
246
|
+
is_user_teacher = hasattr(level.owner, "teacher")
|
|
247
|
+
|
|
248
|
+
# when level is created
|
|
249
|
+
if levelId is None:
|
|
250
|
+
teacher = None
|
|
251
|
+
|
|
252
|
+
# if level owner is a school student, share with teacher automatically if they aren't an admin
|
|
253
|
+
if is_user_school_student:
|
|
254
|
+
teacher = level.owner.student.class_field.teacher
|
|
255
|
+
if not teacher.is_admin:
|
|
256
|
+
level.shared_with.add(teacher.new_user)
|
|
257
|
+
|
|
258
|
+
if not data["anonymous"]:
|
|
259
|
+
level_management.email_new_custom_level(
|
|
260
|
+
teacher.new_user.email,
|
|
261
|
+
request.build_absolute_uri(reverse("level_moderation")),
|
|
262
|
+
request.build_absolute_uri(
|
|
263
|
+
reverse("play_custom_level", kwargs={"levelId": level.id})
|
|
264
|
+
),
|
|
265
|
+
str(level.owner.student),
|
|
266
|
+
level.owner.student.class_field.name,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
elif is_user_teacher:
|
|
270
|
+
teacher = level.owner.teacher
|
|
271
|
+
|
|
272
|
+
# if level owner is a teacher or an indy user, approval isn't needed
|
|
273
|
+
if not is_user_school_student:
|
|
274
|
+
level.needs_approval = False
|
|
275
|
+
|
|
276
|
+
# share with all admins of the school if user is in a school
|
|
277
|
+
if not is_user_independent:
|
|
278
|
+
if not teacher.school is None:
|
|
279
|
+
school_admins = teacher.school.admins()
|
|
280
|
+
|
|
281
|
+
[
|
|
282
|
+
level.shared_with.add(school_admin.new_user)
|
|
283
|
+
for school_admin in school_admins
|
|
284
|
+
if school_admin.new_user != request.user
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
# anytime a student edits their level
|
|
288
|
+
if is_user_school_student:
|
|
289
|
+
if not level.needs_approval:
|
|
290
|
+
level.needs_approval = True
|
|
291
|
+
|
|
238
292
|
if not data["anonymous"]:
|
|
239
293
|
level_management.email_new_custom_level(
|
|
240
294
|
level.owner.student.class_field.teacher.new_user.email,
|
|
@@ -242,14 +296,13 @@ def save_level_for_editor(request, levelId=None):
|
|
|
242
296
|
request.build_absolute_uri(
|
|
243
297
|
reverse("play_custom_level", kwargs={"levelId": level.id})
|
|
244
298
|
),
|
|
245
|
-
request.build_absolute_uri(reverse("home")),
|
|
246
299
|
str(level.owner.student),
|
|
247
300
|
level.owner.student.class_field.name,
|
|
248
301
|
)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
302
|
+
|
|
303
|
+
level.save()
|
|
304
|
+
response = {"id": level.id}
|
|
305
|
+
return HttpResponse(json.dumps(response), content_type="application/json")
|
|
253
306
|
|
|
254
307
|
|
|
255
308
|
@transaction.atomic
|
|
@@ -286,7 +339,7 @@ def generate_random_map_for_editor(request):
|
|
|
286
339
|
|
|
287
340
|
|
|
288
341
|
class SharingInformationForEditor(APIView):
|
|
289
|
-
"""Returns
|
|
342
|
+
"""Returns information about who the level can be and is shared with. This uses
|
|
290
343
|
the CanShareLevel permission."""
|
|
291
344
|
|
|
292
345
|
authentication_classes = (SessionAuthentication,)
|
|
@@ -320,10 +373,10 @@ class SharingInformationForEditor(APIView):
|
|
|
320
373
|
).exclude(id=student.id)
|
|
321
374
|
valid_recipients["classmates"] = [
|
|
322
375
|
{
|
|
323
|
-
"id": classmate.
|
|
324
|
-
"name": app_tags.make_into_username(classmate.
|
|
376
|
+
"id": classmate.new_user.id,
|
|
377
|
+
"name": app_tags.make_into_username(classmate.new_user),
|
|
325
378
|
"shared": level.owner == classmate.user
|
|
326
|
-
or level.shared_with.filter(id=classmate.
|
|
379
|
+
or level.shared_with.filter(id=classmate.new_user.id).exists(),
|
|
327
380
|
}
|
|
328
381
|
for classmate in classmates
|
|
329
382
|
]
|
|
@@ -331,10 +384,10 @@ class SharingInformationForEditor(APIView):
|
|
|
331
384
|
# Then add their teacher as well
|
|
332
385
|
teacher = class_.teacher
|
|
333
386
|
valid_recipients["teacher"] = {
|
|
334
|
-
"id": teacher.
|
|
335
|
-
"name": app_tags.make_into_username(teacher.
|
|
387
|
+
"id": teacher.new_user.id,
|
|
388
|
+
"name": app_tags.make_into_username(teacher.new_user),
|
|
336
389
|
"shared": level.owner == teacher.user
|
|
337
|
-
or level.shared_with.filter(id=teacher.
|
|
390
|
+
or level.shared_with.filter(id=teacher.new_user.id).exists(),
|
|
338
391
|
}
|
|
339
392
|
|
|
340
393
|
elif hasattr(userprofile, "teacher"):
|
|
@@ -342,22 +395,29 @@ class SharingInformationForEditor(APIView):
|
|
|
342
395
|
|
|
343
396
|
# First get all the students they teach
|
|
344
397
|
valid_recipients["classes"] = []
|
|
345
|
-
|
|
398
|
+
if teacher.is_admin:
|
|
399
|
+
classes_taught = teacher.school.classes()
|
|
400
|
+
else:
|
|
401
|
+
classes_taught = teacher.class_teacher.all()
|
|
346
402
|
for class_ in classes_taught:
|
|
347
403
|
students = Student.objects.filter(
|
|
348
404
|
class_field=class_, new_user__is_active=True
|
|
349
405
|
)
|
|
350
406
|
valid_recipients["classes"].append(
|
|
351
407
|
{
|
|
352
|
-
"name":
|
|
408
|
+
"name": (
|
|
409
|
+
f"{class_.name} ({app_tags.make_into_username(class_.teacher.new_user)})"
|
|
410
|
+
if teacher.is_admin
|
|
411
|
+
else class_.name
|
|
412
|
+
),
|
|
353
413
|
"id": class_.id,
|
|
354
414
|
"students": [
|
|
355
415
|
{
|
|
356
|
-
"id": student.
|
|
357
|
-
"name": app_tags.make_into_username(student.
|
|
416
|
+
"id": student.new_user.id,
|
|
417
|
+
"name": app_tags.make_into_username(student.new_user),
|
|
358
418
|
"shared": level.owner == student.user
|
|
359
419
|
or level.shared_with.filter(
|
|
360
|
-
id=student.
|
|
420
|
+
id=student.new_user.id
|
|
361
421
|
).exists(),
|
|
362
422
|
}
|
|
363
423
|
for student in students
|
|
@@ -371,11 +431,12 @@ class SharingInformationForEditor(APIView):
|
|
|
371
431
|
fellow_teachers = Teacher.objects.filter(school=teacher.school)
|
|
372
432
|
valid_recipients["teachers"] = [
|
|
373
433
|
{
|
|
374
|
-
"id": fellow_teacher.
|
|
375
|
-
"name": app_tags.make_into_username(fellow_teacher.
|
|
434
|
+
"id": fellow_teacher.new_user.id,
|
|
435
|
+
"name": app_tags.make_into_username(fellow_teacher.new_user),
|
|
436
|
+
"admin": fellow_teacher.is_admin,
|
|
376
437
|
"shared": level.owner == fellow_teacher.user
|
|
377
438
|
or level.shared_with.filter(
|
|
378
|
-
id=fellow_teacher.
|
|
439
|
+
id=fellow_teacher.new_user.id
|
|
379
440
|
).exists(),
|
|
380
441
|
}
|
|
381
442
|
for fellow_teacher in fellow_teachers
|