This commit is contained in:
Egor Matveev 2021-07-11 10:28:12 +03:00
commit 9c0123cbf2
102 changed files with 6285 additions and 0 deletions

117
.gitignore vendored Normal file
View File

@ -0,0 +1,117 @@
# Django #
*.log
*.pot
*.pyc
__pycache__
db.sqlite3
media
data
# Backup files #
*.bak
# If you are using PyCharm #
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/gradle.xml
.idea/**/libraries
*.iws /out/
# Python #
*.py[cod]
*$py.class
# Distribution / packaging
.Python build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
.pytest_cache/
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery
celerybeat-schedule.*
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# mkdocs documentation
/site
# mypy
.mypy_cache/
# Sublime Text #
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
*.sublime-workspace
*.sublime-project
# sftp configuration file
sftp-config.json
# Package control specific files Package
Control.last-run
Control.ca-list
Control.ca-bundle
Control.system-ca-bundle
GitHub.sublime-settings
# Visual Studio Code #
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/../../../../../../:\Users\79175\Desktop\Sprint_server\.idea/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

32
.idea/Sprint_server.iml Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="Sprint/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/venv/lib/python3.7/site-packages/django/forms/templates" />
</list>
</option>
</component>
</module>

View File

@ -0,0 +1,28 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
</code_scheme>
</component>

456
.idea/dbnavigator.xml Normal file
View File

@ -0,0 +1,456 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DBNavigator.Project.DataEditorManager">
<record-view-column-sorting-type value="BY_INDEX" />
<value-preview-text-wrapping value="true" />
<value-preview-pinned value="false" />
</component>
<component name="DBNavigator.Project.DataExportManager">
<export-instructions>
<create-header value="true" />
<friendly-headers value="false" />
<quote-values-containing-separator value="true" />
<quote-all-values value="false" />
<value-separator value="" />
<file-name value="" />
<file-location value="" />
<scope value="GLOBAL" />
<destination value="FILE" />
<format value="EXCEL" />
<charset value="UTF-8" />
</export-instructions>
</component>
<component name="DBNavigator.Project.DatabaseBrowserManager">
<autoscroll-to-editor value="false" />
<autoscroll-from-editor value="true" />
<show-object-properties value="true" />
<loaded-nodes />
</component>
<component name="DBNavigator.Project.DatabaseFileManager">
<open-files />
</component>
<component name="DBNavigator.Project.EditorStateManager">
<last-used-providers />
</component>
<component name="DBNavigator.Project.ExecutionManager">
<retain-sticky-names value="false" />
</component>
<component name="DBNavigator.Project.MethodExecutionManager">
<method-browser />
<execution-history>
<group-entries value="true" />
<execution-inputs />
</execution-history>
<argument-values-cache />
</component>
<component name="DBNavigator.Project.ObjectDependencyManager">
<last-used-dependency-type value="INCOMING" />
</component>
<component name="DBNavigator.Project.ObjectQuickFilterManager">
<last-used-operator value="EQUAL" />
<filters />
</component>
<component name="DBNavigator.Project.ScriptExecutionManager" clear-outputs="true">
<recently-used-interfaces />
</component>
<component name="DBNavigator.Project.Settings">
<connections />
<browser-settings>
<general>
<display-mode value="TABBED" />
<navigation-history-size value="100" />
<show-object-details value="false" />
</general>
<filters>
<object-type-filter>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="true" />
<object-type name="ROLE" enabled="true" />
<object-type name="PRIVILEGE" enabled="true" />
<object-type name="CHARSET" enabled="true" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="MATERIALIZED_VIEW" enabled="true" />
<object-type name="NESTED_TABLE" enabled="true" />
<object-type name="COLUMN" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET_TRIGGER" enabled="true" />
<object-type name="DATABASE_TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="true" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
<object-type name="ARGUMENT" enabled="true" />
<object-type name="DIMENSION" enabled="true" />
<object-type name="CLUSTER" enabled="true" />
<object-type name="DBLINK" enabled="true" />
</object-type-filter>
</filters>
<sorting>
<object-type name="COLUMN" sorting-type="NAME" />
<object-type name="FUNCTION" sorting-type="NAME" />
<object-type name="PROCEDURE" sorting-type="NAME" />
<object-type name="ARGUMENT" sorting-type="POSITION" />
</sorting>
<default-editors>
<object-type name="VIEW" editor-type="SELECTION" />
<object-type name="PACKAGE" editor-type="SELECTION" />
<object-type name="TYPE" editor-type="SELECTION" />
</default-editors>
</browser-settings>
<navigation-settings>
<lookup-filters>
<lookup-objects>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="false" />
<object-type name="ROLE" enabled="false" />
<object-type name="PRIVILEGE" enabled="false" />
<object-type name="CHARSET" enabled="false" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="MATERIALIZED VIEW" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET TRIGGER" enabled="true" />
<object-type name="DATABASE TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="false" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="DIMENSION" enabled="false" />
<object-type name="CLUSTER" enabled="false" />
<object-type name="DBLINK" enabled="true" />
</lookup-objects>
<force-database-load value="false" />
<prompt-connection-selection value="true" />
<prompt-schema-selection value="true" />
</lookup-filters>
</navigation-settings>
<dataset-grid-settings>
<general>
<enable-zooming value="true" />
<enable-column-tooltip value="true" />
</general>
<sorting>
<nulls-first value="true" />
<max-sorting-columns value="4" />
</sorting>
<tracking-columns>
<columnNames value="" />
<visible value="true" />
<editable value="false" />
</tracking-columns>
</dataset-grid-settings>
<dataset-editor-settings>
<text-editor-popup>
<active value="false" />
<active-if-empty value="false" />
<data-length-threshold value="100" />
<popup-delay value="1000" />
</text-editor-popup>
<values-actions-popup>
<show-popup-button value="true" />
<element-count-threshold value="1000" />
<data-length-threshold value="250" />
</values-actions-popup>
<general>
<fetch-block-size value="100" />
<fetch-timeout value="30" />
<trim-whitespaces value="true" />
<convert-empty-strings-to-null value="true" />
<select-content-on-cell-edit value="true" />
<large-value-preview-active value="true" />
</general>
<filters>
<prompt-filter-dialog value="true" />
<default-filter-type value="BASIC" />
</filters>
<qualified-text-editor text-length-threshold="300">
<content-types>
<content-type name="Text" enabled="true" />
<content-type name="Properties" enabled="true" />
<content-type name="XML" enabled="true" />
<content-type name="DTD" enabled="true" />
<content-type name="HTML" enabled="true" />
<content-type name="XHTML" enabled="true" />
<content-type name="CSS" enabled="true" />
<content-type name="SQL" enabled="true" />
<content-type name="PL/SQL" enabled="true" />
<content-type name="JavaScript" enabled="true" />
<content-type name="JSON" enabled="true" />
<content-type name="JSON5" enabled="true" />
<content-type name="YAML" enabled="true" />
</content-types>
</qualified-text-editor>
<record-navigation>
<navigation-target value="VIEWER" />
</record-navigation>
</dataset-editor-settings>
<code-editor-settings>
<general>
<show-object-navigation-gutter value="false" />
<show-spec-declaration-navigation-gutter value="true" />
<enable-spellchecking value="true" />
<enable-reference-spellchecking value="false" />
</general>
<confirmations>
<save-changes value="false" />
<revert-changes value="true" />
</confirmations>
</code-editor-settings>
<code-completion-settings>
<filters>
<basic-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="false" />
<filter-element type="OBJECT" id="view" selected="false" />
<filter-element type="OBJECT" id="materialized view" selected="false" />
<filter-element type="OBJECT" id="index" selected="false" />
<filter-element type="OBJECT" id="constraint" selected="false" />
<filter-element type="OBJECT" id="trigger" selected="false" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="false" />
<filter-element type="OBJECT" id="procedure" selected="false" />
<filter-element type="OBJECT" id="function" selected="false" />
<filter-element type="OBJECT" id="package" selected="false" />
<filter-element type="OBJECT" id="type" selected="false" />
<filter-element type="OBJECT" id="dimension" selected="false" />
<filter-element type="OBJECT" id="cluster" selected="false" />
<filter-element type="OBJECT" id="dblink" selected="false" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</basic-filter>
<extended-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</extended-filter>
</filters>
<sorting enabled="true">
<sorting-element type="RESERVED_WORD" id="keyword" />
<sorting-element type="RESERVED_WORD" id="datatype" />
<sorting-element type="OBJECT" id="column" />
<sorting-element type="OBJECT" id="table" />
<sorting-element type="OBJECT" id="view" />
<sorting-element type="OBJECT" id="materialized view" />
<sorting-element type="OBJECT" id="index" />
<sorting-element type="OBJECT" id="constraint" />
<sorting-element type="OBJECT" id="trigger" />
<sorting-element type="OBJECT" id="synonym" />
<sorting-element type="OBJECT" id="sequence" />
<sorting-element type="OBJECT" id="procedure" />
<sorting-element type="OBJECT" id="function" />
<sorting-element type="OBJECT" id="package" />
<sorting-element type="OBJECT" id="type" />
<sorting-element type="OBJECT" id="dimension" />
<sorting-element type="OBJECT" id="cluster" />
<sorting-element type="OBJECT" id="dblink" />
<sorting-element type="OBJECT" id="schema" />
<sorting-element type="OBJECT" id="role" />
<sorting-element type="OBJECT" id="user" />
<sorting-element type="RESERVED_WORD" id="function" />
<sorting-element type="RESERVED_WORD" id="parameter" />
</sorting>
<format>
<enforce-code-style-case value="true" />
</format>
</code-completion-settings>
<execution-engine-settings>
<statement-execution>
<fetch-block-size value="100" />
<execution-timeout value="20" />
<debug-execution-timeout value="600" />
<focus-result value="false" />
<prompt-execution value="false" />
</statement-execution>
<script-execution>
<command-line-interfaces />
<execution-timeout value="300" />
</script-execution>
<method-execution>
<execution-timeout value="30" />
<debug-execution-timeout value="600" />
<parameter-history-size value="10" />
</method-execution>
</execution-engine-settings>
<operation-settings>
<transactions>
<uncommitted-changes>
<on-project-close value="ASK" />
<on-disconnect value="ASK" />
<on-autocommit-toggle value="ASK" />
</uncommitted-changes>
<multiple-uncommitted-changes>
<on-commit value="ASK" />
<on-rollback value="ASK" />
</multiple-uncommitted-changes>
</transactions>
<session-browser>
<disconnect-session value="ASK" />
<kill-session value="ASK" />
<reload-on-filter-change value="false" />
</session-browser>
<compiler>
<compile-type value="KEEP" />
<compile-dependencies value="ASK" />
<always-show-controls value="false" />
</compiler>
<debugger>
<debugger-type value="JDBC" />
<use-generic-runners value="true" />
</debugger>
</operation-settings>
<ddl-file-settings>
<extensions>
<mapping file-type-id="VIEW" extensions="vw" />
<mapping file-type-id="TRIGGER" extensions="trg" />
<mapping file-type-id="PROCEDURE" extensions="prc" />
<mapping file-type-id="FUNCTION" extensions="fnc" />
<mapping file-type-id="PACKAGE" extensions="pkg" />
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
<mapping file-type-id="TYPE" extensions="tpe" />
<mapping file-type-id="TYPE_SPEC" extensions="tps" />
<mapping file-type-id="TYPE_BODY" extensions="tpb" />
</extensions>
<general>
<lookup-ddl-files value="true" />
<create-ddl-files value="false" />
<synchronize-ddl-files value="true" />
<use-qualified-names value="false" />
<make-scripts-rerunnable value="true" />
</general>
</ddl-file-settings>
<general-settings>
<regional-settings>
<date-format value="MEDIUM" />
<number-format value="UNGROUPED" />
<locale value="SYSTEM_DEFAULT" />
<use-custom-formats value="false" />
</regional-settings>
<environment>
<environment-types>
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
</environment-types>
<visibility-settings>
<connection-tabs value="true" />
<dialog-headers value="true" />
<object-editor-tabs value="true" />
<script-editor-tabs value="false" />
<execution-result-tabs value="true" />
</visibility-settings>
</environment>
</general-settings>
</component>
<component name="DBNavigator.Project.StatementExecutionManager">
<execution-variables />
</component>
</project>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Sprint_server.iml" filepath="$PROJECT_DIR$/.idea/Sprint_server.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

28
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,28 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Django",
"type": "python",
"request": "launch",
"program": ".\\manage.py",
"args": [
"runserver"
],
"django": true
},
{
"name": "Python: Текущий файл",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"args": [
"runserver"
]
}
]
}

243
Main/Tester.py Normal file
View File

@ -0,0 +1,243 @@
from os import listdir, mkdir
from os.path import basename, isdir
from shutil import rmtree, copyfile
from threading import Thread
from xml.dom.minidom import parse
from Main.models import *
from .main import solution_path
def start_new(host):
in_queue = list(Solution.objects.filter(result='IN QUEUE'))
if in_queue:
sol = in_queue[0]
for s in in_queue:
dif = s.task.block.priority * 10 + s.task.priority - sol.task.block.priority * 10 - sol.task.priority
if dif > 0:
sol = s
elif dif == 0 and s.id < sol.id:
sol = s
Tester(sol, host).test()
def is_project(path):
return any([x.endswith('.csproj') for x in listdir(path)])
def get_node_value(element):
return element[0].firstChild.nodeValue
def nunit_path(working_dir):
return '..{}'.format(sep) * len(working_dir.split(sep)) + 'nunit_console{}nunit3-console.exe'.format(sep)
class Tester:
def __init__(self, solution, host):
self.solution = solution
self.host = host
self.working_dir = ''
self.files = []
# функция компиляции
def build(self, path):
# решение для UNIX
# shell('msbuild ' + path + ' /p:Configuration=Debug')
# решение для Windows
cmd = 'dotnet build {} -o {}\\bin\\Debug'.format(path, path)
with self.solution.log_fs as fs:
shell(cmd, fs)
def build_and_copy(self, path, working_dir):
if exists(join(path, 'bin', 'Debug')):
rmtree(join(path, 'bin', 'Debug'))
self.build(path)
name = basename(path)
if not exists(join(path, 'bin', 'Debug')) or not any(
x.endswith('.exe') for x in listdir(join(path, 'bin', 'Debug'))):
return False
self.files.append(basename(path))
for file in listdir(join(path, 'bin', 'Debug')):
if exists(join(path, 'bin', 'Debug', file)):
new_file = join(working_dir, basename(file))
try:
copyfile(join(path, 'bin', 'Debug', file), new_file)
except:
pass
else:
return False
return True
def push(self):
solution = self.solution
if solution.result == 'SOLUTION ERROR':
return
solution.result = 'IN QUEUE'
solution.save()
from Main.models import System
if len(Solution.objects.filter(result='TESTING')) < int(System.objects.get(key='queue_size').value):
self.test()
def delete_everything(self):
ssp = solution_path(self.solution.path())
sln_path = join(ssp, '.idea')
if exists(sln_path):
rmtree(sln_path)
sln_path = join(ssp, '.vs')
if exists(sln_path):
rmtree(sln_path)
sln_path = ssp
for p in listdir(sln_path):
if isdir(join(sln_path, p)):
if exists(join(sln_path, p, 'bin')):
rmtree(join(sln_path, p, 'bin'))
if exists(join(sln_path, p, 'obj')):
rmtree(join(sln_path, p, 'obj'))
if exists(self.working_dir):
rmtree(self.working_dir)
if exists(join(self.solution.path(), 'solution.zip')):
remove(join(self.solution.path(), 'solution.zip'))
if exists(join(self.solution.path(), '__MACOSX')):
rmtree(join(self.solution.path(), '__MACOSX'))
if exists(join(sln_path, '.DS_Store')):
remove(join(sln_path, '.DS_Store'))
if exists(join(sln_path, 'test_folder')):
rmtree(join(sln_path, 'test_folder'))
def nunit_testing(self):
solution = self.solution
with self.solution.log_fs as fs:
fs.write(b'Building image\n')
shell('docker build -t solution_{} {}'.format(self.solution.id, self.working_dir))
with self.solution.log_fs as fs:
fs.write(b'Image built successfully\n')
def execute():
with self.solution.log_fs as fs:
shell('docker run --name solution_container_{} solution_{}'.format(self.solution.id, self.solution.id),
output=fs)
solution.write_log('Running container')
t = Thread(target=execute)
t.start()
t.join(self.solution.task.time_limit / 1000)
solution.write_log('Running finished')
with self.solution.log_fs as fs:
shell('docker cp solution_container_{}:/app/TestResults.xml {}'.format(self.solution.id, self.working_dir),
fs)
with self.solution.log_fs as fs:
shell('docker rm --force solution_container_{}'.format(self.solution.id), fs)
with self.solution.log_fs as fs:
shell('docker image rm solution_{}'.format(self.solution.id), fs)
if not exists(join(self.working_dir, 'TestResults.xml')):
self.solution.set_result('Time limit')
solution.write_log('Result file not found in container')
return
solution.write_log('Result file found in container')
try:
doc = parse(join(self.working_dir, 'TestResults.xml'))
res = get_node_value(doc.getElementsByTagName('Passed')) + '/' + get_node_value(
doc.getElementsByTagName('Total'))
self.solution.details = ''
for el in doc.getElementsByTagName('Result'):
self.solution.details += '<h5><b>' + get_node_value(el.getElementsByTagName('MethodName')) + '</b></h5>'
r = get_node_value(el.getElementsByTagName('Successful'))
if r == 'true':
self.solution.details += '<div style="color: green;">Passed</div>'
else:
self.solution.details += '<div style="color: red;">Failed</div>'
mes = get_node_value(el.getElementsByTagName('Message'))
self.solution.details += '<pre>{}</pre>'.format(mes)
except:
solution.write_log('Unknown error')
res = 'TEST ERROR'
self.solution.set_result(res)
def test(self):
solution = self.solution
solution.result = 'TESTING'
solution.save()
try:
if not exists(self.solution.task.tests_path()):
with self.solution.log_fs as fs:
fs.write(b'No test file found\n')
solution.set_result('TEST ERROR')
solution.save()
self.delete_everything()
start_new(self.host)
return
sln_path = solution_path(join(MEDIA_ROOT, 'solutions', str(solution.id)))
if sln_path == '':
solution.set_result('TEST ERROR')
solution.save()
self.delete_everything()
start_new(self.host)
return
working_dir = join(sln_path, 'test_folder')
if exists(working_dir):
try:
rmtree(working_dir)
except:
remove(working_dir)
mkdir(working_dir)
with self.solution.log_fs as fs:
fs.write(b'Testing directory created\n')
for project in listdir(sln_path):
solution.write_log('Checking if {} is project'.format(project))
prj = project
project = join(sln_path, project)
if isdir(project) and is_project(project) and basename(project) != 'TestsProject':
if not self.build_and_copy(project, working_dir):
solution.set_result('Compilation error')
solution.write_log('Failed to compile project {}'.format(prj))
solution.save()
self.delete_everything()
start_new(self.host)
return
dll_path = solution.task.tests_path()
solution.write_log('Copying test file to working directory')
copyfile(dll_path, join(working_dir, str(solution.task.id) + '.cs'))
solution.write_log('Test file copied')
for file in listdir('SprintTest'):
try:
copyfile(join('SprintTest', file), join(working_dir, file))
except:
pass
self.working_dir = working_dir
build_tests_cmd = 'csc -out:{} -t:library /r:{} /r:{} /r:{} '.format(join(self.working_dir, 'tests.dll'),
join(self.working_dir,
'SprintTest.dll'),
join(working_dir,
'System.Runtime.dll'),
join(working_dir,
'System.Reflection.dll'))
for file in self.files:
build_tests_cmd += '/r:{}.dll '.format(join(self.working_dir, file))
build_tests_cmd += self.solution.task.tests_path()
if exists(join(self.working_dir, 'tests.dll')):
remove(join(self.working_dir, 'tests.dll'))
solution.write_log('Building tests file started')
with self.solution.log_fs as fs:
shell(build_tests_cmd, fs)
with self.solution.log_fs as fs:
fs.write(b'Building tests file finished\n')
if exists(join(self.working_dir, 'tests.dll')):
with self.solution.log_fs as fs:
fs.write(b'Got .dll tests file\n')
for file in ExtraFile.objects.filter(task=self.solution.task):
copyfile(file.path, join(working_dir, file.filename))
self.nunit_testing()
else:
solution.set_result('TEST ERROR')
solution.write_log('Failed to compile tests')
except:
solution.set_result('TEST ERROR')
raise
with self.solution.log_fs as fs:
fs.write(b'Unknown error\n')
solution.save()
self.delete_everything()
start_new(self.host)

