apply languages

This commit is contained in:
Egor Matveev 2022-02-16 17:40:46 +03:00
parent 1e455346ff
commit 2948c8252e
39 changed files with 758 additions and 12 deletions

0
Checker/__init__.py Normal file
View File

3
Checker/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
Checker/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class CheckerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'Checker'

View File

@ -0,0 +1,27 @@
# Generated by Django 3.2.4 on 2022-02-15 18:22
import Checker.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('Main', '0020_languageapply'),
]
operations = [
migrations.CreateModel(
name='Checker',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(db_index=True, default=Checker.models.generate_token, max_length=30)),
('last_request', models.DateTimeField()),
('set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.set')),
('testing_solution', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='Main.solution')),
],
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.4 on 2022-02-15 18:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('Main', '0020_languageapply'),
('Checker', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='checker',
name='set',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checkers', to='Main.set'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2022-02-15 18:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Checker', '0002_alter_checker_set'),
]
operations = [
migrations.AddField(
model_name='checker',
name='name',
field=models.CharField(default='', max_length=50),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.4 on 2022-02-15 18:48
import Checker.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Checker', '0003_checker_name'),
]
operations = [
migrations.AlterField(
model_name='checker',
name='token',
field=models.CharField(db_index=True, default=Checker.models.generate_token, max_length=30, unique=True),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.4 on 2022-02-16 07:33
import Checker.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Checker', '0004_alter_checker_token'),
]
operations = [
migrations.AddField(
model_name='checker',
name='dynamic_token',
field=models.CharField(db_index=True, default=Checker.models.generate_token, max_length=30, unique=True),
),
]

View File

28
Checker/models.py Normal file
View File

@ -0,0 +1,28 @@
from random import choice
from django.db import models
from django.utils import timezone
from Main.models import Solution, Set
def generate_token():
letters = '1234567890qwertyuiopasdfghjklzxcvbnm!@#$%^&*()QWERTYUIOPASDFGHJKLZXCVBNM'
return ''.join([choice(letters) for _ in range(30)])
class Checker(models.Model):
token = models.CharField(max_length=30, default=generate_token, db_index=True, unique=True)
dynamic_token = models.CharField(max_length=30, default=generate_token, db_index=True, unique=True)
testing_solution = models.ForeignKey(Solution, on_delete=models.SET_NULL, null=True)
set = models.ForeignKey(Set, on_delete=models.CASCADE, related_name="checkers")
last_request = models.DateTimeField()
name = models.CharField(max_length=50, default='')
@property
def status(self):
if self.testing_solution is not None:
return 'Testing'
if (timezone.now() - self.last_request).total_seconds() > 3:
return 'Disabled'
return 'Active'

3
Checker/tests.py Normal file
View File

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

10
Checker/urls.py Normal file
View File

@ -0,0 +1,10 @@
from django.urls import path
from Checker import views
urlpatterns = [
path("status", views.status),
path("available", views.available),
path("get_dynamic", views.get_dynamic),
path("set_result", views.set_result),
]

73
Checker/views.py Normal file
View File

@ -0,0 +1,73 @@
from os.path import join
from tempfile import TemporaryDirectory
from zipfile import ZipFile
from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse, HttpResponse
from django.utils import timezone
from Checker.models import Checker, generate_token
from FileStorage.sync import synchronized_method
from Main.models import Solution, SolutionFile, ExtraFile
def get_dynamic(request):
try:
checker = Checker.objects.get(token=request.GET['token'])
if checker.status == 'Active':
return JsonResponse({"status": "Another checker is working"}, status=403)
checker.dynamic_token = generate_token()
checker.save()
return JsonResponse({"token": checker.dynamic_token})
except ObjectDoesNotExist:
return JsonResponse({"status": "incorrect token"}, status=403)
def status(request):
try:
checker = Checker.objects.get(dynamic_token=request.GET['token'])
now = timezone.now()
checker.last_request = now
checker.save()
return JsonResponse({"status": "ok"})
except ObjectDoesNotExist:
return JsonResponse({"status": "incorrect token"}, status=403)
@synchronized_method
def available(request):
try:
checker = Checker.objects.get(dynamic_token=request.GET['token'])
solution = Solution.objects.filter(set=checker.set, result="In queue").order_by('time_sent').first()
if solution is None:
return JsonResponse({"id": None})
solution.result = "Testing"
solution.save()
with TemporaryDirectory() as tempdir:
with ZipFile(join(tempdir, "package.zip"), 'w') as zip_file:
for sf in SolutionFile.objects.filter(solution=solution):
zip_file.writestr(sf.path, sf.bytes)
for ef in ExtraFile.objects.filter(task=solution.task):
zip_file.writestr(ef.filename, ef.bytes)
response = HttpResponse(open(join(tempdir, 'package.zip'), 'rb').read(), content_type='application/octet-stream', status=201)
response.headers['language_id'] = solution.language_id
response.headers['solution_id'] = solution.id
response.headers['timeout'] = solution.task.time_limit
return response
except ObjectDoesNotExist:
return JsonResponse({"status": "incorrect token"}, status=403)
def set_result(request):
try:
checker = Checker.objects.get(dynamic_token=request.GET['token'])
solution = Solution.objects.get(id=request.GET['solution_id'])
result = request.GET['result']
if checker.set != solution.set:
return JsonResponse({"status": "incorrect solution"}, status=403)
solution.result = result
solution.save()
return JsonResponse({"status": True})
except ObjectDoesNotExist:
return JsonResponse({"status": "incorrect token"}, status=403)

View File

@ -0,0 +1,15 @@
FROM docker:dind
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN apk update && apk add postgresql-dev gcc python3-dev musl-dev jpeg-dev zlib-dev libjpeg
RUN pip3 install --no-cache --upgrade pip setuptools
RUN addgroup -S docker
ENV PYTHONUNBUFFERED 1
RUN mkdir -p /usr/src/app/
WORKDIR /usr/src/app/
COPY . /usr/src/app/
CMD ["python", "main.py"]

View File

View File

@ -0,0 +1,64 @@
from dataclasses import dataclass
@dataclass
class Language:
id: int
name: str
work_name: str
file_type: str
logo_url: str
image: str
highlight: str
def __str__(self):
return self.name
languages = [
Language(
id=0,
name="Python3",
work_name="Python3",
file_type="py",
logo_url="https://entredatos.es/wp-content/uploads/2021/05/1200px-Python-logo-notext.svg.png",
image="python:3.6",
highlight="python",
),
Language(
id=1,
name="Kotlin",
work_name="Kotlin",
file_type="kt",
logo_url="https://upload.wikimedia.org/wikipedia/commons/0/06/Kotlin_Icon.svg",
image="zenika/kotlin",
highlight="kotlin",
),
Language(
id=2,
name="C++",
work_name="Cpp",
file_type="cpp",
logo_url="https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/ISO_C%2B%2B_Logo.svg/1822px-ISO_C%2B%2B_Logo.svg.png",
image="gcc",
highlight="cpp",
),
Language(
id=3,
name="Java",
work_name="Java",
file_type="java",
logo_url="https://upload.wikimedia.org/wikipedia/ru/thumb/3/39/Java_logo.svg/1200px-Java_logo.svg.png",
image="openjdk",
highlight="java",
),
Language(
id=4,
name="C#",
work_name="CSharp",
file_type="cs",
logo_url="https://cdn.worldvectorlogo.com/logos/c--4.svg",
image="mono",
highlight="csharp",
),
]

67
CheckerExecutor/main.py Normal file
View File

@ -0,0 +1,67 @@
from multiprocessing import Process
from os import getenv
from os.path import join
from tempfile import TemporaryDirectory
from time import sleep
from zipfile import ZipFile
from requests import get
from language import languages
from testers import *
def process_solution(path, data, language_id, solution_id, timeout):
with open(join(path, "package.zip"), 'wb') as fs:
fs.write(data)
with ZipFile(join(path, "package.zip"), 'r') as zip_ref:
zip_ref.extractall(path)
language = languages[language_id]
try:
result = eval(language.work_name + "Tester")(path, solution_id, language_id, timeout).execute()
except Exception as e:
print(str(e))
result = "TE"
return result
def poll(token):
while True:
print(get("http://127.0.0.1:8000/checker/status", params={"token": token}).json())
sleep(2)
def main():
request = get("http://127.0.0.1:8000/checker/get_dynamic", params={"token": getenv("TOKEN")})
if request.status_code != 200:
print("Error happened: " + request.json()['status'])
exit(1)
dynamic_token = request.json()['token']
p = Process(target=poll, args=(dynamic_token,))
p.start()
while True:
data = get("http://127.0.0.1:8000/checker/available", params={"token": dynamic_token})
if data.status_code == 200:
sleep(2)
continue
elif data.status_code == 201:
with TemporaryDirectory() as tempdir:
result = process_solution(
tempdir,
data.content,
int(data.headers['language_id']),
int(data.headers['solution_id']),
int(data.headers['timeout']),
)
get("http://127.0.0.1:8000/checker/set_result", params={
"token": dynamic_token,
"solution_id": data.headers['solution_id'],
"result": result
})
else:
print("unknown status")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,115 @@
from os import listdir
from os.path import join, exists
from subprocess import call, TimeoutExpired
from language import *
class TestException(Exception):
pass
class BaseTester:
working_directory = "app"
checker_code = None
def exec_command(self, command, working_directory="app", timeout=None):
return call(
f'docker exec -i solution sh -c "cd {working_directory} && {command}"',
shell=True,
timeout=timeout,
)
def before_test(self):
files = [
file
for file in listdir(self.path)
if file.endswith("." + self.language.file_type)
]
code = self.exec_command(
f'{self.build_command} {" ".join(files)}',
working_directory=self.working_directory,
)
if code != 0:
raise TestException("CE")
def test(self, filename):
code = self.exec_command(
f"< {filename} {self.command} > output.txt",
timeout=self.timeout / 1000,
)
if code != 0:
raise TestException("RE")
result = open(join(self.path, "output.txt"), "r").read().strip().replace('\r\n', '\n')
print("got result", result)
if self.checker_code is not None:
print('using checker')
with open(join(self.path, 'expected.txt'), 'w') as fs:
fs.write(self.predicted)
with open(join(self.path, 'checker.py'), 'w') as fs:
fs.write(self.checker_code)
code = call(f'docker exec -i checker sh -c "cd app && python checker.py"', shell=True, timeout=1)
if code != 0:
raise TestException("WA")
else:
print('using simple check')
if result != self.predicted:
print('incorrect')
raise TestException("WA")
print('correct')
def after_test(self):
pass
@property
def command(self):
return "./executable.exe"
@property
def build_command(self):
return ""
@property
def path(self):
return self._path
@property
def language(self):
return languages[self.language_id]
def __init__(self, path, solution_id, language_id, timeout):
self.solution_id = solution_id
self._path = path
self.language_id = language_id
self.timeout = timeout
def execute(self):
docker_command = f"docker run --name solution --volume={self.path}:/{self.working_directory} -t -d {self.language.image}"
print(docker_command)
call(docker_command, shell=True)
checker = join(self.path, 'checker.py')
if exists(checker):
self.checker_code = open(checker, 'r').read()
call(f"docker run --name checker --volume={self.path}:/app -t -d python:3.6", shell=True)
print("Container created")
result = None
try:
self.before_test()
print("before test finished")
for file in listdir(self.path):
if not file.endswith(".a") and exists(join(self.path, file + '.a')):
self.predicted = open(join(self.path, file + '.a'), 'r').read().strip().replace('\r\n', '\n')
print('predicted:', self.predicted)
self.test(file)
self.after_test()
result = "OK"
except TestException as e:
result = str(e)
except TimeoutExpired:
result = "TL"
except Exception as e:
print(str(e))
result = "TE"
call(f"docker rm --force solution", shell=True)
call(f"docker rm --force checker", shell=True)
return result

View File

@ -0,0 +1,11 @@
from .BaseTester import BaseTester
class CSharpTester(BaseTester):
@property
def build_command(self):
return "csc /out:executable.exe"
@property
def command(self):
return "mono executable.exe"

View File

@ -0,0 +1,7 @@
from .BaseTester import BaseTester
class CppTester(BaseTester):
@property
def build_command(self):
return "g++ -o executable.exe"

View File

@ -0,0 +1,8 @@
from .BaseTester import BaseTester
class GoTester(BaseTester):
working_directory = "../app"
def build_command(self):
return "go build -o executable.exe"

View File

@ -0,0 +1,27 @@
from os import listdir
from .BaseTester import BaseTester, TestException
class JavaTester(BaseTester):
_executable = None
def before_test(self):
files = [
file
for file in listdir(self.path)
if file.endswith(".java")
]
code = self.exec_command(f"javac {' '.join(files)}")
if code != 0:
raise TestException("CE")
for file in listdir(self.path):
if file.endswith(".class"):
self._executable = file.rstrip(".class")
break
if self._executable is None:
raise TestException("TE")
@property
def command(self):
return f"java -classpath . {self._executable}"

View File

@ -0,0 +1,21 @@
from os import listdir
from .BaseTester import BaseTester, TestException
class KotlinTester(BaseTester):
def before_test(self):
files = [
file
for file in listdir(self.path)
if file.endswith(".kt")
]
code = self.exec_command(
f'kotlinc {" ".join(files)} -include-runtime -d solution.jar'
)
if code != 0:
raise TestException("CE")
@property
def command(self):
return "java -jar solution.jar"

View File

@ -0,0 +1,19 @@
from os import listdir
from .BaseTester import BaseTester, TestException
class Python3Tester(BaseTester):
file = None
def before_test(self):
for file in listdir(self.path):
if file.endswith(".py") and file != 'checker.py':
self.file = file
break
if self.file is None:
raise TestException("TE")
@property
def command(self):
return f"python3 {self.file}"

View File

@ -0,0 +1,7 @@
from .BaseTester import BaseTester
from .Python3Tester import Python3Tester
from .CppTester import CppTester
from .GoTester import GoTester
from .JavaTester import JavaTester
from .CSharpTester import CSharpTester
from .KotlinTester import KotlinTester

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.4 on 2022-02-15 19:11
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('Main', '0020_languageapply'),
]
operations = [
migrations.AddField(
model_name='solution',
name='set',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='Main.set'),
),
migrations.AddIndex(
model_name='solution',
index=models.Index(fields=['set', '-time_sent'], name='Main_soluti_set_id_19e6c7_idx'),
),
]

