├── .circleci └── config.yml ├── .flake8 ├── .gitignore ├── .readthedocs.yml ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── autojudge ├── __init__.py ├── settings.py ├── settings_production.py ├── urls.py └── wsgi.py ├── content ├── Dockerfile ├── compile_and_test.py ├── main_compiler.sh └── main_tester.sh ├── docs ├── Makefile ├── requirements.txt └── source │ ├── _images │ ├── contest-created.png │ ├── contest-detail-click.gif │ ├── contest-form.gif │ ├── log-in.png │ ├── new-contest-dashboard.png │ ├── new-problem-contest.png │ ├── poster-view.png │ ├── problem-edit-delete.png │ ├── problem-form.gif │ ├── problem-test-case.gif │ └── submission-participant.gif │ ├── api.rst │ ├── api │ ├── forms.rst │ ├── handler.rst │ ├── models.rst │ └── views.rst │ ├── conf.py │ ├── index.rst │ ├── usage.rst │ └── usage │ ├── install.rst │ └── manual.rst ├── judge ├── __init__.py ├── admin.py ├── apps.py ├── default │ ├── comment.yml │ ├── compilation_script.sh │ ├── examples │ │ └── difffloat.py │ ├── inputfile.txt │ ├── outputfile.txt │ └── test_script.sh ├── forms.py ├── handler.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── static │ └── assets │ │ ├── css │ │ └── argon.min.css │ │ ├── js │ │ └── argon.min.js │ │ └── vendor │ │ ├── bootstrap │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ │ ├── headroom │ │ └── headroom.min.js │ │ ├── jquery │ │ └── jquery.min.js │ │ ├── nouislider │ │ ├── css │ │ │ ├── nouislider.css │ │ │ └── nouislider.min.css │ │ └── js │ │ │ ├── nouislider.js │ │ │ └── nouislider.min.js │ │ ├── nucleo │ │ ├── css │ │ │ ├── nucleo-svg.css │ │ │ └── nucleo.css │ │ └── fonts │ │ │ ├── nucleo-icons.eot │ │ │ ├── nucleo-icons.svg │ │ │ ├── nucleo-icons.ttf │ │ │ ├── nucleo-icons.woff │ │ │ └── nucleo-icons.woff2 │ │ ├── onscreen │ │ └── onscreen.min.js │ │ ├── popper │ │ └── popper.min.js │ │ └── quill │ │ ├── quill.bubble.css │ │ ├── quill.core.css │ │ ├── quill.core.js │ │ ├── quill.js │ │ ├── quill.min.js │ │ ├── quill.min.js.map │ │ └── quill.snow.css ├── templates │ ├── 404.html │ ├── 500.html │ └── judge │ │ ├── base.html │ │ ├── contest_add_person.html │ │ ├── contest_detail.html │ │ ├── contest_persons.html │ │ ├── edit_problem.html │ │ ├── index.html │ │ ├── new_contest.html │ │ ├── new_problem.html │ │ ├── problem_detail.html │ │ ├── problem_submissions.html │ │ └── submission_detail.html ├── tests.py ├── urls.py └── views.py ├── manage.py ├── requirements.txt └── submission_watcher_saver.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | test: 4 | working_directory: ~/autojudge 5 | docker: 6 | - image: circleci/python:3.7 7 | steps: 8 | - checkout 9 | - run: 10 | name: Install Django 11 | command: | 12 | sudo pip install Django 13 | sudo pip install social-auth-app-django 14 | - run: 15 | name: Install flake8 and mypy 16 | command: | 17 | sudo pip install flake8 18 | sudo pip install mypy 19 | - run: 20 | name: Lint Check 21 | command: | 22 | flake8 23 | mypy --ignore-missing-imports judge/ 24 | - run: 25 | name: Django tests 26 | command: | 27 | export GOOGLE_OAUTH2_KEY='' 28 | export GOOGLE_OAUTH2_SECRET='' 29 | python manage.py test 30 | workflows: 31 | version: 2 32 | commit: 33 | jobs: 34 | - test 35 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 100 3 | exclude = judge/migrations,content/**/* 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/django 3 | # Edit at https://www.gitignore.io/?templates=django 4 | 5 | ### Django ### 6 | *.log 7 | *.pot 8 | *.pyc 9 | __pycache__/ 10 | local_settings.py 11 | db.sqlite3 12 | media 13 | 14 | # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ 15 | # in your Git repository. Update and uncomment the following line accordingly. 16 | # /staticfiles/ 17 | 18 | ### Django.Python Stack ### 19 | # Byte-compiled / optimized / DLL files 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | 26 | # Distribution / packaging 27 | .Python 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | pip-wheel-metadata/ 41 | share/python-wheels/ 42 | *.egg-info/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .nox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage.xml 66 | *.cover 67 | .hypothesis/ 68 | .pytest_cache/ 69 | 70 | # Translations 71 | *.mo 72 | 73 | # Django stuff: 74 | 75 | # Flask stuff: 76 | instance/ 77 | .webassets-cache 78 | 79 | # Scrapy stuff: 80 | .scrapy 81 | 82 | # Sphinx documentation 83 | docs/_build/ 84 | 85 | # PyBuilder 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | 91 | # IPython 92 | profile_default/ 93 | ipython_config.py 94 | 95 | # pyenv 96 | .python-version 97 | 98 | # pipenv 99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 101 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not 102 | # install all needed dependencies. 103 | #Pipfile.lock 104 | 105 | # celery beat schedule file 106 | celerybeat-schedule 107 | 108 | # SageMath parsed files 109 | *.sage.py 110 | 111 | # Environments 112 | .env 113 | .venv 114 | env/ 115 | venv/ 116 | ENV/ 117 | env.bak/ 118 | venv.bak/ 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | 138 | # Contents of "content" 139 | content/problems/ 140 | content/submissions/ 141 | content/testcase/ 142 | content/contests/ 143 | content/tmp/* 144 | 145 | # End of https://www.gitignore.io/api/django 146 | 147 | # Docs 148 | docs/build 149 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/source/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | # Optionally build your docs in additional formats such as PDF and ePub 17 | formats: all 18 | 19 | # Optionally set the version of Python and requirements required to build your docs 20 | python: 21 | version: 3.7 22 | install: 23 | - requirements: docs/requirements.txt 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Django", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/manage.py", 12 | "console": "integratedTerminal", 13 | "args": [ 14 | "runserver", 15 | "--noreload" 16 | ], 17 | "django": true 18 | }, 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.flake8Enabled": true, 3 | "python.linting.enabled": true, 4 | "python.linting.flake8Args": [ 5 | "--max-line-length=100" 6 | ], 7 | "python.linting.pylintEnabled": false, 8 | "python.linting.mypyEnabled": true, 9 | "files.insertFinalNewline": true, 10 | "restructuredtext.confPath": "${workspaceFolder}/docs/source", 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Vaibhav Sinha, Prateek Kumar, Vishwak Srinivasan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [autojudge: An Online Judge for Coding contests](https://github.com/vbsinha/autojudge) 2 | 3 | [![CircleCI](https://circleci.com/gh/vbsinha/autojudge.svg?style=svg)](https://circleci.com/gh/vbsinha/autojudge) [![Documentation Status](https://readthedocs.org/projects/autojudge/badge/?version=latest)](https://autojudge.readthedocs.io/en/latest/?badge=latest) 4 | 5 | --- 6 | This is an implementation of an online judge that can be used for conducting programming contests as well as managing assignments in a university. 7 | 8 | Apart from the facilities of usual programming contest portals, this portal provides facilities which are well suited for assignment submission. 9 | 10 | These additional features include: Instructor (Problem poster) grades above judge scores, soft and hard deadlines for assignments including penalties, customizable compilation and test script, linter scores et cetera. 11 | 12 | Currently, the judge supports 5 languages: C, C++, Python, Go and Haskell but this list can be extended easily. 13 | 14 | ## Requirements 15 | 16 | To run this application, you will require **Python 3.6 or above**. While we tested this primarily with Python 3.6 or above, we expect it to work for other Python versions > 3 that support Django 3.1.6. 17 | 18 | Other primary requirements are specified in [requirements.txt](requirements.txt). To setup documentation locally, please check the requirements specified in [docs/requirements.txt](docs/requirements.txt). 19 | 20 | ## Setting up and running the application 21 | 22 | The instructions to setup and run this application are specified in our [documentation](https://autojudge.readthedocs.io/en/latest/usage.html). 23 | 24 | ## Understanding how `autojudge` works 25 | 26 | If you are interested in understanding how `autojudge` works, please find the API documentation [here](https://autojudge.readthedocs.io/en/latest/api.html). 27 | 28 | ## License 29 | 30 | This code is licensed under [MIT](LICENSE). 31 | -------------------------------------------------------------------------------- /autojudge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/autojudge/__init__.py -------------------------------------------------------------------------------- /autojudge/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for autojudge. 3 | """ 4 | 5 | import os 6 | from typing import List 7 | 8 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 9 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 10 | 11 | 12 | # Quick-start development settings - unsuitable for production 13 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 14 | 15 | # SECURITY WARNING: keep the secret key used in production secret! 16 | SECRET_KEY = '%iyygwb3^wj9p^m8xzo0%5(v$9a0f&(tovjpfb*h1$11h-hk6f' 17 | 18 | # SECURITY WARNING: don't run with debug turned on in production! 19 | DEBUG = True 20 | 21 | ALLOWED_HOSTS: List[str] = ['*'] 22 | 23 | # The path where content folder is stored: MEDIA_ROOT/content/ 24 | MEDIA_ROOT = BASE_DIR 25 | 26 | MEDIA_URL = '/media/' 27 | 28 | 29 | # Application definition 30 | 31 | INSTALLED_APPS = [ 32 | 'judge.apps.JudgeConfig', 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 'social_django', 40 | ] 41 | 42 | MIDDLEWARE = [ 43 | 'django.middleware.security.SecurityMiddleware', 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.messages.middleware.MessageMiddleware', 49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 50 | ] 51 | 52 | ROOT_URLCONF = 'autojudge.urls' 53 | 54 | TEMPLATES = [ 55 | { 56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 57 | 'DIRS': [], 58 | 'APP_DIRS': True, 59 | 'OPTIONS': { 60 | 'context_processors': [ 61 | 'django.template.context_processors.debug', 62 | 'django.template.context_processors.request', 63 | 'django.contrib.auth.context_processors.auth', 64 | 'django.contrib.messages.context_processors.messages', 65 | 'social_django.context_processors.backends', 66 | 'social_django.context_processors.login_redirect', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'autojudge.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-gb' 109 | 110 | TIME_ZONE = 'Asia/Kolkata' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | 124 | AUTHENTICATION_BACKENDS = ( 125 | # Backends for Google authentication 126 | 'social_core.backends.open_id.OpenIdAuth', 127 | 'social_core.backends.google.GoogleOAuth2', 128 | 129 | 'django.contrib.auth.backends.ModelBackend', 130 | ) 131 | 132 | LOGIN_URL = 'judge:login' 133 | LOGOUT_URL = 'judge:logout' 134 | LOGIN_REDIRECT_URL = 'judge:index' 135 | LOGOUT_REDIRECT_URL = 'judge:index' 136 | SOCIAL_AUTH_URL_NAMESPACE = 'judge:social' 137 | 138 | SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.environ['GOOGLE_OAUTH2_KEY'] 139 | SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.environ['GOOGLE_OAUTH2_SECRET'] 140 | -------------------------------------------------------------------------------- /autojudge/settings_production.py: -------------------------------------------------------------------------------- 1 | # To use these settings during development, run with 2 | # python manage.py runserver --settings=autojudge.settings_production.py 3 | 4 | import os 5 | from typing import List 6 | from .settings import * # noqa: F401, F403 7 | 8 | SECRET_KEY = os.environ.get('AUTOJUDGE_SECRET_KEY') 9 | DEBUG = False 10 | 11 | # Edit/Add the settings during deployment 12 | ALLOWED_HOSTS: List[str] = ['autojudge.com'] 13 | 14 | # Configure PostgreSQL as database 15 | # https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-DATABASES 16 | 17 | # Configure static root and static url 18 | # https://docs.djangoproject.com/en/2.2/howto/static-files/deployment/#serving-static-files-in-production 19 | STATIC_ROOT = 'static_files' 20 | STATIC_URL = 'https://static.autojudge.com/' 21 | 22 | # Security 23 | SECURE_CONTENT_TYPE_NOSNIFF = True 24 | SECURE_BROWSER_XSS_FILTER = True 25 | SECURE_SSL_REDIRECT = True 26 | SESSION_COOKIE_SECURE = True 27 | X_FRAME_OPTIONS = 'DENY' 28 | CSRF_COOKIE_SECURE = True 29 | -------------------------------------------------------------------------------- /autojudge/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | autojudge URL Configuration. 3 | """ 4 | from django.contrib import admin 5 | from django.urls import include, path 6 | from django.views.generic import RedirectView 7 | 8 | urlpatterns = [ 9 | path('', RedirectView.as_view(url='/judge/')), 10 | path('judge/', include('judge.urls')), 11 | path('admin/', admin.site.urls), 12 | ] 13 | -------------------------------------------------------------------------------- /autojudge/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for autojudge. 3 | """ 4 | 5 | import os 6 | 7 | from django.core.wsgi import get_wsgi_application 8 | 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'autojudge.settings') 10 | 11 | application = get_wsgi_application() 12 | -------------------------------------------------------------------------------- /content/Dockerfile: -------------------------------------------------------------------------------- 1 | # Official Ubuntu 18.04 2 | FROM ubuntu:18.04 3 | 4 | ### LANGUAGE SUPPORT BELOW 5 | ### Currently we support C, C++, Python3.6, Java, Go and Haskell 6 | 7 | # Install basic dependencies 8 | # Installing build-essential installs gcc-7 and g++-7 for C / C++ and 9 | # Installing software-properties-common installs python3.6 for Python 10 | RUN apt-get update && apt-get install -y --no-install-recommends \ 11 | build-essential \ 12 | make \ 13 | curl \ 14 | wget \ 15 | ca-certificates \ 16 | software-properties-common && \ 17 | rm -rf /var/lib/apt/lists/* 18 | 19 | # Install golang for Go 20 | RUN apt-get update && apt-get install -y --no-install-recommends \ 21 | golang && \ 22 | rm -rf /var/lib/apt/lists/* 23 | 24 | # Set the PATH variables after golang install 25 | ENV GOPATH=${HOME}/go 26 | ENV PATH=${PATH}:${GOPATH}/bin 27 | 28 | # Install ghc for Haskell 29 | RUN add-apt-repository -y ppa:hvr/ghc && \ 30 | apt-get update && apt-get install -y --no-install-recommends \ 31 | ghc-8.6.5 && \ 32 | rm -rf /var/lib/apt/lists/* 33 | 34 | # Set the PATH variables after ghc install 35 | ENV PATH=${PATH}:/opt/ghc/bin 36 | 37 | ### TIMER_TOOL SUPPORT BELOW 38 | 39 | # Install dependencies for timer_tool (a.k.a. runsolver) 40 | RUN apt-get update && apt-get install -y libnuma-dev && \ 41 | rm -rf /var/lib/apt/lists/* 42 | 43 | # Install timer_tool (a.k.a. runsolver) 44 | RUN curl https://www.cril.univ-artois.fr/~roussel/runsolver/runsolver-3.4.0.tar.bz2 -o ~/runsolver.tar.bz2 && \ 45 | tar -xjf ~/runsolver.tar.bz2 -C ~/ && \ 46 | rm ~/runsolver.tar.bz2 && \ 47 | sed -i 's/include/-include/g' ~/runsolver/src/Makefile && \ 48 | make -C ~/runsolver/src runsolver && \ 49 | mv ~/runsolver/src/runsolver ~/timer_tool && \ 50 | rm -r ~/runsolver/ && \ 51 | mv ~/timer_tool /bin/timer_tool 52 | 53 | # Submission ID, to be set at runtime in `docker run` calls 54 | ENV SUB_ID=-1 55 | 56 | # Set working directory 57 | WORKDIR /app 58 | 59 | # Run the meta script 60 | CMD python3.6 compile_and_test.py --submission_config tmp/sub_run_${SUB_ID}.txt 61 | -------------------------------------------------------------------------------- /content/compile_and_test.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import subprocess 3 | 4 | parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) 5 | parser.add_argument('--submission_config', type=str, 6 | help="""Submission configuration file. Format of this file is: 7 | PROBLEM_CODE 8 | SUBMISSION_ID 9 | SUBMISSION_FORMAT 10 | TIME_LIMIT 11 | MEMORY_LIMIT 12 | TESTCASE_1_ID 13 | TESTCASE_2_ID 14 | TESTCASE_3_ID 15 | ...""") 16 | args = parser.parse_args() 17 | 18 | with open(args.submission_config) as f: 19 | sub_info = [x[:-1] for x in f.readlines()] 20 | 21 | # Retain the first 2 lines alone 22 | subprocess.call(['rm', args.submission_config]) 23 | with open(args.submission_config, "w") as stat_file: 24 | stat_file.write("{}\n{}\n".format(sub_info[0], sub_info[1])) 25 | 26 | # First compile 27 | try: 28 | subprocess.check_output(['./main_compiler.sh', sub_info[0], 29 | 'submission_{}{}'.format(sub_info[1], sub_info[2])], 30 | stderr=subprocess.STDOUT) 31 | except subprocess.CalledProcessError as e: # If compilation fails, end this script here 32 | error_msg = str(e.output.decode('utf-8')) 33 | with open(args.submission_config, "a") as stat_file: 34 | for testcase_id in sub_info[5:]: 35 | log_file_name = 'sub_run_{}_{}.log'.format(sub_info[1], testcase_id) 36 | 37 | with open('tmp/' + log_file_name, "w") as log_file: 38 | log_file.write(error_msg) 39 | 40 | stat_file.write("{} {} 0 0 {}\n" 41 | .format(testcase_id, 42 | 'CE' if e.returncode == 1 else 'NA', log_file_name)) 43 | else: 44 | subprocess.call(['./main_tester.sh'] + sub_info[0:2] + sub_info[3:]) # run tests 45 | subprocess.call(['rm', 'submissions/submission_{}'.format(sub_info[1])]) # remove executable 46 | -------------------------------------------------------------------------------- /content/main_compiler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #################################################### 4 | # Using this script 5 | # ./main_compiler.sh $PROB_CODE $SUB_FILE 6 | # where 7 | # - $PROB_CODE is the problem code of the problem 8 | # - $SUB_FILE is the file that is submitted 9 | #################################################### 10 | 11 | 12 | # All Error Codes pertaining to Compilation 13 | SUCCESS=0 14 | FAILURE=1 15 | 16 | SUB_FDR="submissions" 17 | PROB_FDR="problems" 18 | 19 | PROB_CODE=$1 20 | SUB_FILE=$2 21 | 22 | . ${PROB_FDR}/${PROB_CODE}/compilation_script.sh 23 | 24 | if ! file --mime -b ${SUBPATH} | grep -i -q "ascii" ; then # checking for ASCII source files 25 | return $FAILURE 26 | fi 27 | 28 | # Now perform string-matching to get the extension 29 | # and the corresponding "executable" 30 | SUBPATH=${SUB_FDR}/${SUB_FILE} 31 | EXECPATH=${SUBPATH%.*} 32 | 33 | case "$SUBPATH" in 34 | *.c) 35 | EXTENSION=".c" 36 | compile_c $SUBPATH $EXECPATH 37 | chmod 555 $EXECPATH 38 | ;; 39 | *.cpp) 40 | EXTENSION=".cpp" 41 | compile_cpp $SUBPATH $EXECPATH 42 | chmod 555 $EXECPATH 43 | ;; 44 | *.py) 45 | EXTENSION=".py" 46 | compile_py $SUBPATH $EXECPATH 47 | chmod 555 $EXECPATH 48 | ;; 49 | *.go) 50 | EXTENSION=".go" 51 | compile_go $SUBPATH $EXECPATH 52 | chmod 555 $EXECPATH 53 | ;; 54 | *.hs) 55 | EXTENSION=".hs" 56 | compile_hs $SUBPATH $EXECPATH 57 | chmod 555 $EXECPATH 58 | ;; 59 | *) 60 | EXTENSION=".none" 61 | ;; 62 | esac 63 | 64 | # Return the status of compilation 65 | return $? 66 | -------------------------------------------------------------------------------- /content/main_tester.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ############################################################################# 4 | # Using this script 5 | # ./main_tester.sh $PROB_CODE $SUB_ID $TIMELIMIT $MEMLIMIT [$TESTCASE_ID]+ 6 | # where 7 | # - $PROB_CODE is the problem code 8 | # - $SUB_ID is the submission ID 9 | # - $TIMELIMIT is the time limit 10 | # - $MEMLIMIT is the memory limit 11 | # - $TESTCASE_ID is the testcase ID (pass atleast 1) 12 | ############################################################################# 13 | 14 | # All Error Codes pertaining to Testing 15 | PASS=0 16 | FAIL=1 17 | TLE=2 18 | OOM=3 19 | RE=4 20 | NA=5 21 | 22 | # Assume a directory structure 23 | # content/ 24 | # ├── problems/ 25 | # ├── submissions/ 26 | # ├── testcase/ 27 | # ├── tmp/ 28 | # ├── compile_and_test.py 29 | # ├── main_compiler.sh 30 | # ├── main_tester.sh 31 | # └── Dockerfile 32 | 33 | PROB_FDR="problems" 34 | SUB_FDR="submissions" 35 | TEST_FDR="testcase" 36 | TMP="tmp" 37 | 38 | # Problem code 39 | PROB_CODE=$1 40 | shift 41 | 42 | # Submission ID 43 | SUB_ID=$1 44 | shift 45 | 46 | # Time Limit 47 | TIMELIMIT=$1 48 | shift 49 | 50 | # Memory Limit 51 | MEMLIMIT=$1 52 | shift 53 | 54 | # Run submission function 55 | # If the executable runs fine, then the output validation is done 56 | # Otherwise, an appropriate error code is returned 57 | run_submission() { 58 | SID=$1 59 | TID=$2 60 | TLIMIT=$3 61 | MLIMIT=$4 62 | 63 | # First two lines specify the flags required 64 | # -w /dev/null pipes output of the tool to /dev/null, --vsize-limit gives the virtual size limit 65 | # --cores 0 limits to only one core, --wall-clock-limit handles the time limit 66 | # --var provides a file with specific flags which are used for checking 67 | # The last line runs the process 68 | timer_tool -w /dev/null --vsize-limit $MLIMIT --cores 0 --wall-clock-limit $TLIMIT \ 69 | --var ${TMP}/submission_status_${SID}_${TID}.txt \ 70 | ${SUB_FDR}/submission_${SID} < ${TEST_FDR}/inputfile_${TID}.txt > ${TMP}/sub_output_${SID}_${TID}.txt 2> /dev/null 71 | 72 | # Make all the flags as env vars for checking and remove this file 73 | . ${TMP}/submission_status_${SID}_${TID}.txt 74 | rm ${TMP}/submission_status_${SID}_${TID}.txt 75 | 76 | # This is what we do: 77 | # - Run it with the timer_tool, and then check if the limits are maintained 78 | # - If no, return the appropriate errors 79 | # - If yes, re-run again to get the final submission output 80 | # This is then checked normally using a diff 81 | # The status is appended to the verdict_string along with the memory and time consumed 82 | VERDICT="" 83 | if [ "$TIMEOUT" = true ] ; then 84 | VERDICT=$(error_code_to_string $TLE ${TID}) 85 | echo "Time limit exceeded" > ${TMP}/sub_run_${SID}_${TID}.log 86 | elif [ "$MEMOUT" = true ] ; then 87 | VERDICT=$(error_code_to_string $OOM ${TID}) 88 | echo "Memory limit exceeded" > ${TMP}/sub_run_${SID}_${TID}.log 89 | else 90 | clean_generated_output ${SID} ${TID} # Delete the generated file to prevent any mismatch 91 | ${SUB_FDR}/submission_${SID} < ${TEST_FDR}/inputfile_${TID}.txt > ${TMP}/sub_output_${SID}_${TID}.txt 2> ${TMP}/sub_run_${SID}_${TID}.log 92 | 93 | case "$?" in 94 | "0") 95 | ./${PROB_FDR}/${PROB_CODE}/test_script ${TEST_FDR}/outputfile_${TID}.txt ${TMP}/sub_output_${SID}_${TID}.txt > /dev/null 96 | VERDICT=$(error_code_to_string $? ${TID}) 97 | ;; 98 | "1") 99 | VERDICT=$(error_code_to_string $RE ${TID}) 100 | ;; 101 | *) 102 | VERDICT=$(error_code_to_string $NA ${TID}) 103 | ;; 104 | esac 105 | fi 106 | VERDICT="${VERDICT} ${WCTIME} ${MAXVM} sub_run_${SID}_${TID}.log" 107 | echo ${VERDICT} 108 | } 109 | 110 | clean_generated_output() { 111 | rm ${TMP}/sub_output_${1}_${2}.txt 112 | } 113 | 114 | 115 | # Convert error code to character string 116 | error_code_to_string() { 117 | ERRCODE=$1 118 | TID=$2 119 | 120 | case "$ERRCODE" in 121 | "$PASS") 122 | STRCODE="P" 123 | ;; 124 | "$FAIL") 125 | STRCODE="F" 126 | ;; 127 | "$TLE") 128 | STRCODE="TE" 129 | ;; 130 | "$OOM") 131 | STRCODE="ME" 132 | ;; 133 | "$CE") 134 | STRCODE="CE" 135 | ;; 136 | "$RE") 137 | STRCODE="RE" 138 | ;; 139 | "$NA") 140 | STRCODE="NA" 141 | ;; 142 | *) 143 | STRCODE="NA" 144 | ;; 145 | esac 146 | 147 | echo "$TESTCASE_ID $STRCODE" 148 | } 149 | 150 | # Add executable permission 151 | chmod +x ${PROB_FDR}/${PROB_CODE}/test_script 152 | 153 | # Iterate over all testcase IDs passed as command line arguments 154 | for TESTCASE_ID in "$@"; 155 | do 156 | # Run the submission using run_submission 157 | run_submission ${SUB_ID} ${TESTCASE_ID} ${TIMELIMIT} ${MEMLIMIT} >> ${TMP}/sub_run_${SUB_ID}.txt 158 | 159 | # Remove the generated output files 160 | clean_generated_output ${SUB_ID} ${TESTCASE_ID} 161 | done 162 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | 3 | # You can set these variables from the command line. 4 | SPHINXOPTS = 5 | SPHINXBUILD = sphinx-build 6 | SPHINXPROJ = autojudge 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.1.6 2 | social-auth-app-django==4.0.0 3 | sphinx-autodoc-typehints 4 | -------------------------------------------------------------------------------- /docs/source/_images/contest-created.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/contest-created.png -------------------------------------------------------------------------------- /docs/source/_images/contest-detail-click.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/contest-detail-click.gif -------------------------------------------------------------------------------- /docs/source/_images/contest-form.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/contest-form.gif -------------------------------------------------------------------------------- /docs/source/_images/log-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/log-in.png -------------------------------------------------------------------------------- /docs/source/_images/new-contest-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/new-contest-dashboard.png -------------------------------------------------------------------------------- /docs/source/_images/new-problem-contest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/new-problem-contest.png -------------------------------------------------------------------------------- /docs/source/_images/poster-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/poster-view.png -------------------------------------------------------------------------------- /docs/source/_images/problem-edit-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/problem-edit-delete.png -------------------------------------------------------------------------------- /docs/source/_images/problem-form.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/problem-form.gif -------------------------------------------------------------------------------- /docs/source/_images/problem-test-case.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/problem-test-case.gif -------------------------------------------------------------------------------- /docs/source/_images/submission-participant.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/docs/source/_images/submission-participant.gif -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | The autojudge API Reference 2 | =========================== 3 | This part of the documentation specifies routines and their description used in this project. 4 | 5 | .. toctree:: 6 | :maxdepth: 3 7 | 8 | api/models 9 | api/forms 10 | api/views 11 | api/handler 12 | -------------------------------------------------------------------------------- /docs/source/api/forms.rst: -------------------------------------------------------------------------------- 1 | Forms and input pre-processing 2 | ============================== 3 | 4 | .. automodule:: judge.forms 5 | 6 | Creation forms 7 | -------------- 8 | 9 | NewContestForm 10 | ~~~~~~~~~~~~~~ 11 | .. autoclass:: NewContestForm 12 | :members: 13 | 14 | NewProblemForm 15 | ~~~~~~~~~~~~~~ 16 | .. autoclass:: NewProblemForm 17 | :members: 18 | 19 | NewSubmissionForm 20 | ~~~~~~~~~~~~~~~~~ 21 | .. autoclass:: NewSubmissionForm 22 | :members: 23 | 24 | NewCommentForm 25 | ~~~~~~~~~~~~~~ 26 | .. autoclass:: NewCommentForm 27 | :members: 28 | 29 | Extension forms 30 | --------------- 31 | 32 | AddPersonToContestForm 33 | ~~~~~~~~~~~~~~~~~~~~~~ 34 | .. autoclass:: AddPersonToContestForm 35 | :members: 36 | 37 | AddTestCaseForm 38 | ~~~~~~~~~~~~~~~ 39 | .. autoclass:: AddTestCaseForm 40 | :members: 41 | 42 | AddPosterScoreForm 43 | ~~~~~~~~~~~~~~~~~~ 44 | .. autoclass:: AddPosterScoreForm 45 | :members: 46 | 47 | Updation forms 48 | -------------- 49 | 50 | UpdateContestForm 51 | ~~~~~~~~~~~~~~~~~ 52 | .. autoclass:: UpdateContestForm 53 | :members: 54 | 55 | EditProblemForm 56 | ~~~~~~~~~~~~~~~ 57 | .. autoclass:: EditProblemForm 58 | :members: 59 | 60 | Deletion forms 61 | -------------- 62 | 63 | DeletePersonFromContestForm 64 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 65 | .. autoclass:: DeletePersonFromContestForm 66 | :members: 67 | -------------------------------------------------------------------------------- /docs/source/api/handler.rst: -------------------------------------------------------------------------------- 1 | Handlers and database management 2 | ================================ 3 | 4 | .. automodule:: judge.handler 5 | 6 | Process Functions 7 | ----------------- 8 | 9 | .. autofunction:: process_contest 10 | .. autofunction:: process_problem 11 | .. autofunction:: process_submission 12 | .. autofunction:: process_testcase 13 | .. autofunction:: process_person 14 | .. autofunction:: process_comment 15 | 16 | Addition Functions 17 | ------------------ 18 | 19 | .. autofunction:: add_person_to_contest 20 | .. autofunction:: add_persons_to_contest 21 | 22 | Update Functions 23 | ---------------- 24 | 25 | .. autofunction:: update_problem 26 | .. autofunction:: update_poster_score 27 | .. autofunction:: update_leaderboard 28 | 29 | Getter Functions 30 | ---------------- 31 | 32 | .. autofunction:: get_personcontest_permission 33 | .. autofunction:: get_personproblem_permission 34 | .. autofunction:: get_posters 35 | .. autofunction:: get_participants 36 | .. autofunction:: get_personcontest_score 37 | .. autofunction:: get_submission_status 38 | .. autofunction:: get_submissions 39 | .. autofunction:: get_leaderboard 40 | .. autofunction:: get_comments 41 | .. autofunction:: get_csv 42 | 43 | Deletion Functions 44 | ------------------ 45 | 46 | .. autofunction:: delete_contest 47 | .. autofunction:: delete_problem 48 | .. autofunction:: delete_testcase 49 | .. autofunction:: delete_personcontest 50 | -------------------------------------------------------------------------------- /docs/source/api/models.rst: -------------------------------------------------------------------------------- 1 | Models and Database Schema 2 | ========================== 3 | 4 | .. automodule:: judge.models 5 | 6 | Base Models 7 | ----------- 8 | 9 | Contest 10 | ~~~~~~~ 11 | .. autoclass:: Contest 12 | :members: 13 | :exclude-members: DoesNotExist, MultipleObjectsReturned 14 | 15 | Problem 16 | ~~~~~~~~~ 17 | .. autoclass:: Problem 18 | :members: 19 | :exclude-members: DoesNotExist, MultipleObjectsReturned 20 | 21 | Submission 22 | ~~~~~~~~~~ 23 | .. autoclass:: Submission 24 | :members: 25 | :exclude-members: DoesNotExist, MultipleObjectsReturned 26 | 27 | TestCase 28 | ~~~~~~~~ 29 | .. autoclass:: TestCase 30 | :members: 31 | :exclude-members: DoesNotExist, MultipleObjectsReturned 32 | 33 | Person 34 | ~~~~~~ 35 | .. autoclass:: Person 36 | :members: 37 | :exclude-members: DoesNotExist, MultipleObjectsReturned 38 | 39 | Comment 40 | ~~~~~~~ 41 | .. autoclass:: Comment 42 | :members: 43 | :exclude-members: DoesNotExist, MultipleObjectsReturned 44 | 45 | Derived Models 46 | -------------- 47 | 48 | ContestPerson 49 | ~~~~~~~~~~~~~ 50 | .. autoclass:: ContestPerson 51 | :members: 52 | :exclude-members: DoesNotExist, MultipleObjectsReturned 53 | 54 | SubmissionTestCase 55 | ~~~~~~~~~~~~~~~~~~ 56 | .. autoclass:: SubmissionTestCase 57 | :members: 58 | :exclude-members: DoesNotExist, MultipleObjectsReturned 59 | 60 | PersonProblemFinalScore 61 | ~~~~~~~~~~~~~~~~~~~~~~~ 62 | .. autoclass:: PersonProblemFinalScore 63 | :members: 64 | :exclude-members: DoesNotExist, MultipleObjectsReturned 65 | -------------------------------------------------------------------------------- /docs/source/api/views.rst: -------------------------------------------------------------------------------- 1 | Views and page rendering 2 | ======================== 3 | 4 | .. automodule:: judge.views 5 | 6 | Default Views 7 | ------------- 8 | 9 | .. autofunction:: index 10 | 11 | .. autofunction:: handler404 12 | 13 | .. autofunction:: handler500 14 | 15 | Creation Views 16 | -------------- 17 | 18 | .. autofunction:: new_contest 19 | .. autofunction:: new_problem 20 | 21 | Modification Views 22 | ------------------ 23 | 24 | .. autofunction:: edit_problem 25 | .. autofunction:: add_person 26 | .. autofunction:: add_poster 27 | .. autofunction:: add_participant 28 | 29 | Detail Views 30 | ------------ 31 | 32 | .. autofunction:: contest_detail 33 | .. autofunction:: problem_detail 34 | .. autofunction:: submission_detail 35 | .. autofunction:: get_people 36 | .. autofunction:: get_posters 37 | .. autofunction:: get_participants 38 | .. autofunction:: problem_submissions 39 | 40 | Deletion Views 41 | -------------- 42 | 43 | .. autofunction:: delete_contest 44 | .. autofunction:: delete_problem 45 | .. autofunction:: delete_testcase 46 | 47 | Downloading Views 48 | ----------------- 49 | 50 | .. autofunction:: contest_scores_csv 51 | .. autofunction:: problem_starting_code 52 | .. autofunction:: problem_compilation_script 53 | .. autofunction:: problem_test_script 54 | .. autofunction:: problem_default_script 55 | .. autofunction:: submission_download 56 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- Path setup -------------------------------------------------------------- 3 | 4 | import os 5 | import sys 6 | import django 7 | import sphinx_rtd_theme 8 | 9 | sys.path.insert(0, os.path.abspath('../..')) 10 | 11 | os.environ["DJANGO_SETTINGS_MODULE"] = "autojudge.settings" 12 | django.setup() 13 | 14 | # -- Project information ----------------------------------------------------- 15 | 16 | project = 'autojudge' 17 | copyright = '2019, Vaibhav Sinha, Prateek Kumar, Vishwak Srinivasan' 18 | author = 'Vaibhav Sinha, Prateek Kumar, Vishwak Srinivasan' 19 | 20 | # The short X.Y version 21 | version = '' 22 | # The full version, including alpha/beta/rc tags 23 | release = '' 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | extensions = [ 28 | 'sphinx.ext.autodoc', 29 | 'sphinx_autodoc_typehints' 30 | ] 31 | 32 | autodoc_member_order = 'bysource' 33 | source_suffix = '.rst' 34 | master_doc = 'index' 35 | language = None 36 | 37 | exclude_patterns = [] 38 | pygments_style = 'sphinx' 39 | 40 | html_theme = 'sphinx_rtd_theme' 41 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 42 | html_theme_options = { 43 | 'collapse_navigation': False 44 | } 45 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to autojudge's documentation! 2 | ===================================== 3 | 4 | Setting up and running ``autojudge`` 5 | ------------------------------------ 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | 10 | usage 11 | 12 | The internals of the ``judge`` app in ``autojudge`` 13 | --------------------------------------------------- 14 | 15 | .. toctree:: 16 | :maxdepth: 1 17 | 18 | api 19 | -------------------------------------------------------------------------------- /docs/source/usage.rst: -------------------------------------------------------------------------------- 1 | The autojudge "Install and Use" Reference 2 | ========================================= 3 | This section of the documentation will describe how to install the tool in your system / server and subsequently play around with it - basically how to use ``autojudge``. 4 | 5 | .. toctree:: 6 | :maxdepth: 3 7 | 8 | usage/install 9 | usage/manual 10 | -------------------------------------------------------------------------------- /docs/source/usage/install.rst: -------------------------------------------------------------------------------- 1 | Installing ``autojudge`` 2 | ======================== 3 | 4 | ``autojudge`` is a tool developed for automating evaluation for code submission in coding contests and in assigments at college. For your convenience, we have split this usage manual into 3 phases. 5 | 6 | Phase 1 : Get ``autojudge`` and set up your environment 7 | ------------------------------------------------------- 8 | 9 | Phase 1a: Getting ``autojudge`` 10 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | ``autojudge`` is available on GitHub, and you can download a version of your choice from the `releases page `_. We prefer that you use the latest version. 13 | 14 | Extract the compressed files and you now have ``autojudge`` ready to use. 15 | 16 | If you are a fan of ``master``, then clone the repository, either using ``git`` or by downloading from GitHub from `here `__. 17 | 18 | Phase 1b: Setting up your environment 19 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | 21 | The evaluation of submissions are conducted on a Docker image that is built while initializing the application. Please install Docker using the instructions provided on `their installation page `__. 22 | 23 | If you are very conservative about populating your default environment with random Python packages, create a virtual environment for installing some *new* packages either using ``virtualenv`` or ``conda-env``. 24 | 25 | Install the requirements specified in |requirements.txt|_. Don't forget to activate your environment if you have one. 26 | 27 | .. |requirements.txt| replace:: ``requirements.txt`` 28 | .. _requirements.txt: ../../../requirements.txt 29 | 30 | If you going to deploy ``autojudge``, please install PostgreSQL using the instructions provided on `their installation page `__. 31 | 32 | Phase 2 : Run ``autojudge`` 33 | --------------------------- 34 | 35 | Activate your environment. 36 | 37 | Create and apply database migrations in Django with the following commands: 38 | 39 | .. code:: bash 40 | 41 | python manage.py makemigrations 42 | python manage.py migrate 43 | 44 | There are two ways of using ``autojudge``. 45 | 46 | Development 47 | ~~~~~~~~~~~ 48 | 49 | To run ``autojudge`` locally: 50 | 51 | .. code:: bash 52 | 53 | python manage.py runserver 54 | 55 | Go to ``localhost:8000`` in your favourite browser. Keep yourself connected to internet for full functionality as certain frontend support such as JavaScript scripts are pulled from the internet. 56 | 57 | The program |submission_watcher_saver.py|_ scores the submissions serially in the chronological order of submissions. It can be started anytime after the server has started, but it is preferred that the program be kept running in parallel with the server. Run it using: 58 | 59 | .. |submission_watcher_saver.py| replace:: ``submission_watcher_saver.py`` 60 | .. _submission_watcher_saver.py: ../../../submission_watcher_saver.py 61 | 62 | .. code:: bash 63 | 64 | python submission_watcher_saver.py 65 | 66 | 67 | Production 68 | ~~~~~~~~~~ 69 | 70 | The procedure to deploy ``autojudge`` is different from running locally. Below are a series of steps that will help you deploy ``autojudge``. 71 | 72 | Set the environmental variable ``AUTOJUDGE_SECRET_KEY`` to a random string, which should not be exposed to anyone. Think of it to be a private key. 73 | 74 | Now modify a few more settings to |settings_production.py|_. The first is to setup the database. We suggest using a PostgreSQL database. This modification can be done by adding the below dictionary to |settings_production.py|_. Modify the values ``NAME``, ``USER``, ``PASSWORD``, ``HOST`` and ``PORT`` accordingly. 75 | 76 | .. code:: python 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.postgresql', 81 | 'NAME': 'mydatabase', # Sample 82 | 'USER': 'mydatabaseuser', # Sample 83 | 'PASSWORD': 'mypassword', # Sample 84 | 'HOST': '127.0.0.1', # Sample 85 | 'PORT': '5432', # Sample 86 | } 87 | } 88 | 89 | 90 | Next we setup the ``STATIC_ROOT`` path, the location where you would like the static files to be generated. This has to be set in |settings_production.py|_. 91 | 92 | To generate the static files, run: 93 | 94 | .. code:: bash 95 | 96 | python manage.py collectstatic --settings=autojudge.settings_production.py 97 | 98 | The static files are generated in the path specified by ``STATIC_ROOT`` previously. 99 | 100 | Now host the static files on a server and configure the URL in ``STATIC_URL`` in |settings_production.py|_. If you have hosted the generated static files at https://static.autojudge.com, then change the ``STATIC_URL`` to https://static.autojudge.com/ (note the trailing slash is required). 101 | 102 | You could optionally setup a cache server. Instructions to do this are specified `here `__. 103 | 104 | Configure the security settings in |settings_production.py|_ (leave it to the default values if you will be hosting on ``https``). 105 | 106 | .. |settings_production.py| replace:: ``settings_production.py`` 107 | .. _settings_production.py: ../../../autojudge/settings_production.py 108 | 109 | To configure the Apache server using ``WSGI``, follow the instructions `here `__. 110 | 111 | And finally, set environment variable ``DJANGO_SETTINGS_MODULE`` to ``autojudge.settings_production`` as opposed to ``autojudge.settings`` which is present by default. 112 | -------------------------------------------------------------------------------- /docs/source/usage/manual.rst: -------------------------------------------------------------------------------- 1 | User Manual for ``autojudge`` 2 | ============================= 3 | 4 | Some important abstractions / terminology used in ``autojudge`` 5 | --------------------------------------------------------------- 6 | 7 | .. note:: 8 | Please make note of the terms in **bold** 9 | 10 | The judge works on graph between **contests** and **users**. A **contest** consists of a set of **problems**. A **user** is, well, a **user** - with different roles. 11 | 12 | A user can be either a **poster**, **participant** or neither. A **user** is associated with the **contest** with one and only one role - either a **poster**, **participant** or neither. 13 | 14 | The user who creates a new **contest** becomes the **poster** for the **contest** by default. 15 | This user can add more **posters** to help coordinate the **contest** (perhaps by setting new **problems**, verifying and commenting on **submissions**, and so on). 16 | 17 | While creating a new **contest**, the first **poster** has an option to either allow select **participants**, or to leave it open for all. 18 | The former kind of a **contest** is a **private contest**, and the latter kind of a **contest** is a **public contest** (for obvious reasons). No **poster** is allowed to take part in a **contest** as a **participant** i.e., he/she cannot submit solutions. 19 | 20 | If the **contest** is **public**, every user is either a **poster** or a **participant**. If the **contest** is **private**, a user can either be a **poster**, a **participant** or neither - in which case, he/she will not be permitted to participant in the **contest**. 21 | 22 | Maybe a short example will help you understand if something is confusing.... 23 | 24 | Example: 25 | ~~~~~~~~ 26 | 27 | Take the case of a course assignment with programming questions. These programming questions could compose a **contest**, where each question is a **problem**. The instructor and the TAs can be thought of as the **posters**, while registered students for the course would be **participants**. Students not registered for the course will not be able to participate in this **contest** - as you would expect. 28 | 29 | Hands-on with ``autojudge`` 30 | --------------------------- 31 | 32 | Creating your account / Logging in 33 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 | 35 | You need to be logged in to use ``autojudge``. On the home page, click ``LOG IN`` (see the top right corner in the image below) 36 | 37 | .. figure:: ../_images/log-in.png 38 | :width: 400 39 | :align: center 40 | :alt: Log in 41 | 42 | If this is being used at an institution, please make sure you log in with your institutional account. Currently, we support Google OAuth logins. 43 | 44 | Creating a contest 45 | ~~~~~~~~~~~~~~~~~~ 46 | 47 | Once you are logged in, follow the steps below to create a new contest. 48 | 49 | 1. Click the ``New Contest`` button on the dashboard (see beneath the blue header in the image below) 50 | 51 | .. figure:: ../_images/new-contest-dashboard.png 52 | :width: 400 53 | :align: center 54 | :alt: New contest dashboard 55 | 56 | 2. Fill out the form for creating a new contest. 57 | 58 | .. figure:: ../_images/contest-form.gif 59 | :width: 400 60 | :align: center 61 | :alt: Contest form 62 | 63 | .. note:: 64 | The contest name distinguishes contests, hence every contest must have a unique name. Failure to provide a unique name will throw an interactive error. 65 | 66 | .. note:: 67 | *Penalty* is a value between 0 and 1 and specifies the per day penalty on submissions made after *soft end date*. For example: a contest having 0.1 penalty for example, would give 90% of the actually scored points by a submission if it is made within 24 hours after *soft end date* but before *hard end date*. 68 | 69 | .. note:: 70 | It is advised that *linter scoring* be disabled unless all code submissions are made in Python. 71 | 72 | .. note:: 73 | Enable *poster scoring* if you would like the posters to give points in addition to those given by the judge. 74 | 75 | You should be able to see the newly created contest on your dashboard. No one else would be able to see this new contest on their dashboard until the start time of this contest. 76 | 77 | .. figure:: ../_images/contest-created.png 78 | :width: 400 79 | :align: center 80 | :alt: Contest created 81 | 82 | Click on the contest in the link on the dashboard to edit it. 83 | 84 | .. figure:: ../_images/contest-detail-click.gif 85 | :width: 400 86 | :align: center 87 | :alt: Contest detail click 88 | 89 | To add more posters to the contest, click on ``SEE POSTERS``. 90 | You can add one or more posters by adding their emails in a comma separated list after clicking on ``ADD POSTER``. 91 | The new poster(s) would now be able to see this contest on their dashboard (even before the start time). They can also edit the contest. 92 | To delete posters, click on the red bin button adjacent to each poster's email ID. 93 | 94 | .. figure:: ../_images/poster-view.png 95 | :width: 400 96 | :align: center 97 | :alt: Poster view 98 | 99 | In the case of a private contest, the poster(s) can also see a ``SEE PARTICIPANTS`` button. 100 | Clicking this will lead them to a page where they can edit the ``Participant`` list in the same manner as the poster list. 101 | 102 | .. note:: 103 | Trying to add a user both as a participant and a poster will not be permitted. 104 | 105 | Any of the posters can update the dates of the contest by clicking on ``UPDATE DATES``. 106 | Please update the dates before they pass, and attempting to do so will throw an interactive error. 107 | 108 | Note that a participant cannot add or delete other participants or posters. Also he/she cannot update the dates. 109 | 110 | A poster can also delete a contest using the button at the bottom of the contest page. 111 | 112 | Managing problems in a contest 113 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 114 | 115 | A contest consists of problems. Problems can be added, edited or deleted by the posters of the contest. 116 | 117 | A problem can be added to a contest only before the start time of the contest. 118 | To add a problem to the contest, follow the steps below: 119 | 120 | 1. Click ``ADD PROBLEM`` from the contest's homepage. 121 | 122 | .. figure:: ../_images/new-problem-contest.png 123 | :width: 400 124 | :align: center 125 | :alt: New problem contest 126 | 127 | 2. Fill the form that follows. Short descriptions for fields in the form are provided. 128 | 129 | .. figure:: ../_images/problem-form.gif 130 | :width: 400 131 | :align: center 132 | :alt: Problem form 133 | 134 | .. note:: 135 | The problem code distinctly identifies a problem, hence every problem must have a unique name. Failure to provide a unique name will throw an interactive error. 136 | 137 | .. note:: 138 | In case the compilation script and test script are left empty, the default ones are used. The default scripts can be downloaded from the links just below the Browse button for each of them. 139 | 140 | 3. After submission, you can add or delete **test cases** on the problem page. There are two kinds of test cases - **public test cases** and **private test cases**. **Public test cases** would be visible to the participants while **private test cases** won't be visible. 141 | 142 | .. figure:: ../_images/problem-test-case.gif 143 | :width: 400 144 | :align: center 145 | :alt: Problem test case 146 | 147 | .. note:: 148 | Test case addition and deletion will be allowed only till the start of the contest. 149 | 150 | Posters can edit or delete an existing problem in the contest using the 2 icons on the top-right of the problem page (see to the right of the problem title). 151 | 152 | .. figure:: ../_images/problem-edit-delete.png 153 | :width: 400 154 | :align: center 155 | :alt: Problem edit delete 156 | 157 | .. note:: 158 | Deletion of a problem is only allowed until the contest begins. 159 | 160 | Submitting and checking submissions: Participant end 161 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 162 | 163 | A participant can make **submission** for a problem from the problem page. Select the language and upload the submission file. 164 | 165 | To check your previous **submissions**, and the judge's score for your **submissions**, click ``SEE MY PREVIOUS SUBMISSIONS`` at the bottom of the problem page. 166 | 167 | If you want a detailed verdict of the judge for a **submission**, click on that **submission**. You can see the verdict of the judge on individual **test cases** concisely below or in detail by clicking on a **test case**. You can also download your **submission** from here as well. 168 | 169 | Once the contest begins and participants start submitting, the **leaderboard** is initialized and can be seen on the contest page. 170 | The leaderboard lists the participants in the decreasing order of sum of scores in individual problems in the contest. 171 | 172 | Please note that the *max score* seen on the problem page is the maximum score possible per test case. For example, if there are 5 test cases and *max score* is 10 points, then a participant can score at most 50 points for that problem by the judge (i.e., notwithstanding the linter score and/or the poster score). 173 | 174 | Managing submissions from the poster's side 175 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 176 | 177 | Posters can see all the **submissions** pertaining to a problem in the problem page by clicking ``SEE ALL SUBMISSIONS`` at the bottom of the page. 178 | 179 | **Submissions** made by all the participants for a given problem would be available here. Click on any **submission** to open the **submission** page. The layout is the same as that seen by the participants. 180 | 181 | In case *poster scoring* is enabled for the contest, the poster can give a score from the **submission** page by clicking on ``UPDATE POSTER SCORE`` on the top right adjacent to the download icon. Poster score can be any integer. 182 | 183 | The poster can also view the submission file from the **submission** page by downloading it via the ``DOWNLOAD`` button on the top right. 184 | 185 | Commenting 186 | ~~~~~~~~~~ 187 | 188 | Posters and participants can also comment. A **comment** by a participant to a problem can be viewed by all posters but not by any other participants - similar to private comments on Google Classroom. 189 | 190 | To see old **comments** or create a new one, click on ``SEE ALL SUBMISSIONS`` on the problem page. 191 | 192 | Miscellaneous 193 | ~~~~~~~~~~~~~ 194 | 195 | A poster can download a CSV file containing the best scores of all participants in a contest by clicking on ``DOWNLOAD SCORES`` from the contest page. 196 | -------------------------------------------------------------------------------- /judge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/judge/__init__.py -------------------------------------------------------------------------------- /judge/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import Contest, Problem, Person, Submission, TestCase, Comment 5 | from .models import ContestPerson, SubmissionTestCase, PersonProblemFinalScore 6 | 7 | 8 | class ContestAdmin(admin.ModelAdmin): 9 | readonly_fields = ('id',) 10 | 11 | 12 | admin.site.register(Contest, ContestAdmin) 13 | admin.site.register(Problem) 14 | admin.site.register(Person) 15 | admin.site.register(Submission) 16 | admin.site.register(TestCase) 17 | admin.site.register(Comment) 18 | admin.site.register(ContestPerson) 19 | admin.site.register(SubmissionTestCase) 20 | admin.site.register(PersonProblemFinalScore) 21 | -------------------------------------------------------------------------------- /judge/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class JudgeConfig(AppConfig): 5 | name = 'judge' 6 | -------------------------------------------------------------------------------- /judge/default/comment.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/judge/default/comment.yml -------------------------------------------------------------------------------- /judge/default/compilation_script.sh: -------------------------------------------------------------------------------- 1 | # Enter the compilation script here. 2 | # For every language, write a function named "compile_()" 3 | # SUBPATH is the path of the file that has been submitted 4 | # EXPATH is the path for the executable generated 5 | 6 | # All required error codes 7 | SUCCESS=0 8 | FAILURE=1 9 | 10 | # This is the function to compile .c files using gcc 11 | compile_c() { 12 | SUBPATH=$1 13 | EXPATH=$2 14 | if gcc $SUBPATH -lm -o $EXPATH ; then 15 | return $SUCCESS 16 | else 17 | return $FAILURE 18 | fi 19 | } 20 | 21 | # This is the function to compile .cpp files using g++ 22 | compile_cpp() { 23 | SUBPATH=$1 24 | EXPATH=$2 25 | if g++ $SUBPATH -lm -o $EXPATH ; then 26 | return $SUCCESS 27 | else 28 | return $FAILURE 29 | fi 30 | } 31 | 32 | # This is the function to "compile" Python files 33 | compile_py() { 34 | SUBPATH=$1 35 | EXPATH=$2 36 | echo "#!/usr/bin/python3.6" > $EXPATH 37 | cat $SUBPATH >> $EXPATH 38 | return $SUCCESS 39 | } 40 | 41 | # This is the function to compile .go files using go build 42 | compile_go() { 43 | SUBPATH=$1 44 | EXPATH=$2 45 | if go build $SUBPATH -o $EXPATH ; then 46 | return $SUCCESS 47 | else 48 | return $FAILURE 49 | fi 50 | } 51 | 52 | # This is the function to compile .hs files using ghc 53 | compile_hs() { 54 | SUBPATH=$1 55 | EXPATH=$2 56 | if ghc $SUBPATH -o $EXPATH ; then 57 | rm -f $EXPATH.hi $EXPATH.o 58 | return $SUCCESS 59 | else 60 | return $FAILURE 61 | fi 62 | } 63 | -------------------------------------------------------------------------------- /judge/default/examples/difffloat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | 5 | PASS = 0 6 | FAIL = 1 7 | NA = 5 8 | 9 | 10 | def wa_exit(): 11 | sys.exit(FAIL) 12 | 13 | 14 | # Call the progarm by difffloat.py testcaseoutput.txt producedoutput.txt 15 | if len(sys.argv) != 3: 16 | print('Incorrect number of arguments') 17 | sys.exit(NA) 18 | 19 | testcasefile = open(sys.argv[1], 'r') 20 | outputfile = open(sys.argv[2], 'r') 21 | 22 | testcaselines = [] 23 | try: 24 | for line in testcasefile.readlines(): 25 | testcaselines.append([float(x) for x in line.strip().split()]) 26 | except ValueError: 27 | print('Non convertible to float') 28 | wa_exit() 29 | 30 | print(testcaselines) 31 | try: 32 | for i, line in enumerate(outputfile.readlines()): 33 | float_line = [float(x) for x in line.strip().split()] 34 | if i >= len(testcaselines) or len(testcaselines[i]) != len(float_line): 35 | print('Different number of lines or number of values on some line') 36 | wa_exit() 37 | for j, vapprox in enumerate(float_line): 38 | if abs(1 - (vapprox / testcaselines[i][j])) > 1e-6: 39 | # Test for relative error 40 | # See https://en.wikipedia.org/wiki/Approximation_error 41 | print('Wrong value') 42 | wa_exit() 43 | except ValueError: 44 | print('Non convertible to float') 45 | wa_exit() 46 | 47 | # Correct answer 48 | print('Correct answer') 49 | sys.exit(PASS) 50 | -------------------------------------------------------------------------------- /judge/default/inputfile.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/judge/default/inputfile.txt -------------------------------------------------------------------------------- /judge/default/outputfile.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/judge/default/outputfile.txt -------------------------------------------------------------------------------- /judge/default/test_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # All required error codes 4 | PASS=0 5 | FAIL=1 6 | NA=5 7 | 8 | # Enter the testing script here. 9 | # If the output "passes" the requirements, then 10 | # return $PASS, otherwise return $FAIL 11 | # Any technical glitch returns $NA 12 | diff -b -Z $1 $2 > /dev/null 13 | 14 | case "$?" in 15 | "0") 16 | return $PASS 17 | ;; 18 | "1") 19 | return $FAIL 20 | ;; 21 | *) 22 | return $NA 23 | ;; 24 | esac 25 | -------------------------------------------------------------------------------- /judge/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.core.validators import RegexValidator, validate_email, EMPTY_VALUES 3 | 4 | 5 | CONTEST_START_SOFT_END_INVALID = -1 6 | CONTEST_SOFT_END_HARD_END_INVALID = +1 7 | CONTEST_VALID = 0 8 | 9 | 10 | def _check_valid_date(cleaned_data): 11 | cont_start = cleaned_data.get("contest_start") 12 | cont_soft_end = cleaned_data.get("contest_soft_end") 13 | cont_hard_end = cleaned_data.get("contest_hard_end") 14 | if cont_start and cont_soft_end and cont_hard_end: 15 | if cont_start > cont_soft_end: 16 | return CONTEST_START_SOFT_END_INVALID 17 | if cont_soft_end > cont_hard_end: 18 | return CONTEST_SOFT_END_HARD_END_INVALID 19 | return CONTEST_VALID 20 | 21 | 22 | class MultiEmailField(forms.Field): 23 | """ 24 | Subclass of forms.Field to support a list of email addresses. 25 | """ 26 | description = 'Email addresses' 27 | 28 | def __init__(self, *args, **kwargs): 29 | self.token = kwargs.pop('token', ',') 30 | super(MultiEmailField, self).__init__(*args, **kwargs) 31 | 32 | def to_python(self, value): 33 | if value in EMPTY_VALUES: 34 | return [] 35 | return [item.strip() for item in value.split(self.token) if item.strip()] 36 | 37 | def clean(self, value): 38 | value = self.to_python(value) 39 | if value in EMPTY_VALUES and self.required: 40 | raise forms.ValidationError('This field is required.') 41 | for email in value: 42 | try: 43 | validate_email(email) 44 | except forms.ValidationError: 45 | raise forms.ValidationError("'{}' is not a valid email address.".format(email)) 46 | return value 47 | 48 | 49 | class NewContestForm(forms.Form): 50 | """ 51 | Form for creating a new Contest. 52 | """ 53 | 54 | contest_name = forms.CharField(label='Contest name', max_length=50, strip=True, 55 | widget=forms.TextInput(attrs={'class': 'form-control'}), 56 | help_text='Enter the name of the contest.') 57 | """Contest Name""" 58 | 59 | contest_start = forms.DateTimeField(label='Start Date', 60 | widget=forms.DateTimeInput(attrs={'class': 'form-control'}), 61 | help_text='Specify when the contest begins.') 62 | """Contest Start Timestamp""" 63 | 64 | contest_soft_end = forms.DateTimeField(label='Soft End Date for contest', 65 | widget=forms.DateTimeInput( 66 | attrs={'class': 'form-control'}), 67 | help_text='Specify after when would you like to \ 68 | penalize submissions.') 69 | """Contest Soft End Timestamp""" 70 | 71 | contest_hard_end = forms.DateTimeField(label='Hard End Date for contest', 72 | widget=forms.DateTimeInput( 73 | attrs={'class': 'form-control'}), 74 | help_text='Specify when the contest completely ends.') 75 | """Contest Hard End Timestamp""" 76 | 77 | penalty = forms.DecimalField(label='Penalty', min_value=0.0, max_value=1.0, 78 | widget=forms.NumberInput(attrs={'class': 'form-control'}), 79 | help_text='Enter a penalty factor between 0 and 1.') 80 | """Contest Penalty factor""" 81 | 82 | is_public = forms.BooleanField(label='Is this contest public?', required=False) 83 | """Contest is_public property""" 84 | 85 | enable_linter_score = forms.BooleanField(label='Enable linter scoring', 86 | required=False) 87 | """Contest enable_linter_score property""" 88 | 89 | enable_poster_score = forms.BooleanField(label='Enable poster scoring', 90 | required=False, initial=True) 91 | """Contest enable_poster_score property""" 92 | 93 | def clean(self): 94 | cleaned_data = super().clean() 95 | error = _check_valid_date(cleaned_data) 96 | if error == CONTEST_START_SOFT_END_INVALID: 97 | err_ = forms.ValidationError("Contest cannot end before it starts!", 98 | code='invalid') 99 | self.add_error('contest_start', err_) 100 | self.add_error('contest_soft_end', err_) 101 | 102 | elif error == CONTEST_SOFT_END_HARD_END_INVALID: 103 | err_ = forms.ValidationError( 104 | "The final deadline cannot be before the soft deadline.", code='invalid') 105 | self.add_error('contest_soft_end', err_) 106 | self.add_error('contest_hard_end', err_) 107 | 108 | 109 | class UpdateContestForm(forms.Form): 110 | """ 111 | Form to update the timeline of the Contest 112 | """ 113 | 114 | contest_start = forms.DateTimeField(label='Start Date', 115 | widget=forms.DateTimeInput(attrs={'class': 'form-control'}), 116 | help_text='Specify when the contest begins.') 117 | """Contest Start Timestamp""" 118 | 119 | contest_soft_end = forms.DateTimeField(label='Soft End Date', 120 | widget=forms.DateTimeInput( 121 | attrs={'class': 'form-control'}), 122 | help_text='Specify after when would you like to \ 123 | penalize submissions.') 124 | """Contest Soft End Timestamp""" 125 | 126 | contest_hard_end = forms.DateTimeField(label='Hard End Date', 127 | widget=forms.DateTimeInput( 128 | attrs={'class': 'form-control'}), 129 | help_text='Specify when the contest completely ends.') 130 | """Contest Hard End Timestamp""" 131 | 132 | def clean(self): 133 | cleaned_data = super().clean() 134 | error = _check_valid_date(cleaned_data) 135 | if error == CONTEST_START_SOFT_END_INVALID: 136 | err_ = forms.ValidationError("Contest cannot end before it starts!", 137 | code='invalid') 138 | self.add_error('contest_start', err_) 139 | self.add_error('contest_soft_end', err_) 140 | 141 | elif error == CONTEST_SOFT_END_HARD_END_INVALID: 142 | err_ = forms.ValidationError( 143 | "The final deadline cannot be before the soft deadline.", code='invalid') 144 | self.add_error('contest_soft_end', err_) 145 | self.add_error('contest_hard_end', err_) 146 | 147 | 148 | class AddPersonToContestForm(forms.Form): 149 | """ 150 | Form to add a Person to a Contest. 151 | """ 152 | 153 | emails = MultiEmailField( 154 | label='Emails', 155 | widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 156 | help_text='Enter emails separated using commas') 157 | """Email ID of the person""" 158 | 159 | 160 | class DeletePersonFromContestForm(forms.Form): 161 | """ 162 | Form to remove a Person from a Contest. 163 | """ 164 | 165 | email = forms.EmailField(label='Email', widget=forms.HiddenInput()) 166 | """Email ID of the person""" 167 | 168 | 169 | class NewProblemForm(forms.Form): 170 | """ 171 | Form for adding a new Problem. 172 | """ 173 | 174 | code = forms.CharField(label='Code', max_length=10, widget=forms.TextInput( 175 | attrs={'class': 'form-control'}), 176 | validators=[RegexValidator(r'^[a-z0-9]+$')], 177 | help_text='Enter a alphanumeric code in lowercase letters as a \ 178 | unique identifier for the problem.') 179 | """Problem Code Field""" 180 | 181 | name = forms.CharField(label='Title', max_length=50, strip=True, 182 | widget=forms.TextInput(attrs={'class': 'form-control'}), 183 | help_text='Give a catchy problem name.') 184 | """Problem Name Field""" 185 | 186 | statement = forms.CharField(label='Statement', strip=True, widget=forms.HiddenInput(), 187 | help_text='Provide a descriptive statement of the problem.') 188 | """Problem Statement Field""" 189 | 190 | input_format = forms.CharField(label='Input Format', strip=True, widget=forms.HiddenInput(), 191 | help_text='Give a lucid format for the input that a \ 192 | participant should expect.') 193 | """Problem Input Format Field""" 194 | 195 | output_format = forms.CharField(label='Output Format', strip=True, widget=forms.HiddenInput(), 196 | help_text='Give a lucid format for the output that a \ 197 | participant should follow.') 198 | """Problem Output Format Field""" 199 | 200 | difficulty = forms.IntegerField(label='Difficulty', 201 | widget=forms.NumberInput(attrs={'class': 'form-control'}), 202 | min_value=0, max_value=5, initial=0, 203 | help_text='Specify a difficulty level between 1 and 5 for \ 204 | the problem. If this is unknown, leave it as 0.') 205 | """Problem Difficulty Field""" 206 | 207 | time_limit = forms.DurationField(label='Time Limit (in seconds)', 208 | widget=forms.NumberInput(attrs={'class': 'form-control'}), 209 | initial=0, help_text='Specify a time limit in seconds \ 210 | for the execution of the program.') 211 | """Problem Time limit""" 212 | 213 | memory_limit = forms.IntegerField(label='Memory Limit (in MB)', 214 | widget=forms.NumberInput(attrs={'class': 'form-control'}), 215 | initial=0, min_value=0, 216 | help_text='Specify a memory limit in MB for the execution \ 217 | of the program.') 218 | """Problem Memory limit""" 219 | 220 | file_exts = forms.CharField(label='Permitted File extensions for submissions', 221 | widget=forms.TextInput(attrs={'class': 'form-control'}), 222 | max_length=100, required=False, 223 | validators=[RegexValidator(r'^\.[a-zA-Z0-9]+(,\.[a-zA-Z0-9]+)*$')], 224 | empty_value='.py,.cpp', 225 | help_text='Give a comma separated list of extensions accepted \ 226 | for submissions.') 227 | """Problem File Extensions""" 228 | 229 | starting_code = forms.FileField(label='Starting code', 230 | widget=forms.FileInput(attrs={'class': 'form-control-file'}), 231 | allow_empty_file=False, required=False, 232 | help_text='Upload some starting code to help participants \ 233 | get started.') 234 | """Problem Starting code""" 235 | 236 | max_score = forms.IntegerField(label='Maximum score', 237 | widget=forms.NumberInput(attrs={'class': 'form-control'}), 238 | initial=10, min_value=0, 239 | help_text='Specify the maximum score that passing testcase \ 240 | for this problem would award.') 241 | """Problem Max Score""" 242 | 243 | compilation_script = forms.FileField(label='Compilation script', 244 | widget=forms.FileInput( 245 | attrs={'class': 'form-control-file'}), 246 | allow_empty_file=False, required=False, 247 | help_text='Upload a custom compilation script.') 248 | """Problem Compilation Script""" 249 | 250 | test_script = forms.FileField(label='Testing script', 251 | widget=forms.FileInput(attrs={'class': 'form-control-file'}), 252 | allow_empty_file=False, required=False, 253 | help_text='Upload a custom testing script.') 254 | """Problem Test Script""" 255 | 256 | 257 | class EditProblemForm(forms.Form): 258 | """ 259 | Form for editing an existing problem. 260 | """ 261 | 262 | name = forms.CharField(label='Title', max_length=50, strip=True, 263 | widget=forms.TextInput(attrs={'class': 'form-control'}), 264 | help_text='Give a catchy problem name.') 265 | """Problem Name Field""" 266 | 267 | statement = forms.CharField(label='Statement', strip=True, widget=forms.HiddenInput(), 268 | help_text='Provide a descriptive statement of the problem.') 269 | """Problem Statement Field""" 270 | 271 | input_format = forms.CharField(label='Input Format', strip=True, widget=forms.HiddenInput(), 272 | help_text='Give a lucid format for the input that a \ 273 | participant should expect.') 274 | """Problem Input Format Field""" 275 | 276 | output_format = forms.CharField(label='Output Format', strip=True, widget=forms.HiddenInput(), 277 | help_text='Give a lucid format for the output that a \ 278 | participant should follow.') 279 | """Problem Output Format Field""" 280 | 281 | difficulty = forms.IntegerField(label='Difficulty', 282 | widget=forms.NumberInput(attrs={'class': 'form-control'}), 283 | initial=0, min_value=0, max_value=5, 284 | help_text='Specify a difficulty level between 1 and 5 for \ 285 | the problem. If this is unknown, leave it as 0.') 286 | """Problem Difficulty Field""" 287 | 288 | 289 | class NewSubmissionForm(forms.Form): 290 | """ 291 | Form to create a new Submission. 292 | """ 293 | # TODO For now choices are hard coded 294 | file_type = forms.ChoiceField(label='File type', choices=[ 295 | ('.cpp', 'C++'), 296 | ('.c', 'C'), 297 | ('.py', 'Python3.6'), 298 | ('.go', 'Go'), 299 | ('.hs', 'Haskell'), 300 | ]) 301 | """Choices of file type""" 302 | 303 | submission_file = forms.FileField(label='Choose file', required=True, allow_empty_file=False, 304 | widget=forms.FileInput(attrs={'class': 'custom-file-input'}), 305 | help_text='Upload your submission.') 306 | """Submission File""" 307 | 308 | 309 | class AddTestCaseForm(forms.Form): 310 | """ 311 | Form to create a new TestCase 312 | """ 313 | 314 | test_type = forms.ChoiceField(label='Test type', choices=[ 315 | ('public', 'Public'), 316 | ('private', 'Private') 317 | ]) 318 | """TestCase Type""" 319 | 320 | input_file = forms.FileField(label='Input file', allow_empty_file=False, required=True, 321 | help_text='Upload input for test case.') 322 | """TestCase Input""" 323 | 324 | output_file = forms.FileField(label='Output file', allow_empty_file=False, required=True, 325 | help_text='Upload output for test case.') 326 | """TestCase Output""" 327 | 328 | 329 | class NewCommentForm(forms.Form): 330 | """ 331 | Form to add a new comment 332 | """ 333 | 334 | participant_email = forms.EmailField(label='Email', widget=forms.HiddenInput()) 335 | """Email of participant""" 336 | 337 | comment = forms.CharField(label='Comment', required=True, widget=forms.Textarea( 338 | attrs={'class': 'form-control', 'rows': 2})) 339 | """Comment content""" 340 | 341 | 342 | class AddPosterScoreForm(forms.Form): 343 | """ 344 | Form to add poster score for a submission 345 | """ 346 | 347 | score = forms.IntegerField( 348 | label='Poster Score', widget=forms.NumberInput(attrs={'class': 'form-control'}), 349 | initial=0) 350 | """Score field""" 351 | -------------------------------------------------------------------------------- /judge/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-07-23 17:58 2 | 3 | import datetime 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import django.utils.timezone 7 | import functools 8 | import judge.models 9 | import uuid 10 | 11 | from typing import Tuple, List 12 | 13 | 14 | class Migration(migrations.Migration): 15 | 16 | initial = True 17 | 18 | dependencies = [ 19 | ] # type: List[Tuple[str, str]] 20 | 21 | operations = [ 22 | migrations.CreateModel( 23 | name='Contest', 24 | fields=[ 25 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('name', models.CharField(default='Unnamed Contest', max_length=50, unique=True)), 27 | ('start_datetime', models.DateTimeField()), 28 | ('soft_end_datetime', models.DateTimeField()), 29 | ('hard_end_datetime', models.DateTimeField()), 30 | ('penalty', models.FloatField(default=0.0)), 31 | ('public', models.BooleanField(default=False)), 32 | ('enable_linter_score', models.BooleanField(default=True)), 33 | ('enable_poster_score', models.BooleanField(default=True)), 34 | ], 35 | ), 36 | migrations.CreateModel( 37 | name='Person', 38 | fields=[ 39 | ('email', models.EmailField(max_length=254, primary_key=True, serialize=False)), 40 | ('rank', models.PositiveIntegerField(default=0)), 41 | ], 42 | ), 43 | migrations.CreateModel( 44 | name='Problem', 45 | fields=[ 46 | ('code', models.CharField(default='UNSET', max_length=10, primary_key=True, serialize=False)), 47 | ('name', models.CharField(default='Name not set', max_length=50)), 48 | ('statement', models.TextField(default='The problem statement is empty.')), 49 | ('input_format', models.TextField(default='No input format specified.')), 50 | ('output_format', models.TextField(default='No output format specified.')), 51 | ('difficulty', models.PositiveSmallIntegerField(default=0)), 52 | ('time_limit', models.DurationField(default=datetime.timedelta(0, 10))), 53 | ('memory_limit', models.PositiveIntegerField(default=200000)), 54 | ('file_exts', models.CharField(default='.py,.cpp', max_length=100)), 55 | ('starting_code', models.FileField(null=True, upload_to=judge.models.starting_code_name)), 56 | ('max_score', models.PositiveSmallIntegerField(default=0)), 57 | ('compilation_script', models.FileField(default='./default/compilation_script.sh', upload_to=functools.partial(judge.models.compilation_test_upload_location, *(), **{'is_compilation': True}))), 58 | ('test_script', models.FileField(default='./default/test_script.sh', upload_to=functools.partial(judge.models.compilation_test_upload_location, *(), **{'is_compilation': False}))), 59 | ('contest', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='judge.Contest')), 60 | ], 61 | ), 62 | migrations.CreateModel( 63 | name='Submission', 64 | fields=[ 65 | ('id', models.CharField(default=uuid.uuid4, max_length=32, primary_key=True, serialize=False)), 66 | ('file_type', models.CharField(choices=[('.none', 'NOT_SELECTED'), ('.c', 'C'), ('.cpp', 'C++'), ('.py', 'Python3.6'), ('.go', 'Go'), ('.hs', 'Haskell')], default='.none', max_length=5)), 67 | ('submission_file', models.FileField(upload_to=judge.models.submission_upload_location)), 68 | ('timestamp', models.DateTimeField()), 69 | ('judge_score', models.PositiveSmallIntegerField(default=0)), 70 | ('poster_score', models.SmallIntegerField(default=0)), 71 | ('linter_score', models.FloatField(default=0.0)), 72 | ('final_score', models.FloatField(default=0.0)), 73 | ('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Person')), 74 | ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Problem')), 75 | ], 76 | ), 77 | migrations.CreateModel( 78 | name='TestCase', 79 | fields=[ 80 | ('public', models.BooleanField()), 81 | ('id', models.CharField(default=uuid.uuid4, max_length=36, primary_key=True, serialize=False)), 82 | ('inputfile', models.FileField(default='./default/inputfile.txt', upload_to=functools.partial(judge.models.testcase_upload_location, *(), **{'is_input': True}))), 83 | ('outputfile', models.FileField(default='./default/outputfile.txt', upload_to=functools.partial(judge.models.testcase_upload_location, *(), **{'is_input': False}))), 84 | ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Problem')), 85 | ], 86 | ), 87 | migrations.CreateModel( 88 | name='Comment', 89 | fields=[ 90 | ('id', models.CharField(default=uuid.uuid4, max_length=32, primary_key=True, serialize=False)), 91 | ('timestamp', models.DateTimeField(default=django.utils.timezone.now)), 92 | ('comment', models.TextField()), 93 | ('commenter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='commenter', to='judge.Person')), 94 | ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='person', to='judge.Person')), 95 | ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Problem')), 96 | ], 97 | ), 98 | migrations.CreateModel( 99 | name='SubmissionTestCase', 100 | fields=[ 101 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 102 | ('verdict', models.CharField(choices=[('F', 'Failed'), ('P', 'Passed'), ('R', 'Running'), ('TE', 'Time Limit Exceeded'), ('ME', 'Out Of Memory'), ('CE', 'Compilation Error'), ('RE', 'Runtime Error'), ('NA', 'Internal Failure')], default='NA', max_length=2)), 103 | ('memory_taken', models.PositiveIntegerField()), 104 | ('time_taken', models.DurationField()), 105 | ('message', models.TextField(default='')), 106 | ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Submission')), 107 | ('testcase', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.TestCase')), 108 | ], 109 | options={ 110 | 'unique_together': {('submission', 'testcase')}, 111 | }, 112 | ), 113 | migrations.CreateModel( 114 | name='PersonProblemFinalScore', 115 | fields=[ 116 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 117 | ('score', models.FloatField(default=0.0)), 118 | ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Person')), 119 | ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Problem')), 120 | ], 121 | options={ 122 | 'unique_together': {('problem', 'person')}, 123 | }, 124 | ), 125 | migrations.CreateModel( 126 | name='ContestPerson', 127 | fields=[ 128 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 129 | ('role', models.BooleanField()), 130 | ('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Contest')), 131 | ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Person')), 132 | ], 133 | options={ 134 | 'unique_together': {('contest', 'person')}, 135 | }, 136 | ), 137 | ] 138 | -------------------------------------------------------------------------------- /judge/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/judge/migrations/__init__.py -------------------------------------------------------------------------------- /judge/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from uuid import uuid4 4 | from os.path import splitext 5 | from functools import partial 6 | from datetime import timedelta 7 | from django.utils import timezone 8 | 9 | 10 | def starting_code_name(instance, filename): 11 | return 'content/problems/{}/start_code{}'.format(instance.code, splitext(filename)[1]) 12 | 13 | 14 | def compilation_test_upload_location(instance, filename, is_compilation): 15 | # We disregard the filename argument 16 | file_prefix = 'compilation' if is_compilation else 'test' 17 | file_extension = '.sh' if is_compilation else '' 18 | file_name = '{}_script{}'.format(file_prefix, file_extension) 19 | return 'content/problems/{}/{}'.format(instance.code, file_name) 20 | 21 | 22 | def testcase_upload_location(instance, filename, is_input): 23 | # We disregard the filename argument 24 | file_prefix = 'input' if is_input else 'output' 25 | file_name = '{}file_{}.txt'.format(file_prefix, instance.id) 26 | return 'content/testcase/{}'.format(file_name) 27 | 28 | 29 | def submission_upload_location(instance, filename): 30 | # We disregard the filename argument 31 | file_name = 'submission_{}{}'.format(instance.id, instance.file_type) 32 | return 'content/submissions/{}'.format(file_name) 33 | 34 | 35 | def comment_upload_location(instance, filename): 36 | # We disregard the filename argument 37 | return 'content/comment/{}.yml'.format(instance.id) 38 | 39 | 40 | class Contest(models.Model): 41 | """ 42 | Model for Contest. 43 | """ 44 | 45 | name = models.CharField( 46 | max_length=50, default='Unnamed Contest', unique=True) 47 | """Contest name""" 48 | 49 | start_datetime = models.DateTimeField() 50 | """Start Date and Time for Contest""" 51 | 52 | soft_end_datetime = models.DateTimeField() 53 | """"Soft" End Date and Time for Contest""" 54 | 55 | hard_end_datetime = models.DateTimeField() 56 | """"Hard" End Date and Time for Contest""" 57 | 58 | penalty = models.FloatField(default=0.0) 59 | """Penalty for late-submission""" 60 | 61 | public = models.BooleanField(default=False) 62 | """Is the contest public?""" 63 | 64 | enable_linter_score = models.BooleanField(default=True) 65 | """Enable linter scoring""" 66 | 67 | enable_poster_score = models.BooleanField(default=True) 68 | """Enable poster scoring""" 69 | 70 | def __str__(self): 71 | return self.name 72 | 73 | 74 | class Problem(models.Model): 75 | """ 76 | Model for a Problem. 77 | """ 78 | # UNSET is a special problem code which other problems must not use. 79 | code = models.CharField(max_length=10, primary_key=True, default='UNSET') 80 | """Problem code""" 81 | 82 | contest = models.ForeignKey(Contest, on_delete=models.CASCADE, null=True) 83 | """Foreign key to contest for the problem""" 84 | 85 | name = models.CharField(max_length=50, default='Name not set') 86 | """Problem name""" 87 | 88 | statement = models.TextField(default='The problem statement is empty.') 89 | """Problem statement""" 90 | 91 | input_format = models.TextField(default='No input format specified.') 92 | """Problem input format""" 93 | 94 | output_format = models.TextField(default='No output format specified.') 95 | """Problem output format""" 96 | 97 | difficulty = models.PositiveSmallIntegerField(default=0) 98 | """Problem difficulty""" 99 | 100 | time_limit = models.DurationField(default=timedelta(seconds=10)) 101 | """Problem time limit""" 102 | 103 | # Currently this is specified in mega-bytes 104 | memory_limit = models.PositiveIntegerField(default=200000) 105 | """Problem memory limit""" 106 | 107 | # Support upto 30 file formats 108 | file_exts = models.CharField(max_length=100, default='.py,.cpp') 109 | """Accepted file extensions for submissions to problem""" 110 | 111 | starting_code = models.FileField(upload_to=starting_code_name, null=True) 112 | """Problem starting code""" 113 | 114 | max_score = models.PositiveSmallIntegerField(default=0) 115 | """Maximum score for a test case for the problem""" 116 | 117 | compilation_script = models.FileField( 118 | upload_to=partial(compilation_test_upload_location, 119 | is_compilation=True), 120 | default='./default/compilation_script.sh') 121 | """Problem compilation script""" 122 | 123 | test_script = models.FileField( 124 | upload_to=partial(compilation_test_upload_location, 125 | is_compilation=False), 126 | default='./default/test_script.sh') 127 | """Problem test script""" 128 | 129 | def __str__(self): 130 | return self.code 131 | 132 | 133 | class Person(models.Model): 134 | """ 135 | Model for Person. 136 | """ 137 | 138 | email = models.EmailField(primary_key=True) 139 | """Email ID of the Person""" 140 | 141 | rank = models.PositiveIntegerField(default=0) 142 | """Rank of the Person""" 143 | 144 | def __str__(self): 145 | return self.email 146 | 147 | 148 | class Submission(models.Model): 149 | """ 150 | Model for a Submission. 151 | """ 152 | # Self Generated PrimaryKey 153 | id = models.CharField(max_length=36, primary_key=True, default=uuid4) 154 | 155 | problem = models.ForeignKey(Problem, on_delete=models.CASCADE) 156 | """Foreign key to problem for which this is a submission""" 157 | 158 | participant = models.ForeignKey(Person, on_delete=models.CASCADE) 159 | """Foreign key to person who submitted the solution""" 160 | 161 | # This has to be updated periodically 162 | PERMISSIBLE_FILE_TYPES = ( 163 | ('.none', 'NOT_SELECTED'), 164 | ('.c', 'C'), 165 | ('.cpp', 'C++'), 166 | ('.py', 'Python3.6'), 167 | ('.go', 'Go'), 168 | ('.hs', 'Haskell'), 169 | ) 170 | 171 | file_type = models.CharField( 172 | max_length=5, choices=PERMISSIBLE_FILE_TYPES, default='.none') 173 | """File type of submission""" 174 | 175 | submission_file = models.FileField(upload_to=submission_upload_location) 176 | """Submission file""" 177 | 178 | timestamp = models.DateTimeField() 179 | """Timestamp of submission""" 180 | 181 | judge_score = models.PositiveSmallIntegerField(default=0) 182 | """Judge score""" 183 | 184 | poster_score = models.SmallIntegerField(default=0) 185 | """Poster score""" 186 | 187 | linter_score = models.FloatField(default=0.0) 188 | """Linter score""" 189 | 190 | final_score = models.FloatField(default=0.0) 191 | """Final score""" 192 | 193 | 194 | class ContestPerson(models.Model): 195 | """ 196 | Model for ContestPerson. 197 | This maps how (either as a Participant or Poster) persons have access to the contests. 198 | """ 199 | 200 | contest = models.ForeignKey(Contest, on_delete=models.CASCADE) 201 | """Foreign key to contest in which this person is taking part""" 202 | 203 | person = models.ForeignKey(Person, on_delete=models.CASCADE) 204 | """Foreign key to the actual person""" 205 | 206 | # True for Poster and False for Participant 207 | role = models.BooleanField() 208 | """Determines if Person is a Poster or a Participant""" 209 | 210 | class Meta: 211 | unique_together = (('contest', 'person'),) 212 | 213 | 214 | class TestCase(models.Model): 215 | """ 216 | Model for TestCase. 217 | Maintains testcases and mapping between TestCase and Problem. 218 | """ 219 | 220 | problem = models.ForeignKey(Problem, on_delete=models.CASCADE) 221 | """Foreign key to problem for which this is a test case""" 222 | 223 | # True for Public and False for Private 224 | public = models.BooleanField() 225 | """Determines if the test case is a public test case or a private test case""" 226 | 227 | # Self Generated PrimaryKey 228 | id = models.CharField(max_length=36, primary_key=True, default=uuid4) 229 | 230 | # Sample: ./content/testcase/inputfile_UUID.txt 231 | inputfile = models.FileField(upload_to=partial(testcase_upload_location, is_input=True), 232 | default='./default/inputfile.txt') 233 | """Input file for the test case""" 234 | 235 | # ./content/testcase/outputfile_UUID.txt 236 | outputfile = models.FileField(upload_to=partial(testcase_upload_location, is_input=False), 237 | default='./default/outputfile.txt') 238 | """Output file for the test case""" 239 | 240 | 241 | class SubmissionTestCase(models.Model): 242 | """ 243 | Model for SubmissionTestCase. 244 | Maintains mapping between TestCase and Submission. 245 | """ 246 | 247 | # Possible Verdicts 248 | VERDICT = ( 249 | ('F', 'Failed'), 250 | ('P', 'Passed'), 251 | ('R', 'Running'), 252 | ('TE', 'Time Limit Exceeded'), 253 | ('ME', 'Out Of Memory'), 254 | ('CE', 'Compilation Error'), 255 | ('RE', 'Runtime Error'), 256 | ('NA', 'Internal Failure')) 257 | 258 | submission = models.ForeignKey(Submission, on_delete=models.CASCADE) 259 | """Foreign key to submission""" 260 | 261 | testcase = models.ForeignKey(TestCase, on_delete=models.CASCADE) 262 | """Foreign key to test case""" 263 | 264 | verdict = models.CharField(max_length=2, choices=VERDICT, default='NA') 265 | """Verdict by the judge""" 266 | 267 | memory_taken = models.PositiveIntegerField() 268 | """Virtual memory consumed by the submission""" 269 | 270 | time_taken = models.DurationField() 271 | """Time taken by the submission""" 272 | 273 | message = models.TextField(default='') 274 | """Message placeholder, used for erroneous submissions""" 275 | 276 | class Meta: 277 | unique_together = (('submission', 'testcase'),) 278 | 279 | 280 | class Comment(models.Model): 281 | """ 282 | Model for Comment. 283 | """ 284 | 285 | problem = models.ForeignKey(Problem, on_delete=models.CASCADE) 286 | """Foreign key to problem relating to the comment""" 287 | 288 | person = models.ForeignKey( 289 | Person, on_delete=models.CASCADE, related_name='person') 290 | """Foreign key to person""" 291 | 292 | # Self Generated PrimaryKey 293 | id = models.CharField(max_length=36, primary_key=True, default=uuid4) 294 | 295 | commenter = models.ForeignKey( 296 | Person, on_delete=models.CASCADE, related_name='commenter') 297 | """Foreign key to person who commented""" 298 | 299 | timestamp = models.DateTimeField(default=timezone.now) 300 | """Timestamp of the comment""" 301 | 302 | comment = models.TextField() 303 | """Content of the comment""" 304 | 305 | 306 | class PersonProblemFinalScore(models.Model): 307 | """ 308 | Model to store the final score assigned to a person for a problem. 309 | """ 310 | 311 | problem = models.ForeignKey(Problem, on_delete=models.CASCADE) 312 | """Foreign key to problem for which the score is saved""" 313 | 314 | person = models.ForeignKey(Person, on_delete=models.CASCADE) 315 | """Foreign key to person whose submission's score is saved""" 316 | 317 | score = models.FloatField(default=0.0) 318 | """Final score saved""" 319 | 320 | class Meta: 321 | unique_together = (('problem', 'person'),) 322 | -------------------------------------------------------------------------------- /judge/static/assets/js/argon.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Argon Design System - v1.0.1 5 | ========================================================= 6 | 7 | * Product Page: https://www.creative-tim.com/product/argon-design-system 8 | * Copyright 2018 Creative Tim (https://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/argon-design-system/blob/master/LICENSE.md) 10 | 11 | * Coded by www.creative-tim.com 12 | 13 | ========================================================= 14 | 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | */ 18 | "use strict";$(document).ready(function(){($(".navbar-main .collapse").on("hide.bs.collapse",function(){$(this).addClass("collapsing-out")}),$(".navbar-main .collapse").on("hidden.bs.collapse",function(){$(this).removeClass("collapsing-out")}),$(".navbar-main .dropdown").on("hide.bs.dropdown",function(){var e=$(this).find(".dropdown-menu");e.addClass("close"),setTimeout(function(){e.removeClass("close")},200)}),$(".headroom")[0])&&new Headroom(document.querySelector("#navbar-main"),{offset:300,tolerance:{up:30,down:30}}).init();if($(".datepicker")[0]&&$(".datepicker").each(function(){$(".datepicker").datepicker({disableTouchKeyboard:!0,autoclose:!1})}),$('[data-toggle="tooltip"]').tooltip(),$('[data-toggle="popover"]').each(function(){var e="";$(this).data("color")&&(e="popover-"+$(this).data("color")),$(this).popover({trigger:"focus",template:''})}),$(".form-control").on("focus blur",function(e){$(this).parents(".form-group").toggleClass("focused","focus"===e.type||0this.getScrollerHeight();return b||c},toleranceExceeded:function(a,b){return Math.abs(a-this.lastKnownScrollY)>=this.tolerance[b]},shouldUnpin:function(a,b){var c=a>this.lastKnownScrollY,d=a>=this.offset;return c&&d&&b},shouldPin:function(a,b){var c=athis.lastKnownScrollY?"down":"up",c=this.toleranceExceeded(a,b);this.isOutOfBounds(a)||(a<=this.offset?this.top():this.notTop(),a+this.getViewportHeight()>=this.getScrollerHeight()?this.bottom():this.notBottom(),this.shouldUnpin(a,c)?this.unpin():this.shouldPin(a,c)&&this.pin(),this.lastKnownScrollY=a)}},e.options={tolerance:{up:0,down:0},offset:0,scroller:window,classes:{pinned:"headroom--pinned",unpinned:"headroom--unpinned",top:"headroom--top",notTop:"headroom--not-top",bottom:"headroom--bottom",notBottom:"headroom--not-bottom",initial:"headroom"}},e.cutsTheMustard="undefined"!=typeof f&&f.rAF&&f.bind&&f.classList,e}); -------------------------------------------------------------------------------- /judge/static/assets/vendor/nouislider/css/nouislider.css: -------------------------------------------------------------------------------- 1 | /*! nouislider - 11.0.3 - 2018-01-21 14:04:07 */ 2 | /* Functional styling; 3 | * These styles are required for noUiSlider to function. 4 | * You don't need to change these rules to apply your design. 5 | */ 6 | .noUi-target, 7 | .noUi-target * { 8 | -webkit-touch-callout: none; 9 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 10 | -webkit-user-select: none; 11 | -ms-touch-action: none; 12 | touch-action: none; 13 | -ms-user-select: none; 14 | -moz-user-select: none; 15 | user-select: none; 16 | -moz-box-sizing: border-box; 17 | box-sizing: border-box; 18 | } 19 | .noUi-target { 20 | position: relative; 21 | direction: ltr; 22 | } 23 | .noUi-base, 24 | .noUi-connects { 25 | width: 100%; 26 | height: 100%; 27 | position: relative; 28 | z-index: 1; 29 | } 30 | /* Wrapper for all connect elements. 31 | */ 32 | .noUi-connects { 33 | overflow: hidden; 34 | z-index: 0; 35 | } 36 | .noUi-connect, 37 | .noUi-origin { 38 | will-change: transform; 39 | position: absolute; 40 | z-index: 1; 41 | top: 0; 42 | left: 0; 43 | height: 100%; 44 | width: 100%; 45 | -webkit-transform-origin: 0 0; 46 | transform-origin: 0 0; 47 | } 48 | /* Offset direction 49 | */ 50 | html:not([dir="rtl"]) .noUi-horizontal .noUi-origin { 51 | left: auto; 52 | right: 0; 53 | } 54 | /* Give origins 0 height/width so they don't interfere with clicking the 55 | * connect elements. 56 | */ 57 | .noUi-vertical .noUi-origin { 58 | width: 0; 59 | } 60 | .noUi-horizontal .noUi-origin { 61 | height: 0; 62 | } 63 | .noUi-handle { 64 | position: absolute; 65 | } 66 | .noUi-state-tap .noUi-connect, 67 | .noUi-state-tap .noUi-origin { 68 | -webkit-transition: transform 0.3s; 69 | transition: transform 0.3s; 70 | } 71 | .noUi-state-drag * { 72 | cursor: inherit !important; 73 | } 74 | /* Slider size and handle placement; 75 | */ 76 | .noUi-horizontal { 77 | height: 18px; 78 | } 79 | .noUi-horizontal .noUi-handle { 80 | width: 34px; 81 | height: 28px; 82 | left: -17px; 83 | top: -6px; 84 | } 85 | .noUi-vertical { 86 | width: 18px; 87 | } 88 | .noUi-vertical .noUi-handle { 89 | width: 28px; 90 | height: 34px; 91 | left: -6px; 92 | top: -17px; 93 | } 94 | html:not([dir="rtl"]) .noUi-horizontal .noUi-handle { 95 | right: -17px; 96 | left: auto; 97 | } 98 | /* Styling; 99 | * Giving the connect element a border radius causes issues with using transform: scale 100 | */ 101 | .noUi-target { 102 | background: #FAFAFA; 103 | border-radius: 4px; 104 | border: 1px solid #D3D3D3; 105 | box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB; 106 | } 107 | .noUi-connects { 108 | border-radius: 3px; 109 | } 110 | .noUi-connect { 111 | background: #3FB8AF; 112 | } 113 | /* Handles and cursors; 114 | */ 115 | .noUi-draggable { 116 | cursor: ew-resize; 117 | } 118 | .noUi-vertical .noUi-draggable { 119 | cursor: ns-resize; 120 | } 121 | .noUi-handle { 122 | border: 1px solid #D9D9D9; 123 | border-radius: 3px; 124 | background: #FFF; 125 | cursor: default; 126 | box-shadow: inset 0 0 1px #FFF, inset 0 1px 7px #EBEBEB, 0 3px 6px -3px #BBB; 127 | } 128 | .noUi-active { 129 | box-shadow: inset 0 0 1px #FFF, inset 0 1px 7px #DDD, 0 3px 6px -3px #BBB; 130 | } 131 | /* Handle stripes; 132 | */ 133 | .noUi-handle:before, 134 | .noUi-handle:after { 135 | content: ""; 136 | display: block; 137 | position: absolute; 138 | height: 14px; 139 | width: 1px; 140 | background: #E8E7E6; 141 | left: 14px; 142 | top: 6px; 143 | } 144 | .noUi-handle:after { 145 | left: 17px; 146 | } 147 | .noUi-vertical .noUi-handle:before, 148 | .noUi-vertical .noUi-handle:after { 149 | width: 14px; 150 | height: 1px; 151 | left: 6px; 152 | top: 14px; 153 | } 154 | .noUi-vertical .noUi-handle:after { 155 | top: 17px; 156 | } 157 | /* Disabled state; 158 | */ 159 | [disabled] .noUi-connect { 160 | background: #B8B8B8; 161 | } 162 | [disabled].noUi-target, 163 | [disabled].noUi-handle, 164 | [disabled] .noUi-handle { 165 | cursor: not-allowed; 166 | } 167 | /* Base; 168 | * 169 | */ 170 | .noUi-pips, 171 | .noUi-pips * { 172 | -moz-box-sizing: border-box; 173 | box-sizing: border-box; 174 | } 175 | .noUi-pips { 176 | position: absolute; 177 | color: #999; 178 | } 179 | /* Values; 180 | * 181 | */ 182 | .noUi-value { 183 | position: absolute; 184 | white-space: nowrap; 185 | text-align: center; 186 | } 187 | .noUi-value-sub { 188 | color: #ccc; 189 | font-size: 10px; 190 | } 191 | /* Markings; 192 | * 193 | */ 194 | .noUi-marker { 195 | position: absolute; 196 | background: #CCC; 197 | } 198 | .noUi-marker-sub { 199 | background: #AAA; 200 | } 201 | .noUi-marker-large { 202 | background: #AAA; 203 | } 204 | /* Horizontal layout; 205 | * 206 | */ 207 | .noUi-pips-horizontal { 208 | padding: 10px 0; 209 | height: 80px; 210 | top: 100%; 211 | left: 0; 212 | width: 100%; 213 | } 214 | .noUi-value-horizontal { 215 | -webkit-transform: translate(-50%, 50%); 216 | transform: translate(-50%, 50%); 217 | } 218 | .noUi-rtl .noUi-value-horizontal { 219 | -webkit-transform: translate(50%, 50%); 220 | transform: translate(50%, 50%); 221 | } 222 | .noUi-marker-horizontal.noUi-marker { 223 | margin-left: -1px; 224 | width: 2px; 225 | height: 5px; 226 | } 227 | .noUi-marker-horizontal.noUi-marker-sub { 228 | height: 10px; 229 | } 230 | .noUi-marker-horizontal.noUi-marker-large { 231 | height: 15px; 232 | } 233 | /* Vertical layout; 234 | * 235 | */ 236 | .noUi-pips-vertical { 237 | padding: 0 10px; 238 | height: 100%; 239 | top: 0; 240 | left: 100%; 241 | } 242 | .noUi-value-vertical { 243 | -webkit-transform: translate(0, -50%); 244 | transform: translate(0, -50%, 0); 245 | padding-left: 25px; 246 | } 247 | .noUi-rtl .noUi-value-vertical { 248 | -webkit-transform: translate(0, 50%); 249 | transform: translate(0, 50%); 250 | } 251 | .noUi-marker-vertical.noUi-marker { 252 | width: 5px; 253 | height: 2px; 254 | margin-top: -1px; 255 | } 256 | .noUi-marker-vertical.noUi-marker-sub { 257 | width: 10px; 258 | } 259 | .noUi-marker-vertical.noUi-marker-large { 260 | width: 15px; 261 | } 262 | .noUi-tooltip { 263 | display: block; 264 | position: absolute; 265 | border: 1px solid #D9D9D9; 266 | border-radius: 3px; 267 | background: #fff; 268 | color: #000; 269 | padding: 5px; 270 | text-align: center; 271 | white-space: nowrap; 272 | } 273 | .noUi-horizontal .noUi-tooltip { 274 | -webkit-transform: translate(-50%, 0); 275 | transform: translate(-50%, 0); 276 | left: 50%; 277 | bottom: 120%; 278 | } 279 | .noUi-vertical .noUi-tooltip { 280 | -webkit-transform: translate(0, -50%); 281 | transform: translate(0, -50%); 282 | top: 50%; 283 | right: 120%; 284 | } 285 | -------------------------------------------------------------------------------- /judge/static/assets/vendor/nouislider/css/nouislider.min.css: -------------------------------------------------------------------------------- 1 | /*! nouislider - 11.0.3 - 2018-01-21 14:04:07 */.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative;direction:ltr}.noUi-base,.noUi-connects{width:100%;height:100%;position:relative;z-index:1}.noUi-connects{overflow:hidden;z-index:0}.noUi-connect,.noUi-origin{will-change:transform;position:absolute;z-index:1;top:0;left:0;height:100%;width:100%;-webkit-transform-origin:0 0;transform-origin:0 0}html:not([dir=rtl]) .noUi-horizontal .noUi-origin{left:auto;right:0}.noUi-vertical .noUi-origin{width:0}.noUi-horizontal .noUi-origin{height:0}.noUi-handle{position:absolute}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{-webkit-transition:transform .3s;transition:transform .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;left:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;left:-6px;top:-17px}html:not([dir=rtl]) .noUi-horizontal .noUi-handle{right:-17px;left:auto}.noUi-target{background:#FAFAFA;border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-connects{border-radius:3px}.noUi-connect{background:#3FB8AF}.noUi-draggable{cursor:ew-resize}.noUi-vertical .noUi-draggable{cursor:ns-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect{background:#B8B8B8}[disabled] .noUi-handle,[disabled].noUi-handle,[disabled].noUi-target{cursor:not-allowed}.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;white-space:nowrap;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-large,.noUi-marker-sub{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{-webkit-transform:translate(-50%,50%);transform:translate(-50%,50%)}.noUi-rtl .noUi-value-horizontal{-webkit-transform:translate(50%,50%);transform:translate(50%,50%)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{-webkit-transform:translate(0,-50%);transform:translate(0,-50%,0);padding-left:25px}.noUi-rtl .noUi-value-vertical{-webkit-transform:translate(0,50%);transform:translate(0,50%)}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #D9D9D9;border-radius:3px;background:#fff;color:#000;padding:5px;text-align:center;white-space:nowrap}.noUi-horizontal .noUi-tooltip{-webkit-transform:translate(-50%,0);transform:translate(-50%,0);left:50%;bottom:120%}.noUi-vertical .noUi-tooltip{-webkit-transform:translate(0,-50%);transform:translate(0,-50%);top:50%;right:120%} -------------------------------------------------------------------------------- /judge/static/assets/vendor/nucleo/css/nucleo-svg.css: -------------------------------------------------------------------------------- 1 | /* Generated using nucleoapp.com */ 2 | /* -------------------------------- 3 | 4 | Icon colors 5 | 6 | -------------------------------- */ 7 | 8 | .icon { 9 | display: inline-block; 10 | /* icon primary color */ 11 | color: #111111; 12 | height: 1em; 13 | width: 1em; 14 | } 15 | 16 | .icon use { 17 | /* icon secondary color - fill */ 18 | fill: #7ea6f6; 19 | } 20 | 21 | .icon.icon-outline use { 22 | /* icon secondary color - stroke */ 23 | stroke: #7ea6f6; 24 | } 25 | 26 | /* -------------------------------- 27 | 28 | Change icon size 29 | 30 | -------------------------------- */ 31 | 32 | .icon-xs { 33 | height: 0.5em; 34 | width: 0.5em; 35 | } 36 | 37 | .icon-sm { 38 | height: 0.8em; 39 | width: 0.8em; 40 | } 41 | 42 | .icon-lg { 43 | height: 1.6em; 44 | width: 1.6em; 45 | } 46 | 47 | .icon-xl { 48 | height: 2em; 49 | width: 2em; 50 | } 51 | 52 | /* -------------------------------- 53 | 54 | Align icon and text 55 | 56 | -------------------------------- */ 57 | 58 | .icon-text-aligner { 59 | /* add this class to parent element that contains icon + text */ 60 | display: flex; 61 | align-items: center; 62 | } 63 | 64 | .icon-text-aligner .icon { 65 | color: inherit; 66 | margin-right: 0.4em; 67 | } 68 | 69 | .icon-text-aligner .icon use { 70 | color: inherit; 71 | fill: currentColor; 72 | } 73 | 74 | .icon-text-aligner .icon.icon-outline use { 75 | stroke: currentColor; 76 | } 77 | 78 | /* -------------------------------- 79 | 80 | Icon reset values - used to enable color customizations 81 | 82 | -------------------------------- */ 83 | 84 | .icon { 85 | fill: currentColor; 86 | stroke: none; 87 | } 88 | 89 | .icon.icon-outline { 90 | fill: none; 91 | stroke: currentColor; 92 | } 93 | 94 | .icon use { 95 | stroke: none; 96 | } 97 | 98 | .icon.icon-outline use { 99 | fill: none; 100 | } 101 | 102 | /* -------------------------------- 103 | 104 | Stroke effects - Nucleo outline icons 105 | 106 | - 16px icons -> up to 1px stroke (16px outline icons do not support stroke changes) 107 | - 24px, 32px icons -> up to 2px stroke 108 | - 48px, 64px icons -> up to 4px stroke 109 | 110 | -------------------------------- */ 111 | 112 | .icon-outline.icon-stroke-1 { 113 | stroke-width: 1px; 114 | } 115 | 116 | .icon-outline.icon-stroke-2 { 117 | stroke-width: 2px; 118 | } 119 | 120 | .icon-outline.icon-stroke-3 { 121 | stroke-width: 3px; 122 | } 123 | 124 | .icon-outline.icon-stroke-4 { 125 | stroke-width: 4px; 126 | } 127 | 128 | .icon-outline.icon-stroke-1 use, 129 | .icon-outline.icon-stroke-3 use { 130 | -webkit-transform: translateX(0.5px) translateY(0.5px); 131 | -moz-transform: translateX(0.5px) translateY(0.5px); 132 | -ms-transform: translateX(0.5px) translateY(0.5px); 133 | -o-transform: translateX(0.5px) translateY(0.5px); 134 | transform: translateX(0.5px) translateY(0.5px); 135 | } -------------------------------------------------------------------------------- /judge/static/assets/vendor/nucleo/css/nucleo.css: -------------------------------------------------------------------------------- 1 | /*-------------------------------- 2 | 3 | hermes-dashboard-icons Web Font - built using nucleoapp.com 4 | License - nucleoapp.com/license/ 5 | 6 | -------------------------------- */ 7 | @font-face { 8 | font-family: 'NucleoIcons'; 9 | src: url('../fonts/nucleo-icons.eot'); 10 | src: url('../fonts/nucleo-icons.eot') format('embedded-opentype'), url('../fonts/nucleo-icons.woff2') format('woff2'), url('../fonts/nucleo-icons.woff') format('woff'), url('../fonts/nucleo-icons.ttf') format('truetype'), url('../fonts/nucleo-icons.svg') format('svg'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | /*------------------------ 15 | base class definition 16 | -------------------------*/ 17 | .ni { 18 | display: inline-block; 19 | font: normal normal normal 14px/1 NucleoIcons; 20 | font-size: inherit; 21 | text-rendering: auto; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | /*------------------------ 26 | change icon size 27 | -------------------------*/ 28 | .ni-lg { 29 | font-size: 1.33333333em; 30 | line-height: 0.75em; 31 | vertical-align: -15%; 32 | } 33 | .ni-2x { 34 | font-size: 2em; 35 | } 36 | .ni-3x { 37 | font-size: 3em; 38 | } 39 | .ni-4x { 40 | font-size: 4em; 41 | } 42 | .ni-5x { 43 | font-size: 5em; 44 | } 45 | 46 | /*---------------------------------- 47 | add a square/circle background 48 | -----------------------------------*/ 49 | .ni.square, 50 | .ni.circle { 51 | padding: 0.33333333em; 52 | vertical-align: -16%; 53 | background-color: #eee; 54 | } 55 | .ni.circle { 56 | border-radius: 50%; 57 | } 58 | /*------------------------ 59 | list icons 60 | -------------------------*/ 61 | .ni-ul { 62 | padding-left: 0; 63 | margin-left: 2.14285714em; 64 | list-style-type: none; 65 | } 66 | .ni-ul > li { 67 | position: relative; 68 | } 69 | .ni-ul > li > .ni { 70 | position: absolute; 71 | left: -1.57142857em; 72 | top: 0.14285714em; 73 | text-align: center; 74 | } 75 | .ni-ul > li > .ni.lg { 76 | top: 0; 77 | left: -1.35714286em; 78 | } 79 | .ni-ul > li > .ni.circle, 80 | .ni-ul > li > .ni.square { 81 | top: -0.19047619em; 82 | left: -1.9047619em; 83 | } 84 | /*------------------------ 85 | spinning icons 86 | -------------------------*/ 87 | .ni.spin { 88 | -webkit-animation: nc-spin 2s infinite linear; 89 | -moz-animation: nc-spin 2s infinite linear; 90 | animation: nc-spin 2s infinite linear; 91 | } 92 | @-webkit-keyframes nc-spin { 93 | 0% { 94 | -webkit-transform: rotate(0deg); 95 | } 96 | 100% { 97 | -webkit-transform: rotate(360deg); 98 | } 99 | } 100 | @-moz-keyframes nc-spin { 101 | 0% { 102 | -moz-transform: rotate(0deg); 103 | } 104 | 100% { 105 | -moz-transform: rotate(360deg); 106 | } 107 | } 108 | @keyframes nc-spin { 109 | 0% { 110 | -webkit-transform: rotate(0deg); 111 | -moz-transform: rotate(0deg); 112 | -ms-transform: rotate(0deg); 113 | -o-transform: rotate(0deg); 114 | transform: rotate(0deg); 115 | } 116 | 100% { 117 | -webkit-transform: rotate(360deg); 118 | -moz-transform: rotate(360deg); 119 | -ms-transform: rotate(360deg); 120 | -o-transform: rotate(360deg); 121 | transform: rotate(360deg); 122 | } 123 | } 124 | /*------------------------ 125 | rotated/flipped icons 126 | -------------------------*/ 127 | .ni.rotate-90 { 128 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 129 | -webkit-transform: rotate(90deg); 130 | -moz-transform: rotate(90deg); 131 | -ms-transform: rotate(90deg); 132 | -o-transform: rotate(90deg); 133 | transform: rotate(90deg); 134 | } 135 | .ni.rotate-180 { 136 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 137 | -webkit-transform: rotate(180deg); 138 | -moz-transform: rotate(180deg); 139 | -ms-transform: rotate(180deg); 140 | -o-transform: rotate(180deg); 141 | transform: rotate(180deg); 142 | } 143 | .ni.rotate-270 { 144 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 145 | -webkit-transform: rotate(270deg); 146 | -moz-transform: rotate(270deg); 147 | -ms-transform: rotate(270deg); 148 | -o-transform: rotate(270deg); 149 | transform: rotate(270deg); 150 | } 151 | .ni.flip-y { 152 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0); 153 | -webkit-transform: scale(-1, 1); 154 | -moz-transform: scale(-1, 1); 155 | -ms-transform: scale(-1, 1); 156 | -o-transform: scale(-1, 1); 157 | transform: scale(-1, 1); 158 | } 159 | .ni.flip-x { 160 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 161 | -webkit-transform: scale(1, -1); 162 | -moz-transform: scale(1, -1); 163 | -ms-transform: scale(1, -1); 164 | -o-transform: scale(1, -1); 165 | transform: scale(1, -1); 166 | } 167 | /*------------------------ 168 | font icons 169 | -------------------------*/ 170 | 171 | .ni-active-40::before { 172 | content: "\ea02"; 173 | } 174 | 175 | .ni-air-baloon::before { 176 | content: "\ea03"; 177 | } 178 | 179 | .ni-album-2::before { 180 | content: "\ea04"; 181 | } 182 | 183 | .ni-align-center::before { 184 | content: "\ea05"; 185 | } 186 | 187 | .ni-align-left-2::before { 188 | content: "\ea06"; 189 | } 190 | 191 | .ni-ambulance::before { 192 | content: "\ea07"; 193 | } 194 | 195 | .ni-app::before { 196 | content: "\ea08"; 197 | } 198 | 199 | .ni-archive-2::before { 200 | content: "\ea09"; 201 | } 202 | 203 | .ni-atom::before { 204 | content: "\ea0a"; 205 | } 206 | 207 | .ni-badge::before { 208 | content: "\ea0b"; 209 | } 210 | 211 | .ni-bag-17::before { 212 | content: "\ea0c"; 213 | } 214 | 215 | .ni-basket::before { 216 | content: "\ea0d"; 217 | } 218 | 219 | .ni-bell-55::before { 220 | content: "\ea0e"; 221 | } 222 | 223 | .ni-bold-down::before { 224 | content: "\ea0f"; 225 | } 226 | 227 | .ni-bold-left::before { 228 | content: "\ea10"; 229 | } 230 | 231 | .ni-bold-right::before { 232 | content: "\ea11"; 233 | } 234 | 235 | .ni-bold-up::before { 236 | content: "\ea12"; 237 | } 238 | 239 | .ni-bold::before { 240 | content: "\ea13"; 241 | } 242 | 243 | .ni-book-bookmark::before { 244 | content: "\ea14"; 245 | } 246 | 247 | .ni-books::before { 248 | content: "\ea15"; 249 | } 250 | 251 | .ni-box-2::before { 252 | content: "\ea16"; 253 | } 254 | 255 | .ni-briefcase-24::before { 256 | content: "\ea17"; 257 | } 258 | 259 | .ni-building::before { 260 | content: "\ea18"; 261 | } 262 | 263 | .ni-bulb-61::before { 264 | content: "\ea19"; 265 | } 266 | 267 | .ni-bullet-list-67::before { 268 | content: "\ea1a"; 269 | } 270 | 271 | .ni-bus-front-12::before { 272 | content: "\ea1b"; 273 | } 274 | 275 | .ni-button-pause::before { 276 | content: "\ea1c"; 277 | } 278 | 279 | .ni-button-play::before { 280 | content: "\ea1d"; 281 | } 282 | 283 | .ni-button-power::before { 284 | content: "\ea1e"; 285 | } 286 | 287 | .ni-calendar-grid-58::before { 288 | content: "\ea1f"; 289 | } 290 | 291 | .ni-camera-compact::before { 292 | content: "\ea20"; 293 | } 294 | 295 | .ni-caps-small::before { 296 | content: "\ea21"; 297 | } 298 | 299 | .ni-cart::before { 300 | content: "\ea22"; 301 | } 302 | 303 | .ni-chart-bar-32::before { 304 | content: "\ea23"; 305 | } 306 | 307 | .ni-chart-pie-35::before { 308 | content: "\ea24"; 309 | } 310 | 311 | .ni-chat-round::before { 312 | content: "\ea25"; 313 | } 314 | 315 | .ni-check-bold::before { 316 | content: "\ea26"; 317 | } 318 | 319 | .ni-circle-08::before { 320 | content: "\ea27"; 321 | } 322 | 323 | .ni-cloud-download-95::before { 324 | content: "\ea28"; 325 | } 326 | 327 | .ni-cloud-upload-96::before { 328 | content: "\ea29"; 329 | } 330 | 331 | .ni-compass-04::before { 332 | content: "\ea2a"; 333 | } 334 | 335 | .ni-controller::before { 336 | content: "\ea2b"; 337 | } 338 | 339 | .ni-credit-card::before { 340 | content: "\ea2c"; 341 | } 342 | 343 | .ni-curved-next::before { 344 | content: "\ea2d"; 345 | } 346 | 347 | .ni-delivery-fast::before { 348 | content: "\ea2e"; 349 | } 350 | 351 | .ni-diamond::before { 352 | content: "\ea2f"; 353 | } 354 | 355 | .ni-email-83::before { 356 | content: "\ea30"; 357 | } 358 | 359 | .ni-fat-add::before { 360 | content: "\ea31"; 361 | } 362 | 363 | .ni-fat-delete::before { 364 | content: "\ea32"; 365 | } 366 | 367 | .ni-fat-remove::before { 368 | content: "\ea33"; 369 | } 370 | 371 | .ni-favourite-28::before { 372 | content: "\ea34"; 373 | } 374 | 375 | .ni-folder-17::before { 376 | content: "\ea35"; 377 | } 378 | 379 | .ni-glasses-2::before { 380 | content: "\ea36"; 381 | } 382 | 383 | .ni-hat-3::before { 384 | content: "\ea37"; 385 | } 386 | 387 | .ni-headphones::before { 388 | content: "\ea38"; 389 | } 390 | 391 | .ni-html5::before { 392 | content: "\ea39"; 393 | } 394 | 395 | .ni-istanbul::before { 396 | content: "\ea3a"; 397 | } 398 | 399 | .ni-key-25::before { 400 | content: "\ea3b"; 401 | } 402 | 403 | .ni-laptop::before { 404 | content: "\ea3c"; 405 | } 406 | 407 | .ni-like-2::before { 408 | content: "\ea3d"; 409 | } 410 | 411 | .ni-lock-circle-open::before { 412 | content: "\ea3e"; 413 | } 414 | 415 | .ni-map-big::before { 416 | content: "\ea3f"; 417 | } 418 | 419 | .ni-mobile-button::before { 420 | content: "\ea40"; 421 | } 422 | 423 | .ni-money-coins::before { 424 | content: "\ea41"; 425 | } 426 | 427 | .ni-note-03::before { 428 | content: "\ea42"; 429 | } 430 | 431 | .ni-notification-70::before { 432 | content: "\ea43"; 433 | } 434 | 435 | .ni-palette::before { 436 | content: "\ea44"; 437 | } 438 | 439 | .ni-paper-diploma::before { 440 | content: "\ea45"; 441 | } 442 | 443 | .ni-pin-3::before { 444 | content: "\ea46"; 445 | } 446 | 447 | .ni-planet::before { 448 | content: "\ea47"; 449 | } 450 | 451 | .ni-ruler-pencil::before { 452 | content: "\ea48"; 453 | } 454 | 455 | .ni-satisfied::before { 456 | content: "\ea49"; 457 | } 458 | 459 | .ni-scissors::before { 460 | content: "\ea4a"; 461 | } 462 | 463 | .ni-send::before { 464 | content: "\ea4b"; 465 | } 466 | 467 | .ni-settings-gear-65::before { 468 | content: "\ea4c"; 469 | } 470 | 471 | .ni-settings::before { 472 | content: "\ea4d"; 473 | } 474 | 475 | .ni-single-02::before { 476 | content: "\ea4e"; 477 | } 478 | 479 | .ni-single-copy-04::before { 480 | content: "\ea4f"; 481 | } 482 | 483 | .ni-sound-wave::before { 484 | content: "\ea50"; 485 | } 486 | 487 | .ni-spaceship::before { 488 | content: "\ea51"; 489 | } 490 | 491 | .ni-square-pin::before { 492 | content: "\ea52"; 493 | } 494 | 495 | .ni-support-16::before { 496 | content: "\ea53"; 497 | } 498 | 499 | .ni-tablet-button::before { 500 | content: "\ea54"; 501 | } 502 | 503 | .ni-tag::before { 504 | content: "\ea55"; 505 | } 506 | 507 | .ni-tie-bow::before { 508 | content: "\ea56"; 509 | } 510 | 511 | .ni-time-alarm::before { 512 | content: "\ea57"; 513 | } 514 | 515 | .ni-trophy::before { 516 | content: "\ea58"; 517 | } 518 | 519 | .ni-tv-2::before { 520 | content: "\ea59"; 521 | } 522 | 523 | .ni-umbrella-13::before { 524 | content: "\ea5a"; 525 | } 526 | 527 | .ni-user-run::before { 528 | content: "\ea5b"; 529 | } 530 | 531 | .ni-vector::before { 532 | content: "\ea5c"; 533 | } 534 | 535 | .ni-watch-time::before { 536 | content: "\ea5d"; 537 | } 538 | 539 | .ni-world::before { 540 | content: "\ea5e"; 541 | } 542 | 543 | .ni-zoom-split-in::before { 544 | content: "\ea5f"; 545 | } 546 | 547 | .ni-collection::before { 548 | content: "\ea60"; 549 | } 550 | 551 | .ni-image::before { 552 | content: "\ea61"; 553 | } 554 | 555 | .ni-shop::before { 556 | content: "\ea62"; 557 | } 558 | 559 | .ni-ungroup::before { 560 | content: "\ea63"; 561 | } 562 | 563 | .ni-world-2::before { 564 | content: "\ea64"; 565 | } 566 | 567 | .ni-ui-04::before { 568 | content: "\ea65"; 569 | } 570 | 571 | 572 | /* all icon font classes list here */ 573 | -------------------------------------------------------------------------------- /judge/static/assets/vendor/nucleo/fonts/nucleo-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/judge/static/assets/vendor/nucleo/fonts/nucleo-icons.eot -------------------------------------------------------------------------------- /judge/static/assets/vendor/nucleo/fonts/nucleo-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/judge/static/assets/vendor/nucleo/fonts/nucleo-icons.ttf -------------------------------------------------------------------------------- /judge/static/assets/vendor/nucleo/fonts/nucleo-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/judge/static/assets/vendor/nucleo/fonts/nucleo-icons.woff -------------------------------------------------------------------------------- /judge/static/assets/vendor/nucleo/fonts/nucleo-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbsinha/autojudge/adb93228cc0e5881713fd35807430fd4be8e3f9c/judge/static/assets/vendor/nucleo/fonts/nucleo-icons.woff2 -------------------------------------------------------------------------------- /judge/static/assets/vendor/onscreen/onscreen.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * onScreen 0.0.0 3 | * Checks if matched elements are inside the viewport. 4 | * Built on Mon Mar 09 2015 12:00:07 5 | * 6 | * Copyright 2015 Silvestre Herrera and contributors, Licensed under the MIT license: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * 9 | * You can find a list of contributors at: 10 | * https://github.com/silvestreh/onScreen/graphs/contributors 11 | */ 12 | 13 | 14 | !function(a){a.fn.onScreen=function(b){var c={container:window,direction:"vertical",toggleClass:null,doIn:null,doOut:null,tolerance:0,throttle:null,lazyAttr:null,lazyPlaceholder:"data:image/gif;base64,R0lGODlhEAAFAIAAAP///////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCQAAACwAAAAAEAAFAAACCIyPqcvtD00BACH5BAkJAAIALAAAAAAQAAUAgfT29Pz6/P///wAAAAIQTGCiywKPmjxUNhjtMlWrAgAh+QQJCQAFACwAAAAAEAAFAIK8urzc2tzEwsS8vrzc3tz///8AAAAAAAADFEiyUf6wCEBHvLPemIHdTzCMDegkACH5BAkJAAYALAAAAAAQAAUAgoSChLS2tIyKjLy+vIyOjMTCxP///wAAAAMUWCQ09jAaAiqQmFosdeXRUAkBCCUAIfkECQkACAAsAAAAABAABQCDvLq83N7c3Nrc9Pb0xMLE/P78vL68/Pr8////AAAAAAAAAAAAAAAAAAAAAAAAAAAABCEwkCnKGbegvQn4RjGMx8F1HxBi5Il4oEiap2DcVYlpZwQAIfkECQkACAAsAAAAABAABQCDvLq85OLkxMLE9Pb0vL685ObkxMbE/Pr8////AAAAAAAAAAAAAAAAAAAAAAAAAAAABCDwnCGHEcIMxPn4VAGMQNBx0zQEZHkiYNiap5RaBKG9EQAh+QQJCQAJACwAAAAAEAAFAIOEgoTMysyMjozs6uyUlpSMiozMzsyUkpTs7uz///8AAAAAAAAAAAAAAAAAAAAAAAAEGTBJiYgoBM09DfhAwHEeKI4dGKLTIHzCwEUAIfkECQkACAAsAAAAABAABQCDvLq85OLkxMLE9Pb0vL685ObkxMbE/Pr8////AAAAAAAAAAAAAAAAAAAAAAAAAAAABCAQSTmMEGaco8+UBSACwWBqHxKOJYd+q1iaXFoRRMbtEQAh+QQJCQAIACwAAAAAEAAFAIO8urzc3tzc2tz09vTEwsT8/vy8vrz8+vz///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAEIhBJWc6wJZAtJh3gcRBAaXiIZV2kiRbgNZbA6VXiUAhGL0QAIfkECQkABgAsAAAAABAABQCChIKEtLa0jIqMvL68jI6MxMLE////AAAAAxRoumxFgoxGCbiANos145e3DJcQJAAh+QQJCQAFACwAAAAAEAAFAIK8urzc2tzEwsS8vrzc3tz///8AAAAAAAADFFi6XCQwtCmAHbPVm9kGWKcEQxkkACH5BAkJAAIALAAAAAAQAAUAgfT29Pz6/P///wAAAAIRlI8SAZsPYnuJMUCRnNksWwAAOw==",debug:!1};return"remove"!==b&&a.extend(c,b),"check"!==b&&a.extend(c,b),this.each(function(){function d(){a(l).off("scroll.onScreen resize.onScreen"),a(window).off("resize.onScreen")}function e(){return z?v-t+c.tolerance}function f(){return z?v+(t-c.tolerance)r-c.tolerance:v>p-c.tolerance||-t+c.tolerance>v}function g(){return z?w-u+c.tolerance}function h(){return z?w+(u-c.tolerance)s-c.tolerance:w>q-c.tolerance||-u+c.tolerance>w}function i(){return x?!1:"horizontal"===c.direction?g():e()}function j(){return x?"horizontal"===c.direction?h():f():!1}function k(a,b,c){var d,e,f;return function(){e=arguments,f=!0,c=c||this,d||!function(){f?(a.apply(c,e),f=!1,d=setTimeout(arguments.callee,b)):d=null}()}}var l=this;if("remove"===b)return void d();var m,n,o,p,q,r,s,t,u,v,w,x=!1,y=a(this),z=a.isWindow(c.container),A=function(){if(z||"static"!==a(c.container).css("position")||a(c.container).css("position","relative"),o=a(c.container),p=o.height(),q=o.width(),r=o.scrollTop()+p,s=o.scrollLeft()+q,t=y.outerHeight(!0),u=y.outerWidth(!0),z){var d=y.offset();v=d.top,w=d.left}else{var e=y.position();v=e.top,w=e.left}if(m=o.scrollTop(),n=o.scrollLeft(),c.debug,i()){if(c.toggleClass&&y.addClass(c.toggleClass),a.isFunction(c.doIn)&&c.doIn.call(y[0]),c.lazyAttr&&"IMG"===y.prop("tagName")){var f=y.attr(c.lazyAttr);f!==y.prop("src")&&(y.css({background:"url("+c.lazyPlaceholder+") 50% 50% no-repeat",minHeight:"5px",minWidth:"16px"}),y.prop("src",f).load(function(){a(this).css({background:"none"})}))}x=!0}else j()&&(c.toggleClass&&y.removeClass(c.toggleClass),a.isFunction(c.doOut)&&c.doOut.call(y[0]),x=!1);return"check"===b?x:void 0};window.location.hash?k(A,50):A(),c.throttle&&(A=k(A,c.throttle)),a(c.container).on("scroll.onScreen resize.onScreen",A),z||a(window).on("resize.onScreen",A),"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=jQuery:"function"==typeof define&&define.amd&&define("jquery-onscreen",[],function(){return jQuery})})}}(jQuery); 15 | -------------------------------------------------------------------------------- /judge/static/assets/vendor/quill/quill.core.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Quill Editor v1.3.6 3 | * https://quilljs.com/ 4 | * Copyright (c) 2014, Jason Chen 5 | * Copyright (c) 2013, salesforce.com 6 | */ 7 | .ql-container { 8 | box-sizing: border-box; 9 | font-family: Helvetica, Arial, sans-serif; 10 | font-size: 13px; 11 | height: 100%; 12 | margin: 0px; 13 | position: relative; 14 | } 15 | .ql-container.ql-disabled .ql-tooltip { 16 | visibility: hidden; 17 | } 18 | .ql-container.ql-disabled .ql-editor ul[data-checked] > li::before { 19 | pointer-events: none; 20 | } 21 | .ql-clipboard { 22 | left: -100000px; 23 | height: 1px; 24 | overflow-y: hidden; 25 | position: absolute; 26 | top: 50%; 27 | } 28 | .ql-clipboard p { 29 | margin: 0; 30 | padding: 0; 31 | } 32 | .ql-editor { 33 | box-sizing: border-box; 34 | line-height: 1.42; 35 | height: 100%; 36 | outline: none; 37 | overflow-y: auto; 38 | padding: 12px 15px; 39 | tab-size: 4; 40 | -moz-tab-size: 4; 41 | text-align: left; 42 | white-space: pre-wrap; 43 | word-wrap: break-word; 44 | } 45 | .ql-editor > * { 46 | cursor: text; 47 | } 48 | .ql-editor p, 49 | .ql-editor ol, 50 | .ql-editor ul, 51 | .ql-editor pre, 52 | .ql-editor blockquote, 53 | .ql-editor h1, 54 | .ql-editor h2, 55 | .ql-editor h3, 56 | .ql-editor h4, 57 | .ql-editor h5, 58 | .ql-editor h6 { 59 | margin: 0; 60 | padding: 0; 61 | counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; 62 | } 63 | .ql-editor ol, 64 | .ql-editor ul { 65 | padding-left: 1.5em; 66 | } 67 | .ql-editor ol > li, 68 | .ql-editor ul > li { 69 | list-style-type: none; 70 | } 71 | .ql-editor ul > li::before { 72 | content: '\2022'; 73 | } 74 | .ql-editor ul[data-checked=true], 75 | .ql-editor ul[data-checked=false] { 76 | pointer-events: none; 77 | } 78 | .ql-editor ul[data-checked=true] > li *, 79 | .ql-editor ul[data-checked=false] > li * { 80 | pointer-events: all; 81 | } 82 | .ql-editor ul[data-checked=true] > li::before, 83 | .ql-editor ul[data-checked=false] > li::before { 84 | color: #777; 85 | cursor: pointer; 86 | pointer-events: all; 87 | } 88 | .ql-editor ul[data-checked=true] > li::before { 89 | content: '\2611'; 90 | } 91 | .ql-editor ul[data-checked=false] > li::before { 92 | content: '\2610'; 93 | } 94 | .ql-editor li::before { 95 | display: inline-block; 96 | white-space: nowrap; 97 | width: 1.2em; 98 | } 99 | .ql-editor li:not(.ql-direction-rtl)::before { 100 | margin-left: -1.5em; 101 | margin-right: 0.3em; 102 | text-align: right; 103 | } 104 | .ql-editor li.ql-direction-rtl::before { 105 | margin-left: 0.3em; 106 | margin-right: -1.5em; 107 | } 108 | .ql-editor ol li:not(.ql-direction-rtl), 109 | .ql-editor ul li:not(.ql-direction-rtl) { 110 | padding-left: 1.5em; 111 | } 112 | .ql-editor ol li.ql-direction-rtl, 113 | .ql-editor ul li.ql-direction-rtl { 114 | padding-right: 1.5em; 115 | } 116 | .ql-editor ol li { 117 | counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; 118 | counter-increment: list-0; 119 | } 120 | .ql-editor ol li:before { 121 | content: counter(list-0, decimal) '. '; 122 | } 123 | .ql-editor ol li.ql-indent-1 { 124 | counter-increment: list-1; 125 | } 126 | .ql-editor ol li.ql-indent-1:before { 127 | content: counter(list-1, lower-alpha) '. '; 128 | } 129 | .ql-editor ol li.ql-indent-1 { 130 | counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; 131 | } 132 | .ql-editor ol li.ql-indent-2 { 133 | counter-increment: list-2; 134 | } 135 | .ql-editor ol li.ql-indent-2:before { 136 | content: counter(list-2, lower-roman) '. '; 137 | } 138 | .ql-editor ol li.ql-indent-2 { 139 | counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; 140 | } 141 | .ql-editor ol li.ql-indent-3 { 142 | counter-increment: list-3; 143 | } 144 | .ql-editor ol li.ql-indent-3:before { 145 | content: counter(list-3, decimal) '. '; 146 | } 147 | .ql-editor ol li.ql-indent-3 { 148 | counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; 149 | } 150 | .ql-editor ol li.ql-indent-4 { 151 | counter-increment: list-4; 152 | } 153 | .ql-editor ol li.ql-indent-4:before { 154 | content: counter(list-4, lower-alpha) '. '; 155 | } 156 | .ql-editor ol li.ql-indent-4 { 157 | counter-reset: list-5 list-6 list-7 list-8 list-9; 158 | } 159 | .ql-editor ol li.ql-indent-5 { 160 | counter-increment: list-5; 161 | } 162 | .ql-editor ol li.ql-indent-5:before { 163 | content: counter(list-5, lower-roman) '. '; 164 | } 165 | .ql-editor ol li.ql-indent-5 { 166 | counter-reset: list-6 list-7 list-8 list-9; 167 | } 168 | .ql-editor ol li.ql-indent-6 { 169 | counter-increment: list-6; 170 | } 171 | .ql-editor ol li.ql-indent-6:before { 172 | content: counter(list-6, decimal) '. '; 173 | } 174 | .ql-editor ol li.ql-indent-6 { 175 | counter-reset: list-7 list-8 list-9; 176 | } 177 | .ql-editor ol li.ql-indent-7 { 178 | counter-increment: list-7; 179 | } 180 | .ql-editor ol li.ql-indent-7:before { 181 | content: counter(list-7, lower-alpha) '. '; 182 | } 183 | .ql-editor ol li.ql-indent-7 { 184 | counter-reset: list-8 list-9; 185 | } 186 | .ql-editor ol li.ql-indent-8 { 187 | counter-increment: list-8; 188 | } 189 | .ql-editor ol li.ql-indent-8:before { 190 | content: counter(list-8, lower-roman) '. '; 191 | } 192 | .ql-editor ol li.ql-indent-8 { 193 | counter-reset: list-9; 194 | } 195 | .ql-editor ol li.ql-indent-9 { 196 | counter-increment: list-9; 197 | } 198 | .ql-editor ol li.ql-indent-9:before { 199 | content: counter(list-9, decimal) '. '; 200 | } 201 | .ql-editor .ql-indent-1:not(.ql-direction-rtl) { 202 | padding-left: 3em; 203 | } 204 | .ql-editor li.ql-indent-1:not(.ql-direction-rtl) { 205 | padding-left: 4.5em; 206 | } 207 | .ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right { 208 | padding-right: 3em; 209 | } 210 | .ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right { 211 | padding-right: 4.5em; 212 | } 213 | .ql-editor .ql-indent-2:not(.ql-direction-rtl) { 214 | padding-left: 6em; 215 | } 216 | .ql-editor li.ql-indent-2:not(.ql-direction-rtl) { 217 | padding-left: 7.5em; 218 | } 219 | .ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right { 220 | padding-right: 6em; 221 | } 222 | .ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right { 223 | padding-right: 7.5em; 224 | } 225 | .ql-editor .ql-indent-3:not(.ql-direction-rtl) { 226 | padding-left: 9em; 227 | } 228 | .ql-editor li.ql-indent-3:not(.ql-direction-rtl) { 229 | padding-left: 10.5em; 230 | } 231 | .ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right { 232 | padding-right: 9em; 233 | } 234 | .ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right { 235 | padding-right: 10.5em; 236 | } 237 | .ql-editor .ql-indent-4:not(.ql-direction-rtl) { 238 | padding-left: 12em; 239 | } 240 | .ql-editor li.ql-indent-4:not(.ql-direction-rtl) { 241 | padding-left: 13.5em; 242 | } 243 | .ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right { 244 | padding-right: 12em; 245 | } 246 | .ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right { 247 | padding-right: 13.5em; 248 | } 249 | .ql-editor .ql-indent-5:not(.ql-direction-rtl) { 250 | padding-left: 15em; 251 | } 252 | .ql-editor li.ql-indent-5:not(.ql-direction-rtl) { 253 | padding-left: 16.5em; 254 | } 255 | .ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right { 256 | padding-right: 15em; 257 | } 258 | .ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right { 259 | padding-right: 16.5em; 260 | } 261 | .ql-editor .ql-indent-6:not(.ql-direction-rtl) { 262 | padding-left: 18em; 263 | } 264 | .ql-editor li.ql-indent-6:not(.ql-direction-rtl) { 265 | padding-left: 19.5em; 266 | } 267 | .ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right { 268 | padding-right: 18em; 269 | } 270 | .ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right { 271 | padding-right: 19.5em; 272 | } 273 | .ql-editor .ql-indent-7:not(.ql-direction-rtl) { 274 | padding-left: 21em; 275 | } 276 | .ql-editor li.ql-indent-7:not(.ql-direction-rtl) { 277 | padding-left: 22.5em; 278 | } 279 | .ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right { 280 | padding-right: 21em; 281 | } 282 | .ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right { 283 | padding-right: 22.5em; 284 | } 285 | .ql-editor .ql-indent-8:not(.ql-direction-rtl) { 286 | padding-left: 24em; 287 | } 288 | .ql-editor li.ql-indent-8:not(.ql-direction-rtl) { 289 | padding-left: 25.5em; 290 | } 291 | .ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right { 292 | padding-right: 24em; 293 | } 294 | .ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right { 295 | padding-right: 25.5em; 296 | } 297 | .ql-editor .ql-indent-9:not(.ql-direction-rtl) { 298 | padding-left: 27em; 299 | } 300 | .ql-editor li.ql-indent-9:not(.ql-direction-rtl) { 301 | padding-left: 28.5em; 302 | } 303 | .ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right { 304 | padding-right: 27em; 305 | } 306 | .ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right { 307 | padding-right: 28.5em; 308 | } 309 | .ql-editor .ql-video { 310 | display: block; 311 | max-width: 100%; 312 | } 313 | .ql-editor .ql-video.ql-align-center { 314 | margin: 0 auto; 315 | } 316 | .ql-editor .ql-video.ql-align-right { 317 | margin: 0 0 0 auto; 318 | } 319 | .ql-editor .ql-bg-black { 320 | background-color: #000; 321 | } 322 | .ql-editor .ql-bg-red { 323 | background-color: #e60000; 324 | } 325 | .ql-editor .ql-bg-orange { 326 | background-color: #f90; 327 | } 328 | .ql-editor .ql-bg-yellow { 329 | background-color: #ff0; 330 | } 331 | .ql-editor .ql-bg-green { 332 | background-color: #008a00; 333 | } 334 | .ql-editor .ql-bg-blue { 335 | background-color: #06c; 336 | } 337 | .ql-editor .ql-bg-purple { 338 | background-color: #93f; 339 | } 340 | .ql-editor .ql-color-white { 341 | color: #fff; 342 | } 343 | .ql-editor .ql-color-red { 344 | color: #e60000; 345 | } 346 | .ql-editor .ql-color-orange { 347 | color: #f90; 348 | } 349 | .ql-editor .ql-color-yellow { 350 | color: #ff0; 351 | } 352 | .ql-editor .ql-color-green { 353 | color: #008a00; 354 | } 355 | .ql-editor .ql-color-blue { 356 | color: #06c; 357 | } 358 | .ql-editor .ql-color-purple { 359 | color: #93f; 360 | } 361 | .ql-editor .ql-font-serif { 362 | font-family: Georgia, Times New Roman, serif; 363 | } 364 | .ql-editor .ql-font-monospace { 365 | font-family: Monaco, Courier New, monospace; 366 | } 367 | .ql-editor .ql-size-small { 368 | font-size: 0.75em; 369 | } 370 | .ql-editor .ql-size-large { 371 | font-size: 1.5em; 372 | } 373 | .ql-editor .ql-size-huge { 374 | font-size: 2.5em; 375 | } 376 | .ql-editor .ql-direction-rtl { 377 | direction: rtl; 378 | text-align: inherit; 379 | } 380 | .ql-editor .ql-align-center { 381 | text-align: center; 382 | } 383 | .ql-editor .ql-align-justify { 384 | text-align: justify; 385 | } 386 | .ql-editor .ql-align-right { 387 | text-align: right; 388 | } 389 | .ql-editor.ql-blank::before { 390 | color: rgba(0,0,0,0.6); 391 | content: attr(data-placeholder); 392 | font-style: italic; 393 | left: 15px; 394 | pointer-events: none; 395 | position: absolute; 396 | right: 15px; 397 | } 398 | -------------------------------------------------------------------------------- /judge/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "judge/base.html" %} 2 | 3 | {% block title %}404 | Not found{% endblock %} 4 | 5 | {% block content %} 6 |

