Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TS-86: Updated design #39

Merged
merged 9 commits into from
Dec 21, 2024
35 changes: 18 additions & 17 deletions source/web/main/admin.py
Original file line number Diff line number Diff line change
@@ -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,
)
Expand Down Expand Up @@ -142,6 +142,7 @@ class AnswerAdmin(BaseAdmin):
'id',
'task',
'verdict',
'contest',
]

actions = [
Expand Down
19 changes: 19 additions & 0 deletions source/web/main/migrations/0013_alter_answer_contest.py
Original file line number Diff line number Diff line change
@@ -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'),
),
]
19 changes: 19 additions & 0 deletions source/web/main/migrations/0014_alter_answer_verdict.py
Original file line number Diff line number Diff line change
@@ -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'),
),
]
25 changes: 25 additions & 0 deletions source/web/main/mixins.py
Original file line number Diff line number Diff line change
@@ -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)
12 changes: 10 additions & 2 deletions source/web/main/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@
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}"

Expand Down Expand Up @@ -217,6 +217,14 @@
res.append((item.order, item.task))
return res

@property
def participant_count(self):
users_to_contest = UserToContest.objects.filter(

Check warning on line 222 in source/web/main/models.py

View check run for this annotation

Codecov / codecov/patch

source/web/main/models.py#L222

Added line #L222 was not covered by tests
contest=self,
role=ContestRole.objects.get(name="Participant")
)
return users_to_contest.count()

Check warning on line 226 in source/web/main/models.py

View check run for this annotation

Codecov / codecov/patch

source/web/main/models.py#L226

Added line #L226 was not covered by tests


class TaskOnContest(BaseModel):
order = models.IntegerField()
Expand Down
18 changes: 18 additions & 0 deletions source/web/main/services.py
Original file line number Diff line number Diff line change
@@ -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,
)
23 changes: 17 additions & 6 deletions source/web/main/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -14,6 +16,7 @@
TaskOnContest,
)
from main.standings import create_standings
from main.services import get_user_answers


User = get_user_model()
Expand All @@ -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,
})
Expand All @@ -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'],
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand All @@ -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,
Expand All @@ -178,13 +186,16 @@ 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,
{
'task_template': template_name,
'task': task,
'contest': contest,
'answers': answers,
}
)

Expand All @@ -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(
Expand Down
Loading
Loading