View File

@ -4,9 +4,14 @@ from SprintLib.utils import get_bytes, write_bytes, delete_file
class FileStorageMixin:
@cached_property
def bytes(self):
return get_bytes(self.fs_id)
@cached_property
def text(self):
return get_bytes(self.fs_id).decode("utf-8")
return self.bytes.decode("utf-8")
def write(self, bytes):
self.fs_id = write_bytes(bytes)

View File

@ -7,6 +7,7 @@ from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone
from Main.models import Set
from Main.models.solution_file import SolutionFile
from Main.models.task import Task
from Sprint.settings import CONSTS
@ -20,11 +21,13 @@ class Solution(models.Model):
time_sent = models.DateTimeField(default=timezone.now)
result = models.TextField(default=CONSTS["in_queue_status"])
test = models.IntegerField(default=None, null=True)
set = models.ForeignKey(Set, null=True, on_delete=models.SET_NULL)
class Meta:
indexes = [
models.Index(fields=['task', 'user', '-time_sent']),
models.Index(fields=['task', '-time_sent'])
models.Index(fields=['task', '-time_sent']),
models.Index(fields=['set', '-time_sent']),
]
@property

View File

@ -1,6 +1,6 @@
from django import template
from Main.models import Solution
from Main.models import Solution, SetTask
register = template.Library()
@ -13,3 +13,8 @@ def solved(user, task):
if len(solutions) != 0:
return False
return None
@register.filter('settask')
def settask(set, task):
return SetTask.objects.get(set=set, task=task)