404 Not found

7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /judge/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "judge/base.html" %} 2 | 3 | {% block title %}500 | Error{% endblock %} 4 | 5 | {% block content %} 6 |

500 Error

7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /judge/templates/judge/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block title %}Home{% endblock %} | AUTOJUDGE 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 25 | {% block styles %} 26 | {% endblock %} 27 | 28 | 29 | 30 | {% url 'judge:index' as homepage %} 31 | 105 |
106 | {% if request.path != homepage %} 107 | 114 | {% endif %} 115 |
116 |
117 | {% block content %} 118 | {% endblock %} 119 |
120 |
121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 137 | {% block scripts %} 138 | {% endblock %} 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /judge/templates/judge/contest_add_person.html: -------------------------------------------------------------------------------- 1 | {% extends 'judge/base.html' %} 2 | 3 | {% block title %}Add {{ type }} | Contest {{ contest_id }}{% endblock %} 4 | 5 | {% block breadcrumb %} 6 | 7 | {% if type == 'Poster' %} 8 | 9 | {% endif %} 10 | {% if type == 'Participant' %} 11 | 12 | {% endif %} 13 | 14 | {% endblock %} 15 | 16 | {% block content %} 17 |
18 |
19 |

Add {{ type }}

20 |
21 |
22 |
23 | {% if form.non_field_errors %} 24 | {% for nfe in form.non_field_errors %} 25 | 28 | {% endfor %} 29 | {% endif %} 30 | {% csrf_token %} 31 | {% for field in form %} 32 |
33 | {{ field.label_tag }} {{ field }} 34 | {% if field.help_text %} 35 | {{ field.help_text|safe }} 36 | {% endif %} 37 | {% if field.errors %} 38 | {% for fe in field.errors %} 39 | 42 | {% endfor %} 43 | {% endif %} 44 |
45 | {% endfor %} 46 | 47 |
48 |
49 |
50 | {% endblock %} 51 | -------------------------------------------------------------------------------- /judge/templates/judge/contest_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'judge/base.html' %} 2 | 3 | {% load tz %} 4 | 5 | {% block title %}{{ contest.name }}{% endblock %} 6 | 7 | {% block breadcrumb %} 8 | 9 | {% endblock %} 10 | 11 | {% block scripts %} 12 | 13 | 25 | {% if form.errors %} 26 | 31 | {% endif %} 32 | {% endblock %} 33 | 34 | {% block content %} 35 |
36 |
37 |