29
Main/Timer.py Normal file
View File

@ -0,0 +1,29 @@
class Method:
def __init__(self, meth, name):
self.meth = meth
self.name = name
def __eq__(self, other):
return self.name == other.name
def execute(self):
self.meth()
class Timer:
methods = []
def push(self, meth):
methods.append(math)
def polling(self):
for i in range(len(self.methods)):
methods[i].execute()
def remove(method_name):
for method in self.methods:
if method.name == method_name:
self.methods.remove(method)
return
raise IndexError("No method in list")

0
Main/__init__.py Normal file
View File

14
Main/admin.py Normal file
View File

@ -0,0 +1,14 @@
from django.contrib import admin
from Main.models import *
# Register your models here.
admin.site.register(Course)
admin.site.register(Block)
admin.site.register(Task)
admin.site.register(Subscribe)
admin.site.register(Restore)
admin.site.register(UserInfo)
admin.site.register(Solution)
admin.site.register(System)
admin.site.register(ExtraFile)

5
Main/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class MainConfig(AppConfig):
name = 'Main'

8
Main/commands.py Normal file
View File

@ -0,0 +1,8 @@
from subprocess import Popen
from sys import stdout
def shell(cmd, output=stdout):
p = Popen(cmd, shell=True, stdout=output)
p.wait()
p.kill()

View File

@ -0,0 +1,8 @@
from .main import check_admin
def attributes(request):
return {
'current_page': 'settings' if '/settings' == request.path else 'admin' if '/admin/' in request.path else 'main',
'is_admin': check_admin(request.user)
}

32
Main/forms.py Normal file
View File

@ -0,0 +1,32 @@
from django import forms
class PasswordField(forms.CharField):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.widget = forms.PasswordInput()
class LoginForm(forms.Form):
email = forms.EmailField(widget=forms.TextInput(attrs={"class": "input_simple"}))
password = forms.CharField(widget=forms.PasswordInput(attrs={"class": "input_simple"}))
class FileForm(forms.Form):
file = forms.FileField(widget=forms.FileInput(attrs={'class': 'input_simple'}), required=False)
class TestsForm(forms.Form):
tests = forms.FileField(widget=forms.FileInput(), required=False)
class ChangePasswordForm(forms.Form):
old = PasswordField(label='Старый пароль')
new = PasswordField(label='Новый пароль')
again = PasswordField(label='Еще раз')
class ResetPasswordForm(forms.Form):
new = PasswordField(label='Новый пароль')
again = PasswordField(label='Еще раз')

339
Main/main.py Normal file
View File

@ -0,0 +1,339 @@
import smtplib
from contextlib import contextmanager
from json import dumps
from os import listdir, mkdir
from os.path import isdir, basename, dirname, join, exists
from random import choice
from shutil import copyfile, rmtree
from string import ascii_letters
from threading import Thread
from time import sleep
import copydetect
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.db.transaction import atomic
from django.utils import timezone
from django.utils.datastructures import MultiValueDictKeyError
from Main.models import Course, Block, Solution, ThreadSafe, Restore, System, Subscribe, UserInfo
from Sprint.settings import MEDIA_ROOT
base_dir = 'data'
@contextmanager
def lock(key):
pk = ThreadSafe.objects.get_or_create(key=key)[0].pk
try:
objs = ThreadSafe.objects.filter(pk=pk).select_for_update()
with atomic():
list(objs)
yield None
finally:
pass
def get_in_html_tag(full, tag_name):
try:
return full.split('<div class="{}">'.format(tag_name))[1].split('</div>')[0]
except IndexError:
return ''
def random_string():
letters = ascii_letters
return ''.join(choice(letters) for _ in range(20))
def get_restore_hash():
available = [r.code for r in Restore.objects.all()]
while True:
s = random_string()
if s not in available:
break
return s
def send(subject, to_addr, body_text):
from_addr = System.objects.get(key='email_address').value
body = "\r\n".join((
"From: %s" % from_addr,
"To: %s" % to_addr,
"Subject: %s" % subject,
"",
body_text
))
server = smtplib.SMTP('SMTP.Office365.com', 587)
server.starttls()
server.login(System.objects.get(key='email_address').value, System.objects.get(key='email_password').value)
server.sendmail(from_addr, [to_addr], body)
server.quit()
def send_email(subject, to_addr, body_text):
Thread(target=lambda: send(subject, to_addr, body_text)).start()
def check_login(user):
return user.is_authenticated
def check_admin(user):
if check_teacher(user):
return True
if not check_login(user):
return False
return len(Subscribe.objects.filter(user=user, is_assistant=True)) > 0
def check_teacher(user):
return user.is_staff and check_login(user)
def check_god(user):
return user.is_superuser and check_login(user)
def courses_available(user):
if user.is_superuser:
return Course.objects.all()
else:
return [s.course for s in Subscribe.objects.filter(user=user)]
def blocks_available(user):
courses = courses_available(user)
blocks = {}
is_admin = check_admin(user)
for course in courses:
if is_admin:
blocks[course] = Block.objects.filter(
course=course
)
else:
blocks[course] = Block.objects.filter(
opened=True,
time_start__lte=timezone.now(),
course=course
)
return blocks
def can_send_solution(user, task):
if user.is_superuser:
return True
try:
s = Subscribe.objects.get(course=task.block.course, user=user)
except ObjectDoesNotExist:
return False
if s.is_assistant:
return True
return task.block.time_start <= timezone.now() <= task.block.time_end and task.max_solutions_count > len(Solution.objects.filter(user=user, task=task)) and task.block.opened
def check_permission_block(user, block):
blocks = blocks_available(user)
for course in blocks.keys():
if block in blocks[course]:
return True
return False
def is_integer(x):
try:
int(x)
return True
except ValueError:
return False
def check_admin_on_course(user, course):
if user.is_superuser:
return True
try:
s = Subscribe.objects.get(user=user, course=course)
except ObjectDoesNotExist:
return False
return s.is_assistant or user.is_staff
def comparer(value1, value2):
if value1 < value2:
return 1
elif value1 == value2:
return 0
else:
return -1
def result_comparer(result1, result2):
verdicts = ['IN QUEUE', 'TESTING', 'TEST ERROR', 'SOLUTION ERROR', 'Compilation error', 'Time limit']
if result1 in verdicts and result2 in verdicts:
return comparer(verdicts.index(result1), verdicts.index(result2))
if result1 in verdicts and result2 not in verdicts:
return 1
if result1 not in verdicts and result2 in verdicts:
return -1
return comparer(int(result1.split('/')[0]), int(result2.split('/')[0]))
def solutions_filter(request):
try:
solutions = list(reversed(Solution.objects.filter(task__block_id=request['block_id'])))
except MultiValueDictKeyError as e:
return [Solution.objects.get(id=request['id'])]
if 'solution_id' in request.keys():
solutions = [solution for solution in solutions if any([solution.id == int(i) for i in request['solution_id'].strip().split()])]
if 'task_name' in request.keys():
solutions = [solution for solution in solutions if solution.task.name == request['task_name']]
if 'user' in request.keys():
solutions = [solution for solution in solutions if str(solution.userinfo) == request['user']]
if 'group' in request.keys():
solutions = [solution for solution in solutions if solution.userinfo.group == request['group']]
if 'best_result' in request.keys():
sols = {}
for solution in solutions:
if (solution.user.username, solution.task.id) in sols.keys():
comp = result_comparer(sols[(solution.user.username, solution.task.id)][0].result, solution.result)
if comp == 1:
sols[(solution.user.username, solution.task.id)] = [solution]
elif comp == 0:
sols[(solution.user.username, solution.task.id)].append(solution)
else:
sols[(solution.user.username, solution.task.id)] = [solution]
solutions = []
for sol in sols.values():
for val in sol:
solutions.append(val)
solutions = list(sorted(solutions, key=lambda s: s.id, reverse=True))
if 'last_solution' in request.keys():
visited = []
new_solutions = []
for solution in solutions:
if (solution.user.username, solution.task.id) not in visited:
visited.append((solution.user.username, solution.task.id))
new_solutions.append(solution)
solutions = new_solutions
if 'only_students' in request.keys():
solutions = [solution for solution in solutions if not check_admin_on_course(solution.user, solution.task.block.course)]
if 'not_seen' in request.keys():
solutions = [solution for solution in solutions if solution.mark == None]
return sorted(solutions, key=lambda s: s.id, reverse=True)
def re_test(solutions_request, request):
from .Tester import Tester
for sol in solutions_request:
sol.details = ''
with open(sol.log_file, 'wb') as fs:
fs.write(b'')
sol.save()
Thread(target=lambda: Tester(sol, request.META['HTTP_HOST']).push()).start()
sleep(.1)
def block_solutions_info(block):
all_solutions = Solution.objects.filter(task__block=block)
all_users = [solution.userinfo for solution in all_solutions]
return {
'tasks': sorted(list(set([solution.task for solution in all_solutions])), key=lambda x: x.name),
'users': sorted(list(set(all_users)), key=lambda x: str(x)),
'groups': sorted(list(set([userinfo.group for userinfo in all_users])), key=lambda x: str(x))
}
def delete_folder(path):
flag = True
while flag:
try:
rmtree(dirname(path))
flag = False
except:
pass
def solution_path(path):
files = [x for x in listdir(path) if x.endswith('.sln') and not x.startswith('.')]
if files:
return path
return ''.join([solution_path(join(path, file)) for file in listdir(path) if isdir(join(path, file))])
def register_user(u):
password = random_string()
user = User.objects.create_user(username=u['email'], email=u['email'], password=password)
UserInfo.objects.create(
surname=u['surname'],
name=u['name'],
middle_name=u['middle_name'],
group=u['group'],
user=user
)
send_email('You have been registered in Sprint!', u['email'],
'Your password is: {}\nPlease change it after login in settings!\nhttps://sprint.cshse.ru/'.format(password))
return user
def check_cheating(solutions, block, cheating_percent):
block.cheating_checking = True
block.save()
try:
cheating_data = {}
cheating_path = join(MEDIA_ROOT, 'cheating', str(block.id))
if exists(cheating_path):
rmtree(cheating_path)
mkdir(cheating_path)
for solution in solutions:
for file in solution.user_files.keys():
user_file = join(MEDIA_ROOT, 'solutions', str(solution.id), file)
dest_file = join(cheating_path, '_'.join([str(solution.id), basename(file)]))
copyfile(user_file, dest_file)
files_len = len(solutions)
files = listdir(cheating_path)
for i in range(len(files) - 1):
for j in range(i + 1, len(files)):
file1 = files[i]
file2 = files[j]
s1 = file1.split('_')
s2 = file2.split('_')
sol1 = Solution.objects.get(id=int(s1[0]))
sol2 = Solution.objects.get(id=int(s2[0]))
filename1 = '_'.join(s1[1:])
filename2 = '_'.join(s2[1:])
if sol1.user == sol2.user or sol1.task != sol2.task or filename1 != filename2:
continue
fp1 = copydetect.CodeFingerprint(join(cheating_path, file1), 25, 1)
fp2 = copydetect.CodeFingerprint(join(cheating_path, file2), 25, 1)
token_overlap, similarities, slices = copydetect.compare_files(fp1, fp2)
similarity = (similarities[0] + similarities[1]) / 2
if similarity >= cheating_percent / 100:
if sol1.user.id not in cheating_data.keys():
cheating_data[sol1.user.id] = []
if sol2.user.id not in cheating_data.keys():
cheating_data[sol2.user.id] = []
cheating_data[sol1.user.id].append({
'source': True,
'solution': sol1.id,
'file': filename1,
'similar': sol2.id,
'similarity': round(similarity * 100, 2)
})
cheating_data[sol2.user.id].append({
'source': False,
'solution': sol2.id,
'file': filename2,
'similar': sol1.id,
'similarity': round(similarity * 100, 2)
})
finally:
if exists(cheating_path):
rmtree(cheating_path)
with open(block.cheating_results_path, 'w') as fs:
fs.write(dumps(cheating_data))
block = Block.objects.get(id=block.id)
block.cheating_checking = False
block.save()
print('finished')

View File

@ -0,0 +1,98 @@
# Generated by Django 3.0.2 on 2020-06-25 20:11
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Block',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField()),
('time_start', models.DateTimeField()),
('time_end', models.DateTimeField()),
('opened', models.IntegerField()),
],
),
migrations.CreateModel(
name='Course',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField()),
],
),
migrations.CreateModel(
name='UserInfo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('surname', models.TextField()),
('name', models.TextField()),
('middle_name', models.TextField()),
('group_name', models.TextField()),
('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Task',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField()),
('legend', models.TextField()),
('input', models.TextField()),
('output', models.TextField()),
('specifications', models.TextField()),
('time_limit', models.IntegerField()),
('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Block')),
],
),
migrations.CreateModel(
name='Subscribe',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_assistant', models.IntegerField()),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Course')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Solution',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('result', models.TextField()),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Task')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Restore',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.TextField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Mark',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mark', models.IntegerField()),
('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Block')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='block',
name='course',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Course'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.2 on 2020-06-26 09:46
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('Main', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='userinfo',
old_name='group_name',
new_name='group',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.2 on 2020-06-27 19:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0002_auto_20200626_0946'),
]
operations = [
migrations.AlterField(
model_name='subscribe',
name='is_assistant',
field=models.IntegerField(default=0),
),
]

View File