View File

@ -0,0 +1,24 @@
import datetime
from typing import Optional
import pytz
from django.utils import timezone
from Checker.models import Checker
from Main.models import SetTask, Set
from SprintLib.BaseView import BaseView, AccessError
from SprintLib.language import languages
class CheckersView(BaseView):
required_login = True
view_file = "checkers.html"
endpoint = "admin/checkers"
def pre_handle(self):
self.current_set = self.entities.set
if (
self.request.user != self.current_set.creator
and self.request.user.username not in self.current_set.editors
):
raise AccessError()

View File

@ -4,6 +4,7 @@ from typing import Optional
import pytz
from django.utils import timezone
from Checker.models import Checker
from Main.models import SetTask, Set
from SprintLib.BaseView import BaseView, AccessError
from SprintLib.language import languages
@ -114,3 +115,11 @@ class SetSettingsView(BaseView):
self.entities.set.languages.remove(t)
self.entities.set.save()
return "/admin/set?set_id=" + str(self.entities.set.id)
def post_new_checker(self):
Checker.objects.create(name=self.request.POST['name'], set=self.entities.set, last_request=timezone.now() - datetime.timedelta(days=1))
return '/admin/set?set_id=' + str(self.entities.set.id)
def post_delete_checker(self):
Checker.objects.get(id=self.request.POST['checker_id']).delete()
return '/admin/set?set_id=' + str(self.entities.set.id)