{{ contest.name }}

38 |
39 | {% if type == 'Poster' %} 40 | {% if curr_time < contest.start_datetime %}Add Problem{% endif %} 42 | Download 43 | Scores 44 | {% endif %} 45 | See posters 46 | {% if not contest.public %}See 48 | participants{% endif %} 49 | {% if type == 'Poster' and curr_time < contest.hard_end_datetime %}{% endif %} 52 |
53 |
54 | {% if type == 'Poster' and curr_time < contest.hard_end_datetime %} 55 | 100 | {% endif %} 101 |
102 | Starts at {{ contest.start_datetime|localtime }}
103 | {% if contest.soft_end_datetime != contest.hard_end_datetime %} 104 | Submissions penalized after 105 | {{ contest.soft_end_datetime|localtime }}
106 | {% endif %} 107 | Ends at {{ contest.hard_end_datetime|localtime }}
108 |
109 |
110 | 111 |
112 |
113 | {% for problem in problems %} 114 |
115 |
116 |

[{{ problem.code }}] {{ problem.name }}

117 | Go to problem page 118 |
119 |
120 | {% empty %} 121 |
122 |
123 | No problems posted yet. 124 |
125 |
126 | {% endfor %} 127 |
128 |
129 | {% if leaderboard_status %} 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | {% for entry in leaderboard %} 140 | 141 | 142 | 143 | 144 | 145 | {% endfor %} 146 | 147 |
RankParticipantScore
{{ forloop.counter }}{{ entry.0 }}{{ entry.1 }}
148 | {% else %} 149 |