@ -0,0 +1,38 @@
# Generated by Django 3.0.2 on 2020-06-28 09:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0003_auto_20200627_1959'),
]
operations = [
migrations.AlterField(
model_name='task',
name='input',
field=models.TextField(default=''),
),
migrations.AlterField(
model_name='task',
name='legend',
field=models.TextField(default=''),
),
migrations.AlterField(
model_name='task',
name='output',
field=models.TextField(default=''),
),
migrations.AlterField(
model_name='task',
name='specifications',
field=models.TextField(default=''),
),
migrations.AlterField(
model_name='task',
name='time_limit',
field=models.IntegerField(default=10000),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.2 on 2020-06-28 10:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0004_auto_20200628_0917'),
]
operations = [
migrations.AddField(
model_name='solution',
name='time_sent',
field=models.DateTimeField(null=True),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.0.2 on 2020-06-28 13:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('Main', '0005_solution_time_sent'),
]
operations = [
migrations.AlterField(
model_name='solution',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.UserInfo'),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.0.2 on 2020-06-29 08:33
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('Main', '0006_auto_20200628_1315'),
]
operations = [
migrations.AlterField(
model_name='solution',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.0.2 on 2020-07-02 18:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0007_auto_20200629_0833'),
]
operations = [
migrations.AddField(
model_name='userinfo',
name='mark_notification',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='userinfo',
name='new_block_notification',
field=models.IntegerField(default=0),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.0.2 on 2020-07-04 14:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0008_auto_20200702_2140'),
]
operations = [
migrations.AddField(
model_name='solution',
name='comment',
field=models.TextField(default=''),
),
migrations.AddField(
model_name='solution',
name='mark',
field=models.IntegerField(null=True),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.0.2 on 2020-07-24 10:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0009_auto_20200704_1703'),
]
operations = [
migrations.CreateModel(
name='System',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.TextField()),
('value', models.TextField()),
],
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.0.2 on 2020-08-14 17:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0010_system'),
]
operations = [
migrations.CreateModel(
name='ThreadSafe',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.CharField(max_length=80, unique=True)),
],
),
migrations.DeleteModel(
name='Mark',
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 3.1 on 2020-09-01 08:54
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('Main', '0011_auto_20200814_2035'),
]
operations = [
migrations.AddField(
model_name='task',
name='weight',
field=models.FloatField(default=1.0),
),
migrations.CreateModel(
name='ExtraFile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(upload_to='')),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')),
],
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 3.1 on 2020-09-01 09:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('Main', '0012_auto_20200901_1154'),
]
operations = [
migrations.DeleteModel(
name='ExtraFile',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1 on 2020-09-01 09:12
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('Main', '0013_delete_extrafile'),
]
operations = [
migrations.CreateModel(
name='ExtraFile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(upload_to='')),
('filename', models.TextField()),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')),
],
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1 on 2020-09-02 12:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0014_extrafile'),
]
operations = [
migrations.AddField(
model_name='task',
name='max_mark',
field=models.IntegerField(default=10),
),
migrations.AlterField(
model_name='extrafile',
name='file',
field=models.FileField(upload_to='data\\extra_files'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-09-02 13:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0015_auto_20200902_1555'),
]
operations = [
migrations.AddField(
model_name='task',
name='max_solutions_count',
field=models.IntegerField(default=10),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-09-05 12:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0016_task_max_solutions_count'),
]
operations = [
migrations.AddField(
model_name='solution',
name='details',
field=models.TextField(default=''),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1 on 2020-09-17 08:45
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('Main', '0017_solution_details'),
]
operations = [
migrations.RemoveField(
model_name='extrafile',
name='file',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-09-17 09:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0018_remove_extrafile_file'),
]
operations = [
migrations.AddField(
model_name='task',
name='show_details',
field=models.IntegerField(default=1),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-10-08 08:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0019_task_show_details'),
]
operations = [
migrations.AddField(
model_name='task',
name='solution_type',
field=models.TextField(default='Решение'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1 on 2020-10-08 10:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('Main', '0020_task_solution_type'),
]
operations = [
migrations.RemoveField(
model_name='task',
name='solution_type',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-10-22 14:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0021_remove_task_solution_type'),
]
operations = [
migrations.AddField(
model_name='task',
name='full_solution',
field=models.IntegerField(default=0),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-11-01 19:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0022_task_full_solution'),
]
operations = [
migrations.AddField(
model_name='extrafile',
name='for_compilation',
field=models.IntegerField(default=0),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-11-06 08:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0023_extrafile_for_compilation'),
]
operations = [
migrations.AddField(
model_name='extrafile',
name='sample',
field=models.IntegerField(default=0),
),
]

View File

@ -0,0 +1,51 @@
# Generated by Django 3.1 on 2020-11-06 15:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0024_extrafile_sample'),
]
operations = [
migrations.RemoveField(
model_name='userinfo',
name='mark_notification',
),
migrations.RemoveField(
model_name='userinfo',
name='new_block_notification',
),
migrations.AlterField(
model_name='block',
name='opened',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='extrafile',
name='for_compilation',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='extrafile',
name='sample',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='subscribe',
name='is_assistant',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='task',
name='full_solution',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='task',
name='show_details',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.3 on 2020-12-01 08:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0025_auto_20201106_1848'),
]
operations = [
migrations.AddField(
model_name='block',
name='show_rating',
field=models.BooleanField(default=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.3 on 2020-12-26 13:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0026_block_show_rating'),
]
operations = [
migrations.AddField(
model_name='task',
name='mark_formula',
field=models.TextField(default='None'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.3 on 2021-01-01 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0027_task_mark_formula'),
]
operations = [
migrations.AddField(
model_name='task',
name='show_result',
field=models.BooleanField(default=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1.3 on 2021-01-30 16:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0028_task_show_result'),
]
operations = [
migrations.AddField(
model_name='block',
name='priority',
field=models.IntegerField(default=5),
),
migrations.AddField(
model_name='task',
name='priority',
field=models.IntegerField(default=5),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 3.1.3 on 2021-02-06 21:17
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('Main', '0029_auto_20210130_1950'),
]
operations = [
migrations.CreateModel(
name='Message',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('for_all', models.BooleanField()),
('text', models.TextField()),
('reply_to', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Main.message')),
('sender', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.3 on 2021-03-13 08:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0030_message'),
]
operations = [
migrations.AddField(
model_name='block',
name='cheating_checking',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.3 on 2021-03-14 12:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Main', '0031_block_cheating_checking'),
]
operations = [
migrations.AddField(
model_name='block',
name='cheating_data',
field=models.TextField(default='[]'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1.3 on 2021-03-14 13:15
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('Main', '0032_block_cheating_data'),
]
operations = [
migrations.RemoveField(
model_name='block',
name='cheating_data',
),
]

View File

471
Main/models.py Normal file
View File

@ -0,0 +1,471 @@
from django.contrib.auth.models import User
from django.db import models
from django.dispatch import receiver
from django.db.models.signals import post_delete
from os.path import sep, join, exists
from os import remove
from Main.commands import shell
from Sprint.settings import MEDIA_ROOT
from django.core.exceptions import ObjectDoesNotExist
from json import loads
base_dir = 'data'
class ThreadSafe(models.Model):
key = models.CharField(max_length=80, unique=True)
class Course(models.Model):
name = models.TextField()
@property
def teachers(self):
return [UserInfo.objects.get(user=s.user) for s in Subscribe.objects.filter(user__is_staff=1, course=self)]
@property
def subscribes(self):
return sorted(Subscribe.objects.filter(course=self), key=lambda s: s.user.email)
@property
def students(self):
userinfo = lambda sub: sub.user.userinfo
return sorted(Subscribe.objects.filter(course=self, is_assistant=False, user__is_staff=False), key=lambda s: userinfo(s).surname + userinfo(s).name + userinfo(s).middle_name)
def __str__(self):
return self.name
class Block(models.Model):
name = models.TextField()
course = models.ForeignKey(Course, on_delete=models.CASCADE)
time_start = models.DateTimeField()
time_end = models.DateTimeField()
opened = models.BooleanField(default=False)
show_rating = models.BooleanField(default=True)
priority = models.IntegerField(default=5)
cheating_checking = models.BooleanField(default=False)
@property
def messages(self):
return Message.objects.filter(task__block=self)
def __str__(self):
return self.name
@property
def tasks(self):
return Task.objects.filter(block=self)
@property
def time_start_chrome(self):
return self.time_start.strftime("%Y-%m-%dT%H:%M")
@property
def time_end_chrome(self):
return self.time_end.strftime("%Y-%m-%dT%H:%M")
@property
def is_opened(self):
return 'checked' if self.opened else ''
@property
def solutions(self):
return reversed(Solution.objects.filter(task__block=self))
@property
def subscribed_users(self):
return [UserInfo.objects.get(user=s.user) for s in Subscribe.objects.filter(course=self.course)]
@property
def cheating_results_path(self):
return join(MEDIA_ROOT, 'cheating_results', str(self.id))
@property
def cheating_checked(self):
return self.cheating_results != {}
@property
def cheating_results(self):
return loads(open(self.cheating_results_path, 'r').read()) if exists(self.cheating_results_path) else {}
@property
def cheating_status(self):
if self.cheating_checking:
return 'Идет проверка'
if not exists(self.cheating_results_path):
return 'Еще не проверено'
return 'Проверка завершена'
class Restore(models.Model):
code = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.user.username
class Subscribe(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
is_assistant = models.BooleanField(default=False)
def __str__(self):
return self.user.username + '|' + self.course.name
@property
def userinfo(self):
return UserInfo.objects.get(user=self.user)
@property
def role(self):
if self.user.is_superuser:
return 'Администратор'
if self.user.is_staff:
return 'Преподаватель'
return 'Ассистент' if self.is_assistant else 'Студент'
class Task(models.Model):
name = models.TextField()
block = models.ForeignKey(Block, on_delete=models.CASCADE)
legend = models.TextField(default='')
input = models.TextField(default='')
output = models.TextField(default='')
specifications = models.TextField(default='')
time_limit = models.IntegerField(default=10000)
weight = models.FloatField(default=1.0)
max_mark = models.IntegerField(default=10)
max_solutions_count = models.IntegerField(default=10)
show_result = models.BooleanField(default=True)
show_details = models.BooleanField(default=False)
full_solution = models.BooleanField(default=False)
mark_formula = models.TextField(default='None')
priority = models.IntegerField(default=5)
@property
def students_solutions(self):
students = [sub.user for sub in Subscribe.objects.filter(course=self.block.course)]
solutions = Solution.objects.filter(task=self)
return [sol for sol in solutions if sol.user in students]
@property
def correct_count(self):
solutions = self.students_solutions
count = 0
for sol in solutions:
res = sol.result.split('/')
if len(res) == 2 and res[0] == res[1]:
count += 1
return count
@property
def solutions_count(self):
return len(self.students_solutions)
@property
def partially_passed(self):
solutions = self.students_solutions
count = 0
for sol in solutions:
res = sol.result.split('/')
if len(res) == 2 and res[0] != res[1]:
count += 1
return count
@property
def solutions_with_error(self):
return self.solutions_count - self.correct_count - self.partially_passed
@property
def samples(self):
return [{
'input': file,
'output': file.answer
} for file in ExtraFile.objects.filter(task=self, sample=True).order_by('filename')]
def __hash__(self):
return self.id
@property
def showable(self):
return 'checked' if self.show_details else ''
def __str__(self):
return self.name
def tests_path(self):
return join(base_dir, 'tests', str(self.id) + '.cs')
@property
def tests_text(self):
try:
return open(self.tests_path(), 'r').read()
except FileNotFoundError:
with open(self.tests_path(), 'w') as fs:
pass
return ''
@property
def tests_uploaded(self):
from os.path import exists
return exists(self.tests_path())
@property
def files(self):
return ExtraFile.objects.filter(task=self).order_by('filename')
@property
def files_for_compilation(self):
return ExtraFile.objects.filter(task=self, for_compilation=True)
@property
def is_full_solution(self):
return 'checked' if self.full_solution else ''
class UserInfo(models.Model):
surname = models.TextField()
name = models.TextField()
middle_name = models.TextField()
group = models.TextField()
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
def __eq__(self, obj):
return str(self) == str(obj)
def __hash__(self):
return self.id
def __str__(self):
return "{} {} {}".format(self.surname, self.name, self.middle_name)
class Solution(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
result = models.TextField()
details = models.TextField(default='')
time_sent = models.DateTimeField(null=True)
mark = models.IntegerField(null=True)
comment = models.TextField(default='')
def set_result(self, result):
self.result = result
if len(result.split('/')) != 1:
result = int(result.split('/')[0])
try:
self.mark = eval(self.task.mark_formula)
except:
self.mark = None
self.save()
def __str__(self):
return str(self.id)
def path(self):
return join(base_dir, 'solutions', str(self.id))
def write_log(self, text):
with self.log_fs as fs:
fs.write(bytes(text + '\n', 'cp866'))
@property
def log_file(self):
return join(MEDIA_ROOT, 'logs', str(self.id) + '.log')
@property
def log_text(self):
try:
return open(self.log_file, 'rb').read().decode('cp866')
except FileNotFoundError:
return ''
@property
def log_fs(self):
return open(self.log_file, 'ab')
@property
def userinfo(self):
return UserInfo.objects.get(user=self.user)
@property
def mark_property(self):
return str(self.mark) if self.mark is not None else 'нет оценки'
@property
def mark_select(self):
line = ''
if self.mark:
line += '<option value="нет оценки">нет оценки</option>'
else:
line += '<option value="нет оценки" selected>нет оценки</option>'
for mark in range(self.task.max_mark + 1):
if mark == self.mark:
line += '<option value="{}" selected>{}</option>'.format(mark, mark)
else:
line += '<option value="{}">{}</option>'.format(mark, mark)
return line
@property
def comment_property(self):
return self.comment if self.comment else 'нет комментария'
@staticmethod
def get_files(path):
from os import listdir
from os.path import isfile, join
files_dict = {}
for file in listdir(path):
if file == '__MACOSX' or file == 'test_folder' or file == 'bin' or file == 'obj' or file == '.vs':
continue
current_file = join(path, file)
if isfile(current_file):
if not current_file.endswith('.csproj') and not current_file.endswith('.sln'):
try:
files_dict[sep.join(current_file.split('solutions' + sep)[1].split(sep)[1:])] \
= open(current_file, 'rb').read().decode('UTF-8')
except UnicodeDecodeError:
pass
else:
files_dict = {**files_dict, **Solution.get_files(current_file)}
return files_dict
@property
def files(self):
return Solution.get_files(self.path())
@property
def user_files(self):
f = {}
comp_files = [ef.filename for ef in ExtraFile.objects.filter(task=self.task, for_compilation=True)]
for fi in self.files.keys():
if not fi in comp_files:
f[fi] = self.files[fi]
return f
@property
def passed_all_tests(self):
spl = self.result.split('/')
return len(spl) == 2 and spl[0] == spl[1]
class System(models.Model):
key = models.TextField()
value = models.TextField()
def __str__(self):
return self.key
class ExtraFile(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE)
filename = models.TextField()
for_compilation = models.BooleanField(default=False)
sample = models.BooleanField(default=False)
@property
def answer(self):
try:
return ExtraFile.objects.get(task=self.task, filename=self.filename + '.a')
except ObjectDoesNotExist:
return None
@property
def num(self):
try:
return int(self.filename.split('.')[0])
except ValueError:
return ''
@property
def is_for_compilation(self):
return 'checked' if self.for_compilation else ''
@property
def is_sample(self):
return 'checked' if self.sample else ''
@property
def can_be_sample(self):
try:
int(self.filename)
except:
return False
try:
ans = ExtraFile.objects.get(task=self.task, filename=self.filename + '.a')
except ObjectDoesNotExist:
return False
return self.readable and ans.readable
@property
def path(self):
return join(MEDIA_ROOT, 'extra_files', str(self.id))
@property
def readable(self):
try:
open(self.path, 'rb').read().decode('UTF-8')
return True
except UnicodeDecodeError:
return False
@property
def text(self):
return open(self.path, 'rb').read().decode('UTF-8')
def __str__(self):
return self.filename
def write(self, data):
with open(self.path, 'wb') as fs:
fs.write(data)
class Message(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE)
sender = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
reply_to = models.ForeignKey('Message', on_delete=models.CASCADE, null=True)
for_all = models.BooleanField()
text = models.TextField()
@receiver(post_delete, sender=Task)
def delete_task_hook(sender, instance, using, **kwargs):
if exists(instance.tests_path()):
from os import remove
remove(instance.tests_path())
@receiver(post_delete, sender=Solution)
def delete_solution_hook(sender, instance, using, **kwargs):
if exists(instance.path()):
from shutil import rmtree
rmtree(instance.path())
shell('docker rm --force solution_container_{}'.format(instance.id))
shell('docker image rm solution_{}'.format(instance.id))
@receiver(post_delete, sender=ExtraFile)
def delete_file_hook(sender, instance, using, **kwargs):
try:
if exists(instance.path):
remove(instance.path)
except ValueError:
pass
if instance.filename.endswith('.a'):
try:
t = ExtraFile.objects.get(task=instance.task, filename=instance.filename[:-2])
except ObjectDoesNotExist:
return
t.sample = False
t.save()

View File

@ -0,0 +1,11 @@
.main_div {
margin-left: 5%;
margin-right: 5%;
}
.top-buffer {
margin-bottom:15px;
}
.top-button {
min-width: 100px;
display: inline-block;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

1
Main/static/img/icon.svg Normal file
View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="random" class="svg-inline--fa fa-random fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504.971 359.029c9.373 9.373 9.373 24.569 0 33.941l-80 79.984c-15.01 15.01-40.971 4.49-40.971-16.971V416h-58.785a12.004 12.004 0 0 1-8.773-3.812l-70.556-75.596 53.333-57.143L352 336h32v-39.981c0-21.438 25.943-31.998 40.971-16.971l80 79.981zM12 176h84l52.781 56.551 53.333-57.143-70.556-75.596A11.999 11.999 0 0 0 122.785 96H12c-6.627 0-12 5.373-12 12v56c0 6.627 5.373 12 12 12zm372 0v39.984c0 21.46 25.961 31.98 40.971 16.971l80-79.984c9.373-9.373 9.373-24.569 0-33.941l-80-79.981C409.943 24.021 384 34.582 384 56.019V96h-58.785a12.004 12.004 0 0 0-8.773 3.812L96 336H12c-6.627 0-12 5.373-12 12v56c0 6.627 5.373 12 12 12h110.785c3.326 0 6.503-1.381 8.773-3.812L352 176h32z"></path></svg>

After

Width:  |  Height:  |  Size: 904 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

12
Main/static/js/scripts.js Normal file
View File

@ -0,0 +1,12 @@
function main() {
window.location.href = "/main"
}
function logout() {
window.location.href = "/exit"
}
function settings() {
window.location.href = "/settings"
}
function admin() {
window.location.href = '../..'
}

View File

View File

@ -0,0 +1,112 @@
from django import template
from Main.models import Solution, Task, UserInfo
from django.contrib.auth.models import User
register = template.Library()
@register.filter('mark_for_task')
def mark_for_task(task, user):
try:
return round(list(Solution.objects.filter(task=task, user=user, mark__isnull=False))[-1].mark * 10 / task.max_mark)
except IndexError:
return 0
@register.filter('mark_for_block')
def mark_for_block(block, user):
tasks = Task.objects.filter(block=block)
mark = 0
for task in tasks:
mft = mark_for_task(task, user)
mark += mft * task.weight
return round(mark)
@register.filter('marked')
def marked(mark):
return mark != -1
@register.filter('mark_color')
def mark_color(mark):
mark = round(mark)
if mark > 7:
return '#00FF00'
elif mark > 5:
return '#FFFF00'
elif mark > 3:
return '#FAD7A0'
elif mark > 0:
return '#F1948A'
else:
return '#FFFFFF'
@register.filter('in_dict')
def in_dict(value, dict):
return value in dict.keys()
@register.filter('last_attempts')
def last_attempts(user, task):
return task.max_solutions_count - len(Solution.objects.filter(task=task, user=user))
@register.filter('userinfo_by_user')
def userinfo_by_user(user):
return UserInfo.objects.get(user=user)
@register.filter('mark_status')
def mark_status(user, task):
sols = Solution.objects.filter(user=user, task=task)
if len(sols) == 0:
return '-'
return sols.last().result
@register.filter('fully_marked')
def fully_marked(user, task):
return len(Solution.objects.filter(user=user, task=task, mark=None)) == 0
@register.filter('is_code')
def is_code(path):
return path.endswith('.cs')
@register.filter('num_range')
def num_range(n):
return range(1, n + 1)
@register.filter('length')
def length(collection):
return len(collection)
@register.filter('user_by_id')
def user_by_id(user_id):
return User.objects.get(id=user_id)
@register.filter('dict_key')
def dict_key(d, k):
return d[k]
@register.filter('solution_by_id')
def solution_by_id(solution_id):
return Solution.objects.get(id=solution_id)
@register.filter('solution_file_text')
def solution_file_text(solution, filename):
files = solution.user_files
for key in files.keys():
value = files[key]
if key.endswith(filename):
return value
raise Exception(f'No such file for solution {solution.id} and filename {filename}')

3
Main/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

787
Main/views.py Normal file
View File

@ -0,0 +1,787 @@
from django.core.exceptions import ObjectDoesNotExist
from json import load, dumps, loads
from os import remove, mkdir, listdir, rename
from os.path import sep, join, exists, isfile, dirname
from shutil import rmtree, copytree, make_archive, copyfile
from threading import Thread
from zipfile import ZipFile, BadZipFile
from datetime import datetime, timedelta
from django.contrib.auth import login, authenticate, logout
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render
from django.utils import timezone
from django.utils.timezone import make_aware
from Main.templatetags.filters import *
from Sprint.settings import MEDIA_ROOT
from .Tester import Tester
from .forms import *
from .main import solutions_filter, check_admin_on_course, re_test, check_admin, check_teacher, random_string, \
send_email, check_permission_block, is_integer, check_god, blocks_available, check_login, \
get_restore_hash, block_solutions_info, solution_path, can_send_solution, get_in_html_tag, register_user, \
check_cheating, result_comparer
from .models import Block, Subscribe, Course, Restore, ExtraFile, Message
def download_rating(request):
block = Block.objects.get(id=request.GET['block_id'])
if not check_admin_on_course(request.user, block.course):
return HttpResponseRedirect('/main')
s = 'ФИО,'
tasks = block.tasks
users = [UserInfo.objects.get(user=user.user) for user in Subscribe.objects.filter(course=block.course, is_assistant=False, user__is_staff=False)]
for task in tasks:
try:
s += task.name.split('.')[0] + ','
except IndexError:
s += task.name + ','
s += 'Score,Summ\n'
for user in users:
s += str(user) + ','
for task in tasks:
s += str(mark_for_task(task, user.user)) + ','
s += '0,' + str(mark_for_block(block, user.user)) + '\n'
response = HttpResponse(bytes(s, encoding='utf-8'), content_type='application/force-download')
response['Content-Disposition'] = 'inline; filename=rating.csv'
return response
def download(request):
if not check_admin(request.user) or not check_admin_on_course(request.user, Block.objects.get(id=request.GET['block_id']).course):
return HttpResponseRedirect('/main')
sols = solutions_filter(request.GET)
if len(sols) == 0:
return HttpResponseRedirect('/admin/solutions?block_id=' + request.GET['block_id'])
new_folder = join(MEDIA_ROOT, request.user.username)
if exists(new_folder):
try:
rmtree(new_folder)
except:
remove(new_folder)
mkdir(new_folder)
cur_folder = join(new_folder, 'solutions')
mkdir(cur_folder)
for sol in sols:
uinfo = UserInfo.objects.get(user=sol.user)
folder = join(cur_folder, str(uinfo) + '-' + str(uinfo.user.id))
if not exists(folder):
mkdir(folder)
files = sol.files
files_for_compilation = [f.filename for f in sol.task.files_for_compilation]
for f in files.copy().keys():
if f.split(sep)[-1] in files_for_compilation:
del files[f]
if len(files.keys()) == 1:
dest = join(folder, "-".join([sol.task.name.split('.')[0], str(sol.id), 'dotnet', sol.result.replace('/', 'from')])) + '.cs'
source = join(sol.path(), list(files.keys())[0])
with open(dest, 'wb') as fs:
fs.write(open(source, 'rb').read())
else:
newname = join(folder, '-'.join([sol.task.name.split('.')[0], str(sol.id), 'dotnet', sol.result])).replace('/', 'from')
mkdir(newname)
for f in files.keys():
with open(join(newname, f.split(sep)[-1]), 'wb') as fs:
fs.write(open(join(sol.path(), f), 'rb').read())
make_archive(newname, 'zip', newname)
rmtree(newname)
zip_folder = join(dirname(cur_folder), 'solutions')
make_archive(zip_folder, 'zip', cur_folder)
response = HttpResponse(open(zip_folder + '.zip', 'rb').read(), content_type='application/force-download')
response['Content-Disposition'] = 'inline; filename=solutions.zip'
rmtree(dirname(cur_folder))
return response
def queue(request):
block = Block.objects.get(id=request.GET['block_id'])
if not check_admin_on_course(request.user, block.course):
return HttpResponseRedirect('/main')
return render(request, 'queue.html', {'Block': block})
def docs(request):
if not check_admin(request.user):
return HttpResponseRedirect('/main')
return render(request, "docs.html", context={'is_teacher': check_teacher(request.user)})
def retest(request):
solutions_request = solutions_filter(request.GET)
if not check_admin_on_course(request.user, Block.objects.get(id=request.GET['block_id']).course):
return HttpResponseRedirect('/main')
req = '?block_id=' + str(request.GET['block_id'])
for key in request.GET.keys():
if key != 'block_id':
req += '&{}={}'.format(key, request.GET[key])
Thread(target=lambda: re_test(solutions_request, request)).start()
if 'next' in request.GET.keys():
return HttpResponseRedirect(request.GET['next'])
return HttpResponseRedirect('/admin/solutions%s' % req)
def solution(request):
current_solution = Solution.objects.get(id=request.GET['id'])
if not request.user.is_authenticated:
return HttpResponseRedirect('/main')
if current_solution.user != request.user:
try:
Subscribe.objects.get(user=request.user, is_assistant=True, course=current_solution.task.block.course)
except:
if not request.user.is_superuser:
return HttpResponseRedirect('/main')
can_edit = check_admin_on_course(request.user, current_solution.task.block.course)
if not can_edit:
# тут по хорошему надо использовать регулярки, но я что-то не разобрался
while True:
i = current_solution.details.find('<pre>')
if i == -1:
break
j = current_solution.details.find('</pre>') + 6
current_solution.details = current_solution.details.replace(current_solution.details[i:j], '')
if current_solution.user != request.user:
return HttpResponseRedirect('/main')
solutions_request = solutions_filter(request.GET)
if request.path == '/admin/solution':
from_admin = True
else:
from_admin = False
if request.method == 'POST' and can_edit:
if request.POST['action'] == 'Зачесть':
current_solution.mark = None if request.POST['mark'] == 'нет оценки' else int(request.POST['mark'])
elif request.POST['action'] == 'Незачесть':
current_solution.mark = 0
else:
current_solution.mark = current_solution.task.max_mark
current_solution.comment = request.POST['comment']
current_solution.save()
return render(request, 'solution.html', context={'solution': current_solution,
'from_admin': from_admin,
'can_edit': can_edit,
'path': request.path})
def solutions(request):
current_block = Block.objects.get(id=request.GET['block_id'])
try:
if not request.user.is_superuser:
s = Subscribe.objects.get(user=request.user, course=current_block.course)
if not s.is_assistant and not s.user.is_staff:
return HttpResponseRedirect('/main')
except ObjectDoesNotExist:
return HttpResponseRedirect('/main')
req = ''
sols = solutions_filter(request.GET)
for key in request.GET.keys():
req += '&{}={}'.format(key, request.GET[key])
if request.method == 'POST':
Solution.objects.get(id=request.POST['DELETE_SOLUTION']).delete()
return HttpResponseRedirect('/admin/solutions?block_id={}{}'.format(current_block.id, req))
return render(request, 'solutions.html', context={'Block': current_block,
'filter': ' '.join([str(sol.id) for sol in sols]),
'solutions': sols,
'req': req,
'options': {key: request.GET[key] for key in request.GET.keys()},
'solutions_info': block_solutions_info(current_block)})
def messages(request):
return HttpResponseRedirect('/main')
current_block = Block.objects.get(id=request.GET['block_id'])
return render(request, 'messages.html', context={'Block': current_block})
def users_settings(request):
current_course = Course.objects.get(id=request.GET['course_id'])
if not check_admin_on_course(request.user, current_course):
if not request.user.is_superuser:
return HttpResponseRedirect('/main')
if request.method == 'POST':
if 'input' in request.POST.keys():
line = request.POST['input']
if '@' in line:
users = UserInfo.objects.filter(user__email=line)
elif any(c.isdigit() for c in line):
users = UserInfo.objects.filter(group=line)
else:
try:
s, n, m = line.split(' ')
except ValueError:
s, n, m = '', '', ''
users = list(UserInfo.objects.filter(surname=s, name=n, middle_name=m)) + list(UserInfo.objects.filter(group=line))
for user in users:
try:
Subscribe.objects.get(user=user.user, course=current_course)
except ObjectDoesNotExist:
Subscribe.objects.create(user=user.user, course=current_course)
elif 'user_delete' in request.POST.keys():
username = request.POST['user_delete']
course_id = request.GET['course_id']
Subscribe.objects.get(user__email=username, course_id=course_id).delete()
elif 'file' in request.FILES.keys():
users = load(request.FILES['file'])
for u in users:
password = random_string()
flag = False
try:
user = User.objects.get(email=u['email'])
except ObjectDoesNotExist:
flag = True
if flag:
user = register_user(u)
try:
Subscribe.objects.get(user=user, course=current_course)
except ObjectDoesNotExist:
Subscribe.objects.create(user=user, course=current_course, is_assistant=False)
else:
key = list(request.POST.keys())[1]
role = request.POST[key]
username = '_'.join(key.split('_')[1:])
s = Subscribe.objects.get(user__email=username, course=current_course)
s.is_assistant = role == 'Ассистент'
s.save()
return HttpResponseRedirect('/admin/users_settings?course_id=' + str(current_course.id))
return render(request, 'users_settings.html', context={'course': current_course})
def task(request):
current_task = Task.objects.get(id=request.GET['id'])
user = request.user
if not check_permission_block(user, current_task.block):
return HttpResponseRedirect('/main')
can_send = can_send_solution(user, current_task)
if request.method == 'POST':
if 'message' in request.POST.keys():
Message.objects.create(
task=current_task,
sender=request.user,
reply_to=None,
for_all=False,
text=request.POST['message']
)
return HttpResponseRedirect('/task?id=' + str(current_task.id))
if 'file' in request.FILES.keys() and can_send:
current_solution = Solution.objects.create(
task=current_task,
user=request.user,
result='IN QUEUE',
time_sent=timezone.now()
)
log_file_path = current_solution.log_file
with open(log_file_path, 'wb') as fs:
pass
solution_dir = current_solution.path() + sep
if exists(solution_dir):
rmtree(solution_dir)
mkdir(solution_dir)
with open(solution_dir + 'solution.zip', 'wb') as fs:
for chunk in request.FILES['file'].chunks():
fs.write(chunk)
flag = False
solution_created = False
try:
with ZipFile(solution_dir + 'solution.zip') as obj:
obj.extractall(solution_dir)
except BadZipFile:
rename(solution_dir + 'solution.zip', solution_dir + request.FILES['file'].name)
sln_path = solution_path(solution_dir)
if current_task.full_solution != bool(sln_path):
current_solution.result = 'TEST ERROR'
with open(log_file_path, 'ab') as fs:
fs.write(b'Can\'t find sln file in solution' if current_task.full_solution else b'Sln file in path')
current_solution.save()
return HttpResponseRedirect('/task?id=' + str(current_task.id))
if not bool(sln_path):
copytree('SampleSolution', join(solution_dir, 'Solution'))
for file in listdir(solution_dir):
if file == 'solution.zip' or file == 'Solution':
continue
cur_file = join(solution_dir, file)
if isfile(cur_file):
copyfile(cur_file, join(solution_dir, 'Solution', 'SampleProject', file))
remove(cur_file)
else:
rmtree(cur_file)
if not current_task.full_solution:
for file in current_task.files_for_compilation:
copyfile(file.path, join(solution_dir, 'Solution', 'SampleProject', file.filename))
#Tester(current_solution, request.META['HTTP_HOST']).push()
Thread(target=lambda: Tester(current_solution, request.META['HTTP_HOST']).push()).start()
return HttpResponseRedirect('/task?id=' + str(current_task.id))
return render(request, 'task.html', context={'task': current_task,
'solutions': reversed(Solution.objects.filter(task=current_task, user=user)),
'can_send': can_send,
'can_edit': check_admin_on_course(request.user, current_task.block.course)})
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def task_test(request):
current_task = Task.objects.get(id=request.GET['id'])
user = request.user
if not check_permission_block(user, current_task.block):
return HttpResponseRedirect('/main')
can_send = can_send_solution(user, current_task)
if request.method == 'POST':
if 'file' in request.FILES.keys() and can_send:
current_solution = Solution.objects.create(
task=current_task,
user=request.user,
result='IN QUEUE',
time_sent=timezone.now()
)
log_file_path = current_solution.log_file
with open(log_file_path, 'wb') as fs:
pass
solution_dir = current_solution.path() + sep
if exists(solution_dir):
rmtree(solution_dir)
mkdir(solution_dir)
with open(solution_dir + 'solution.zip', 'wb') as fs:
for chunk in request.FILES['file'].chunks():
fs.write(chunk)
flag = False
solution_created = False
try:
with ZipFile(solution_dir + 'solution.zip') as obj:
obj.extractall(solution_dir)
except BadZipFile:
rename(solution_dir + 'solution.zip', solution_dir + request.FILES['file'].name)
sln_path = solution_path(solution_dir)
if current_task.full_solution != bool(sln_path):
current_solution.result = 'TEST ERROR'
with open(log_file_path, 'ab') as fs:
fs.write(b'Can\'t find sln file in solution' if current_task.full_solution else b'Sln file in path')
current_solution.save()
return HttpResponseRedirect('/task?id=' + str(current_task.id))
if not bool(sln_path):
copytree('SampleSolution', join(solution_dir, 'Solution'))
for file in listdir(solution_dir):
if file == 'solution.zip' or file == 'Solution':
continue
cur_file = join(solution_dir, file)
if isfile(cur_file):
copyfile(cur_file, join(solution_dir, 'Solution', 'SampleProject', file))
remove(cur_file)
else:
rmtree(cur_file)
if not current_task.full_solution:
for file in current_task.files_for_compilation:
copyfile(file.path, join(solution_dir, 'Solution', 'SampleProject', file.filename))
#Tester(current_solution, request.META['HTTP_HOST']).push()
Thread(target=lambda: Tester(current_solution, request.META['HTTP_HOST']).push()).start()
return HttpResponseRedirect('/task?id=' + str(current_task.id))
def block(request):
current_block = Block.objects.get(id=request.GET['id'])
if not check_permission_block(request.user, current_block):
return HttpResponseRedirect('/main')
return render(request, 'block.html', context={'Block': current_block,
'is_admin': check_admin(request.user),
'can_edit': check_admin_on_course(request.user, current_block.course),
'user': request.user})
def task_settings(request):
if not check_admin(request.user):
return HttpResponseRedirect('/main')
current_task = Task.objects.get(id=request.GET['id'])
if request.method == 'POST':
action = request.POST['ACTION']
if action == 'DELETE':
t = Task.objects.get(id=request.GET['id'])
block_id = t.block.id
t.delete()
return HttpResponseRedirect('/admin/block?id=' + str(block_id))
elif action.startswith('SAVE_EXTRA_FILE_'):
i = action.split('_')[-1]
ef = ExtraFile.objects.get(id=int(i))
with open(ef.path, 'wb') as fs:
file_text = request.POST['extra_file_text_' + i]
fs.write(bytes(file_text, encoding='utf-8'))
ef.for_compilation = '{}_for_compilation'.format(ef.id) in request.POST.keys()
ef.save()
elif action == 'SAVE':
current_task.legend, current_task.input, current_task.output, current_task.specifications = \
request.POST['legend'], request.POST['input'], request.POST['output'], request.POST['specifications']
current_task.time_limit = int(request.POST['time_limit']) if is_integer(request.POST['time_limit']) else 10000
current_task.show_details = 'show_details' in request.POST.keys()
current_task.full_solution = 'full_solution' in request.POST.keys()
current_task.mark_formula = request.POST['mark_formula']
current_task.show_result = 'show_result' in request.POST.keys()
current_task.priority = request.POST['priority']
for ef in ExtraFile.objects.filter(task=current_task):
ef.sample = 'sample_' + str(ef.id) in request.POST.keys()
ef.save()
try:
current_task.weight = float(request.POST['weight'].replace(',', '.'))
except ValueError:
current_task.weight = 1.0
try:
current_task.max_mark = int(request.POST['max_mark'])
if current_task.max_mark == 0:
raise ValueError
except ValueError:
current_task.max_mark = 10
try:
current_task.max_solutions_count = int(request.POST['max_solutions_count'])
except ValueError:
current_task.max_solutions_count = 10
elif action == 'UPLOAD_EXTRA_FILE':
if request.FILES['file'].name.endswith('.zip'):
try:
wdir = join(MEDIA_ROOT, 'extra_files', 'files' + str(current_task.id))
if exists(wdir):
rmtree(wdir)
mkdir(wdir)
with open(join(wdir, 'file.zip'), 'wb') as fs:
for chunk in request.FILES['file'].chunks():
fs.write(chunk)
with ZipFile(join(wdir, 'file.zip')) as obj:
obj.extractall(wdir)
remove(join(wdir, 'file.zip'))
for file in listdir(wdir):
if isfile(join(wdir, file)):
try:
ef = ExtraFile.objects.get(filename=file, task=current_task)
except ObjectDoesNotExist:
ef = ExtraFile.objects.create(filename=file, task=current_task)
ef.write(open(join(wdir, file), 'rb').read())
rmtree(wdir)
except BadZipFile:
pass
else:
try:
ef = ExtraFile.objects.get(filename=request.FILES['file'].name, task=current_task)
except ObjectDoesNotExist:
ef = ExtraFile.objects.create(filename=request.FILES['file'].name, task=current_task)
with open(ef.path, 'wb') as fs:
for chunk in request.FILES['file'].chunks():
fs.write(chunk)
elif action == 'CREATE_EXTRA_FILE':
try:
ExtraFile.objects.get(task=current_task, filename=request.POST['newfile_name'])
except ObjectDoesNotExist:
ef = ExtraFile.objects.create(task=current_task, filename=request.POST['newfile_name'])
f = open(join(MEDIA_ROOT, 'extra_files', str(ef.id)), 'w')
f.close()
elif action.startswith('DELETE_FILE_'):
ExtraFile.objects.get(id=int(action.split('_')[-1])).delete()
elif action == 'SAVE_TESTS':
tt = request.POST['tests_text']
cs_file = current_task.tests_path()
with open(cs_file, 'wb') as fs:
fs.write(bytes(tt, encoding='utf-8'))
else:
raise NotImplementedError()
current_task.save()
return HttpResponseRedirect('/admin/task?id=' + str(current_task.id))
return render(request, 'task_settings.html', context={'task': current_task,
'tests': TestsForm(),
'is_superuser': check_teacher(request.user)})
def block_settings(request):
if not check_admin(request.user):
return HttpResponseRedirect('/main')
current_block = Block.objects.get(id=request.GET['id'])
if not check_permission_block(request.user, current_block):
return HttpResponseRedirect('/main')
if request.method == 'POST':
if 'name' in request.POST.keys():
current_block = Block.objects.get(id=request.POST['block_id'])
if not check_teacher(request.user) or not check_permission_block(request.user, current_block):
return HttpResponseRedirect('/main')
task_name = request.POST['name']
current_task = Task.objects.create(
name=task_name,
block=current_block
)
with open(current_task.tests_path(), 'w') as fs:
pass
return HttpResponseRedirect('/admin/task?id=' + str(current_task.id))
if 'file' in request.FILES.keys():
if exists(request.user.username):
rmtree(request.user.username)
mkdir(request.user.username)
with open(join(request.user.username, 'task.zip'), 'wb') as fs:
for chunk in request.FILES['file'].chunks():
fs.write(chunk)
try:
with ZipFile(join(request.user.username, 'task.zip')) as obj:
obj.extractall(request.user.username)
except BadZipFile:
rmtree(request.user.username)
return HttpResponseRedirect('/admin/block?id={}'.format(current_block.id))
task = Task.objects.create(name='Новый таск', block=current_block)
root = request.user.username
if exists(join(root, 'meta.json')):
data = loads(open(join(root, 'meta.json'), 'r', encoding='utf-8').read())
task.name = data['localizedNames']['ru']
task.time_limit = data['invocationLimits']['idlenessLimitMillis']
for f in sorted(listdir(join(root, 'tests'))):
e = ExtraFile.objects.create(
task=task,
filename=f,
for_compilation=False
)
try:
e.sample=data['testSets'][str(int(f.split('.')[0]))]['example'] and not f.endswith('.a')
except KeyError:
e.sample = False
e.save()
copyfile(join(root, 'tests', f), join(MEDIA_ROOT, 'extra_files', str(e.id)))
statements = open(join(root, 'statements', 'ru', 'html', 'statement.html'), 'r', encoding='utf-8').read()
task.legend = get_in_html_tag(statements, 'legend')
task.input = get_in_html_tag(statements, 'input-specification')
task.output = get_in_html_tag(statements, 'output-specification')
task.specifications = get_in_html_tag(statements, 'notes')
task.save()
with open(join(MEDIA_ROOT, 'tests', str(task.id) + '.cs'), 'w') as fs:
pass
rmtree(root)
return HttpResponseRedirect('/admin/task?id={}'.format(task.id))
if 'block_delete' in request.POST.keys():
Block.objects.get(id=request.POST['block_delete']).delete()
return HttpResponseRedirect('/admin/main')
else:
time_start = make_aware(datetime.strptime(request.POST['time_start'], "%Y-%m-%dT%H:%M") + timedelta(hours=3))
time_end = make_aware(datetime.strptime(request.POST['time_end'], "%Y-%m-%dT%H:%M") + timedelta(hours=3))
current_block.opened = 'opened' in request.POST.keys()
current_block.time_start = time_start
current_block.time_end = time_end
current_block.show_rating = "rating" in request.POST.keys()
current_block.priority = request.POST['priority']
current_block.save()
return HttpResponseRedirect('/admin/block?id={}'.format(current_block.id))
return render(request, 'block_settings.html', context={'is_superuser': check_teacher(request.user),
'Block': current_block})
def solutions_table(request):
current_task = Task.objects.get(id=request.GET['id'])
user = request.user
if not check_permission_block(user, current_task.block):
return HttpResponse("done")
sols = Solution.objects.filter(task=current_task, user=user)
can_edit = check_admin_on_course(request.user, current_task.block.course)
# тут по хорошему надо использовать регулярки, но я что-то не разобрался
if not can_edit:
for sol in sols:
while True:
i = sol.details.find('<pre>')
if i == -1:
break
j = sol.details.find('</pre>') + 6
sol.details = sol.details.replace(sol.details[i:j], '')
if any(sol.result == 'TESTING' or sol.result == 'IN QUEUE' for sol in sols) or 'render' in request.GET.keys():
return render(request, 'solutions_table.html', context={
'solutions': reversed(sols),
'can_edit': can_edit,
'task': current_task})
return HttpResponse('done')
def queue_table(request):
block = Block.objects.get(id=request.GET['block_id'])
if not check_admin_on_course(request.user, block):
return HttpResponse('get away from here')
sols = list(Solution.objects.filter(task__block=block, result='TESTING')) + list(Solution.objects.filter(task__block=block, result='IN QUEUE'))
return render(request, 'queue_table.html', {'solutions': sorted(sols, key=lambda x: -x.task.block.priority * 10 - x.task.priority)})
def get_result_data(request):
solution = Solution.objects.get(id=request.GET['id'])
if not check_admin_on_course(request.user, solution.task.block.course):
return HttpResponse(dumps({'success': False}))
return HttpResponse(dumps({
'success': True,
'results_text': solution.details,
'tests_text': solution.task.tests_text,
'log_text': solution.log_text
}))
def get_comment_data(request):
solution = Solution.objects.get(id=request.GET['id'])
if not check_admin_on_course(request.user, solution.task.block.course):
return HttpResponse(dumps({'success': False}))
return HttpResponse(dumps({
'success': True,
'comment_text': solution.comment
}))
def rating(request):
current_block = Block.objects.get(id=request.GET['block_id'])
if not check_admin_on_course(request.user, current_block.course) and not current_block.show_rating:
return HttpResponseRedirect('/main')
return render(request, 'rating.html', context={'Block': Block.objects.get(id=request.GET['block_id']), 'admin_course': check_admin_on_course(request.user, current_block.course)})
def cheating(request):
current_block = Block.objects.get(id=request.GET['block_id'])
if not check_admin_on_course(request.user, current_block.course):
return HttpResponseRedirect('/main')
if request.method == 'POST':
if not current_block.cheating_checking:
req = ['_'.join(elem.split('_')[1:]) for elem in request.POST.keys() if elem.startswith('check_')]
tasks = Task.objects.filter(id__in=[int(elem.split('_')[1]) for elem in req if elem.startswith('task')])
users = User.objects.filter(id__in=[int(elem.split('_')[1]) for elem in req if elem.startswith('user')])
solutions = Solution.objects.filter(user__in=users).filter(task__in=tasks)
if 'all_tests' in request.POST.keys():
solutions = [sol for sol in solutions if sol.passed_all_tests]
if 'best_result' in request.POST.keys():
sols = {}
for solution in solutions:
if (solution.user.username, solution.task.id) in sols.keys():
comp = result_comparer(sols[(solution.user.username, solution.task.id)][0].result, solution.result)
if comp == 1:
sols[(solution.user.username, solution.task.id)] = [solution]
elif comp == 0:
sols[(solution.user.username, solution.task.id)].append(solution)
else:
sols[(solution.user.username, solution.task.id)] = [solution]
solutions = [val for sol in sols.values() for val in sol]
solutions = list(sorted(solutions, key=lambda s: s.id))
if 'last_solution' in request.POST.keys():
sols = {}
for sol in solutions:
pair = sol.user, sol.task
if pair not in sols.keys():
sols[pair] = []
sols[pair].append(sol)
solutions = [sols[key][len(sols[key]) - 1] for key in sols.keys()]
Thread(target=check_cheating, args=(solutions, current_block, int(request.POST['cheating_percent']))).start()
return HttpResponseRedirect('/admin/cheating?block_id=' + str(current_block.id))
return render(request, 'cheating.html', {'Block': current_block})
def admin(request):
if not check_admin(request.user):
return HttpResponseRedirect('/main')
if request.method == 'POST':
if 'invite' in request.POST.keys():
register_user(request.POST)
return HttpResponseRedirect('/admin/main')
name = request.POST['name']
course = Course.objects.get(id=request.POST['course_id'])
current_block = Block.objects.create(name=name,
course=course,
opened=False,
time_start=timezone.now(),
time_end=timezone.now())
if not check_teacher(request.user):
return HttpResponseRedirect('/main')
try:
Subscribe.objects.get(user=request.user, course=course)
except ObjectDoesNotExist:
if not request.user.is_superuser:
return HttpResponseRedirect('/main')
return HttpResponseRedirect('/admin/block?id=' + str(current_block.id))
return render(request, "admin.html", context={"blocks": blocks_available(request.user),
'is_superuser': check_god(request.user),
'is_teacher': check_teacher(request.user)})
def reset_password(request):
code = request.GET['code']
try:
res = Restore.objects.get(code=code)
except ObjectDoesNotExist:
return HttpResponseRedirect('/enter')
context = {'form': ResetPasswordForm()}
if request.method == 'GET':
return render(request, 'reset_password.html', context=context)
else:
if request.POST['new'] != request.POST['again']:
context['error'] = 'Пароли не совпадают'
return render(request, 'reset_password.html', context=context)
else:
res.user.set_password(request.POST['new'])
res.user.save()
res.delete()
return HttpResponseRedirect('/enter')
def settings(request):
if not check_login(request.user):
return HttpResponseRedirect('/enter')
context = {'is_admin': check_admin(request.user), 'form': ChangePasswordForm()}
if request.method == 'POST':
old = request.POST['old']
new = request.POST['new'].strip()
again = request.POST['again'].strip()
username = request.user.username
user = request.user
if user is None:
context['error'] = 'Неверный пароль'
if len(new) < 8 or not any([a.isdigit() for a in new]) or new.lower() == new:
context['error'] = 'Пароль слишком слабый'
elif new != again:
context['error'] = 'Пароли не совпадают'
elif new == '' or new.replace(' ', '') == '':
context['error'] = 'Некорректный пароль'
else:
user.set_password(new)
user.save()
context['error'] = 'Пароль успешно изменен'
login(request, user)
return render(request, 'settings.html', context=context)
def exit(request):
logout(request)
return HttpResponseRedirect('/enter')
def redirect(request):
return HttpResponseRedirect('/main')
def main(request):
if not check_login(request.user):
return HttpResponseRedirect('/enter')
return render(request, 'main.html', context={'blocks': blocks_available(request.user)})
def restore(request):
if check_login(request.user):
return HttpResponseRedirect('/main')
elif request.method == 'GET':
return render(request, 'restore.html')
else:
email = request.POST['email']
try:
user = User.objects.get(email=email)
except ObjectDoesNotExist:
return HttpResponseRedirect('/enter')
h = get_restore_hash()
try:
r = Restore.objects.get(user__email=email)
r.code = h
r.save()
except ObjectDoesNotExist:
Restore.objects.create(user=user, code=h)
send_email('Reset password',
email,
'Restore your password using this link:\nhttp://{}/reset_password?code={}'
.format(request.META['HTTP_HOST'], h))
return HttpResponseRedirect('/enter')
def enter(request):
if check_login(request.user):
return HttpResponseRedirect('/main')
if request.method == 'POST':
user = authenticate(username=request.POST['email'].strip(), password=request.POST['password'].strip())
if user is not None:
login(request, user)
return HttpResponseRedirect('/enter')
else:
return render(request, "enter.html", context={"form": LoginForm()})

0
Sprint/__init__.py Normal file
View File

16
Sprint/asgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
ASGI config for Sprint project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Sprint.settings')
application = get_asgi_application()

153
Sprint/settings.py Normal file
View File

@ -0,0 +1,153 @@
"""
Django settings for Sprint project.
Generated by 'django-admin startproject' using Django 3.0.7.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '-w#*mn6*fa8a=(-c0@klx&$vl%hpiy&l(u*3%0a#2)wdt##(z2'
DEPLOY = False
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = not DEPLOY
SECURE_SSL_REDIRECT = DEPLOY
ALLOWED_HOSTS = [
'*'
]
# Application definition
INSTALLED_APPS = [
'grappelli',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'Main.apps.MainConfig'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
]
ROOT_URLCONF = 'Sprint.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'Main.context_processors.attributes'
],
},
},
]
WSGI_APPLICATION = 'Sprint.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
_ = lambda s: s
LANGUAGES = (
('en', _('English')),
('ru', _('Russian'))
)
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Moscow'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static_root")
MEDIA_ROOT = os.path.join(BASE_DIR, 'data')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "Main/static"),
]
# Authentication backends
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)

38
Sprint/urls.py Normal file
View File

@ -0,0 +1,38 @@
from django.contrib import admin
from django.urls import path, re_path, include
from Main import views
urlpatterns = [
path('grappelli/', include('grappelli.urls')), # grappelli URLS
path('main', views.main),
path('settings', views.settings),
path('enter', views.enter, name='enter'),
path('restore', views.restore, name='restore'),
path('reset_password', views.reset_password),
path('exit', views.exit),
path('block', views.block),
path('task', views.task),
path('solution', views.solution),
path('rating', views.rating),
path('messages', views.messages),
path('admin/rating', views.rating),
path('admin/download_rating', views.download_rating),
path('admin/solution', views.solution),
path('admin/retest', views.retest),
path('admin/docs', views.docs),
path('admin/block', views.block_settings),
path('admin/task', views.task_settings),
path('admin/main', views.admin),
path('admin/solutions', views.solutions),
path('admin/users_settings', views.users_settings),
path('admin/download', views.download),
path('admin/queue', views.queue),
path('admin/cheating', views.cheating),
path('queue_table', views.queue_table),
path('task_test', views.task_test),
path('solutions_table', views.solutions_table),
path('get_result_data', views.get_result_data),
path('get_comment_data', views.get_comment_data),
path('admin/', admin.site.urls),
re_path('^', views.redirect)
]

16
Sprint/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for Sprint project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Sprint.settings')
application = get_wsgi_application()

21
manage.py Normal file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Sprint.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

90
templates/admin.html Normal file
View File

@ -0,0 +1,90 @@
{% extends 'base.html' %}
{% block title %}Админка{% endblock %}
{% block styles %}
.form {
margin-bottom: 15px;
}
{% endblock %}
{% block scripts %}
function change() {
let surname = document.getElementById('surname').value;
let name = document.getElementById('name').value;
let middle_name = document.getElementById('middle_name').value;
let group = document.getElementById('group').value;
let email = document.getElementById('email').value;
let invite = document.getElementById('inviteButton');
console.log(surname + ' ' + name + ' ' + middle_name + ' ' + group + ' ' + email);
let dis = surname == '' || name == '' || middle_name == '' || group == '' || email == '' || email.indexOf('@') == -1 || email.indexOf('.') == -1 || email.indexOf('.') < email.indexOf('@');
console.log(dis);
invite.disabled = dis;
}
{% endblock %}
{% block content %}
<h2>Доступные курсы</h2>
{% for key, value in blocks.items %}
<h5>{{ key.name }}</h5>
{% for block in value %}
<a href="/admin/block?id={{ block.id }}">{{ block.name }}</a><br>
{% endfor %}
{% if is_teacher %}
<button type="button" class="btn btn-dark" data-toggle="modal" data-target="#example_{{ key.id }}" style="margin-top: 20px;">
<i class="fa fa-plus-circle"></i> Новый блок
</button>
<!-- Modal -->
<div class="modal fade" id="example_{{ key.id }}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLongTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<form method="POST">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">Новый блок в курсе {{ key.name }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-12">
{% csrf_token %}
<input type="text" name="name" placeholder="Имя блока">
<input type="hidden" name="course_id" value="{{ key.id }}">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-times-circle"></i> Закрыть</button>
<button type="submit" class="btn btn-success"><i class="fa fa-plus-circle"></i> Создать</button>
</div>
</div>
</form>
</div>
</div>
<button class="btn btn-dark" onclick="window.location.href = '/admin/users_settings?course_id={{ key.id }}'" style="margin-top: 20px;"><i class="fa fa-users"></i> Участники курса</button>
{% endif %}
{% endfor %}
{% if is_superuser %}
<h3 style="margin-top: 30px;">Пригласить пользователя</h3>
<form method="POST">
{% csrf_token %}
<input name="surname" placeholder="Фамилия" class="form" id="surname" onchange="change();"><br>
<input name="name" placeholder="Имя" class="form" id="name" onchange="change();"><br>
<input name="middle_name" placeholder="Отчество" class="form" id="middle_name" onchange="change();"><br>
<input name="group" placeholder="Группа" class="form" id="group" onchange="change();"><br>
<input name="email" placeholder="Почта" class="form" id="email" onchange="change();"><br>
<button type="submit" id='inviteButton' name="invite" value="Пригласить" class="btn btn-dark" disabled>Пригласить</button>
</form>
<div>
<center>
<button target="_blank" rel="noopener noreferrer" type="submit" class="btn btn-dark" onclick="window.location.href = '/admin/'" value="Django admin"><i class="fa fa-shield"></i> Django admin</button>
</center>
</div>
{% endif %}
<center>
<button class="btn btn-dark" style="margin-top: 20px;" onclick="window.location.href='/admin/docs'"><i class="fa fa-file"></i> Методичка</button>
</center>
{% endblock %}

62
templates/base.html Normal file
View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
{% load static %}
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>
Sprint | {% block title %}{% endblock %}
</title>
{% block links %}
{% endblock %}
<link rel="shortcut icon" href="{% static "img/icon.svg" %}" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href={% static "css/styles.css" %}>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://use.fontawesome.com/49b98aaeb5.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script type="text/javascript" id="MathJax-script" async
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js">
</script>
<script type="text/javascript" src={% static "js/scripts.js" %}></script>
<style type="text/css">
{% block styles %}{% endblock %}
</style>
<script type="text/javascript">
{% block scripts %}{% endblock %}
</script>
</head>
<body onload="{% block onload %}{% endblock %}">
<div class="main_div">
<header class="mt-2 mb-2" role="banner">
<div class="row no-gutters top-buffer">
<div class="col" style="min-width: 100px;">
<button type="button" class="btn {% if current_page == 'main' %}btn-dark{% else %}btn-light{% endif %} btn-block top-button" onclick="main()"><i class="fa fa-random"></i> Sprint</button>
</div>
{% if is_admin %}
<div class="col" style="min-width: 100px;">
<button type="button" class="btn {% if current_page == 'admin' %}btn-dark{% else %}btn-light{% endif %} btn-block top-button" onclick="admin()"><i class="fa fa-user"></i> Админка</button>
</div>
{% endif %}
<div class="col" style="min-width: 100px;">
<button type="button" class="btn {% if current_page == 'settings' %}btn-dark{% else %}btn-light{% endif %} btn-block top-button" onclick="settings()"><i class="fa fa-cog"></i> Настройки</button>
</div>
<div class="col-{% if is_admin %}8{% else %}9{% endif %}">
</div>
<div class="col" style="min-width: 100px;">
<button type="button" style="max-width: 200px;" class="btn btn-light btn-block top-button" onclick="logout()"><i class="fa fa-sign-out"></i> Выход</button>
</div>
</div>
</header>
{% block content %}{% endblock %}
</div>
</body>
</html>

39
templates/block.html Normal file
View File

@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% load filters %}
{% block title %}{{ Block.name }}{% endblock %}
{% block content %}
<h3>{{ Block.name }} {% if can_edit %}<a style="color: black;" href="/admin/block?id={{ Block.id }}"><i class="fa fa-pencil"></i></a> {% endif %}</h3>
<h5>Таски</h5>
<table>
{% for task in Block.tasks %}
<tr>
<td>
<a href="/task?id={{ task.id }}">{{ task.name }}</a>
</td>
<td>
{% with mark=task|mark_for_task:user %}
{% if mark|marked %}
<div style="margin-left: 20px; border: 1px solid black; background: {{ mark|mark_color }}; width: 25px; text-align: center;">
{{ mark }}
</div>
{% endif %}
{% endwith %}
</td>
</tr>
{% endfor %}
</table>
{% if Block.show_rating %}
<a href="/rating?block_id={{ Block.id }}" class="btn btn-dark" style="margin-top: 15px;"><i class="fa fa-star"></i> Рейтинг</a>
{% endif %}
<a href="/messages?block_id={{ Block.id }}" class="btn btn-dark" style="margin-top: 15px;"><i class="fa fa-comments"></i> Сообщения</a>
<hr>
{% if Block.opened %}
Открыто для просмотра
{% else %}
Закрыто для просмотра
{% endif %}
<br>Доступно с <b>{{ Block.time_start }}</b> до <b>{{ Block.time_end }}</b>
{% endblock %}

View File

@ -0,0 +1,126 @@
{% extends 'base.html' %}
{% load filters %}
{% block title %}{{ Block.name }}|настройки{% endblock %}
{% block styles %}
input[type="file"] {
display: none;
}
{% endblock %}
{% block scripts %}
function uploaded() {
document.getElementById('is_uploaded').style.display = 'block';
document.getElementById('is_uploaded').nodeValue = document.getElementById('file-upload').nodeValue;
}
{% endblock %}
{% block content %}
<h3>{{ Block.name }} <a style="color: black;" href="/block?id={{ Block.id }}"><i class="fa fa-eye"></i></a></h3>
<h5>Таски</h5>
{% for task in Block.tasks %}
<a href="/admin/task?id={{ task.id }}">{{ task.name }}</a><br>
{% endfor %}
{% if is_superuser %}
<button type="button" class="btn btn-dark" data-toggle="modal" data-target="#example" style="margin-top: 20px;">
<i class="fa fa-plus-circle"></i> Новый таск
</button>
<!-- Modal -->
<div class="modal fade" id="example" tabindex="-1" role="dialog" aria-labelledby="exampleModalLongTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<form method="POST">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">Новый таск в блоке {{ Block.name }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-12">
{% csrf_token %}
<input type="text" name="name" placeholder="Имя таска">
<input type="hidden" name="block_id" value="{{ Block.id }}">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-times-circle"></i> Закрыть</button>
<button type="submit" class="btn btn-success"><i class="fa fa-plus-circle"></i> Создать</button>
</div>
</div>
</form>
</div>
</div>
<hr>
<h5>Импортировать задачу из Я.Контест</h5>
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<label for="file-upload" class="btn btn-dark" style="margin-top: 20px;">
<i class="fa fa-upload"></i> Загрузить архив
</label>
<span id="is_uploaded" style="display: none;">Архив загружен</span>
<input type="file" class="btn form-control-file" id="file-upload" value="Выбрать файл" name="file" onchange="uploaded()">
<br><button type="submit" value="Импортировать" class="btn btn-outline-dark"><i class="fa fa-play-circle"></i> Импортировать</button>
</form>
{% endif %}
<hr>
{% if is_superuser %}
<h3>Ограничения по времени</h3>
<form method="POST">
{% csrf_token %}
<table>
<tr>
<td>
<input type="datetime-local" name="time_start" value="{{ Block.time_start_chrome }}">
</td>
<td>
<input type="datetime-local" name="time_end" value="{{ Block.time_end_chrome }}">
</td>
</tr>
<tr>
<td>
Открыто для просмотра<input type="checkbox" name="opened" style="margin-left:15px;" {{ Block.is_opened }}>
</td>
</tr>
<tr>
<td>
Показывать рейтинг участникам<input type="checkbox" name="rating" style="margin-left: 15px;" {% if Block.show_rating %}checked{% endif %}>
</td>
</tr>
<tr>
<td>
Приоритет <select name="priority">
{% for i in 10|num_range %}
<option {% if i == Block.priority %}selected{% endif %}>{{ i }}</option>
{% endfor %}
</select>
</td>
</tr>
</table>
<button class="btn btn-dark" value="Установить" type="submit" style="margin-top: 20px;"><i class="fa fa-save"></i> Установить</button>
</form>
<hr>
{% endif %}
<div>
<a class="btn btn-dark" href="/admin/rating?block_id={{ Block.id }}"><i class="fa fa-star"></i> Рейтинг</a>
<button type="button" class="btn btn-dark" onclick="window.location.href = '/admin/solutions?block_id=' + {{ Block.id }}" value="Посмотреть решения"><i class="fa fa-list-ul"></i> Посмотреть решения</button>
<a class="btn btn-dark" href="/admin/queue?block_id={{ Block.id }}"><i class="fa fa-align-left"></i> Очередь тестирования</a>
<a class="btn btn-dark" href="/admin/cheating?block_id={{ Block.id }}"><i class="fa fa-bomb"></i> Проверка на списывание</a>
{% if is_superuser %}
<form method="POST" onsubmit="return confirm('Сейчас ты пытаешься сделать то, что может привести к серьезным последствиям. Если ты удалишь этот блок, то вместе с ним удалятся все таски, условия, тесты, дополнительные файлы и решения, сдаваемые в этот блок. Все данные будут безвозвратно утеряны. Оно нам надо?');">
{% csrf_token %}
<input type="hidden" name="block_delete" value="{{ Block.id }}">
<button type="submit" value="Удалить блок" class="btn btn-dark" style="margin-top: 10px;"><i class="fa fa-trash"></i> Удалить блок</button>
</form>
{% endif %}
</div>
<hr>
{% endblock %}

133
templates/cheating.html Normal file
View File

@ -0,0 +1,133 @@
{% extends 'base.html' %}
{% load filters %}
{% block title %}{{ Block.name }} | списывание{% endblock %}
{% block links %}
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.4.0/build/styles/default.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.4.0/build/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
{% endblock %}
{% block scripts %}
function check_list(entity_type) {
var cbox = document.getElementById('check_' + entity_type + '_all');
var boxes = document.getElementsByClassName('check_' + entity_type);
for (var i = 0; i < boxes.length; i++) {
boxes[i].checked = cbox.checked;
}
}
function prepare_for_submit() {
var conf = confirm('Точно запускаем проверку? Новую проверку можно будет запустить только после завершения данной');
if (conf) {
document.getElementById('main_form').submit();
}
}
{% endblock %}
{% block content %}
<h3>Списывание в <a href="/admin/block?id={{ Block.id }}">{{ Block.name }}</a></h3>
<hr>
<form method="POST" id="main_form">
{% csrf_token %}
<h5>Проверить таски</h5>
<b><input type="checkbox" id="check_task_all" onchange="check_list('task')" checked> Все</b><br>
{% for task in Block.tasks %}
<input type="checkbox" class="check_task" name="check_task_{{ task.id }}" checked> {{ task.name }}<br>
{% endfor %}
<hr>
<h5>Проверить пользователей</h5>
<b><input type="checkbox" id="check_user_all" onchange="check_list('user')" checked> Все</b><br>
{% for sub in Block.course.students %}
<input type="checkbox" class="check_user" name="check_user_{{ sub.user.id }}" checked> {{ sub.user.userinfo }}<br>
{% endfor %}
<hr>
<h5>Дополнительно</h5>
<table>
<tr>
<td>
<input type="checkbox" name="best_result">
</td>
<td>
Проверять только решения с лучшим результатом
</td>
</tr>
<tr>
<td>
<input type="checkbox" name="last_solution">
</td>
<td>
Проверять только последнее решение
</td>
</tr>
<tr>
<td>
<input type="checkbox" name="all_tests">
</td>
<td>
Проверять только прошедшие все тесты
</td>
</tr>
<tr>
<td>
<select name="cheating_percent">
{% for i in 100|num_range %}
<option>{{ i }}</option>
{% endfor %}
</select>
</td>
<td>
Какой процент схожести считать списыванием
</td>
</tr>
</table>
<hr>
<button type="button" {% if Block.cheating_checking %}class="btn btn-secondary"{% else %}class="btn btn-dark" onclick="prepare_for_submit()"{% endif %}><i class="fa fa-rocket"></i> Запустить проверку</button>
</form>
<hr>
<center><h1>{{ Block.cheating_status }}</h1></center>
{% if Block.cheating_checked %}
<h1>Результаты проверки</h1>
{% for data in Block.cheating_results %}
{% with user=data|user_by_id cheating_data=Block.cheating_results|dict_key:data %}
<br><h3>{{ user.userinfo }} [{{ cheating_data|length }}]</h3>
<button class="btn btn-link" onclick="var content = document.getElementById('div_{{ user.id }}'); content.hidden = !content.hidden;">Отчет</button>
<div id="div_{{ user.id }}" hidden>
{% for cheat in cheating_data %}
{% with solution=cheat.solution|solution_by_id %}
<div class="row">
<div class="col-5">
<h5><a href="/solution?id={{ solution.id }}">{{ solution.id }}</a> | {{ solution.user.userinfo }} | {{ cheat.file }}</h5>
<pre>
<code class="c# border border-dark">
{{ solution|solution_file_text:cheat.file }}
</code>
</pre>
</div>
<div class="col-2">
<center>
<a href="/task?id={{ solution.task.id }}">{{ solution.task.name }}</a><br>
<b>{{ cheat.similarity }}%</b><br>
<i class="fa fa-arrow-{% if cheat.source %}right{% else %}left{% endif %}"></i>
</center>
</div>
<div class="col-5">
{% with solution2=cheat.similar|solution_by_id %}
<h5><a href="/solution?id={{ solution2.id }}">{{ solution2.id }}</a> | {{ solution2.user.userinfo }} | {{ cheat.file }}</h5>
<pre>
<code class="c# border border-dark">
{{ solution2|solution_file_text:cheat.file }}
</code>
</pre>
{% endwith %}
</div>
</div>
{% endwith %}
{% endfor %}
</div>
{% endwith %}
{% endfor %}
{% endif %}
{% endblock %}

235
templates/docs.html Normal file
View File

@ -0,0 +1,235 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Методичка{% endblock %}
{% block styles %}
.screenshot {
width: 100%;
}
.screenshot_div {
margin-top: 20px;
width: 80%;
margin-bottom: 20px;
}
.code {
padding: 15px;
margin-top: 15px;
margin-bottom: 15px;
}
{% endblock %}
{% block content %}
<h1>Методичка</h1>
<hr>
<h2><b>О системе</b></h2>
<h3>Структура сервиса</h3>
В сервисе существует два режима работы:
<ul>
<li>Режим студента</li>
<li>Режим администратора</li>
</ul>
И одна компанента:
<ul>
<li>Darwin</li>
</ul>
<hr>
<h3>Режим студента</h3>
Режим студента показывает курсы, на которые подписан пользователь, а также блоки, содержащиеся в них.<br>
Напротив каждого блока стоит (или отсутствует) оценка за данный блок.
<div class="border border-dark screenshot_div">
<img src="{% static 'img/main_page.png' %}" class="screenshot" />
</div>
При нажатии на блок пользователь попадает в настройки выбранного блока.<br>
На странице отображаются таски и (опционально) оценки за них.<br>
Под списком тасков отображается информация о возможности отправки решений в блок, а также временной интервал активности блока.
<div class="border border-dark screenshot_div">
<img src="{% static 'img/block_page.png' %}" class="screenshot" />
</div>
Страница таска представляет из себя подробное описание задания, предлагаемого к решению.<br>
Описание таска содержит в себе следующие атрибуты:
<ul>
<li>Легена</li>
<li>Формат входных данных</li>
<li>Формат выходных данных</li>
<li>Спецификации</li>
</ul>
Далее находится форма для отправки решения (.zip архив) и список отправленных решений.<br>
Решение содержит в себе следующие атрибуты:
<ul>
<li>id</li>
<li>Дата и время отправки</li>
<li>Результат тестирования</li>
<li>Оценка, выставленная преподавателем или ассистентом</li>
<li>Комментарий к решению</li>
</ul>
Sprint поддерживает следующие варианты результатов тестирования:
<ul>
<li>M/N - решение прошло M тестов из N</li>
<li>Time limit - превышен предел времени, отведенного на тестирование</li>
<li>Compilation error - не удалось скомпилировать решение</li>
<li>TEST ERROR - ошибка тестирования (может быть связана с некорректыми тестами)</li>
<li>TESTING - решение находится в процессе тестирования</li>
<li>IN QUEUE - решение находится в очереди на тестирование</li>
</ul>
<div class="border border-dark screenshot_div">
<img src="{% static 'img/task_page.png' %}" class="screenshot" />
</div>
При нажатии на результат тестирование можно получить информацию о том, какие именно тесты были пройдены, а какие - нет.
<div class="border border-dark screenshot_div">
<img src="{% static 'img/test_datails.png' %}" class="screenshot" />
</div>
При нажатии на id решения пользователь попадает на страницу решения.<br>
Страница с решением содержит ту же информацию, что и на странице таска.<br>
Помимо этого, на данной странице можно выставить оценку за решение и оставить комментарий. Для сохранения результата необходимо нажать на кнопку "Зачесть".<br>
Для выставления оценки 0 или максимальной оценки за таск, необходимо нажать на кнопку "Незачесть" или "Выставить максимальный балл" соответственно.<br>
Комментарий будет сохранен при нажатии на любую из кнопок.<br>
Внизу страницы отображаются файлы решения, содержащие только символы Unicode, иначе говоря, файлы с кодом.
<div class="border border-dark screenshot_div">
<img src="{% static 'img/solution_page.png' %}" class="screenshot" />
</div>
<hr>
<h3>Режим администратора</h3>
<i>Некоторые функции доступны только преподавателям</i><br>
Главная страница режима администратора показывает доступные для администрирования курсы, блоки в них и ссылку на данную методичку.<br>
Добавление нового блока в курс происходит по нажатии кнопки "Новый блок".
<div class="border border-dark screenshot_div">
<img src="{% static 'img/admin_page.png' %}" class="screenshot" />
</div>
При нажатии на кнопку "Участники курса" открывается страница с настройками участников курса.<br>
Для подписки существующих студентов или преподавателей на курс, необходимо ввести в форму "Добавить участников" ФИО студента или email, на который зарегестрирован аккаунт.<br>
Для подписки всех студентов определенной группы, нужно ввести название группы.<br>
Для отписки студента от курса, нужно нажать на кнопку "отписка" рядом с нужным студентом.<br>
Для того, чтобы сделать подписчика курса ассистентом или снятия с него таких прав, необходимо выбрать почту этого студента в форме "Назначить или разжаловать" и нажать на кнопку применить. Статус студента будет отображен в таблице ниже.
<div class="border border-dark screenshot_div">
<img src="{% static 'img/users_settings_page.png' %}" class="screenshot" />
</div>
В Sprint отсутствует регистрация в обычном понимании данного термина, поэтому регистрация студентов происходит при первом добавлении его к некоторому курсу.<br>
Прежде всего необходимо создать JSON файл по определенным правилам, описанным на странице участников курса.
<div class="border border-dark screenshot_div">
<img src="{% static 'img/upload_rules.png' %}" class="screenshot" />
</div>
При нажатии на блок, отображается страница с настройками блока.<br>
Страница настроек блока содержит следующие аттрибуты:
<ul>
<li>Набор тасков</li>
<li>Интервал времени, в течение которого можно отправлять решения</li>
<li>Решения, отправленные в данный блок</li>
</ul>
Ограничения по времени были внедрены для того, чтобы отложить запуск блока. Блок будет доступен студентам только если данный момент времени входит в интервал между временем начала и временем конца (границы включаются), и блок открыт для отправки решений.<br>
После установки нужных значений необходимо нажать на кнопку "Сохранить".<br>
Для создания нового таска после списка тасков есть кнопка "Новый таск".
<div class="border border-dark screenshot_div">
<img src="{% static 'img/admin_block_page.png' %}" class="screenshot" />
</div>
При нажатии на таск из списка, откроется страница с настройками таска.<br>
Поля "Легенда", "Входные данные", "Выходные данные" и "Спецификации" заполняются с помощью html разметки, которая потом будет отображена в режиме студента.<br>
<div class="border border-dark screenshot_div">
<img src="{% static 'img/task_settings_page_1.png' %}" class="screenshot" />
</div>
В настройках таска содержатся еще несколько аттрибутов:
<ul>
<li>Ограничения по времени - количество миллисекунд, которое отводится на тестирование решения (целое число)</li>
<li>Вес задачи - доля значимости таска в блоке (вещественное число, может быть больше единицы, а суммарно таски могут иметь вес отличный от единицы)</li>
<li>Максимальная оценка - максмальная оценка, которую можно выставить за таск (целое число)</li>
<li>Максимум решений - максимальное количество решений, которое можно отправить к таску (целое число, ограничение не действует на ассистентов и преподавателей)</li>
</ul>
В форму загрузки тестов загружается .cs файл с Unit тестами библиотеки Nunit. По нажатии на кнопку "Посмотреть" можно посмотреть тесты и отредактировать их. Для сохранения файла необходимо нажать на кнопку "Сохранить" внизу страницы.<br>
В форму "Дополнительные файлы" можно загрузить файлы, которые будут скорированы в директорию с исполняемыми файлами при тестировании.
<div class="border border-dark screenshot_div">
<img src="{% static 'img/task_settings_page_2.png' %}" class="screenshot" />
</div>
При нажатии на кнопку "Посмотреть решения" на странице настроек блока открывается страница решений, загруженных в текущий блок.<br>
Функциональность таблицы решений внизу страницы схожа с подобной в режиме студента.<br>
Фильтр на данной странице содержит следующие поля:
<ul>
<li>id - id решения</li>
<li>Таск - имя таска</li>
<li>Пользователь - почта студента</li>
<li>Группа - решения от студентов из данной группы</li>
<li>Последнее решение - последнее отправленное решение от каждого студента к каждому таску</li>
<li>Лучший результат - решения с наибольшим количеством пройденных тестов от каждого студента к каждому таску</li>
<li>Только студенты - исключить из фильтра решения от ассистентов и преподавателей</li>
</ul>
При нажатии на кнопку "Перетест" отфильтрованные решения будут перетестированы.
<div class="border border-dark screenshot_div">
<img src="{% static 'img/solutions_page.png' %}" class="screenshot" />
</div>
<hr>
<h3>Darwin</h3>
Одним из ограниений Unit тестирования является невозможность тестирования приватных методов и приватных классов.<br>
Для решения данной проблемы в Sprint содержится компонента Darwin. Загрузить библиотеку можно по данной <a href="{% static '/files/Darwin.dll' %}" download>ссылке</a>.<br>
Darwin позволяет создавать объекты приватных классов, задавать значения приватным полям и свойствам, а также вызывать приватные методы.<br>
Основой библиотеки является класс <b>DObject</b>.<br>
В конструктор передается имя того класса, объект которого необходимо создать, с указанием пространства имен.
<div class="border border-dark code">
<pre>
using Darwin;
using Nunit.Framework;
[TestFixture]
public class Tests
{
[Test]
public void Test1()
{
DObject d = new DObject("Calculator.Calc");
}
}
</pre>
</div>
Для доступа к полям и свойствам класса нужно использовать индексатор.<br>
<div class="border border-dark code">
<pre>
using Darwin;
using Nunit.Framework;
[TestFixture]
public class Tests
{
[Test]
public void Test1()
{
DObject d = new DObject("Calculator.Calc");
d["a"] = 5;
Assert.AreEqual(d["a"], 5);
}
}
</pre>
</div>
Для вызова методов класса используется метод InvokeMethod.<br>
В качетсве параметров передается имя метода и массив параметров.<br>
Тип возвращаемого значения метода InvokeMethod - object, поэтому необходимо проводить каст к нужному типу после вызова.
<div class="border border-dark code">
<pre>
using Darwin;
using Nunit.Framework;
[TestFixture]
public class Tests
{
[Test]
public void Test1()
{
DObject d = new DObject("Calculator.Calc");
d["a"] = 5;
Assert.AreEqual(d["a"], 5);
int val = (int)d.InvokeMethod("GetNumber", new object[] { d["a"] });
Assert.AreEqual(val, d["a"]);
}
}
</pre>
</div>
<hr>
<h2><b>Use cases</b></h2>
<h3>Как собрать таск</h3>
<hr>
<h3>Как написать Unit тест правильно</h3>
<hr>
<h3>Как написать тест на входные и выходные данные</h3>
<hr>
<h3>Что необходимо указать в спецификациях</h3>
<hr>
<h3>Полезные html тэги</h3>
{% endblock %}

68
templates/enter.html Normal file
View File

@ -0,0 +1,68 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
{% load static %}
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>
Sprint
</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href={% static "css/styles.css" %}>
<script src="https://use.fontawesome.com/49b98aaeb5.js"></script>
<script type="text/javascript" src={% static "js/scripts.js" %}></script>
<script type="text/javascript">
function restore() {
}
</script>
<style type="text/css">
.center {
height: 400px;
width: 400px;
position: fixed;
top: 50%;
left: 50%;
margin-top: -200px;
margin-left: -200px;
}
h1 {
font-size: 500%;
}
.form {
border: none;
border-bottom: 2px solid;
outline: none;
text-align: center;
width: 300px;
margin-top: 20px;
}
.sub {
margin-top: 20px;
}
</style>
</head>
<body>
<div class="center">
<center>
<div>
<h1>
<i class="fa fa-random"></i> Sprint
</h1>
</div>
<div>
<form method="POST">
{% csrf_token %}
<input type="text" class="form" name="email" placeholder="email"><br>
<input type="password" class="form" name="password" placeholder="password"><br>
<input type="submit" value="Вход" class="sub btn btn-dark form">
</form>
</div>
<div>
<button onclick="window.location.href='/restore'" class="sub btn btn-dark form">Восстановить пароль</button>
</div>
</center>
</div>
</body>
</html>

30
templates/main.html Normal file
View File

@ -0,0 +1,30 @@
{% extends 'base.html' %}
{% load filters %}
{% block title %}Главная{% endblock %}
{% block content %}
<h2>Доступные курсы</h2>
{% for key, value in blocks.items %}
<h5>{{ key.name }}</h5>
{% for block in value %}
<table>
<tr>
<td>
<a href="/block?id={{ block.id }}">{{ block.name }}</a>
</td>
<td>
{% with mark=block|mark_for_block:user %}
{% if mark|marked %}
<div style="margin-left: 20px; border: 1px solid black; background: {{ mark|mark_color }}; width: 25px; text-align: center;">
{{ mark }}
</div>
{% endif %}
{% endwith %}
</td>
</tr>
</table>
{% endfor %}
{% endfor %}
{% endblock %}

31
templates/messages.html Normal file
View File

@ -0,0 +1,31 @@
{% extends 'base.html' %}
{% load filters %}
{% block content %}
<h3 style="margin-bottom: 30px;">Сообщения</h3>
{% for message in Block.messages %}
<div class="row">
<div class="col-2"></div>
<div class="col-8" style="margin-bottom: 50px;">
{% with userinfo=message.sender|userinfo_by_user %}
<div style="background-color: #DDDDDD; padding: 15px;">
<b>
{{ userinfo }} | {{ userinfo.group }} |
<a href="/{% if current_page == 'admin' %}admin/{% endif %}task?id={{ message.task.id }}">{{ message.task.name }}</a>
{% if is_admin %} | <a href="/admin/solutions?block_id={{ Block.id }}&task_name={{ message.task.name }}&user={{ userinfo }}">Решения участника</a>{% endif %}
</b>
</div>
<div class="border border-dark bg-light" style="padding: 30px;">
{{ message.text }}
</div>
<div>
<button class="btn btn-danger" style="right: 0;">Ответить всем</button>
<button class="btn btn-success" style="right: 0;">Ответить</button>
</div>
{% endwith %}
</div>
<div class="col-2"></div>
</div>
{% endfor %}
{% endblock %}

33
templates/queue.html Normal file
View File

@ -0,0 +1,33 @@
{% extends 'base.html' %}
{% load filters %}
{% block title %}{{ Block.name }} | очередь{% endblock %}
{% block scripts %}
function doPoll() {
jQuery.get('/queue_table?block_id={{ Block.id }}', function(data) {
document.getElementById('body').innerHTML = data;
setTimeout(function() {doPoll()}, 2000);
})
}
{% endblock %}
{% block onload %}doPoll(){% endblock %}
{% block content %}
<h3><a href="/admin/block?id={{ Block.id }}">{{ Block.name }}</a></h3>
<h4>Очередь тестирования</h4>
<table class="table">
<thead>
<th scope="col">id</th>
<th scope="col">Таск</th>
<th scope="col">Дата и время отправки</th>
<th scope="col">Студент</th>
<th scope="col">Статус</th>
</thead>
<tbody id="body">
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,19 @@
{% for solution in solutions %}
<tr style="background-color: {% if solution.result == 'TESTING' %}#FFE2B7{% else %}{% if solution.result == 'IN QUEUE' %}#EAEAFF{% endif %}{% endif %};">
<td>
<b><a href="/admin/solution?id={{ solution.id }}">{{ solution.id }}</a></b>
</td>
<td>
<a href="/admin/task?id={{ solution.task.id }}">{{ solution.task.name }}</a>
</td>
<td>
{{ solution.time_sent }}
</td>
<td>
{{ solution.userinfo.surname }} {{ solution.userinfo.name }} {{ solution.userinfo.middle_name }}
</td>
<td>
<b>{{ solution.result }}</b>
</td>
</tr>
{% endfor %}

49
templates/rating.html Normal file
View File

@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% load filters %}
{% block title %}Рейтинг{% endblock %}
{% block content %}
<h3>Рейтинг (beta)</h3>
<h4><a style="color: black;" href="/{% if current_page == 'admin' %}admin/{% endif %}block?id={{ Block.id }}">{{ Block.name }}</a></h4>
<table border="1px solid black" style="width: 100%;">
<thead>
<tr>
<th scope="col">Студент</th>
{% for task in Block.tasks %}
<th>{% if admin_course %}<a href="/admin/solutions?block_id={{ Block.id }}&task_name={{ task.name }}">{{ task.name }}</a>{% else %}{{ task.name }}{% endif %}</th>
{% endfor %}
<th scope="col" style="background-color: lightblue;">Оценка</th>
</tr>
</thead>
<tbody>
{% for sub in Block.course.students %}
<tr>
<th scope="row">{% if admin_course %}<a href="/admin/solutions?block_id={{ Block.id }}&user={{ sub.user|userinfo_by_user }}">{{ sub.user|userinfo_by_user }}</a>{% else %}{{ sub.user|userinfo_by_user }}{% endif %}</th>
{% for task in Block.tasks %}
{% with status=sub.user|mark_status:task %}
<th style="background-color: {% if status == '-' %}white{% else %}{% if sub.user|fully_marked:task %}lime{% else %}yellow{% endif %}{% endif %};">
{% if current_page == 'admin' and status != '-' %}
<a style="color: black;" href="/admin/solutions?block_id={{ Block.id }}&user={{ sub.user|userinfo_by_user }}&task_name={{ task.name }}">{{ status }}</a>
{% else %}
{{ status }}
{% endif %}
</th>
{% endwith %}
{% endfor %}
<td style="background-color: lightblue;">
{% with mark=Block|mark_for_block:sub.user %}
{% if mark|marked %}
<div style="margin-left: 20px; border: 1px solid black; background: {{ mark|mark_color }}; width: 25px; text-align: center;">
{{ mark }}
</div>
{% endif %}
{% endwith %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button class="btn btn-dark" style="margin-top: 30px;" onclick="window.location.href='/admin/download_rating?block_id={{ Block.id }}'">Скачать csv</button>
{% endblock %}

View File

@ -0,0 +1,29 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
{% load static %}
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>
Sprint
</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href={% static "css/styles.css" %}>
<script type="text/javascript" src={% static "js/scripts.js" %}></script>
</head>
<body>
<center>
<h1>
<font color="black"><a href="/enter">Sprint</a></font>
</h1>
{{ error }}
<form method="POST">
{% csrf_token %}
<table>
{{ form }}
</table>
<input type="submit" value="Установить пароль" class="btn btn-dark" style="margin-top: 20px;">
</form>
</center>
</body>
</html>

36
templates/restore.html Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
{% load static %}
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>
Восстановление пароля
</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href={% static "css/styles.css" %}>
<script type="text/javascript" src={% static "js/scripts.js" %}></script>
</head>
<body>
<center>
<h1>
<font color="black"><a href="{% url 'enter' %}">Sprint</a></font>
</h1>
<form method="POST">
{% csrf_token %}
<table>
<tr>
<td>
<font color="black">Почта</font>
</td>
<td>
<input type="email" name="email" class="input_simple">
</td>
</tr>
</table>
<input type="submit" value="Восстановить" class="btn btn-dark" style="margin-top: 20px;">
</form>
Если данный электронный адрес есть в базе, то на него будет отправлена ссылка для восстановления пароля.
</center>
</body>
</html>

18
templates/settings.html Normal file
View File

@ -0,0 +1,18 @@
{% extends 'base.html' %}
{% block title %}Настройки{% endblock %}
{% block content %}
<h2>
Сменить пароль
</h2>
<font color="red">{{ error }}</font>
<form method="POST">
{% csrf_token %}
<table>
{{ form }}
</table>
<button type="submit" class="btn btn-dark" value="Сменить" style="margin-top: 20px;"><i class="fa fa-check"></i> Сменить</button>
</form>
<hr>
{% endblock %}

271
templates/solution.html Normal file
View File

@ -0,0 +1,271 @@
{% extends 'base.html' %}
{% block title %}{{ solution.task.name }}|решение{% endblock %}
{% block links %}
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.4.0/build/styles/default.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.4.0/build/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
{% endblock %}
{% load filters %}
{% block scripts %}
{% if can_edit %}
function findGetParameter(parameterName) {
var result = null,
tmp = [];
location.search
.substr(1)
.split("&")
.forEach(function (item) {
tmp = item.split("=");
if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
});
return result;
}
function next() {
const solutions_request = findGetParameter('solutions');
const solutions = solutions_request.split(' ');
const current = findGetParameter('id');
const current_index = solutions.findIndex((element) => element == current);
if (current_index != solutions.length - 1) {
var next_element = document.getElementById('next');
next_element.setAttribute('href', '/admin/solution?id=' + solutions[current_index + 1] + '&solutions=' + solutions_request);
next_element.innerHTML = '->';
}
}
function previous() {
const solutions_request = findGetParameter('solutions');
const solutions = solutions_request.split(' ');
const current = findGetParameter('id');
const current_index = solutions.findIndex((element) => element == current);
if (current_index != 0) {
var next_element = document.getElementById('previous');
next_element.setAttribute('href', '/admin/solution?id=' + solutions[current_index - 1] + '&solutions=' + solutions_request);
next_element.innerHTML = '<-';
}
}
function fillContent() {
next();
previous();
}
{% if can_edit %}
function showHideTests() {
var text = document.getElementById('tests_text');
var button = document.getElementById('tests_button');
text.hidden = !text.hidden;
if (text.hidden) {
button.textContent = 'Показать тесты';
} else {
button.textContent = 'Скрыть тесты';
}
}
function showHideLog() {
var text = document.getElementById('log_text');
var button = document.getElementById('log_button');
text.hidden = !text.hidden;
if (text.hidden) {
button.textContent = 'Показать лог';
} else {
button.textContent = 'Скрыть лог';
}
}
function retest() {
let del = confirm("Подтвердите перетест");
if (del) {
const sols = findGetParameter('solutions');
const link = '/admin/retest?block_id={{ solution.task.block.id }}&solution_id={{ solution.id }}&next={% autoescape off %}{{ path }}?id={{ solution.id }}{% if current_page == 'admin' %}%26solutions={% endif %}{% endautoescape %}'{% if current_page == 'admin' %} + sols.replaceAll(' ', '%20'){% endif %};
window.location.href = link;
}
}
{% endif %}
{% endif %}
{% endblock %}
{% block onload %}{% if can_edit %}fillContent(){% endif %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-6">
<h5>
<form method="POST">
<table class="table">
<tr>
<td>
Блок
</td>
<td>
<a href="{% if current_page == 'admin' %}/admin{% endif %}/block?id={{ solution.task.block.id }}">{{ solution.task.block.name }}</a>
{% if current_page == 'admin' %}
| <a style="margin-top: 15px;" href="/admin/solutions?block_id={{ solution.task.block.id }}">К посылкам</a> | <a style="margin-top: 15px;" href="/admin/rating?block_id={{ solution.task.block.id }}">К рейтингу</a>
{% endif %}
</td>
</tr>
<tr>
<td>
Таск
</td>
<td>
<a href="{% if current_page == 'admin' %}/admin{% endif %}/task?id={{ solution.task.id }}">{{ solution.task.name }}</a>
</td>
</tr>
<tr>
<td>
Студент
</td>
<td>
{{ solution.userinfo.surname }} {{ solution.userinfo.name }} {{ solution.userinfo.middle_name }}
</td>
</tr>
<tr>
<td>
id решения
</td>
<td>
<div style="text-align: center;"></div>
{% if can_edit %}
<a id="previous"></a>
{% endif %}
{{ solution.id }}
{% if can_edit %}
<a id="next"></a>
{% endif %}
</div>
</td>
</tr>
<tr>
<td>
Результат
</td>
<td>
{% if can_edit or solution.task.show_details %}
<!-- Button trigger modal -->
<button type="button" class="btn btn-link" data-toggle="modal" data-target="#resultModalLong{{ solution.id }}">
{{ solution.result }}
</button>
<!-- Modal -->
<div class="modal fade" id="resultModalLong{{ solution.id }}" tabindex="-1" role="dialog" aria-labelledby="resultModalLongTitle{{ solution.id }}" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="resultModalLongTitle{{ solution.id }}">Подробная информация о тестировании {{ solution.id }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-12">
{% autoescape off %}
{{ solution.details }}
{% endautoescape %}
{% if can_edit %}
<hr>
<pre id="tests_text" hidden>
<h4>Тесты</h4><br>
{{ solution.task.tests_text }}
</pre>
<hr>
<pre id="log_text" hidden>
<h4>Лог</h4><br>
{{ solution.log_text }}
</pre>
{% endif %}
</div>
</div>
</div>
</div>
<div class="modal-footer">
{% if can_edit %}
<button type="button" id="log_button" class="btn btn-warning" onclick="showHideLog()">Показать лог</button>
<button type="button" id="tests_button" class="btn btn-primary" onclick="showHideTests()">Показать тесты</button>
{% endif %}
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% else %}
{% if solution.task.show_result %}
{{ solution.result }}
{% else %}
Accepted
{% endif %}
{% endif %}
</td>
</tr>
<tr>
<td>
Оценка
</td>
<td>
{% if can_edit %}
{% csrf_token %}
<label>
<select name="mark">
{% autoescape off %}
{{ solution.mark_select }}
{% endautoescape %}
</select>
</label>
{% else %}
{% if solution.task.show_result %}
{{ solution.mark_property }}
{% else %}
{% if solution.mark == null %}
Checking
{% else %}
Checked
{% endif %}
{% endif %}
{% endif %}
</td>
</tr>
<tr>
<td>
Комментарий
</td>
<td>
{% if can_edit %}
<textarea rows="10" cols="50" style="resize: none;" name="comment">{{ solution.comment }}</textarea>
{% else %}
<pre>
{{ solution.comment_property }}
</pre>
{% endif %}
</td>
</tr>
</table>
{% if can_edit %}
<button type="submit" name="action" value="Зачесть" class="btn btn-dark"><i class="fa fa-check"></i> Зачесть</button>
<button type="submit" name="action" value="Незачесть" class="btn btn-dark"><i class="fa fa-times"></i> Не зачесть</button>
<button type="submit" name="action" value="Выставить макс. балл" class="btn btn-dark"><i class="fa fa-arrow-up"></i> Выставить макс. балл</button>
<button type="button" class="btn btn-dark" onclick="retest()"><i class="fa fa-undo"></i> Перетест</button>
{% endif %}
<button type="button" class="btn btn-dark"><i class="fa fa-question-circle"></i> Помощь</button>
</form>
</h5>
</div>
</div>
<h3>Files</h3>
{% for filename, text in solution.files.items %}
<h5>{{ filename }}</h5>
{% if filename|is_code %}
<pre>
<code class="c# border border-dark">
{{ text }}
</code>
</pre>
{% else %}
<div class="border border-dark bg-light" style="padding: 15px;">
<pre>
{{ text }}
</pre>
</div>
{% endif %}
<hr>
{% endfor %}
{% endblock %}

315
templates/solutions.html Normal file
View File

@ -0,0 +1,315 @@
{% extends 'base.html' %}
{% load filters %}
{% block title %}{{ Block.name }} | решения{% endblock %}
{% block scripts %}
function filter() {
const idi = document.getElementById('idi').value;
const task = document.getElementById('task_name').value;
const user = document.getElementById('user').value;
const group = document.getElementById('group').value;
const last_solution = document.getElementById('last_solution').checked;
const best_result = document.getElementById('best_result').checked;
const only_students = document.getElementById('only_students').checked;
const not_seen = document.getElementById('not_seen').checked;
var req = '';
if (idi) {
req += '&solution_id=' + idi;
}
if (not_seen) {
req += '&not_seen=' + not_seen;
}
if (task) {
req += '&task_name=' + task;
}
if (user) {
req += '&user=' + user;
}
if (group) {
req += '&group=' + group;
}
if (last_solution) {
req += '&last_solution=' + last_solution;
}
if (best_result) {
req += '&best_result=' + best_result;
}
if (only_students) {
req += '&only_students=' + only_students;
}
window.location.href = '/admin/solutions?block_id={{ Block.id }}' + req;
}
function retest() {
let del = confirm("Подтвердите перетест");
if (del)
window.location.href = '/admin/retest?block_id={{ Block.id }}{% autoescape off %}{{ req }}{% endautoescape %}';
}
function download() {
let del = confirm("Подтвердите скачивание");
if (del) {
window.location.href = '/admin/download?block_id={{ Block.id }}{% autoescape off %}{{ req }}{% endautoescape %}';
}
}
function showHideTests() {
var text = document.getElementById('tests_text');
var button = document.getElementById('tests_button');
text.hidden = !text.hidden;
if (text.hidden) {
button.textContent = 'Показать тесты';
} else {
button.textContent = 'Скрыть тесты';
}
}
function showHideLog() {
var text = document.getElementById('log_text');
var button = document.getElementById('log_button');
text.hidden = !text.hidden;
if (text.hidden) {
button.textContent = 'Показать лог';
} else {
button.textContent = 'Скрыть лог';
}
}
function fillModalResults(id) {
jQuery.get('/get_result_data?id=' + id, function(data) {
const response = JSON.parse(data);
if (response['success'] == true) {
document.getElementById('resultModalLongTitle').innerHTML = 'Подробная информация о тестировании ' + id;
document.getElementById('results_text').innerHTML = response['results_text'];
document.getElementById('tests_text').innerHTML = '<h4>Тесты</h4><br>' + response['tests_text'];
document.getElementById('log_text').innerHTML = '<h4>Лог</h4><br>' + response['log_text'];
}
});
}
function fillModalComments(id) {
jQuery.get('/get_comment_data?id=' + id, function(data) {
const response = JSON.parse(data);
if (response['success'] == true) {
document.getElementById('commentModalLongTitle').innerHTML = 'Комментарий к решению ' + id;
document.getElementById('comment_text').innerHTML = response['comment_text'];
}
});
}
{% endblock %}
{% block styles %}
.input_field {
width: 500px;
}
{% endblock %}
{% block content %}
<!-- Modal -->
<div class="modal fade" id="resultModalLong" tabindex="-1" role="dialog" aria-labelledby="resultModalLongTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="resultModalLongTitle"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div id="results_text"></div>
<hr>
<pre id="tests_text" hidden></pre>
<hr>
<pre id="log_text" hidden></pre>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" id="log_button" class="btn btn-warning" onclick="showHideLog()">Показать лог</button>
<button type="button" id="tests_button" class="btn btn-primary" onclick="showHideTests()">Показать тесты</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="commentModalLong" tabindex="-1" role="dialog" aria-labelledby="commentModalLongTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="commentModalLongTitle"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<pre id="comment_text"></pre>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<h2><a href="/admin/block?id={{ Block.id }}">{{ Block.name }}</a></h2>
<div class="row">
<div class="col-6">
<h4>Фильтр</h4>
<table>
<tr>
<td>Id</td>
<td><input class="input_field" type="text" placeholder="Id" id="idi" name="idi" value="{{ options.solution_id }}"></td>
</tr>
<tr>
<td>Таск</td>
<td>
<select class="input_field" id="task_name" name="task_name">
<option value="">Все</option>
{% for task in solutions_info.tasks %}
<option value="{{ task.name }}" {% if options.task_name == task.name %}selected{% endif %}>{{ task.name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td>Пользователь</td>
<td>
<select class="input_field" id="user" name="user">
<option value="">Все</option>
{% for u in solutions_info.users %}
<option value="{{ u }}" {% if options.user == u %}selected{% endif %}>{{ u }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td>Группа</td>
<td>
<select class="input_field" id="group" name="group">
<option value="">Все</option>
{% for group in solutions_info.groups %}
<option value="{{ group }}" {% if options.group == group %}selected{% endif %}>{{ group }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td>Последнее решение</td>
<td><input type="checkbox" id="last_solution" name="last_solution" {% if options.last_solution %}checked{% endif %}>{{ option.last_solution }}</td>
</tr>
<tr>
<td>Лучший результат</td>
<td><input type="checkbox" id="best_result" name="best_result" {% if options.best_result %}checked{% endif %}></td>
</tr>
<tr>
<td>Только студенты</td>
<td><input type="checkbox" id="only_students" name="only_students" {% if options.only_students %}checked{% endif %}></td>
</tr>
<tr>
<td>Еще не проверено</td>
<td><input type="checkbox" id="not_seen" name="not_seen" {% if options.not_seen %}checked{% endif %}></td>
</tr>
</table>
<button type="button" class="btn btn-dark" id="control" onclick="filter()" style="margin-top: 20px;"><i class="fa fa-check"></i> Применить</button><br>
<button type="button" class="btn btn-dark" id="control" onclick="window.location.href='/admin/solutions?block_id={{ Block.id }}'" style="margin-top: 10px;"><i class="fa fa-times"></i> Сбросить</button><br>
<button style="margin-top: 10px;" class="btn btn-dark" onclick="download()"><i class="fa fa-download"></i> Скачать решения</button><br>
<button style="margin-top: 10px;" class="btn btn-dark" onclick="retest()"><i class="fa fa-undo"></i> Перетест</button>
</div>
<div class="col-6">
<h4>Статистика по таскам</h4>
<table class="table">
<thead>
<th scope="col">Таск</th>
<th scope="col">Верно</th>
<th scope="col">Частично</th>
<th scope="col">С ошибкой</th>
<th scope="col">Все</th>
</thead>
<tbody>
{% for task in Block.tasks %}
<tr>
<th scope="row">{{ task.name }}</th>
<td>{{ task.correct_count }}</td>
<td>{{ task.partially_passed }}</td>
<td>{{ task.solutions_with_error }}</td>
<td>{{ task.solutions_count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<hr>
<h5>Решения</h5>
<form method="POST" id="delete_form">
{% csrf_token %}
</form>
<table class="table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">Таск</th>
<th scope="col">Пользователь</th>
<th scope="col">Время отправки</th>
<th scope="col">Группа</th>
<th scope="col">Результат</th>
<th scope="col">Оценка</th>
<th scope="col">Комментарий</th>
<th scope="col">Действия</th>
</tr>
</thead>
<tbody>
{% for solution in solutions %}
<tr style="background-color: {% if solution.result == 'TESTING' %}#FFE2B7{% else %}{% if solution.result == 'IN QUEUE' %}#EAEAFF{% endif %}{% endif %};">
<th scope="row">
<a href="/admin/solution?id={{ solution.id }}&solutions={{ filter }}">{{ solution.id }}</a>
</th>
<td>
<a href="/admin/task?id={{ solution.task.id }}">{{ solution.task.name }}</a>
</td>
<td>
{{ solution.userinfo.surname }} {{ solution.userinfo.name }} {{ solution.userinfo.middle_name }}
</td>
<td>
{{ solution.time_sent }}
</td>
<td>
{{ solution.userinfo.group }}
</td>
<td>
<!-- Button trigger modal -->
<button type="button" class="btn btn-link" data-toggle="modal" data-target="#resultModalLong" onclick="fillModalResults({{ solution.id }})">
{{ solution.result }}
</button>
</td>
<td>
{{ solution.mark_property }}
</td>
<td>
{% if solution.comment %}
<!-- Button trigger modal -->
<button type="button" class="btn btn-link" data-toggle="modal" data-target="#commentModalLong" onclick="fillModalComments({{ solution.id }})">
Посмотреть
</button>
{% else %}
<p>Нет комментария</p>
{% endif %}
</td>
<td>
<button class="btn btn-primary" onclick="window.location.href='/admin/download?block_id={{ Block.id }}&solution_id={{ solution.id }}';"><i class="fa fa-download"></i></button>
<button name="DELETE_SOLUTION" form="delete_form" value="{{ solution.id }}" onclick="var del = confirm('Подтвердите удаление');if (del){this.form.submit();}" class="btn btn-danger" type="игеещт"><i class="fa fa-trash"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,45 @@
{% for solution in solutions %}
<tr>
<th scope="row">
<a href="/solution?id={{ solution.id }}">{{ solution.id }}</a>
</th>
<td>
{{ solution.time_sent }}
</td>
<td>
{% if can_edit or task.show_details %}
<!-- Button trigger modal -->
<button type="button" class="btn btn-link" data-toggle="modal" data-target="#resultModalLong" onclick="showData('{{ solution.id }}')">
{{ solution.result }}
</button>
<div id="details_{{ solution.id }}" hidden>{% autoescape off %}{{ solution.details }}{% endautoescape %}</div>
{% if can_edit %}
<div id="tests_{{ solution.id }}" hidden>{{ solution.task.tests_text }}</div>
<div id="log_{{ solution.id }}" hidden>{% autoescape off %}{{ solution.log_text }}{% endautoescape %}</div>
{% endif %}
{% else %}{% if task.show_result %}
{{ solution.result }}
{% else %}
Accepted
{% endif %}
{% endif %}
</td>
<td>
{% if task.show_result or can_edit %}
{{ solution.mark_property }}
{% else %}
{% if solution.mark == null %}
Checking
{% else %}
Checked
{% endif %}
{% endif %}
</td>
<td>
<pre>
{{ solution.comment_property }}
</pre>
</td>
</tr>
{% endfor %}

85
templates/superuser.html Normal file
View File

@ -0,0 +1,85 @@
{% extends 'base.html' %}
{% block title %}Режим Бога{% endblock %}
{% block content %}
<h1>Настройки системы</h1>
<form method="POST">
{% csrf_token %}
<div class="row">
<div class="col-6">
<table class="table">
<thead>
<tr>
<th scope="col">Параметр</th>
<th scope="col">Значение</th>
</tr>
</thead>
<tbody>
{% for param in params %}
<tr>
<td>{{ param.key }}</td>
<td><input style="width: 100%;" name="{{ param.key }}" value="{{ param.value }}"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<input type="submit" class="btn btn-dark" value="Сохранить">
</form>
<hr>
<h1>Курсы</h1>
<table border="1px solid black">
<tr>
<td>
<b>id</b>
</td>
<td>
Название
</td>
<td>
Преподаватели
</td>
<td></td>
</tr>
<form method="POST">
{% csrf_token %}
<tr>
<td></td>
<td>
<input type="text" name="course_name">
</td>
<td>
<select name="teacher">
{% for teacher in teachers %}
<option value="{{ teacher.email }}">{{ teacher.email }}</option>
{% endfor %}
</select>
</td>
<td>
<input class="btn btn-dark" type="submit" value="Создать">
</td>
</tr>
</form>
{% for course in courses %}
<tr>
<td>
{{ course.id }}
</td>
<td>
{{ course.name }}
</td>
<td>
{% for teacher in course.teachers %}
{{ teacher.surname }} {{ teacher.name }} {{ teacher.middle_name }}
{% endfor %}
</td>
<td>
</td>
</tr>
{% endfor %}
</table>
<hr>
<button class="btn btn-dark" onclick="window.location.href='/admin/'">Django admin</button>
<hr>
{% endblock %}

290
templates/task.html Normal file
View File

@ -0,0 +1,290 @@
{% extends 'base.html' %}
{% load filters %}
{% block title %}{{ task.name }}{% endblock %}
{% block styles %}
input[type="file"] {
display: none;
}
{% endblock %}
{% block onload %}doPoll(){% endblock %}
{% block links %}
<script>
var current_solution = null;
</script>
{% endblock %}
{% block scripts %}
function partyhard() {
var elem = document.getElementById('paint');
elem.hidden = false;
}
function uploaded() {
document.getElementById('is_uploaded').style.display = 'block';
document.getElementById('is_uploaded').nodeValue = document.getElementById('file-upload').nodeValue;
}
function doPoll() {
jQuery.get('/solutions_table?id={{ task.id }}', function(data) {
if (data == 'done') {
return
}
else {
document.getElementById('solutions').innerHTML = data;
if (current_solution != null) {
{% if can_edit %}
document.getElementById('log').innerHTML = document.getElementById('log_' + current_solution).innerHTML;
{% endif %}
document.getElementById('details').innerHTML = document.getElementById('details_' + current_solution).innerHTML;
}
setTimeout(function() {doPoll()}, 2000);
}
})
jQuery.get('/solutions_table?id={{ task.id }}&render=true', function(data) {
document.getElementById('solutions').innerHTML = data;
if (current_solution != null) {
{% if can_edit %}
document.getElementById('log').innerHTML = document.getElementById('log_' + current_solution).innerHTML;
{% endif %}
document.getElementById('details').innerHTML = document.getElementById('details_' + current_solution).innerHTML;
}
})
}
{% if can_edit %}
function showHideTests(id) {
var text = document.getElementById('tests_text_' + id);
var button = document.getElementById('tests_button_' + id);
text.hidden = !text.hidden;
if (text.hidden) {
button.textContent = 'Показать тесты';
} else {
button.textContent = 'Скрыть тесты';
}
}
function showHideLog(id) {
var text = document.getElementById('log_text_' + id);
var button = document.getElementById('log_button_' + id);
text.hidden = !text.hidden;
if (text.hidden) {
button.textContent = 'Показать лог';
} else {
button.textContent = 'Скрыть лог';
}
}
{% endif %}
{% if task.show_details or can_edit %}
function showData(id) {
current_solution = id;
const dataTypes = ['details'{% if can_edit %}, 'tests', 'log'{% endif %}];
for (const dt of dataTypes) {
document.getElementById(dt).innerHTML = document.getElementById(dt + '_' + id).innerHTML;
}
document.getElementById('resultModalLongTitle').innerHTML = 'Подробная информация о тестировании ' + id;
}
{% endif %}
{% endblock %}
{% block content %}
<!--
Помогите! Меня взяли в заложники и заставляют писать на Python!!!
Я уже пятый день сижу в подвале и прикручиваю Docker, избавьте меня от мучений и спасите!
-->
<h5>
<a href="/block?id={{ task.block.id }}">Обратно к блоку</a>
</h5>
<h2>
{{ task.name }}
{% if can_edit %}
<a style="color: black;" href="/admin/task?id={{ task.id }}"><i class="fa fa-pencil"></i></a>
{% endif %}
<a style="color: black;" href="" data-toggle="modal" data-target="#messageModal"><!--i class="fa fa-comment"></i--></a>
</h2>
<div class="modal fade" id="messageModal" tabindex="-1" role="dialog" aria-labelledby="messageModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="messageModalLabel">Написать сообщение преподавателям</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form method="POST">
{% csrf_token %}
<div class="modal-body">
<textarea name="message" rows="10" cols="50" style="resize: none;"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary">Отправить</button>
</div>
</form>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-9">
<h3>Легенда</h3>
{% autoescape off %}
{{ task.legend }}
{% endautoescape %}
<hr>
<h3>Формат входных данных</h3>
{% autoescape off %}
{{ task.input }}
{% endautoescape %}
<hr>
<h3>Формат выходных данных</h3>
{% autoescape off %}
{{ task.output }}
{% endautoescape %}
<hr>
<h3>Спецификации</h3>
{% autoescape off %}
{{ task.specifications }}
{% endautoescape %}
</div>
<div class="col"></div>
<div class="col-2">
<h5>Таски</h5>
<table>
{% for t in task.block.tasks %}
<tr>
{% with mark=t|mark_for_task:user %}
{% if mark|marked %}
<td>
<div style="margin-left: 20px; border: 1px solid black; background: {{ mark|mark_color }}; width: 25px; text-align: center;">
{{ mark }}
</div>
</td>
{% endif %}
{% endwith %}
<td>
<a href="/task?id={{ t.id }}">{% if t.id == task.id %}<b>{{ t.name }}</b>{% else %}{{ t.name }}{% endif %}</a><br>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<hr>
<h3>Самплы</h3>
{% for sample in task.samples %}
<h5>Пример {{ sample.input.num }}</h5><br>
<b>
<table style="width: 100%">
<tr>
<td>
Входные данные
</td>
<td>
Выходные данные
</td>
</tr>
</table>
</b>
<hr>
<table style="width: 100%;">
<tr>
<td style="width: 50%; vertical-align: top;">
<pre>
{{ sample.input.text }}
</pre>
</td>
<td style="width: 50%; vertical-align: top;">
<pre>
{{ sample.output.text }}
</pre>
</td>
</tr>
</table>
<hr>
{% endfor %}
<hr>
{% if can_send or can_edit %}
<h2>Отправить решение</h2>
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<label for="file-upload" class="btn btn-dark" style="margin-top: 20px;">
<i class="fa fa-upload"></i> Загрузить файл
</label>
<span id="is_uploaded" style="display: none;">Решение загружено</span>
<input type="file" class="btn form-control-file" id="file-upload" value="Выбрать файл" name="file" onchange="uploaded()">
<br><button type="submit" value="Отправить" class="btn btn-outline-dark"><i class="fa fa-share"></i> Отправить</button>
</form>
{% endif %}
{% if not can_edit and can_send %}
Осталось попыток: {{ user|last_attempts:task }}
{% endif %}
{% if can_edit or task.show_details %}
<!-- Modal -->
<div class="modal fade" id="resultModalLong" tabindex="-1" role="dialog" aria-labelledby="resultModalLongTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="resultModalLongTitle">Подробная информация о тестировании</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-12">
{% autoescape off %}
<div id="details"></div>
{% endautoescape %}
{% if can_edit %}
<hr>
<pre id="tests_text_{{ solution.id }}" hidden>
<h4>Тесты</h4><br>
<div id="tests"></div>
</pre>
<hr>
<pre id="log_text_{{ solution.id }}" hidden>
<h4>Лог</h4><br>
<div id="log"></div>
</pre>
{% endif %}
</div>
</div>
</div>
</div>
<div class="modal-footer">
{% if can_edit %}
<button type="button" id="log_button_{{ solution.id }}" class="btn btn-warning" onclick="showHideLog('{{ solution.id }}')">Показать лог</button>
<button type="button" id="tests_button_{{ solution.id }}" class="btn btn-primary" onclick="showHideTests('{{ solution.id }}')">Показать тесты</button>
{% endif %}
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endif %}
<h2 style="margin-top: 20px;">Решения</h2>
<table class="table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">Дата и время отправки</th>
<th scope="col">Результат</th>
<th scope="col">Оценка</th>
<th scope="col">Комментарий</th>
</tr>
</thead>
<tbody id="solutions">
</tbody>
</table>
<iframe id="paint" src="https://jspaint.app/" style="width: 100%; height: 1000px;" hidden></iframe>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More