View File

@ -37,6 +37,7 @@ class TaskView(BaseView):
task=self.entities.task,
user=self.request.user,
language_id=int(self.request.POST["language"]),
set=self.entities.set if hasattr(self.entities, 'setTask') else None
)
def post_0(self):
@ -47,8 +48,8 @@ class TaskView(BaseView):
solution=self.solution,
fs_id=fs_id,
)
send_testing(self.solution.id)
return "task?task_id=" + str(self.entities.task.id)
send_testing(self.solution)
return ("/task?setTask_id=" + str(self.entities.setTask.id)) if hasattr(self.entities, 'setTask') else ("/task?task_id=" + str(self.entities.task.id))
def post_1(self):
# отправка решения через файл
@ -73,5 +74,5 @@ class TaskView(BaseView):
solution=self.solution,
fs_id=fs_id,
)
send_testing(self.solution.id)
return "task?task_id=" + str(self.entities.task.id)
send_testing(self.solution)
return ("/task?setTask_id=" + str(self.entities.setTask.id)) if hasattr(self.entities, 'setTask') else ("/task?task_id=" + str(self.entities.task.id))

View File

@ -20,3 +20,4 @@ from Main.views.ChatWithView import ChatWithView
from Main.views.MessagesView import MessagesView
from Main.views.SetView import SetView
from Main.views.GroupView import GroupView
from Main.views.CheckersView import CheckersView