{{ leaderboard }}

150 | {% endif %} 151 |
152 | {% if type == 'Poster' %} 153 |
154 |
155 | {% csrf_token %} 156 | 160 |
161 |
162 | {% endif %} 163 |
164 | 165 | {% endblock %} 166 | -------------------------------------------------------------------------------- /judge/templates/judge/contest_persons.html: -------------------------------------------------------------------------------- 1 | {% extends 'judge/base.html' %} 2 | 3 | {% block title %}{{ type }}s | Contest {{ contest_id }}{% endblock %} 4 | 5 | {% block breadcrumb %} 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block scripts %} 11 | 17 | {% endblock %} 18 | 19 | {% block content %} 20 |
21 |
22 |

{{ type }}s

23 | {% if permission %} 24 | {% if type == 'Poster' %} 25 | Add Poster 26 | {% else %} 27 | Add Participant 28 | {% endif %} 29 | {% endif %} 30 |
31 |
32 |
{% csrf_token %} 33 | {% if form.non_field_errors %} 34 | {% for nfe in form.non_field_errors %} 35 | 38 | {% endfor %} 39 | {% endif %} 40 | {% for field in form %} 41 | {{ field }} 42 | {% endfor %} 43 |
44 |
45 | {% for person in persons %} 46 |
47 | {{ person }} 48 | {% if permission %} 49 | 50 | {% endif %} 51 |
52 | {% empty %} 53 |

