Skip to content
This repository has been archived by the owner on Jan 3, 2024. It is now read-only.

[feature request] Adicionar página de contest #18

Merged
merged 6 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/contests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.apps import AppConfig

default_app_config = "apps.contests.ContestsConfig"


class ContestsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "appscontests"
47 changes: 47 additions & 0 deletions apps/contests/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import TYPE_CHECKING

from django.contrib.admin import ModelAdmin, register
from django.forms import (
CharField,
ModelForm,
ModelMultipleChoiceField,
Textarea,
)
from django.utils.translation import gettext_lazy as _

from apps.contests.models import Contest
from apps.users.models import User

if TYPE_CHECKING:
ContestAdminBase = ModelAdmin[Contest]
ContestModelFormBase = ModelForm[Contest]
else:
ContestAdminBase = ModelAdmin
ContestModelFormBase = ModelForm


class ContestModelForm(ContestModelFormBase):
description = CharField(widget=Textarea(attrs={"rows": 14, "cols": 80}))
users = ModelMultipleChoiceField(
queryset=User.objects.all(), required=False
)

class Meta:
model = Contest
fields = "__all__"


@register(Contest)
class ContestAdmin(ContestAdminBase):
form = ContestModelForm

list_display = ("title", "start_time", "end_time", "status")
list_filter = ("start_time", "end_time")

fieldsets = [
(_("General"), {"fields": ("title", "description")}),
(
_("Other"),
{"fields": ("start_time", "end_time", "users", "cancelled")},
),
]
8 changes: 8 additions & 0 deletions apps/contests/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from enum import StrEnum


class ContestStatus(StrEnum):
PENDING = "Pending"
RUNNING = "Running"
FINISHED = "Finished"
CANCELLED = "Cancelled"
44 changes: 44 additions & 0 deletions apps/contests/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 4.2.5 on 2023-09-30 05:03

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="Contest",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=256)),
("description", models.CharField(max_length=1024)),
("start_time", models.DateTimeField()),
("end_time", models.DateTimeField()),
(
"users",
models.ManyToManyField(
related_name="contests", to=settings.AUTH_USER_MODEL
),
),
],
options={
"db_table": "contests",
},
),
]
17 changes: 17 additions & 0 deletions apps/contests/migrations/0002_contest_cancelled.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.5 on 2023-09-30 05:21

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("contests", "0001_initial"),
]

operations = [
migrations.AddField(
model_name="contest",
name="cancelled",
field=models.BooleanField(default=False),
),
]
Empty file.
44 changes: 44 additions & 0 deletions apps/contests/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from django.db.models import (
BooleanField,
CharField,
DateTimeField,
ManyToManyField,
)
from django.utils.timezone import now

from apps.contests.enums import ContestStatus
from apps.users.models import User
from core.models import TimestampedModel


class Contest(TimestampedModel):
"""Represents a contest."""

id: int

title = CharField(max_length=256)
description = CharField(max_length=1024)

start_time = DateTimeField()
end_time = DateTimeField()
cancelled = BooleanField(default=False)

users = ManyToManyField(User, related_name="contests")

class Meta:
db_table = "contests"

def __str__(self) -> str:
return f"{self.title} #{self.id}"

@property
def status(self) -> ContestStatus:
if self.cancelled:
return ContestStatus.CANCELLED

if self.start_time > now():
return ContestStatus.PENDING
elif self.end_time < now():
return ContestStatus.FINISHED
else:
return ContestStatus.RUNNING
11 changes: 11 additions & 0 deletions apps/contests/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.urls import path

from apps.contests.views import DetailView, IndexView, send

app_name = "contests"

urlpatterns = [
path("", IndexView.as_view(), name="index"),
path("<int:pk>/", DetailView.as_view(), name="detail"),
path("<int:contest_id>/send/", send, name="send"),
]
51 changes: 51 additions & 0 deletions apps/contests/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import sys
from io import StringIO
from typing import TYPE_CHECKING

from django.db.models.query import QuerySet
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404
from django.views import generic

from apps.contests.models import Contest

if TYPE_CHECKING:
IndexViewBase = generic.ListView[Contest]
DetailViewBase = generic.DetailView[Contest]
else:
IndexViewBase = generic.ListView
DetailViewBase = generic.DetailView


class IndexView(IndexViewBase):
template_name = "contests/index.html"
context_object_name = "contests"

def get_queryset(self) -> QuerySet[Contest]:
return Contest._default_manager.order_by("-start_time")[:5]


class DetailView(DetailViewBase):
model = Contest
template_name = "contests/detail.html"


def send(request: HttpRequest, contest_id: int) -> HttpResponse:
contest = get_object_or_404(Contest, pk=contest_id)
code = request.POST["code"]

old_stdout = sys.stdout
sys.stdout = buffer = StringIO()

try:
eval(code)
except Exception as exc:
sys.stdout = old_stdout
return HttpResponse(f"Contest {contest.title} failed.\n{exc}")

sys.stdout = old_stdout
message = buffer.getvalue()

return HttpResponse(
f"Contest {contest.title} ran successfully.\n{message}"
)
9 changes: 9 additions & 0 deletions apps/users/models.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
from typing import TYPE_CHECKING, Any, List

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db.models import BooleanField, CharField, EmailField

from apps.users.managers import UserManager
from core.models import TimestampedModel

if TYPE_CHECKING:
from apps.contests.models import Contest
else:
Contest = Any


class User(AbstractBaseUser, PermissionsMixin, TimestampedModel):
"""Represents an user."""

contests: List[Contest]

email = EmailField(db_index=True, max_length=256, unique=True)
username = CharField(db_index=True, max_length=128, unique=True)

Expand Down
5 changes: 4 additions & 1 deletion server/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@
"bootstrap5",
]

LOCAL_APPS = ["apps.users"]
LOCAL_APPS = [
"apps.users",
"apps.contests",
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

Expand Down
2 changes: 1 addition & 1 deletion server/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
path("", include("django.contrib.auth.urls")),
# Local views
path("", home_view, name="home"),
path("register/", register_view, name="register"),
path("contests/", include("apps.contests.urls")),
]
2 changes: 1 addition & 1 deletion templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
</li>

<li class="nav-item">
<a class="nav-link text-white" href="#">
<a class="nav-link text-white" href="{% url 'contests:index' %}">
Contests
</a>
</li>
Expand Down
27 changes: 27 additions & 0 deletions templates/contests/detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{% extends "base.html" %}

{% block title %}{{ contest.title }}{% endblock title %}

{% block content %}
<div>
<h1>{{ contest }}</h1>
<p>{{ contest.description }}</p>
</div>

<form action="{% url 'contests:send' contest.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend>
<h1>{{ question.question_text }}</h1>
</legend>

{% if error_message %}
<p><strong>{{ error_message }}</strong></p>
{% endif %}

<textarea name="code" id="code" cols="50" rows="10"></textarea>
</fieldset>

<input type="submit" value="Vote">
</form>
{% endblock content %}
19 changes: 19 additions & 0 deletions templates/contests/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends "base.html" %}

{% block title %}Contests{% endblock title %}

{% block content %}
{% if contests %}
<ul>
{% for contest in contests %}
<li>
<a href="{% url 'contests:detail' contest.id %}">
{{ contest }}
</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No contests are available.</p>
{% endif %}
{% endblock content %}