View File

@ -43,6 +43,7 @@ INSTALLED_APPS = [
"django.contrib.messages",
"django.contrib.staticfiles",
"Main.apps.MainConfig",
"Checker.apps.CheckerConfig",
]
MIDDLEWARE = [

View File

@ -1,10 +1,13 @@
from django.contrib import admin
from django.urls import path
from django.urls import path, include
import Main.views
from Main.views import *
urlpatterns = []
urlpatterns = [
path("checker/", include("Checker.urls"))
]
for v in dir(Main.views):
try:

View File

@ -3,7 +3,9 @@ import pika
from Sprint import settings
def send_testing(solution_id):
def send_testing(solution):
if solution.set is not None and len(solution.set.checkers.all()) != 0:
return
with pika.BlockingConnection(
pika.ConnectionParameters(host=settings.RABBIT_HOST, port=settings.RABBIT_PORT)
) as connection:
@ -12,5 +14,5 @@ def send_testing(solution_id):
channel.basic_publish(
exchange="",
routing_key="test",
body=bytes(str(solution_id), encoding="utf-8"),
body=bytes(str(solution.id), encoding="utf-8"),
)

34
templates/checkers.html Normal file
View File

@ -0,0 +1,34 @@
{% for checker in set.checkers.all %}
{% with status=checker.status %}
<form method="POST">
{% csrf_token %}
<input type="hidden" name="action" value="delete_checker">
<input type="hidden" name="checker_id" value="{{ checker.id }}">
<button type="button" class="btn btn-link" data-toggle="modal" data-target="#exampleCheckers{{ checker.id }}">{{ checker.name }}</button> <span class="badge badge-{% if status == 'Active' %}success{% else %}{% if status == 'Testing' %}info{% else %}danger{% endif %}{% endif %}">{{ status }}</span>{% if status != 'Testing' %}<button type="submit" class="btn btn-link" style="color: black;"><i class="fa fa-times"></i></button>{% endif %}<br>
</form>
{% endwith %}
<div class="modal fade" id="exampleCheckers{{ checker.id }}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLongTitleCheckers{{ checker.id }}" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitleCheckers{{ checker.id }}">Токен {{ checker.name }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-12">
{{ checker.token }}
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-times-circle"></i> Закрыть</button>
</div>
</div>
</div>
</div>
{% endfor %}

View File

@ -3,12 +3,25 @@
{% block title %}{{ set.name }}{% endblock %}
{% block scripts %}
var saved_data = "";
function handle(value) {
const elem = document.getElementById(value);
elem.hidden = !elem.hidden;
}
function doPoll() {
jQuery.get('/admin/checkers?set_id={{ set.id }}', function(data) {
var e = document.getElementById('checkers');
if (saved_data.length != data.length) {
saved_data = data;
e.innerHTML = data;
}
setTimeout(function() {doPoll()}, 2000);
})
}
{% endblock %}
{% block onload %}doPoll(){% endblock %}
{% block main %}
<form method="POST">
{% csrf_token %}
@ -160,4 +173,37 @@
</form>
</div>
</div>
<hr><hr>
<h3>Чекеры</h3>
<div id="checkers"></div>
<button class="btn btn-light" data-toggle="modal" data-target="#exampleCheckers"><i class="fa fa-plus"></i> Добавить чекер</button>
<div class="modal fade" id="exampleCheckers" tabindex="-1" role="dialog" aria-labelledby="exampleModalLongTitleCheckers" aria-hidden="true">
<div class="modal-dialog" role="document">
<form method="POST">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitleCheckers">Создать новый чекер</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-12">
{% csrf_token %}
<input type="hidden" name="action" value="new_checker">
<input type="text" name="name" placeholder="Имя чекера">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-times-circle"></i> Закрыть</button>
<button type="submit" class="btn btn-success"><i class="fa fa-plus-circle"></i> Создать</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@ -1,5 +1,7 @@
{% extends 'base_main.html' %}
{% load filters %}
{% block main %}
<h4>
<table class="table" style="width: 30%;">
@ -16,7 +18,7 @@
Задача
</td>
<td>
<a href="/task?task_id={{ solution.task.id }}">{{ solution.task.name }}</a>
<a href="/task?{% if solution.set %}{% with settask=solution.set|settask:solution.task %}setTask_id={{ settask.id }}{% endwith %}{% else %}task_id={{ solution.task.id }}{% endif %}">{{ solution.task.name }}</a>
</td>
</tr>
<tr>