There are no {{ type|lower }}s for this contest.

54 | {% endfor %} 55 |
56 |
57 |
58 | 59 | {% endblock %} 60 | -------------------------------------------------------------------------------- /judge/templates/judge/edit_problem.html: -------------------------------------------------------------------------------- 1 | {% extends "judge/base.html" %} 2 | 3 | {% block title %}Edit Problem {{ problem.code }} | Contest {{ contest.pk }}{% endblock %} 4 | 5 | {% block breadcrumb %} 6 | 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block styles %} 12 | 13 | {% endblock %} 14 | 15 | {% block scripts %} 16 | 17 | 57 | {% endblock %} 58 | 59 | {% block content %} 60 |
61 |
62 |

Edit Problem

63 |
64 |
65 |
66 |
67 |
68 | {% if form.non_field_errors %} 69 | {% for nfe in form.non_field_errors %} 70 | 73 | {% endfor %} 74 | {% endif %} 75 | {% csrf_token %} 76 | {% for field in form %} 77 |
78 | {{ field.label_tag }} {{ field }} 79 | {% if field.name == 'statement' %} 80 |
81 | {% endif %} 82 | {% if field.name == 'input_format' %} 83 |
84 | {% endif %} 85 | {% if field.name == 'output_format' %} 86 |
87 | {% endif %} 88 | {% if field.help_text %} 89 | {{ field.help_text|safe }} 90 | {% endif %} 91 | {% if field.errors %} 92 | 95 | {% endif %} 96 |
97 | {% endfor %} 98 | 99 |
100 |
101 |
102 | {% endblock %} 103 | -------------------------------------------------------------------------------- /judge/templates/judge/index.html: -------------------------------------------------------------------------------- 1 | {% extends "judge/base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Contests

7 | {% if user.is_authenticated %} 8 | 9 | 10 | New Contest 11 | 12 | {% endif %} 13 |
14 |
15 | {% for contest, permission in contests %} 16 | {% if permission != None %} 17 | 18 |
19 |
20 |
21 |

{{ contest.name }}

22 |
23 | {% if contest.public %}Public{% else %} 24 | Private{% endif %} 25 | {% if permission %}Poster 26 | {% else %}Participant{% endif %} 27 |
28 |
29 | Go to contest page 30 |
31 |
32 | {% endif %} 33 | {% empty %} 34 |
35 | No active contests for the time being. Create a new contest or check later. 36 |
37 | {% endfor %} 38 |
39 |
40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /judge/templates/judge/new_contest.html: -------------------------------------------------------------------------------- 1 | {% extends "judge/base.html" %} 2 | 3 | {% block title %}New Contest{% endblock %} 4 | 5 | {% block styles %} 6 | 7 | {% endblock %} 8 | 9 | {% block scripts %} 10 | 11 | 22 | {% endblock %} 23 | 24 | {% block breadcrumb %} 25 | 26 | {% endblock %} 27 | 28 | {% block content %} 29 |
30 |
31 |

New Contest

32 |
33 |
34 |
35 |
36 |
37 | {% if form.non_field_errors %} 38 | {% for nfe in form.non_field_errors %} 39 | 42 | {% endfor %} 43 | {% endif %} 44 | {% csrf_token %} 45 | {% for field in form %} 46 |
47 | {{ field.label_tag }} {{ field }} 48 | {% if field.help_text %} 49 | {{ field.help_text|safe }} 50 | {% endif %} 51 | {% if field.errors %} 52 | 55 | {% endif %} 56 |
57 | {% endfor %} 58 | 59 |
60 |
61 |
62 | {% endblock %} 63 | -------------------------------------------------------------------------------- /judge/templates/judge/new_problem.html: -------------------------------------------------------------------------------- 1 | {% extends "judge/base.html" %} 2 | 3 | {% block title %}New Problem | Contest {{ contest.pk }}{% endblock %} 4 | 5 | {% block breadcrumb %} 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block styles %} 11 | 12 | {% endblock %} 13 | 14 | {% block scripts %} 15 | 16 | 52 | {% endblock %} 53 | 54 | {% block content %} 55 |
56 |
57 |

