diff --git a/source/web/main/admin.py b/source/web/main/admin.py index 7bbd866..11cb27c 100644 --- a/source/web/main/admin.py +++ b/source/web/main/admin.py @@ -1,24 +1,24 @@ import json from django.contrib import admin from main.models import ( - Profile, - Achievment, - UserAchievments, - TaskType, - Task, - Test, - Verdict, - AnswerOption, - Compiler, - AnswerCode, - Answer, - Category, - ContestType, - Contest, - TaskOnContest, + Profile, + Achievment, + UserAchievments, + TaskType, + Task, + Test, + Verdict, + AnswerOption, + Compiler, + AnswerCode, + Answer, + Category, + ContestType, + Contest, + TaskOnContest, CategoryOnContest, - CompilerOnContest, - ContestRole, + CompilerOnContest, + ContestRole, UserToContest, Checker, ) @@ -142,6 +142,7 @@ class AnswerAdmin(BaseAdmin): 'id', 'task', 'verdict', + 'contest', ] actions = [ diff --git a/source/web/main/migrations/0013_alter_answer_contest.py b/source/web/main/migrations/0013_alter_answer_contest.py new file mode 100644 index 0000000..755fb83 --- /dev/null +++ b/source/web/main/migrations/0013_alter_answer_contest.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.3 on 2024-12-21 21:12 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0012_answer_contest'), + ] + + operations = [ + migrations.AlterField( + model_name='answer', + name='contest', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='main.contest'), + ), + ] diff --git a/source/web/main/migrations/0014_alter_answer_verdict.py b/source/web/main/migrations/0014_alter_answer_verdict.py new file mode 100644 index 0000000..d992f8c --- /dev/null +++ b/source/web/main/migrations/0014_alter_answer_verdict.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.3 on 2024-12-21 21:37 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0013_alter_answer_contest'), + ] + + operations = [ + migrations.AlterField( + model_name='answer', + name='verdict', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.verdict'), + ), + ] diff --git a/source/web/main/mixins.py b/source/web/main/mixins.py new file mode 100644 index 0000000..eedd92b --- /dev/null +++ b/source/web/main/mixins.py @@ -0,0 +1,25 @@ +from django.utils.translation import gettext_lazy as _ +from django.contrib import messages +from django.contrib.auth.mixins import AccessMixin +from django.shortcuts import redirect + +from main.models import Contest + + +class UserContestMixin(AccessMixin): + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + return self.handle_no_permission() + if request.user.is_authenticated: + contest:Contest = self.get_contest(kwargs['id']) + if ( + request.user.profile != contest.author and + request.user.profile not in contest.users.all() + ): + if contest.status == Contest.ContestStatus.OPENED: + messages.info(request, _("MustBeRegistered")) + return redirect('contest_register', contest.pk) + messages.error(request, _("NoContestAccess")) + return redirect('index') + return super().dispatch(request, *args, **kwargs) \ No newline at end of file diff --git a/source/web/main/models.py b/source/web/main/models.py index 0b117ba..175c2d6 100644 --- a/source/web/main/models.py +++ b/source/web/main/models.py @@ -140,9 +140,9 @@ class Answer(BaseModel): task = models.ForeignKey(Task, on_delete=models.CASCADE) answer_option = models.ForeignKey(AnswerOption, null=True, blank=True, on_delete=models.SET_NULL) answer_code = models.ForeignKey(AnswerCode, null=True, blank=True, on_delete=models.SET_NULL) - verdict = models.ForeignKey(Verdict, null=True, on_delete=models.SET_NULL) + verdict = models.ForeignKey(Verdict, null=True, blank=True, on_delete=models.SET_NULL) user = models.ForeignKey(Profile, on_delete=models.CASCADE) - contest = models.ForeignKey("Contest", on_delete=models.CASCADE) + contest = models.ForeignKey("Contest", on_delete=models.CASCADE, null=True, blank=True) def __str__(self): return f"Answer #{self.id} for {self.task}" @@ -217,6 +217,14 @@ def ordered_tasks(self): res.append((item.order, item.task)) return res + @property + def participant_count(self): + users_to_contest = UserToContest.objects.filter( + contest=self, + role=ContestRole.objects.get(name="Participant") + ) + return users_to_contest.count() + class TaskOnContest(BaseModel): order = models.IntegerField() diff --git a/source/web/main/services.py b/source/web/main/services.py new file mode 100644 index 0000000..0fdd512 --- /dev/null +++ b/source/web/main/services.py @@ -0,0 +1,18 @@ +from typing import Optional + +from django.contrib.auth import get_user_model +from django.contrib.auth.models import User +from django.db.models import QuerySet + +from main.models import Answer, Task, Contest + + +def get_user_answers(user: User, task: Task, contest: Optional[Contest]=None) -> QuerySet[Answer]: + if not user.is_authenticated: + return Answer.objects.none() + + return Answer.objects.filter( + user=user.profile, + task=task, + contest=contest, + ) diff --git a/source/web/main/views.py b/source/web/main/views.py index 805def8..5628b2e 100644 --- a/source/web/main/views.py +++ b/source/web/main/views.py @@ -3,7 +3,9 @@ from django.shortcuts import get_object_or_404, redirect, render from django.contrib.auth import get_user_model from django.contrib.auth.views import LoginView +from django.utils.translation import gettext_lazy as _ +from main.mixins import UserContestMixin from main.forms import LoginForm, SignUpForm from main.models import ( Answer, @@ -14,6 +16,7 @@ TaskOnContest, ) from main.standings import create_standings +from main.services import get_user_answers User = get_user_model() @@ -23,7 +26,7 @@ class IndexView(TemplateView): template_name = 'main/index.html' def get(self, request): - opened_contests = Contest.objects.opened_contests() + opened_contests = Contest.objects.opened_contests()[:3] return render(request, self.template_name, { 'opened_contests': opened_contests, }) @@ -42,7 +45,9 @@ def post(self, request): if form.cleaned_data['password'] != form.cleaned_data['password_confirm']: form.add_error('password_confirm', 'Пароли не совпадают') return render(request, self.template_name, {'form': form}) - + if User.objects.filter(username=form.cleaned_data['username']).count(): + form.add_error('username', _("ObjectWithFieldExists")) + return render(request, self.template_name, {'form': form}) user = User.objects.create_user( username=form.cleaned_data['username'], password=form.cleaned_data['password'], @@ -89,7 +94,7 @@ def get_task(self, task_id): def get(self, request, *args, **kwargs): task = self.get_task(kwargs['id']) - answers = Answer.objects.filter(task=task, user=request.user.profile).order_by('-created_at') + answers = get_user_answers(request.user, task) return render( request, @@ -146,7 +151,7 @@ def post(self, request, *args, **kwargs): return render(request, self.template_name, {'contest': contest}) -class ContestDetailView(TemplateView): +class ContestDetailView(UserContestMixin, TemplateView): template_name = 'contests/contest_detail.html' def get_contest(self, contest_id): @@ -164,9 +169,12 @@ def get(self, request, *args, **kwargs): ) -class ContestTaskView(TemplateView): +class ContestTaskView(UserContestMixin, TemplateView): template_name = 'contests/contest_task.html' + def get_contest(self, contest_id): + return get_object_or_404(Contest, pk=contest_id) + def get(self, request, *args, **kwargs): task_on_contest = get_object_or_404( TaskOnContest, @@ -178,6 +186,8 @@ def get(self, request, *args, **kwargs): template_name = f'tasks/{task.task_type.tester_name}.html' + answers = get_user_answers(request.user, task, contest) + return render( request, self.template_name, @@ -185,6 +195,7 @@ def get(self, request, *args, **kwargs): 'task_template': template_name, 'task': task, 'contest': contest, + 'answers': answers, } ) @@ -197,7 +208,7 @@ def get_contest(self, contest_id): def get(self, request, *args, **kwargs): - _contest = self.get_contest(Contest, pk = kwargs["id"]) + _contest = self.get_contest(kwargs["id"]) _data = create_standings(kwargs['id']) return render( diff --git a/source/web/static/css/index.css b/source/web/static/css/index.css index ec56d2c..85d72c0 100644 --- a/source/web/static/css/index.css +++ b/source/web/static/css/index.css @@ -1,8 +1,56 @@ +:root{ + --accent-color: #fe7373; + --color-dark: #252525; + --color-white: #ffffff; + --color-gray: #7d7d7d; + --color-primary: #6b6b6b; + --color-subheader: #3d3d3d; + --bg-color: #f1f1f1; + --color-light-gray: #E7E7E7; + --default-radius: 15px; + --default-padding: 25px; +} + body{ + background: var(--bg-color); + padding: 0; + margin: 0; display: flex; flex-direction: column; min-height: 100vh; justify-content: space-between; + + font-family: Ubuntu, serif; +} + +@media (min-width: 980px) { + .mobile-screen{ + display: none; + } +} + +@media (max-width: 980px) { + .mobile-screen{ + display: block; + position: fixed; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + background: var(--color-subheader); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } +} + +.card-dark{ + background: var(--color-dark); + border-radius: var(--default-radius); + padding: var(--default-padding); + color: var(--color-white); + text-align: center; } main{ @@ -10,7 +58,376 @@ main{ min-height: 90vh; } +.container{ + max-width: 980px; + margin: auto; + height: 100%; +} + +header{ + background: var(--color-dark); + color: var(--color-white); + height: 50px; +} + +header > .container{ + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +header span.title{ + font-size: 20pt; +} + +header a{ + text-decoration: none; + color: var(--color-white); +} + +header a:visited, +header a:active{ + text-decoration: none; + color: var(--color-white); +} + +header a:hover{ + text-decoration: none; + color: var(--color-gray); +} + +header .nav-links{ + list-style-type: none; + display: flex; + flex-direction: row; +} + +.nav-links > li{ + margin: 0 10px; +} + +header .slogan{ + color: var(--color-primary); +} + +.active-link, +.active-link:visited, +.active-link:active +{ + color: var(--accent-color); +} + +.active-link:hover{ + color: var(--color-gray); +} + +.active-text{ + color: var(--accent-color); +} + +nav.subheader{ + height: 30px; + background: var(--color-subheader); +} + +nav.subheader ul{ + padding: 0; + margin: 0; + display: flex; + flex-direction: row; + list-style-type: none; + align-items: center; + height: 100%; +} + +nav.subheader ul>*{ + padding: 0 15px; + height: 100%; + display: flex; + align-items: center; +} + +nav.subheader ul>li:hover{ + background: var(--accent-color); +} + +nav.subheader ul>li>a{ + display: block; +} + +nav.subheader ul>:first-child{ + margin-left: 0; +} +nav.subheader ul>:last-child{ + margin-right: 0; +} + +nav a, +nav a:visited{ + color: var(--color-white); + text-decoration: none; +} + +nav li>a:hover, +nav li>a:active{ + background: var(--accent-color); + color: var(--color-white); +} + footer{ display: flex; justify-content: center; +} + +.contest-slider{ + z-index: 50; +} + +.contest-card{ + display: inline-block; + background: var(--color-white); + padding: 15px; + border-radius: 15px; + max-width: 270px; + min-width: 270px; + margin-right: 20px; + height: 150px; +} + +.contest-card > .contest-name{ + display: block; + font-size: 18pt; + text-overflow: ellipsis; + margin-bottom: 10px; +} + +.contest-card .contest-info{ + list-style-type: none; + margin: 0; + padding: 0; +} + +.contest-card > .contest-footer{ + margin-top: 10px; +} + +.contest-info > li{ + margin: 3px 0; +} + +.contest-block > .container{ + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.contest-sidebar{ + width: 330px; +} + +.contest-content{ + width: 630px; +} + +.card{ + background: var(--color-white); + border-radius: var(--default-radius); + padding: var(--default-padding); + margin: 15px 0; +} + +.card-title{ + font-size: 18pt; + margin: 0; + font-weight: bold; +} + +.tasks-list ul{ + list-style-type: none; + padding: 0; +} + +table{ + width: 100%; + text-align: center; + border-collapse: collapse; +} + +table tr{ + height: 35px; +} + +table tbody tr:nth-child(2n + 1){ + background: #fe737333; +} + +table td{ + border: none; + padding: 0; + margin: 0; + border-spacing: 0; +} + +.submission-verdict{ + border: 1px solid black; + padding: 3px; + border-radius: 5px; +} + +.submission-verdict[type='ok']{ + background: #4c765850; + border-color: #295f38; +} + +.submission-verdict:not([type='ok']){ + background: #85404050; + border-color: #632727; +} + +.statement-block > h3{ + margin: 0; + margin-top: 30px; + padding: 0; +} + +.statement-block > p{ + margin: 0; + margin-top: 10px; +} + +.code-area{ + width: calc(100% - 24px); + min-height: 300px; + max-height: 500px; + resize: none; + padding: 0; + border: 0; + border: 2px solid var(--color-light-gray); + border-radius: 7px; + padding: 10px; +} + +.form-footer{ + margin-top: 15px; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +select{ + width: 200px; + height: 40px; + border: none; + padding-left: 10px; + padding-right: 10px; + border: 2px solid var(--color-light-gray); + border-radius: 7px; + font-size: 12pt; +} + +hr{ + border-color: var(--color-light-gray); + border-style: solid; + margin: 0; +} + +.statement{ + margin-bottom: 25px; +} + +button{ + border: none; + color: white; + background: var(--accent-color); + height: 40px; + min-width: 120px; + border-radius: 10px; + font-size: 12pt; +} + +.answer-options{ + margin: 15px 0; +} + +.system-messages{ + position: absolute; + bottom: 0; + right: 0; + list-style-type: none; + padding: 0; + margin: 0; + margin-right: 20px; +} + +.system-messages > li{ + border: 1px solid black; + background-color: var(--color-gray); + color: black; + border-radius: 7px; + min-width: 400px; + min-height: 20px; + padding: 15px; + margin: 5px 0; +} + +.system-messages>li[type='warning']{ + color: #ffda6a; + background: #332701e0; + border-color: #997404; + +} +.system-messages>li[type='error']{ + color: #ea868f; + background: #2c0b0ee0; + border-color: #842029; +} +.system-messages>li[type='info']{ + color: #6ea8fe; + background: #031633e0; + border-color: #084298; +} +.system-messages>li[type='success']{ + color: #75b798; + background: #051b11e0; + border-color: #0f5132; +} +.system-messages>li[type='debug']{ + color: #f8f9fa; + background: #343a40e0; + border-color: #495057; +} + +.form-control{ + display: flex; + flex-direction: row; + max-width: 500px; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +input[type='text'], +input[type='password'], +input[type='number'], +input[type='email'] +{ + font-size: 11pt; + padding: 0 5px; + width: 300px; + height: 30px; + border-radius: 7px; + border: 1px solid var(--color-light-gray); +} + +input[type='text']:focus-within, +input[type='password']:focus-within, +input[type='number']:focus-within, +input[type='email']:focus-within +{ + width: 300px; + height: 30px; + border-radius: 7px; + outline: none; + border: 2px solid var(--color-gray); } \ No newline at end of file diff --git a/source/web/templates/contests/contest_detail.html b/source/web/templates/contests/contest_detail.html index f85cc57..0853ee7 100644 --- a/source/web/templates/contests/contest_detail.html +++ b/source/web/templates/contests/contest_detail.html @@ -6,6 +6,8 @@ {% endblock %} {% block contest_content %} +
{{contest.name}}
Contest description: ... +
{% endblock %} \ No newline at end of file diff --git a/source/web/templates/contests/contest_standings.html b/source/web/templates/contests/contest_standings.html index 7324ebf..41b02f9 100644 --- a/source/web/templates/contests/contest_standings.html +++ b/source/web/templates/contests/contest_standings.html @@ -1,4 +1,4 @@ -{% extends "wrapper.html" %} +{% extends "contests/contest_wrapper.html" %} {% load i18n %} {% block title %} @@ -6,29 +6,38 @@ {% endblock %} {% block content %} - - - - - - - - {% for task in contest.tasks.all %} - - {% endfor %} - - {% for standing in results %} - - - - - - {% for task in standing.task_results %} - - {% endfor %} - - {% endfor %} -
numnamepointpenalty{{forloop.counter}}
{{forloop.counter}}{{ standing.user }}{{ standing.total_points }}{{ standing.penalty }}{{ task }}
+
+
+ + + + + + + + {% for task in contest.tasks.all %} + + {% endfor %} + + + + {% if results %} + {% for standing in results %} + + + + + + {% for task in standing.task_results %} + + {% endfor %} + + {% endfor %} + {% else %} + {% trans "Empty" %} + {% endif %} + +
#{% trans "Username" %}{% trans "Points" %}{% trans "Penalty" %}{{forloop.counter}}
{{forloop.counter}}{{ standing.user.user.username }}{{ standing.total_points }}{{ standing.penalty }}{{ task }}
+
+
{% endblock %} \ No newline at end of file diff --git a/source/web/templates/contests/contest_task.html b/source/web/templates/contests/contest_task.html index bf127db..7ac687f 100644 --- a/source/web/templates/contests/contest_task.html +++ b/source/web/templates/contests/contest_task.html @@ -2,10 +2,17 @@ {% load i18n %} {% block contest_content %} -
-
+
+
+

{{task.name}}

{% include task_template %}
- {% include "tasks/theta_solutions.html" %}
+{% if request.user.is_authenticated %} +
+
+ {% include "tasks/theta_solutions.html" %} +
+
+{% endif %} {% endblock %} \ No newline at end of file diff --git a/source/web/templates/contests/contest_wrapper.html b/source/web/templates/contests/contest_wrapper.html index 31ef8d0..454fb53 100644 --- a/source/web/templates/contests/contest_wrapper.html +++ b/source/web/templates/contests/contest_wrapper.html @@ -1,29 +1,37 @@ {% extends "wrapper.html" %} +{% load i18n %} {% block title %} {% endblock title %} +{% block subheader %} + {% include "contests/includes/contest_subheader.html" %} +{% endblock %} + {% block content %} -
-
-
+
+
+
+
+

{% trans "Tasks" %}

+ {% if contest.tasks %} +
    + {% for order, task in contest.ordered_tasks %} +
  • + {{order}}.  + + {{task.name}} + +
  • + {% endfor %} +
+ {% endif %} +
+
+
{% block contest_content %} {% endblock contest_content %}
-
- {% if contest.tasks %} - - {% endif %} -
diff --git a/source/web/templates/contests/contests_list.html b/source/web/templates/contests/contests_list.html index 33e272f..5fb42fd 100644 --- a/source/web/templates/contests/contests_list.html +++ b/source/web/templates/contests/contests_list.html @@ -7,8 +7,8 @@ {% block content %}
-
- +
+
diff --git a/source/web/templates/contests/includes/contest_subheader.html b/source/web/templates/contests/includes/contest_subheader.html new file mode 100644 index 0000000..4683186 --- /dev/null +++ b/source/web/templates/contests/includes/contest_subheader.html @@ -0,0 +1,11 @@ +{% load i18n %} + \ No newline at end of file diff --git a/source/web/templates/contests/registration.html b/source/web/templates/contests/registration.html index ca2f4c9..155857e 100644 --- a/source/web/templates/contests/registration.html +++ b/source/web/templates/contests/registration.html @@ -1,14 +1,19 @@ {% extends "wrapper.html" %} +{% load i18n %} {% block title %} -Contest registration +{% trans "ContestRegister" %} {% endblock %} {% block content %} -

Contest registration

-

Rules for contest:

- - {% csrf_token %} - - +
+
+

{% trans "ContestRegistration" %}

+

{% trans "RulesForContest" %}: {{contest.rules}}

+
+ {% csrf_token %} + + +
+
{% endblock %} \ No newline at end of file diff --git a/source/web/templates/includes/header.html b/source/web/templates/includes/header.html index d259bd2..769aab9 100644 --- a/source/web/templates/includes/header.html +++ b/source/web/templates/includes/header.html @@ -1,47 +1,25 @@ {% load i18n %} - + diff --git a/source/web/templates/includes/subheader.html b/source/web/templates/includes/subheader.html new file mode 100644 index 0000000..cc3e7f8 --- /dev/null +++ b/source/web/templates/includes/subheader.html @@ -0,0 +1,10 @@ +{% load i18n %} + \ No newline at end of file diff --git a/source/web/templates/main/index.html b/source/web/templates/main/index.html index e9db90e..cce1192 100644 --- a/source/web/templates/main/index.html +++ b/source/web/templates/main/index.html @@ -6,21 +6,35 @@ {% endblock %} {% block content %} -

{% trans "SysName" %}

-
-
-

{% trans "OpenedContests" %}

-
+

{% trans "OpenedContests" %}

+
{% if opened_contests %} {% for contest in opened_contests %} -
-
-
{{ contest.name }}
-

Authored by {{ contest.author.fullname }}

- {% if not request.user.isAuthenticated %} - Enter - {% endif %} +
+ {{ contest.name }} +
+
    +
  • + {% trans "Participants" %}: + {{ contest.participant_count }} +
  • +
  • + {% trans "StartTime" %}: + {{ contest.start_time|date:'Y-m-d H:i' }} +
  • +
  • + {% trans "TimeDuration" %}: + {{ contest.duration }} +
  • +
  • + {% trans "Author" %}: + {{ contest.author.user.username }} +
  • +
+
+
{% endfor %} diff --git a/source/web/templates/main/users/login.html b/source/web/templates/main/users/login.html index 2e62e47..dbe1561 100644 --- a/source/web/templates/main/users/login.html +++ b/source/web/templates/main/users/login.html @@ -7,45 +7,29 @@ {% block content %}
-

{% trans "LoginPageName" %}

-
-
    +
    +

    {% trans "LoginPageName" %}

    +
      {% for error in form.non_field_errors %} -
    • {{ error }}
    • - {% endfor %} -
    -
    - {% csrf_token %} -
    - -
    +
  • {{ error }}
  • + {% endfor %} +
+ + {% csrf_token %} +
+ + id="username" + name="username" + aria-describedby="emailHelp">
-
    - {% for error in form.username.errors %} -
  • {{ error }}
  • - {% endfor %} -
-
-
- -
- +
+ +
-
    - {% for error in form.password.errors %} -
  • {{ error }}
  • - {% endfor %} -
-
- - - {% if is_register_open %} -

{% trans "NotRegistered" %}?

- {% endif %} + + + {% trans "NotRegistered" %}? +
{% endblock content %} \ No newline at end of file diff --git a/source/web/templates/main/users/signup.html b/source/web/templates/main/users/signup.html index c67601a..cd434c2 100644 --- a/source/web/templates/main/users/signup.html +++ b/source/web/templates/main/users/signup.html @@ -7,11 +7,47 @@ {% block content %}
-
-

{% trans 'SignUpPageName' %}

+
+

{% trans 'SignUpPageName' %}

+
    + {% for field, error in form.errors.items %} +
  • {{ field }}: {{ error }}
  • + {% endfor %} +
{% csrf_token %} - {{ form.as_p }} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
diff --git a/source/web/templates/tasks/task_detail.html b/source/web/templates/tasks/task_detail.html index 093acc1..59b5859 100644 --- a/source/web/templates/tasks/task_detail.html +++ b/source/web/templates/tasks/task_detail.html @@ -3,9 +3,18 @@ {% block content %}
-
- {% include task_template %} +
+
+

{{task.name}}

+ {% include task_template %} +
- {% include "tasks/theta_solutions.html" %} + {% if request.user.is_authenticated %} +
+
+ {% include "tasks/theta_solutions.html" %} +
+
+ {% endif %}
{% endblock %} \ No newline at end of file diff --git a/source/web/templates/tasks/tasks_list.html b/source/web/templates/tasks/tasks_list.html index 0ec8012..5742175 100644 --- a/source/web/templates/tasks/tasks_list.html +++ b/source/web/templates/tasks/tasks_list.html @@ -7,8 +7,8 @@ {% block content %}
-
-
#
+
+
diff --git a/source/web/templates/tasks/theta_code.html b/source/web/templates/tasks/theta_code.html index 0570c9e..bcb0f6b 100644 --- a/source/web/templates/tasks/theta_code.html +++ b/source/web/templates/tasks/theta_code.html @@ -1,27 +1,39 @@ {% load i18n %} -

{% trans "TaskStatement" %}

-

{{task.statement}}

-

{% trans "TaskInputFormat" %}

-

{{task.input}}

-

{% trans "TaskOutputFormat" %}

-

{{task.output}}

- - {% csrf_token %} -
- - +
+
+

{{task.statement | safe}}

- - - +
+

{% trans "TaskInputFormat" %}

+

{{task.input | safe}}

+
+
+

{% trans "TaskOutputFormat" %}

+

{{task.output | safe}}

+
+
+
+{% if request.user.is_authenticated %} +
+

{% trans "PasteCode" %}

+
+ {% csrf_token %} + + + +
+{% endif %} \ No newline at end of file diff --git a/source/web/templates/tasks/theta_quiz.html b/source/web/templates/tasks/theta_quiz.html index a040052..85ee1a8 100644 --- a/source/web/templates/tasks/theta_quiz.html +++ b/source/web/templates/tasks/theta_quiz.html @@ -1,21 +1,30 @@ {% load i18n %} -

{% trans "TaskStatement" %}

-

{{task.statement}}

-
- {% csrf_token %} - {% for option in task.answer_options.all %} - - -
- {% endfor %} - - + +
+

{{task.statement}}

+
+
+{% if request.user.is_authenticated %} +
+
+ {% csrf_token %} +
+ {% for option in task.answer_options.all %} + + +
+ {% endfor %} +
+ + +
+{% endif %} \ No newline at end of file diff --git a/source/web/templates/tasks/theta_solutions.html b/source/web/templates/tasks/theta_solutions.html index 42ff1b3..9c73bd4 100644 --- a/source/web/templates/tasks/theta_solutions.html +++ b/source/web/templates/tasks/theta_solutions.html @@ -1,32 +1,36 @@ {% load i18n %} -

{% trans "Submissions" %}

+

{% trans "Submissions" %}


-
#
+
- + {% if not answers %} - No answers yet. + + + {% else %} {% for answer in answers %} - - + - {% endfor %} diff --git a/source/web/templates/wrapper.html b/source/web/templates/wrapper.html index 0bb8e6b..f585a84 100644 --- a/source/web/templates/wrapper.html +++ b/source/web/templates/wrapper.html @@ -1,38 +1,42 @@ {% load static %} +{% load i18n %} {% block title %}{% endblock %} - + +
+
+

{% trans "SysName" %}

+

{% trans "MobileNotSupported" %}

+
+
{% include "includes/header.html" %} + {% block subheader %} + {% include "includes/subheader.html" %} + {% endblock %}
{% block content %}{% endblock %}
+ {% if messages %} +
    + {% for message in messages %} +
  • + {{ message }} +
  • + {% endfor %} +
+ {% endif %} {% include "includes/footer.html" %} - - \ No newline at end of file
# SolutionVerdict Submission timeVerdict Link
No answers yet.
{{forloop.counter}} {{answer.task.name}}{% if answer.verdict %} - {{answer.verdict.name}} + {{answer.created_at}} + {% if answer.verdict %} + + {{answer.verdict.short_name|upper}} + {% else %} Not tested yet. {% endif %} {{answer.created_at}} Link