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/scoreboard.py
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
from __future__ import absolute_import
|
|
2
|
-
from __future__ import division
|
|
1
|
+
from __future__ import absolute_import, division
|
|
3
2
|
|
|
4
|
-
from builtins import map
|
|
5
|
-
from builtins import next
|
|
6
|
-
from builtins import object
|
|
3
|
+
from builtins import map, next, object
|
|
7
4
|
from datetime import timedelta
|
|
8
5
|
|
|
9
6
|
from common.models import Class, Teacher, Student
|
|
@@ -34,7 +31,7 @@ class StudentRow:
|
|
|
34
31
|
self.total_score = kwargs.get("total_score", 0)
|
|
35
32
|
self.level_scores = kwargs.get("level_scores", {})
|
|
36
33
|
self.completed = kwargs.get("completed", 0)
|
|
37
|
-
self.
|
|
34
|
+
self.success_rate = kwargs.get("success_rate", 0)
|
|
38
35
|
|
|
39
36
|
|
|
40
37
|
# Returns row of student object with values and scores of each selected level
|
|
@@ -51,20 +48,42 @@ def student_row(levels_sorted, student, best_attempts):
|
|
|
51
48
|
level_scores[level.id] = {}
|
|
52
49
|
level_scores[level.id]["score"] = ""
|
|
53
50
|
|
|
54
|
-
if
|
|
51
|
+
if (
|
|
52
|
+
level.episode is None
|
|
53
|
+
and student.new_user not in level.shared_with.all()
|
|
54
|
+
):
|
|
55
55
|
level_scores[level.id]["score"] = "Not shared"
|
|
56
56
|
|
|
57
57
|
if level.owner == student.user:
|
|
58
58
|
level_scores[level.id]["score"] = "Owner"
|
|
59
59
|
|
|
60
60
|
if best_attempts:
|
|
61
|
-
attempts_dict = {
|
|
61
|
+
attempts_dict = {
|
|
62
|
+
best_attempt.level.id: best_attempt
|
|
63
|
+
for best_attempt in best_attempts
|
|
64
|
+
}
|
|
62
65
|
for level in levels_sorted:
|
|
63
66
|
|
|
64
67
|
attempt = attempts_dict.get(level.id)
|
|
68
|
+
|
|
65
69
|
if attempt:
|
|
70
|
+
max_score = 0
|
|
71
|
+
|
|
72
|
+
# Max score logic as follows:
|
|
73
|
+
# - All custom levels have a max score of 10
|
|
74
|
+
# - All official levels have a max score of 20 if both route score and algorithm scores are enabled
|
|
75
|
+
# except for levels 1-12 which have a max score of 20 even though the algorithm score is disabled
|
|
76
|
+
if attempt.level.episode is None:
|
|
77
|
+
max_score = 10
|
|
78
|
+
else:
|
|
79
|
+
if not attempt.level.disable_route_score:
|
|
80
|
+
max_score += 10
|
|
81
|
+
if not attempt.level.disable_algorithm_score:
|
|
82
|
+
max_score += 10
|
|
83
|
+
if int(attempt.level.name) < 13:
|
|
84
|
+
max_score = 20
|
|
85
|
+
|
|
66
86
|
num_all += 1
|
|
67
|
-
max_score = 10 if attempt.level.disable_route_score or attempt.level.episode is None else 20
|
|
68
87
|
if attempt.score:
|
|
69
88
|
if attempt.score / max_score >= threshold:
|
|
70
89
|
num_finished += 1
|
|
@@ -77,17 +96,28 @@ def student_row(levels_sorted, student, best_attempts):
|
|
|
77
96
|
total_possible_score += max_score
|
|
78
97
|
|
|
79
98
|
elapsed_time = attempt.elapsed_time()
|
|
80
|
-
times.append(
|
|
99
|
+
times.append(chop_milliseconds(elapsed_time))
|
|
81
100
|
# '-' is used to show that the student has started the level but has not submitted any attempts
|
|
82
101
|
|
|
83
|
-
level_scores[level.id]["score"] =
|
|
84
|
-
|
|
85
|
-
|
|
102
|
+
level_scores[level.id]["score"] = (
|
|
103
|
+
int(attempt.score) if attempt.score is not None else "-"
|
|
104
|
+
)
|
|
105
|
+
level_scores[level.id]["full_score"] = (
|
|
106
|
+
attempt.score == max_score
|
|
107
|
+
)
|
|
108
|
+
level_scores[level.id]["is_low_attempt"] = (
|
|
109
|
+
attempt.score == 0 or max_score / attempt.score < threshold
|
|
110
|
+
)
|
|
86
111
|
else:
|
|
87
112
|
times.append(timedelta(0))
|
|
88
113
|
|
|
89
114
|
total_time = sum(times, timedelta())
|
|
90
|
-
|
|
115
|
+
|
|
116
|
+
success_rate = (
|
|
117
|
+
total_score / total_possible_score * 100
|
|
118
|
+
if total_possible_score > 0
|
|
119
|
+
else 0
|
|
120
|
+
)
|
|
91
121
|
|
|
92
122
|
row = StudentRow(
|
|
93
123
|
student=student,
|
|
@@ -95,7 +125,7 @@ def student_row(levels_sorted, student, best_attempts):
|
|
|
95
125
|
total_score=int(total_score),
|
|
96
126
|
level_scores=level_scores,
|
|
97
127
|
completed=num_finished,
|
|
98
|
-
|
|
128
|
+
success_rate=success_rate,
|
|
99
129
|
)
|
|
100
130
|
return row
|
|
101
131
|
|
|
@@ -104,29 +134,43 @@ def to_name(level):
|
|
|
104
134
|
return f"L{level.name}"
|
|
105
135
|
|
|
106
136
|
|
|
137
|
+
def to_python_name(level):
|
|
138
|
+
return f"L{int(level.name) - 1000}"
|
|
139
|
+
|
|
140
|
+
|
|
107
141
|
def shared_level_to_name(level, user):
|
|
108
|
-
return
|
|
142
|
+
return (
|
|
143
|
+
f"{level.name} (you)"
|
|
144
|
+
if user == level.owner
|
|
145
|
+
else f"{level.name} ({level.owner})"
|
|
146
|
+
)
|
|
109
147
|
|
|
110
148
|
|
|
111
|
-
def scoreboard_data(episode_ids, attempts_per_students):
|
|
149
|
+
def scoreboard_data(episode_ids, attempts_per_students, language):
|
|
112
150
|
# Show the total score, total time and score of each level
|
|
113
151
|
levels_sorted = []
|
|
114
152
|
for episode_id in episode_ids:
|
|
115
153
|
episode = Episode.objects.get(id=episode_id)
|
|
116
154
|
levels_sorted += episode.levels
|
|
117
155
|
|
|
118
|
-
|
|
156
|
+
to_name_function = to_name if language == "blockly" else to_python_name
|
|
157
|
+
|
|
158
|
+
level_headers = list(map(to_name_function, levels_sorted))
|
|
119
159
|
student_data = [
|
|
120
|
-
student_row(levels_sorted, student, best_attempts)
|
|
160
|
+
student_row(levels_sorted, student, best_attempts)
|
|
161
|
+
for student, best_attempts in attempts_per_students.items()
|
|
121
162
|
]
|
|
122
163
|
|
|
123
164
|
return student_data, Headers, level_headers, levels_sorted
|
|
124
165
|
|
|
125
166
|
|
|
126
167
|
def shared_levels_data(user, shared_levels, attempts_per_students):
|
|
127
|
-
shared_level_headers = list(
|
|
168
|
+
shared_level_headers = list(
|
|
169
|
+
shared_level_to_name(level, user) for level in shared_levels
|
|
170
|
+
)
|
|
128
171
|
shared_student_data = [
|
|
129
|
-
student_row(shared_levels, student, best_attempts)
|
|
172
|
+
student_row(shared_levels, student, best_attempts)
|
|
173
|
+
for student, best_attempts in attempts_per_students.items()
|
|
130
174
|
]
|
|
131
175
|
|
|
132
176
|
return SharedHeaders, shared_level_headers, shared_student_data
|
|
@@ -150,14 +194,20 @@ def _check_attempts(best_attempts):
|
|
|
150
194
|
total_score = 0
|
|
151
195
|
total_possible_score = 0
|
|
152
196
|
# Get the best attempts for the specific Episode
|
|
153
|
-
attempts = [
|
|
197
|
+
attempts = [
|
|
198
|
+
best_attempt
|
|
199
|
+
for best_attempt in best_attempts
|
|
200
|
+
if best_attempt.level.episode.id == episode_id
|
|
201
|
+
]
|
|
154
202
|
for attempt in attempts:
|
|
155
203
|
max_score = 10 if attempt.level.disable_route_score else 20
|
|
156
204
|
|
|
157
205
|
total_score += attempt.score if attempt.score is not None else 0
|
|
158
206
|
total_possible_score += max_score
|
|
159
207
|
|
|
160
|
-
is_low_attempt =
|
|
208
|
+
is_low_attempt = (
|
|
209
|
+
attempt.score == 0 or max_score / attempt.score < threshold
|
|
210
|
+
)
|
|
161
211
|
if is_low_attempt:
|
|
162
212
|
low_episode_ids.add(episode_id)
|
|
163
213
|
|
|
@@ -170,9 +220,14 @@ def get_improvement_data(attempts_per_student):
|
|
|
170
220
|
for student, best_attempts in attempts_per_student.items():
|
|
171
221
|
episodes_of_concern = _check_attempts(best_attempts)
|
|
172
222
|
if episodes_of_concern:
|
|
173
|
-
areas = [
|
|
223
|
+
areas = [
|
|
224
|
+
messages.get_episode_title(ep_id)
|
|
225
|
+
for ep_id in episodes_of_concern
|
|
226
|
+
]
|
|
174
227
|
areas_summary = ", ".join(areas)
|
|
175
|
-
the_students.append(
|
|
228
|
+
the_students.append(
|
|
229
|
+
StudentInTrouble(student=student, areas=areas_summary)
|
|
230
|
+
)
|
|
176
231
|
return the_students
|
|
177
232
|
|
|
178
233
|
|
|
@@ -186,6 +241,7 @@ def scoreboard_view(
|
|
|
186
241
|
shared_headers,
|
|
187
242
|
shared_level_headers,
|
|
188
243
|
shared_student_data,
|
|
244
|
+
language,
|
|
189
245
|
):
|
|
190
246
|
database_episodes = level_selection.fetch_episode_data(False)
|
|
191
247
|
|
|
@@ -204,11 +260,20 @@ def scoreboard_view(
|
|
|
204
260
|
"shared_headers": shared_headers,
|
|
205
261
|
"shared_level_headers": shared_level_headers,
|
|
206
262
|
"shared_student_data": shared_student_data,
|
|
263
|
+
"language": language,
|
|
207
264
|
},
|
|
208
265
|
)
|
|
209
266
|
|
|
210
267
|
|
|
211
|
-
def
|
|
268
|
+
def blockly_scoreboard(request):
|
|
269
|
+
return scoreboard(request, "blockly")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def python_scoreboard(request):
|
|
273
|
+
return scoreboard(request, "python")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def scoreboard(request, language):
|
|
212
277
|
"""
|
|
213
278
|
Renders a page with students' scores. A teacher can see the visible classes in
|
|
214
279
|
their school. Student's view is restricted to their class if their teacher enabled
|
|
@@ -219,7 +284,12 @@ def scoreboard(request):
|
|
|
219
284
|
|
|
220
285
|
user = User(request.user.userprofile)
|
|
221
286
|
users_classes = classes_for(user)
|
|
222
|
-
|
|
287
|
+
|
|
288
|
+
episodes_range = range(1, 10) if language == "blockly" else range(12, 16)
|
|
289
|
+
|
|
290
|
+
all_episode_ids = [
|
|
291
|
+
episode.id for episode in Episode.objects.filter(pk__in=episodes_range)
|
|
292
|
+
]
|
|
223
293
|
|
|
224
294
|
if user.is_independent_student():
|
|
225
295
|
return render_no_permission_error(request)
|
|
@@ -228,7 +298,9 @@ def scoreboard(request):
|
|
|
228
298
|
class_ids = set(map(int, request.POST.getlist("classes")))
|
|
229
299
|
# Show all levels if the teacher doesn't select any
|
|
230
300
|
episode_ids = (
|
|
231
|
-
set(all_episode_ids)
|
|
301
|
+
set(all_episode_ids)
|
|
302
|
+
if "episodes" not in request.POST
|
|
303
|
+
else set(map(int, request.POST.getlist("episodes")))
|
|
232
304
|
)
|
|
233
305
|
else:
|
|
234
306
|
# Show no data on page load by default (if teacher)
|
|
@@ -249,6 +321,7 @@ def scoreboard(request):
|
|
|
249
321
|
form = ScoreboardForm(
|
|
250
322
|
request.POST or None,
|
|
251
323
|
classes=users_classes,
|
|
324
|
+
language=language,
|
|
252
325
|
initial={
|
|
253
326
|
"classes": class_ids,
|
|
254
327
|
"episodes": episode_ids,
|
|
@@ -266,47 +339,74 @@ def scoreboard(request):
|
|
|
266
339
|
all_levels += episode.levels
|
|
267
340
|
|
|
268
341
|
attempts_per_student = {}
|
|
269
|
-
attempts_per_student_shared_levels = {}
|
|
270
|
-
|
|
271
|
-
if user.is_teacher():
|
|
272
|
-
if user.teacher.is_admin:
|
|
273
|
-
# Get all custom levels owned by non-admin teachers
|
|
274
|
-
standard_teachers = Teacher.objects.filter(school=user.teacher.school, is_admin=False)
|
|
275
|
-
for standard_teacher in standard_teachers:
|
|
276
|
-
shared_levels += levels_owned_by(standard_teacher.new_user)
|
|
277
|
-
else:
|
|
278
|
-
# Get logged in teacher's custom levels
|
|
279
|
-
shared_levels += levels_owned_by(request.user)
|
|
280
|
-
|
|
281
|
-
# In all cases, get all admins' custom levels
|
|
282
|
-
school_admins = Teacher.objects.filter(school=user.teacher.school, is_admin=True)
|
|
283
|
-
for school_admin in school_admins:
|
|
284
|
-
shared_levels += levels_owned_by(school_admin.new_user)
|
|
285
|
-
|
|
286
|
-
elif user.is_student():
|
|
287
|
-
shared_levels += levels_shared_with(request.user)
|
|
288
342
|
|
|
289
343
|
for student in students:
|
|
290
344
|
best_attempts = Attempt.objects.filter(
|
|
291
345
|
level__in=all_levels, student=student, is_best_attempt=True
|
|
292
346
|
).select_related("level")
|
|
293
347
|
attempts_per_student[student] = best_attempts
|
|
294
|
-
shared_levels += levels_owned_by(student.new_user)
|
|
295
|
-
best_attempts_shared_levels = Attempt.objects.filter(
|
|
296
|
-
level__in=shared_levels, student=student, is_best_attempt=True
|
|
297
|
-
).select_related("level")
|
|
298
|
-
attempts_per_student_shared_levels[student] = best_attempts_shared_levels
|
|
299
348
|
|
|
300
|
-
(student_data, headers, level_headers, levels_sorted) = scoreboard_data(
|
|
301
|
-
|
|
302
|
-
shared_headers, shared_level_headers, shared_student_data = shared_levels_data(
|
|
303
|
-
request.user.userprofile, shared_levels, attempts_per_student_shared_levels
|
|
349
|
+
(student_data, headers, level_headers, levels_sorted) = scoreboard_data(
|
|
350
|
+
episode_ids, attempts_per_student, language
|
|
304
351
|
)
|
|
352
|
+
improvement_data = get_improvement_data(attempts_per_student)
|
|
353
|
+
|
|
354
|
+
shared_headers = shared_level_headers = shared_student_data = []
|
|
355
|
+
|
|
356
|
+
if language == "blockly":
|
|
357
|
+
attempts_per_student_shared_levels = {}
|
|
358
|
+
|
|
359
|
+
if user.is_teacher():
|
|
360
|
+
if user.teacher.is_admin:
|
|
361
|
+
# Get all custom levels owned by non-admin teachers
|
|
362
|
+
standard_teachers = Teacher.objects.filter(
|
|
363
|
+
school=user.teacher.school, is_admin=False
|
|
364
|
+
)
|
|
365
|
+
for standard_teacher in standard_teachers:
|
|
366
|
+
shared_levels += levels_owned_by(standard_teacher.new_user)
|
|
367
|
+
else:
|
|
368
|
+
# Get logged in teacher's custom levels
|
|
369
|
+
shared_levels += levels_owned_by(request.user)
|
|
370
|
+
|
|
371
|
+
# In all cases, get all admins' custom levels
|
|
372
|
+
school_admins = Teacher.objects.filter(
|
|
373
|
+
school=user.teacher.school, is_admin=True
|
|
374
|
+
)
|
|
375
|
+
for school_admin in school_admins:
|
|
376
|
+
shared_levels += levels_owned_by(school_admin.new_user)
|
|
377
|
+
|
|
378
|
+
elif user.is_student():
|
|
379
|
+
shared_levels += levels_shared_with(request.user)
|
|
380
|
+
|
|
381
|
+
for student in students:
|
|
382
|
+
shared_levels += levels_owned_by(student.new_user)
|
|
383
|
+
best_attempts_shared_levels = Attempt.objects.filter(
|
|
384
|
+
level__in=shared_levels, student=student, is_best_attempt=True
|
|
385
|
+
).select_related("level")
|
|
386
|
+
attempts_per_student_shared_levels[
|
|
387
|
+
student
|
|
388
|
+
] = best_attempts_shared_levels
|
|
389
|
+
|
|
390
|
+
(
|
|
391
|
+
shared_headers,
|
|
392
|
+
shared_level_headers,
|
|
393
|
+
shared_student_data,
|
|
394
|
+
) = shared_levels_data(
|
|
395
|
+
request.user.userprofile,
|
|
396
|
+
shared_levels,
|
|
397
|
+
attempts_per_student_shared_levels,
|
|
398
|
+
)
|
|
305
399
|
|
|
306
400
|
csv_export = "export" in request.POST
|
|
307
401
|
|
|
308
402
|
if csv_export:
|
|
309
|
-
return scoreboard_csv(
|
|
403
|
+
return scoreboard_csv(
|
|
404
|
+
student_data,
|
|
405
|
+
levels_sorted,
|
|
406
|
+
improvement_data,
|
|
407
|
+
shared_level_headers,
|
|
408
|
+
shared_student_data,
|
|
409
|
+
)
|
|
310
410
|
else:
|
|
311
411
|
return scoreboard_view(
|
|
312
412
|
request,
|
|
@@ -318,11 +418,16 @@ def scoreboard(request):
|
|
|
318
418
|
shared_headers,
|
|
319
419
|
shared_level_headers,
|
|
320
420
|
shared_student_data,
|
|
421
|
+
language,
|
|
321
422
|
)
|
|
322
423
|
|
|
323
424
|
|
|
324
425
|
def render_no_permission_error(request):
|
|
325
|
-
return renderError(
|
|
426
|
+
return renderError(
|
|
427
|
+
request,
|
|
428
|
+
messages.no_permission_title(),
|
|
429
|
+
messages.no_permission_scoreboard(),
|
|
430
|
+
)
|
|
326
431
|
|
|
327
432
|
|
|
328
433
|
def is_teacher_with_no_classes_assigned(user, users_classes):
|
|
@@ -352,7 +457,9 @@ def sorted_levels_by(level_ids):
|
|
|
352
457
|
|
|
353
458
|
def are_classes_viewable_by_teacher(class_ids, user):
|
|
354
459
|
teachers = Teacher.objects.filter(school=user.teacher.school)
|
|
355
|
-
classes_in_teachers_school = Class.objects.filter(
|
|
460
|
+
classes_in_teachers_school = Class.objects.filter(
|
|
461
|
+
teacher__in=teachers
|
|
462
|
+
).values_list("id", flat=True)
|
|
356
463
|
for class_id in class_ids:
|
|
357
464
|
is_authorised = class_id in classes_in_teachers_school
|
|
358
465
|
if not is_authorised:
|
|
@@ -367,7 +474,9 @@ def authorised_student_access(class_, class_ids):
|
|
|
367
474
|
def students_visible_to_student(student):
|
|
368
475
|
class_ = student.class_field
|
|
369
476
|
if is_viewable(class_):
|
|
370
|
-
return class_.students.filter(new_user__is_active=True).select_related(
|
|
477
|
+
return class_.students.filter(new_user__is_active=True).select_related(
|
|
478
|
+
"class_field", "user__user"
|
|
479
|
+
)
|
|
371
480
|
else:
|
|
372
481
|
return [student]
|
|
373
482
|
|
|
@@ -381,9 +490,9 @@ def students_visible_to_user(user, classes):
|
|
|
381
490
|
|
|
382
491
|
|
|
383
492
|
def students_of_classes(classes):
|
|
384
|
-
return Student.objects.filter(
|
|
385
|
-
|
|
386
|
-
)
|
|
493
|
+
return Student.objects.filter(
|
|
494
|
+
class_field__in=classes, new_user__is_active=True
|
|
495
|
+
).select_related("class_field", "user__user")
|
|
387
496
|
|
|
388
497
|
|
|
389
498
|
def is_valid_request(user, class_ids):
|
|
@@ -401,7 +510,7 @@ def is_viewable(class_):
|
|
|
401
510
|
return class_.classmates_data_viewable
|
|
402
511
|
|
|
403
512
|
|
|
404
|
-
def
|
|
513
|
+
def chop_milliseconds(delta):
|
|
405
514
|
return delta - timedelta(microseconds=delta.microseconds)
|
|
406
515
|
|
|
407
516
|
|
|
@@ -415,10 +524,16 @@ class User(object):
|
|
|
415
524
|
self.student = profile.student
|
|
416
525
|
|
|
417
526
|
def is_student(self):
|
|
418
|
-
return
|
|
527
|
+
return (
|
|
528
|
+
hasattr(self.profile, "student")
|
|
529
|
+
and not self.profile.student.is_independent()
|
|
530
|
+
)
|
|
419
531
|
|
|
420
532
|
def is_teacher(self):
|
|
421
533
|
return hasattr(self.profile, "teacher")
|
|
422
534
|
|
|
423
535
|
def is_independent_student(self):
|
|
424
|
-
return
|
|
536
|
+
return (
|
|
537
|
+
hasattr(self.profile, "student")
|
|
538
|
+
and self.profile.student.is_independent()
|
|
539
|
+
)
|
game/views/worksheet.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import absolute_import, division
|
|
2
|
+
|
|
3
|
+
from django.shortcuts import render
|
|
4
|
+
|
|
5
|
+
from game import messages
|
|
6
|
+
from game.models import Worksheet
|
|
7
|
+
from .helper import renderError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def worksheet(request, worksheetId):
|
|
11
|
+
if request.user.is_anonymous:
|
|
12
|
+
return renderError(
|
|
13
|
+
request,
|
|
14
|
+
messages.no_permission_python_den_worksheet_title(),
|
|
15
|
+
messages.no_permission_python_den_worksheet_page(),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
worksheet = Worksheet.objects.get(pk=worksheetId)
|
|
19
|
+
starter_code = messages.worksheet_starter_code()
|
|
20
|
+
|
|
21
|
+
return render(
|
|
22
|
+
request,
|
|
23
|
+
"game/python_den_worksheet.html",
|
|
24
|
+
context={"worksheet": worksheet, "starter_code": starter_code},
|
|
25
|
+
)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rapid-router
|
|
3
|
+
Version: 7.6.8
|
|
4
|
+
Classifier: Programming Language :: Python
|
|
5
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
6
|
+
Classifier: Framework :: Django
|
|
7
|
+
License-File: LICENSE.md
|
|
8
|
+
Requires-Dist: asgiref==3.10.0; python_version >= "3.9"
|
|
9
|
+
Requires-Dist: asttokens==3.0.0; python_version >= "3.8"
|
|
10
|
+
Requires-Dist: certifi==2025.10.5; python_version >= "3.7"
|
|
11
|
+
Requires-Dist: cffi==2.0.0; platform_python_implementation != "PyPy"
|
|
12
|
+
Requires-Dist: cfl-common==8.9.8
|
|
13
|
+
Requires-Dist: charset-normalizer==3.4.4; python_version >= "3.7"
|
|
14
|
+
Requires-Dist: cryptography==44.0.1; python_version >= "3.7" and python_full_version not in "3.9.0, 3.9.1"
|
|
15
|
+
Requires-Dist: decorator==5.2.1; python_version >= "3.8"
|
|
16
|
+
Requires-Dist: diff-match-patch==20241021; python_version >= "3.7"
|
|
17
|
+
Requires-Dist: django==5.1.14; python_version >= "3.10"
|
|
18
|
+
Requires-Dist: django-countries==7.6.1
|
|
19
|
+
Requires-Dist: django-csp==3.8
|
|
20
|
+
Requires-Dist: django-formtools==2.5.1; python_version >= "3.8"
|
|
21
|
+
Requires-Dist: django-import-export==4.2.0; python_version >= "3.9"
|
|
22
|
+
Requires-Dist: django-otp==1.6.3; python_version >= "3.7"
|
|
23
|
+
Requires-Dist: django-phonenumber-field==8.3.0; python_version >= "3.9"
|
|
24
|
+
Requires-Dist: django-pipeline==4.0.0; python_version >= "3.9"
|
|
25
|
+
Requires-Dist: django-reverse-js==0.1.8; python_version >= "3.10"
|
|
26
|
+
Requires-Dist: django-two-factor-auth==1.17.0; python_version >= "3.8"
|
|
27
|
+
Requires-Dist: djangorestframework==3.16.0; python_version >= "3.9"
|
|
28
|
+
Requires-Dist: executing==2.2.1; python_version >= "3.8"
|
|
29
|
+
Requires-Dist: idna==3.11; python_version >= "3.8"
|
|
30
|
+
Requires-Dist: ipython==9.7.0; python_version >= "3.11"
|
|
31
|
+
Requires-Dist: ipython-pygments-lexers==1.1.1; python_version >= "3.8"
|
|
32
|
+
Requires-Dist: jedi==0.19.2; python_version >= "3.6"
|
|
33
|
+
Requires-Dist: libsass==0.23.0; python_version >= "3.8"
|
|
34
|
+
Requires-Dist: matplotlib-inline==0.2.1; python_version >= "3.9"
|
|
35
|
+
Requires-Dist: more-itertools==8.7.0; python_version >= "3.5"
|
|
36
|
+
Requires-Dist: numpy==2.3.4; python_version >= "3.11"
|
|
37
|
+
Requires-Dist: pandas==2.3.3; python_version >= "3.9"
|
|
38
|
+
Requires-Dist: parso==0.8.5; python_version >= "3.6"
|
|
39
|
+
Requires-Dist: pexpect==4.9.0; sys_platform != "win32" and sys_platform != "emscripten"
|
|
40
|
+
Requires-Dist: pgeocode==0.4.0; python_version >= "3.8"
|
|
41
|
+
Requires-Dist: prompt-toolkit==3.0.52; python_version >= "3.8"
|
|
42
|
+
Requires-Dist: ptyprocess==0.7.0
|
|
43
|
+
Requires-Dist: pure-eval==0.2.3
|
|
44
|
+
Requires-Dist: pycparser==2.23; implementation_name != "PyPy"
|
|
45
|
+
Requires-Dist: pygments==2.19.2; python_version >= "3.8"
|
|
46
|
+
Requires-Dist: pyhamcrest==2.0.2; python_version >= "3.5"
|
|
47
|
+
Requires-Dist: pyjwt==2.6.0; python_version >= "3.7"
|
|
48
|
+
Requires-Dist: pypng==0.20220715.0
|
|
49
|
+
Requires-Dist: python-dateutil==2.9.0.post0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
50
|
+
Requires-Dist: pytz==2025.2
|
|
51
|
+
Requires-Dist: qrcode==7.4.2; python_version >= "3.7"
|
|
52
|
+
Requires-Dist: requests==2.32.5; python_version >= "3.9"
|
|
53
|
+
Requires-Dist: setuptools==80.9.0; python_version >= "3.9"
|
|
54
|
+
Requires-Dist: six==1.17.0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
55
|
+
Requires-Dist: sqlparse==0.5.3; python_version >= "3.8"
|
|
56
|
+
Requires-Dist: stack-data==0.6.3
|
|
57
|
+
Requires-Dist: tablib==3.7.0; python_version >= "3.9"
|
|
58
|
+
Requires-Dist: traitlets==5.14.3; python_version >= "3.8"
|
|
59
|
+
Requires-Dist: typing-extensions==4.15.0; python_version >= "3.9"
|
|
60
|
+
Requires-Dist: tzdata==2025.2; python_version >= "2"
|
|
61
|
+
Requires-Dist: urllib3==2.5.0; python_version >= "3.9"
|
|
62
|
+
Requires-Dist: wcwidth==0.2.14; python_version >= "3.6"
|
|
63
|
+
Requires-Dist: wheel==0.45.1; python_version >= "3.8"
|
|
64
|
+
Provides-Extra: dev
|
|
65
|
+
Requires-Dist: amqp==5.3.1; python_version >= "3.6" and extra == "dev"
|
|
66
|
+
Requires-Dist: asgiref==3.10.0; python_version >= "3.9" and extra == "dev"
|
|
67
|
+
Requires-Dist: attrs==25.4.0; python_version >= "3.9" and extra == "dev"
|
|
68
|
+
Requires-Dist: billiard==4.2.2; python_version >= "3.7" and extra == "dev"
|
|
69
|
+
Requires-Dist: black==25.11.0; python_version >= "3.9" and extra == "dev"
|
|
70
|
+
Requires-Dist: boto3==1.36.14; python_version >= "3.8" and extra == "dev"
|
|
71
|
+
Requires-Dist: botocore==1.36.26; python_version >= "3.8" and extra == "dev"
|
|
72
|
+
Requires-Dist: celery[sqs]==5.4.0; python_version >= "3.8" and extra == "dev"
|
|
73
|
+
Requires-Dist: certifi==2025.10.5; python_version >= "3.7" and extra == "dev"
|
|
74
|
+
Requires-Dist: cffi==2.0.0; platform_python_implementation != "PyPy" and extra == "dev"
|
|
75
|
+
Requires-Dist: cfl-common==8.9.8; extra == "dev"
|
|
76
|
+
Requires-Dist: charset-normalizer==3.4.4; python_version >= "3.7" and extra == "dev"
|
|
77
|
+
Requires-Dist: click==8.3.0; python_version >= "3.10" and extra == "dev"
|
|
78
|
+
Requires-Dist: click-didyoumean==0.3.1; python_full_version >= "3.6.2" and extra == "dev"
|
|
79
|
+
Requires-Dist: click-plugins==1.1.1.2; extra == "dev"
|
|
80
|
+
Requires-Dist: click-repl==0.3.0; python_version >= "3.6" and extra == "dev"
|
|
81
|
+
Requires-Dist: codeforlife-portal==8.9.8; extra == "dev"
|
|
82
|
+
Requires-Dist: cryptography==44.0.1; (python_version >= "3.7" and python_full_version not in "3.9.0, 3.9.1") and extra == "dev"
|
|
83
|
+
Requires-Dist: diff-match-patch==20241021; python_version >= "3.7" and extra == "dev"
|
|
84
|
+
Requires-Dist: django==5.1.14; python_version >= "3.10" and extra == "dev"
|
|
85
|
+
Requires-Dist: django-classy-tags==4.1.0; python_version >= "3.8" and extra == "dev"
|
|
86
|
+
Requires-Dist: django-countries==7.6.1; extra == "dev"
|
|
87
|
+
Requires-Dist: django-csp==3.8; extra == "dev"
|
|
88
|
+
Requires-Dist: django-extensions==4.1; python_version >= "3.9" and extra == "dev"
|
|
89
|
+
Requires-Dist: django-formtools==2.5.1; python_version >= "3.8" and extra == "dev"
|
|
90
|
+
Requires-Dist: django-import-export==4.2.0; python_version >= "3.9" and extra == "dev"
|
|
91
|
+
Requires-Dist: django-otp==1.6.3; python_version >= "3.7" and extra == "dev"
|
|
92
|
+
Requires-Dist: django-phonenumber-field==8.3.0; python_version >= "3.9" and extra == "dev"
|
|
93
|
+
Requires-Dist: django-pipeline==4.0.0; python_version >= "3.9" and extra == "dev"
|
|
94
|
+
Requires-Dist: django-preventconcurrentlogins==0.8.2; extra == "dev"
|
|
95
|
+
Requires-Dist: django-ratelimit==3.0.1; python_version >= "3.4" and extra == "dev"
|
|
96
|
+
Requires-Dist: django-recaptcha==4.0.0; extra == "dev"
|
|
97
|
+
Requires-Dist: django-sekizai==4.1.0; python_version >= "3.8" and extra == "dev"
|
|
98
|
+
Requires-Dist: django-selenium-clean==1.0.1; extra == "dev"
|
|
99
|
+
Requires-Dist: django-test-migrations==1.4.0; (python_version >= "3.9" and python_version < "4.0") and extra == "dev"
|
|
100
|
+
Requires-Dist: django-treebeard==4.7.1; python_version >= "3.8" and extra == "dev"
|
|
101
|
+
Requires-Dist: django-two-factor-auth==1.17.0; python_version >= "3.8" and extra == "dev"
|
|
102
|
+
Requires-Dist: djangorestframework==3.16.0; python_version >= "3.9" and extra == "dev"
|
|
103
|
+
Requires-Dist: execnet==2.1.1; python_version >= "3.8" and extra == "dev"
|
|
104
|
+
Requires-Dist: gunicorn==23.0.0; python_version >= "3.7" and extra == "dev"
|
|
105
|
+
Requires-Dist: h11==0.16.0; python_version >= "3.8" and extra == "dev"
|
|
106
|
+
Requires-Dist: idna==3.11; python_version >= "3.8" and extra == "dev"
|
|
107
|
+
Requires-Dist: importlib-metadata==4.13.0; python_version >= "3.7" and extra == "dev"
|
|
108
|
+
Requires-Dist: iniconfig==2.3.0; python_version >= "3.10" and extra == "dev"
|
|
109
|
+
Requires-Dist: isort==7.0.0; python_full_version >= "3.10.0" and extra == "dev"
|
|
110
|
+
Requires-Dist: jmespath==1.0.1; python_version >= "3.7" and extra == "dev"
|
|
111
|
+
Requires-Dist: kombu[sqs]==5.6.0; python_version >= "3.9" and extra == "dev"
|
|
112
|
+
Requires-Dist: libsass==0.23.0; python_version >= "3.8" and extra == "dev"
|
|
113
|
+
Requires-Dist: markupsafe==3.0.3; python_version >= "3.9" and extra == "dev"
|
|
114
|
+
Requires-Dist: more-itertools==8.7.0; python_version >= "3.5" and extra == "dev"
|
|
115
|
+
Requires-Dist: mypy-extensions==1.1.0; python_version >= "3.8" and extra == "dev"
|
|
116
|
+
Requires-Dist: numpy==2.3.4; python_version >= "3.11" and extra == "dev"
|
|
117
|
+
Requires-Dist: outcome==1.3.0.post0; python_version >= "3.7" and extra == "dev"
|
|
118
|
+
Requires-Dist: packaging==25.0; python_version >= "3.8" and extra == "dev"
|
|
119
|
+
Requires-Dist: pandas==2.3.3; python_version >= "3.9" and extra == "dev"
|
|
120
|
+
Requires-Dist: pathspec==0.12.1; python_version >= "3.8" and extra == "dev"
|
|
121
|
+
Requires-Dist: pgeocode==0.4.0; python_version >= "3.8" and extra == "dev"
|
|
122
|
+
Requires-Dist: phonenumbers==8.12.12; extra == "dev"
|
|
123
|
+
Requires-Dist: pillow==12.0.0; python_version >= "3.10" and extra == "dev"
|
|
124
|
+
Requires-Dist: platformdirs==4.5.0; python_version >= "3.10" and extra == "dev"
|
|
125
|
+
Requires-Dist: pluggy==1.6.0; python_version >= "3.9" and extra == "dev"
|
|
126
|
+
Requires-Dist: prompt-toolkit==3.0.52; python_version >= "3.8" and extra == "dev"
|
|
127
|
+
Requires-Dist: psycopg2-binary==2.9.9; python_version >= "3.7" and extra == "dev"
|
|
128
|
+
Requires-Dist: pycparser==2.23; implementation_name != "PyPy" and extra == "dev"
|
|
129
|
+
Requires-Dist: pycurl==7.45.7; python_version >= "3.5" and extra == "dev"
|
|
130
|
+
Requires-Dist: pygments==2.19.2; python_version >= "3.8" and extra == "dev"
|
|
131
|
+
Requires-Dist: pyjwt==2.6.0; python_version >= "3.7" and extra == "dev"
|
|
132
|
+
Requires-Dist: pyopenssl==25.1.0; python_version >= "3.7" and extra == "dev"
|
|
133
|
+
Requires-Dist: pypng==0.20220715.0; extra == "dev"
|
|
134
|
+
Requires-Dist: pysocks==1.7.1; extra == "dev"
|
|
135
|
+
Requires-Dist: pytest==8.4.2; python_version >= "3.9" and extra == "dev"
|
|
136
|
+
Requires-Dist: pytest-django==4.8.0; python_version >= "3.8" and extra == "dev"
|
|
137
|
+
Requires-Dist: pytest-order==1.3.0; python_version >= "3.7" and extra == "dev"
|
|
138
|
+
Requires-Dist: pytest-xdist==3.8.0; python_version >= "3.9" and extra == "dev"
|
|
139
|
+
Requires-Dist: python-dateutil==2.9.0.post0; (python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3") and extra == "dev"
|
|
140
|
+
Requires-Dist: python-dotenv==1.0.1; python_version >= "3.8" and extra == "dev"
|
|
141
|
+
Requires-Dist: pytokens==0.3.0; python_version >= "3.8" and extra == "dev"
|
|
142
|
+
Requires-Dist: pytz==2025.2; extra == "dev"
|
|
143
|
+
Requires-Dist: pyyaml==6.0.2; python_version >= "3.8" and extra == "dev"
|
|
144
|
+
Requires-Dist: qrcode==7.4.2; python_version >= "3.7" and extra == "dev"
|
|
145
|
+
Requires-Dist: reportlab==4.4.2; (python_version >= "3.7" and python_version < "4") and extra == "dev"
|
|
146
|
+
Requires-Dist: requests==2.32.5; python_version >= "3.9" and extra == "dev"
|
|
147
|
+
Requires-Dist: requests-toolbelt==1.0.0; (python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3") and extra == "dev"
|
|
148
|
+
Requires-Dist: s3transfer==0.11.3; python_version >= "3.8" and extra == "dev"
|
|
149
|
+
Requires-Dist: selenium==4.29.0; python_version >= "3.9" and extra == "dev"
|
|
150
|
+
Requires-Dist: setuptools==80.9.0; python_version >= "3.9" and extra == "dev"
|
|
151
|
+
Requires-Dist: six==1.17.0; (python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3") and extra == "dev"
|
|
152
|
+
Requires-Dist: sniffio==1.3.1; python_version >= "3.7" and extra == "dev"
|
|
153
|
+
Requires-Dist: sortedcontainers==2.4.0; extra == "dev"
|
|
154
|
+
Requires-Dist: sqlparse==0.5.3; python_version >= "3.8" and extra == "dev"
|
|
155
|
+
Requires-Dist: tablib==3.7.0; python_version >= "3.9" and extra == "dev"
|
|
156
|
+
Requires-Dist: trio==0.32.0; python_version >= "3.10" and extra == "dev"
|
|
157
|
+
Requires-Dist: trio-websocket==0.12.2; python_version >= "3.8" and extra == "dev"
|
|
158
|
+
Requires-Dist: typing-extensions==4.15.0; python_version >= "3.9" and extra == "dev"
|
|
159
|
+
Requires-Dist: tzdata==2025.2; python_version >= "2" and extra == "dev"
|
|
160
|
+
Requires-Dist: urllib3==2.5.0; python_version >= "3.9" and extra == "dev"
|
|
161
|
+
Requires-Dist: uvicorn==0.38.0; python_version >= "3.9" and extra == "dev"
|
|
162
|
+
Requires-Dist: uvicorn-worker==0.2.0; python_version >= "3.8" and extra == "dev"
|
|
163
|
+
Requires-Dist: vine==5.1.0; python_version >= "3.6" and extra == "dev"
|
|
164
|
+
Requires-Dist: wcwidth==0.2.14; python_version >= "3.6" and extra == "dev"
|
|
165
|
+
Requires-Dist: websocket-client==1.9.0; python_version >= "3.9" and extra == "dev"
|
|
166
|
+
Requires-Dist: werkzeug==3.1.3; python_version >= "3.9" and extra == "dev"
|
|
167
|
+
Requires-Dist: wheel==0.45.1; python_version >= "3.8" and extra == "dev"
|
|
168
|
+
Requires-Dist: whitenoise==6.9.0; python_version >= "3.9" and extra == "dev"
|
|
169
|
+
Requires-Dist: wsproto==1.2.0; python_full_version >= "3.7.0" and extra == "dev"
|
|
170
|
+
Requires-Dist: zipp==3.23.0; python_version >= "3.9" and extra == "dev"
|
|
171
|
+
Dynamic: classifier
|
|
172
|
+
Dynamic: license-file
|
|
173
|
+
Dynamic: provides-extra
|
|
174
|
+
Dynamic: requires-dist
|