New Problem

58 |
59 |
60 |
61 |
62 |
63 | {% if form.non_field_errors %} 64 | 70 | {% endif %} 71 | {% csrf_token %} 72 | {% for field in form %} 73 |
74 | {{ field.label_tag }} {{ field }} 75 | {% if field.name == 'statement' %} 76 |
77 | {% endif %} 78 | {% if field.name == 'input_format' %} 79 |
80 | {% endif %} 81 | {% if field.name == 'output_format' %} 82 |
83 | {% endif %} 84 | {% if field.help_text %} 85 | {{ field.help_text|safe }} 86 | {% endif %} 87 | {% if field.name == 'compilation_script' or field.name == 'test_script' %} 88 | The default script can be downloaded from here. 89 | {% endif %} 90 | {% if field.errors %} 91 | 94 | {% endif %} 95 |
96 | {% endfor %} 97 | 98 |
99 |
100 |
101 | {% endblock %} 102 | -------------------------------------------------------------------------------- /judge/templates/judge/problem_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'judge/base.html' %} 2 | 3 | {% block title %}Problem {{ problem.code }} | Contest {{ problem.contest }}{% endblock %} 4 | 5 | {% block breadcrumb %} 6 | 8 | 9 | {% endblock %} 10 | 11 | {% block styles %} 12 | 13 | 38 | {% endblock %} 39 | 40 | {% block scripts %} 41 | 42 | 43 | 84 | 107 | {% endblock %} 108 | 109 | {% block content %} 110 |
111 |
112 |

