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

Commit

Permalink
Merge pull request #18 from unb-mds/feature/contests
Browse files Browse the repository at this point in the history
[feature request] Adicionar página de contest
  • Loading branch information
bitterteriyaki authored Oct 3, 2023
2 parents c57512f + 819783c commit a539ed7
Show file tree
Hide file tree
Showing 15 changed files with 291 additions and 3 deletions.
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 %}

0 comments on commit a539ed7

Please sign in to comment.