[{{ problem.code }}]

113 |

{{ problem.name }}

114 |
115 |
116 | {% if type == 'Poster' %} 117 |
118 | 119 | 120 | 121 | {% if curr_time < problem.contest.start_datetime %} 122 |
123 | {% csrf_token %} 124 | 125 |
126 | {% endif %} 127 |
128 | {% endif %} 129 |
130 |
131 |

Statement

132 |
133 |
134 |
135 |

Input Format

136 |
137 |
138 |
139 |

Output Format

140 |
141 |
142 |
143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
Max. Score{{ problem.max_score }}
Difficulty{{ problem.difficulty }}
Time limit{{ problem.time_limit }}
Memory limit{{ problem.memory_limit }} MB
Allowed file extensions{{ problem.file_exts }}
167 |
168 |
169 |

Public test cases

170 | {% for test in public_tests %} 171 |
Test Case {{ forloop.counter }}
172 | {% if type == 'Poster' and curr_time < problem.contest.start_datetime %} 173 |
174 |
175 | {% csrf_token %} 176 | 177 |
178 |
179 | {% endif %} 180 |
181 |
Input
182 |
183 | 184 |
{{ test.0 }}
186 |
187 |
188 |
189 |
Output
190 |
191 | 192 |
{{ test.1 }}
194 |
195 |
196 | {% endfor %} 197 |
198 | {% if type == 'Poster' %} 199 |
200 |

Private test cases

201 | {% for test in private_tests %} 202 |
Test Case {{ forloop.counter }}
203 | {% if curr_time < problem.contest.start_datetime %} 204 |
205 |
206 | {% csrf_token %} 207 | 208 |
209 |
210 | {% endif %} 211 |
212 |
Input
213 |
214 | 215 |
{{ test.0 }}
217 |
218 |
219 |
220 |
Output
221 |
222 | 223 |
{{ test.1 }}
225 |
226 |
227 | {% endfor %} 228 |
229 | {% endif %} 230 |
231 | {% if problem.starting_code %} 232 | Download starting code 233 | {% endif %} 234 | {% if type == 'Poster' %} 235 | Download Compilation 236 | Script 237 | Download Test Script 238 | {% endif %} 239 |
240 |
241 | 242 | {% if type == 'Participant' and user.is_authenticated %} 243 |
244 |
245 |
246 |
247 |

Submit Solution

248 |
249 |
250 |
251 | {% if form.non_field_errors %} 252 | {% for nfe in form.non_field_errors %} 253 | 256 | {% endfor %} 257 | {% endif %} 258 | {% csrf_token %} 259 | {% for field in form %} 260 | {% if field.name == 'submission_file' %} 261 |
262 | 263 | {{ field }} 264 | {% if field.help_text %} 265 | {{ field.help_text|safe }} 266 | {% endif %} 267 | {% if field.errors %} 268 | 271 | {% endif %} 272 |
273 | {% else %} 274 |
275 | {{ field.label_tag }} 276 | {{ field }} 277 | {% if field.help_text %} 278 | {{ field.help_text|safe }} 279 | {% endif %} 280 | {% if field.errors %} 281 | 284 | {% endif %} 285 |
286 | {% endif %} 287 | {% endfor %} 288 |
289 | 290 |
291 |
292 |
293 |
294 |
295 | 299 |
300 | {% endif %} 301 | 302 | {% if type == 'Poster' %} 303 |
304 | {% if curr_time < problem.contest.start_datetime %} 305 |
306 |
307 |
308 |

Add Test Case

309 |
310 |
311 |
312 | {% if form.non_field_errors %} 313 | 319 | {% endif %} 320 | {% csrf_token %} 321 | {% for field in form %} 322 |
323 | {{ field.label_tag }} 324 | {{ field }} 325 | {% if field.help_text %} 326 | {{ field.help_text|safe }} 327 | {% endif %} 328 | {% if field.errors %} 329 | 332 | {% endif %} 333 |
334 | {% endfor %} 335 | 336 |
337 |
338 |
339 |
340 | {% endif %} 341 |
342 | See all submissions 343 |
344 |
345 | {% endif %} 346 | {% endblock %} 347 | -------------------------------------------------------------------------------- /judge/templates/judge/problem_submissions.html: -------------------------------------------------------------------------------- 1 | {% extends 'judge/base.html' %} 2 | 3 | {% block title %} Submissions | Problem {{ problem.code }} | Contest {{ problem.contest }}{% endblock %} 4 | 5 | {% block breadcrumb %} 6 | 8 | 9 | 10 | {% endblock %} 11 | 12 | {% block content %} 13 | 14 | {% for email, subs in submissions.items %} 15 |
16 |
17 | {% if not participant %}

User: {{ email }}

{% endif %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% for sub in subs.0 %} 29 | 30 | 31 | 32 | 33 | 38 | 39 | {% endfor %} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 73 | 74 | 75 |
SubmissionTimestampFinal score
{{ sub.pk }}{{ sub.timestamp }}{{ sub.final_score }} 34 | 35 | 36 | 37 |
Comments
49 |
    50 | {% for comm in subs.1 %} 51 |
  • 52 |
    53 | {% if user.email == comm.0.email %} 54 | Me{% else %} 55 | {{ comm.0.email }}{% endif %}
    56 | {{ comm.1 }} 57 |

    {{ comm.2 }}

    58 |
  • 59 | {% endfor %} 60 |
61 |
62 | {% csrf_token %} 63 | {% for field in form %} 64 | {% if field.name == 'participant_email' %} 65 | 66 | {% else %} 67 | {{ field.label_tag }}{{ field }} 68 | {% endif %} 69 | {% endfor %} 70 | 71 |
72 |
76 |
77 |
78 | {% endfor %} 79 | 80 | {% endblock %} 81 | -------------------------------------------------------------------------------- /judge/templates/judge/submission_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'judge/base.html' %} 2 | 3 | {% block title %}Submission | Problem {{ problem.code }} | Contest {{ problem.contest }}{% endblock %} 4 | 5 | {% block breadcrumb %} 6 | 8 | 9 | 10 | 11 | {% endblock %} 12 | 13 | {% block scripts %} 14 | {% if form.errors %} 15 | 20 | {% endif %} 21 | {% endblock %} 22 | 23 | {% block content %} 24 |
25 |
26 |
27 | 28 | 29 | Download 30 | 31 | {% if type == 'Poster' and problem.contest.enable_poster_score %} 32 | 37 | 82 | {% endif %} 83 |
84 |
85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | {% if problem.contest.enable_poster_score %} 93 | 94 | 95 | 96 | 97 | {% endif %} 98 | {% if problem.contest.enable_linter_score %} 99 | 100 | 101 | 102 | 103 | {% endif %} 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 |
Judge Score{{ judge_score }}
Poster Score{{ poster_score }}
Linter Score{{ linter_score }}
Final Score{{ final_score }}
Timestamp{{ timestamp }}
File type{{ file_type }}
118 |
119 |
120 |
121 |

Test Cases

122 |
123 |
124 | {% for test_id, res in test_results.items %} 125 |
126 | Test Case {{ forloop.counter }} 127 | {% if res.3 %} 128 | Public 129 | {% else %} 130 | Private 131 | {% endif %} 132 | {% if res.0 == "Passed" %} 133 | {{ res.0 }} 134 | {% elif res.0 == "Running" %} 135 | {{ res.0 }} 136 | {% elif res.0 == "Internal Failure" %} 137 | {{ res.0 }} 138 | {% else %} 139 | {{ res.0 }} 140 | {% endif %} 141 | 182 |
183 | {% endfor %} 184 |
185 |
186 |
187 | 188 | {% endblock %} 189 | -------------------------------------------------------------------------------- /judge/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase, utils 2 | from django.urls import reverse 3 | from django.utils import timezone 4 | from django.contrib.auth.models import User 5 | 6 | from datetime import timedelta 7 | from datetime import datetime 8 | 9 | from . import models 10 | from . import handler 11 | 12 | # Create your tests here. 13 | 14 | 15 | class IndexViewTests(TestCase): 16 | def test_no_contests_index_view(self): 17 | response = self.client.get(reverse('judge:index')) 18 | self.assertEqual(response.status_code, 200) 19 | self.assertContains(response, "No active contests") 20 | self.assertQuerysetEqual(response.context['contests'], []) 21 | 22 | 23 | class ContestProblemTests(TestCase): 24 | def setUp(self): 25 | u = User.objects.create_user( 26 | username='uname', email='admin@admin.org', password='1234') 27 | 28 | models.Person.objects.create(email=u.email) 29 | # models.Person.objects.create(email='testparticipant@iith.ac.in') 30 | poster = models.Person.objects.get(email='admin@admin.org') 31 | # participant = models.Person.objects.get(email='testparticipant@iith.ac.in') 32 | 33 | models.Contest.objects.create(name='Test Contest', start_datetime='2019-04-25T12:30', 34 | soft_end_datetime='2019-04-26T12:30', 35 | hard_end_datetime='2019-04-27T12:30', 36 | penalty=0, public=True) 37 | c = models.Contest.objects.get(name='Test Contest') 38 | 39 | models.ContestPerson.objects.create(contest=c, person=poster, role=True) 40 | 41 | models.Problem.objects.create(code='testprob1', contest=c, name='Test Problem 1', 42 | statement='Test Problem Statement', 43 | input_format='Test input format', 44 | output_format='Test output format', 45 | difficulty=5, time_limit=timedelta(seconds=10), 46 | memory_limit=10000) 47 | 48 | @utils.skipIf(True, "Not working as expected") 49 | def test_contest_check(self): 50 | u = User.objects.get(email='admin@admin.org') 51 | self.client.force_login(u) 52 | response = self.client.get(reverse('judge:index')) 53 | self.assertEqual(response.status_code, 200) 54 | self.assertEqual(response.context['user'].is_authenticated, True) 55 | c = models.Contest.objects.get(name='Test Contest') 56 | contest, perm = next(response.context['contests']) 57 | self.assertEqual(contest.name, 'Test Contest') 58 | self.assertEqual(contest.public, True) 59 | self.assertEqual(perm, True) 60 | self.assertQuerysetEqual(response.context['contests'], zip([c], [False])) 61 | 62 | 63 | class HandlerTests(TestCase): 64 | def test_process_and_delete_contest(self): 65 | status, pk = handler.process_contest(contest_name='Test Contest', 66 | contest_start='2019-04-25T12:30', 67 | contest_soft_end='2019-04-26T12:30', 68 | contest_hard_end='2019-04-27T12:30', 69 | penalty=0, is_public=True, enable_linter_score=True, 70 | enable_poster_score=True) 71 | self.assertTrue(status) 72 | c = models.Contest.objects.filter(pk=int(pk)) 73 | self.assertEqual(len(c), 1) 74 | c = c[0] 75 | self.assertEqual(c.name, 'Test Contest') 76 | # 7 am UTC == 12:30 pm IST 77 | self.assertEqual(c.start_datetime, datetime(2019, 4, 25, 7, 0, tzinfo=timezone.utc)) 78 | self.assertEqual(c.soft_end_datetime, datetime(2019, 4, 26, 7, 0, tzinfo=timezone.utc)) 79 | self.assertEqual(c.hard_end_datetime, datetime(2019, 4, 27, 7, 0, tzinfo=timezone.utc)) 80 | self.assertEqual(c.penalty, 0) 81 | self.assertTrue(c.public) 82 | status, err = handler.delete_contest(contest_id=int(pk)) 83 | self.assertTrue(status) 84 | self.assertIsNone(err) 85 | c = models.Contest.objects.filter(pk=int(pk)) 86 | self.assertEqual(len(c), 0) 87 | 88 | def test_process_update_and_delete_problem(self): 89 | c = models.Contest.objects.create(name='Test Contest', start_datetime='2019-04-25T12:30', 90 | soft_end_datetime='2019-04-26T12:30', 91 | hard_end_datetime='2019-04-27T12:30', 92 | penalty=0, public=True) 93 | status, msg = handler.process_problem( 94 | code='testprob1', contest_id=c.pk, name='Test Problem 1', 95 | statement='Test Problem Statement', 96 | input_format='Test input format', 97 | output_format='Test output format', difficulty=5, 98 | time_limit=timedelta(seconds=10), 99 | memory_limit=10000, file_exts='.py', starting_code=None, 100 | max_score=4, compilation_script=None, test_script=None) 101 | self.assertTrue(status) 102 | self.assertIsNone(msg) 103 | p = models.Problem.objects.filter(pk='testprob1') 104 | self.assertEqual(len(p), 1) 105 | p = p[0] 106 | self.assertEqual(p.code, 'testprob1') 107 | self.assertEqual(p.name, 'Test Problem 1') 108 | self.assertEqual(p.statement, 'Test Problem Statement') 109 | self.assertEqual(p.input_format, 'Test input format') 110 | self.assertEqual(p.output_format, 'Test output format') 111 | self.assertEqual(p.difficulty, 5) 112 | self.assertEqual(p.time_limit, timedelta(seconds=10)) 113 | self.assertEqual(p.memory_limit, 10000) 114 | self.assertEqual(p.file_exts, '.py') 115 | self.assertEqual(p.max_score, 4) 116 | status, msg = handler.update_problem(code=p.code, name='Updated Test Problem 1', 117 | statement='Updated Test Problem Statement', 118 | input_format='Updated Test input format', 119 | output_format='Updated Test output format', 120 | difficulty=4) 121 | self.assertTrue(status) 122 | p = models.Problem.objects.filter(pk='testprob1') 123 | self.assertEqual(len(p), 1) 124 | p = p[0] 125 | self.assertEqual(p.code, 'testprob1') 126 | self.assertEqual(p.name, 'Updated Test Problem 1') 127 | self.assertEqual(p.statement, 'Updated Test Problem Statement') 128 | self.assertEqual(p.input_format, 'Updated Test input format') 129 | self.assertEqual(p.output_format, 'Updated Test output format') 130 | self.assertEqual(p.difficulty, 4) 131 | status, err = handler.delete_problem(problem_id='testprob1') 132 | self.assertTrue(status) 133 | self.assertIsNone(err) 134 | p = models.Problem.objects.filter(pk='testprob1') 135 | self.assertEqual(len(p), 0) 136 | 137 | def test_process_person(self): 138 | person = models.Person.objects.create(email='testing1@test.com', rank=0) 139 | status, message = handler.process_person(person.email, 1) 140 | self.assertTrue(status) 141 | self.assertIsNone(message) 142 | all_persons = models.Person.objects.all() 143 | self.assertEqual(len(all_persons), 1) 144 | one_person = all_persons[0] 145 | self.assertEqual(one_person.email, 'testing1@test.com') 146 | self.assertEqual(one_person.rank, 0) 147 | 148 | def test_add_get_and_delete_personcontest_get_personprob_perm_and_get_poster_participant(self): 149 | c = models.Contest.objects.create(name='Test Contest', start_datetime='2019-04-25T12:30', 150 | soft_end_datetime='2019-04-26T12:30', 151 | hard_end_datetime='2019-04-27T12:30', 152 | penalty=0, public=True) 153 | models.Problem.objects.create(code='testprob1', contest=c, name='Test Problem 1', 154 | statement='Test Problem Statement', 155 | input_format='Test input format', 156 | output_format='Test output format', difficulty=5, 157 | time_limit=timedelta(seconds=10), 158 | memory_limit=10000, file_exts='.py', starting_code=None, 159 | max_score=4, compilation_script=None, test_script=None) 160 | person1 = models.Person.objects.create(email='testing1@test.com', rank=0) 161 | models.Person.objects.create(email='testing2@test.com', rank=0) 162 | status, message = handler.add_person_to_contest(person1.email, c.pk, True) 163 | self.assertTrue(status) 164 | self.assertIsNone(message) 165 | all_personcontest = models.ContestPerson.objects.all() 166 | self.assertEqual(len(all_personcontest), 1) 167 | one_cp = all_personcontest[0] 168 | self.assertTrue(one_cp.role) 169 | self.assertEqual(one_cp.person.email, 'testing1@test.com') 170 | self.assertEqual(one_cp.contest.name, 'Test Contest') 171 | role = handler.get_personcontest_permission(person_id='testing1@test.com', contest_id=c.pk) 172 | self.assertTrue(role) 173 | role = handler.get_personcontest_permission(person_id=None, contest_id=c.pk) 174 | self.assertFalse(role) 175 | role = handler.get_personproblem_permission(person_id='testing1@test.com', 176 | problem_id='testprob1') 177 | self.assertTrue(role) 178 | status, message = handler.delete_personcontest(person_id='testing1@test.com', 179 | contest_id=c.pk) 180 | self.assertFalse(status) 181 | role = handler.get_personcontest_permission(person_id='testing2@test.com', contest_id=c.pk) 182 | self.assertFalse(role) 183 | status, posters = handler.get_posters(contest_id=c.pk) 184 | self.assertTrue(status) 185 | self.assertEqual(len(posters), 1) 186 | self.assertEqual(posters[0], 'testing1@test.com') 187 | status, participants = handler.get_participants(contest_id=c.pk) 188 | self.assertTrue(status) 189 | self.assertEqual(len(participants), 0) 190 | -------------------------------------------------------------------------------- /judge/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.conf.urls import include 3 | from django.contrib.auth.views import LoginView, LogoutView 4 | 5 | from . import views 6 | from . import apps 7 | 8 | app_name = apps.JudgeConfig.name 9 | handler404 = views.handler404 10 | handler500 = views.handler500 11 | 12 | urlpatterns = [ 13 | # General-purpose paths 14 | path('', views.index, name='index'), 15 | path('login/', LoginView.as_view(), name='login'), 16 | path('logout/', LogoutView.as_view(), name='logout'), 17 | path('auth/', include('social_django.urls', namespace='social')), 18 | 19 | # Contest-specific paths 20 | path('contest/new/', views.new_contest, name='new_contest'), 21 | path('contest//', views.contest_detail, name='contest_detail'), 22 | path('contest//scores/', views.contest_scores_csv, name='contest_scores_csv'), 23 | path('contest//delete/', views.delete_contest, name='delete_contest'), 24 | path('contest//problem/new/', 25 | views.new_problem, name='new_problem'), 26 | path('contest//poster/new/', 27 | views.add_poster, name='contest_add_poster'), 28 | path('contest//participant/new/', 29 | views.add_participant, name='contest_add_participant'), 30 | path('contest//posters/', 31 | views.get_posters, name='get_posters'), 32 | path('contest//participants/', 33 | views.get_participants, name='get_participants'), 34 | 35 | # Problem-specific paths 36 | path('problem//', views.problem_detail, name='problem_detail'), 37 | path('problem//delete/', views.delete_problem, name='delete_problem'), 38 | path('problem//starting-code/', 39 | views.problem_starting_code, name='problem_starting_code'), 40 | path('problem//compilation-script/', 41 | views.problem_compilation_script, name='problem_compilation_script'), 42 | path('problem//test-script/', 43 | views.problem_test_script, name='problem_test_script'), 44 | path('problem/default-scripts//', 45 | views.problem_default_script, name='problem_default_script'), 46 | path('problem//edit/', 47 | views.edit_problem, name='edit_problem'), 48 | path('problem//submissions/', 49 | views.problem_submissions, name='problem_submissions'), 50 | 51 | # Submission-specific paths 52 | path('submission//', 53 | views.submission_detail, name='submission_detail'), 54 | path('submission//download/', 55 | views.submission_download, name='submission_download'), 56 | path('problem//testcase//delete/', 57 | views.delete_testcase, name='delete_testcase'), 58 | ] 59 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'autojudge.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.1.6 2 | social-auth-app-django==4.0.0 3 | pycodestyle==2.5.0 4 | typing==3.6.4 5 | -------------------------------------------------------------------------------- /submission_watcher_saver.py: -------------------------------------------------------------------------------- 1 | import os 2 | import django 3 | 4 | from time import sleep 5 | from subprocess import call 6 | from typing import List 7 | from datetime import timedelta 8 | from pycodestyle import Checker 9 | 10 | 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "autojudge.settings") 12 | django.setup() 13 | 14 | from judge import models, handler # noqa: E402 15 | 16 | CONTENT_DIRECTORY = 'content' 17 | TMP_DIRECTORY = 'tmp' 18 | MONITOR_DIRECTORY = os.path.join(CONTENT_DIRECTORY, TMP_DIRECTORY) 19 | DOCKER_IMAGE_NAME = 'autojudge_docker' 20 | 21 | LS: List[str] = [] 22 | # Re-check the status of the submission folder if the number of unscored submissions 23 | # is less than REFRESH_LS_TRIGGER 24 | REFRESH_LS_TRIGGER = 5 25 | # Sleep duration if number of unscored submissions is less than REFRESH_LS_TRIGGER 26 | SLEEP_DUR_BEFORE_REFRESH = 10 27 | 28 | 29 | def _compute_lint_score(report): 30 | if len(report.lines) > 0: 31 | score = 10.0 * (1 - report.total_errors / len(report.lines)) 32 | return max(0.0, score) 33 | 34 | 35 | def saver(sub_id): 36 | update_lb = False 37 | # Based on the result populate SubmsissionTestCase table and return the result 38 | with open(os.path.join(MONITOR_DIRECTORY, 'sub_run_' + sub_id + '.txt'), 'r') as f: 39 | # Assumed format to sub_run_ID.txt file 40 | # PROBLEM_CODE 41 | # SUBMISSION_ID 42 | # TESTCASEID VERDICT TIME MEMORY MESSAGE 43 | # Read the output into verdict, memory and time. 44 | lines = [line[:-1] for line in f.readlines()] 45 | problem = lines[0] 46 | submission = lines[1] 47 | testcase_id, verdict, time, memory, msg = [], [], [], [], [] 48 | for line in lines[2:]: 49 | sep = line.split(' ', maxsplit=4) 50 | testcase_id.append(sep[0]) 51 | verdict.append(sep[1]) 52 | time.append(sep[2]) 53 | memory.append(sep[3]) 54 | with open(os.path.join(MONITOR_DIRECTORY, sep[4])) as log_file: 55 | msg.append(str(log_file.read())) 56 | os.remove(os.path.join(MONITOR_DIRECTORY, sep[4])) # Remove after reading 57 | 58 | # Delete the file after reading 59 | os.remove(os.path.join(MONITOR_DIRECTORY, 'sub_run_' + sub_id + '.txt')) 60 | 61 | problem = models.Problem.objects.get(pk=problem) 62 | s = models.Submission.objects.get(pk=submission) 63 | 64 | score_received = 0 65 | max_score = problem.max_score 66 | for i in range(len(testcase_id)): 67 | if verdict[i] == 'P': 68 | score_received += max_score 69 | st = models.SubmissionTestCase.objects.get(submission=submission, 70 | testcase=testcase_id[i]) 71 | st.verdict = verdict[i] 72 | st.memory_taken = int(memory[i]) 73 | st.time_taken = timedelta(seconds=float(time[i])) 74 | if models.TestCase.objects.get(pk=testcase_id[i]).public: 75 | st.message = msg[i] if len(msg[i]) < 1000 else msg[i][:1000] + '\\nMessage Truncated' 76 | st.save() 77 | 78 | s.judge_score = score_received 79 | 80 | if s.problem.contest.enable_linter_score: 81 | if s.file_type == '.py': 82 | checker = Checker( 83 | os.path.join(CONTENT_DIRECTORY, 84 | 'submissions', 'submission_{}.py'.format(submission)), 85 | quiet=True) 86 | checker.check_all() 87 | s.linter_score = _compute_lint_score(checker.report) 88 | current_final_score = s.judge_score + s.poster_score + s.linter_score 89 | 90 | penalty_multiplier = 1.0 91 | # If the submission crosses soft deadline 92 | # Check if the submission has crossed the hard deadline 93 | # If yes, penalty_multiplier = 0 94 | # Else, penality_multiplier = 1 - num_of_days * penalty 95 | remaining_time = problem.contest.soft_end_datetime - s.timestamp 96 | if s.timestamp > problem.contest.soft_end_datetime: 97 | if s.timestamp > problem.contest.hard_end_datetime: 98 | penalty_multiplier = 0.0 99 | else: 100 | penalty_multiplier += remaining_time.days * problem.contest.penalty 101 | 102 | # If num_of_days * penalty > 1.0, then the score is clamped to zero 103 | s.final_score = max(0.0, current_final_score * penalty_multiplier) 104 | s.save() 105 | 106 | ppf, _ = models.PersonProblemFinalScore.objects.get_or_create(person=s.participant, 107 | problem=problem) 108 | if ppf.score <= s.final_score: 109 | # <= because otherwise when someone submits for the first time and scores 0 110 | # (s)he will not show up in leaderboard 111 | ppf.score = s.final_score 112 | update_lb = True 113 | ppf.save() 114 | 115 | if update_lb: 116 | # Update the leaderboard only if the submission improved the final score 117 | handler.update_leaderboard(problem.contest.pk, s.participant.email) 118 | 119 | return True 120 | 121 | 122 | # Move to ./content 123 | cur_path = os.getcwd() 124 | os.chdir(os.path.join(cur_path, CONTENT_DIRECTORY)) 125 | 126 | out = 1 127 | while out != 0: 128 | print("Building Docker image: {}....".format(DOCKER_IMAGE_NAME)) 129 | # Build docker image using docker run 130 | out = call(['docker', 'build', '-t', DOCKER_IMAGE_NAME, './']) 131 | if out != 0: 132 | print("Build failed, retrying...") 133 | 134 | # Move back to old directory 135 | os.chdir(cur_path) 136 | 137 | print("Docker image: {} built successfully!".format(DOCKER_IMAGE_NAME)) 138 | 139 | 140 | if not os.path.exists(MONITOR_DIRECTORY): 141 | os.makedirs(MONITOR_DIRECTORY) 142 | 143 | 144 | while True: 145 | if len(LS) < REFRESH_LS_TRIGGER: 146 | # Neglect .log files in tmp/; these are for error 147 | # messages arising at any stage of the evaluation 148 | sleep(SLEEP_DUR_BEFORE_REFRESH) 149 | LS = [os.path.join(MONITOR_DIRECTORY, sub_file) 150 | for sub_file in os.listdir(MONITOR_DIRECTORY) if sub_file[:-4] != '.log'] 151 | LS.sort(key=os.path.getctime) 152 | 153 | if len(LS) > 0: 154 | sub_file = LS[0] # The first file submission-wise 155 | sub_id = os.path.basename(sub_file)[8:-4] # This is the submission ID 156 | 157 | # Move to content 158 | cur_dir = os.getcwd() 159 | os.chdir(os.path.join(cur_dir, CONTENT_DIRECTORY)) 160 | 161 | # Run docker image 162 | print("INFO: evaluating submission: {}".format(sub_id)) 163 | call(['docker', 'run', '--rm', '-v', '{}:/app'.format(os.getcwd()), 164 | '-e', 'SUB_ID={}'.format(sub_id), DOCKER_IMAGE_NAME]) 165 | 166 | # Come back to parent directory 167 | os.chdir(cur_dir) 168 | 169 | saver(sub_id) 170 | LS.remove(sub_file) 171 | --------------------------------------------------------------------------------