From e139e0ed33a6a62fc0cab4fa965173f0dbe97e13 Mon Sep 17 00:00:00 2001 From: MMcLovin Date: Fri, 17 Nov 2023 14:19:12 -0300 Subject: [PATCH 01/79] feat: moves DOCTYPE to page's first line --- templates/base.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/base.html b/templates/base.html index acd146e..ed8f6d2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,3 +1,5 @@ + + {% load static i18n %} {% load bootstrap5 %} @@ -11,7 +13,6 @@ {% get_current_language as LANGUAGE_CODE %} - From cd7617a50a6af87bf8500e8910e90d6a42355dd5 Mon Sep 17 00:00:00 2001 From: MMcLovin <122990047+MMcLovin@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:23:16 -0300 Subject: [PATCH 02/79] feat: adds katex typesetting library Katex is used for rendering LATEX expressions in HTML --- templates/tasks/detail.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index 9ab07fc..7cdff8e 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -3,6 +3,9 @@ {% block title %}{{ task.title }}{% endblock title %} {% block content %} + + +

From 7fb74308a8ac1e56a3c9ddce42415194d4064aba Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Fri, 17 Nov 2023 19:33:32 -0300 Subject: [PATCH 03/79] fix(automatic-login): authentication done --- apps/users/views.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/users/views.py b/apps/users/views.py index 012556e..5695a9a 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -1,3 +1,4 @@ +from django.contrib.auth import login from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render from django.views import View @@ -15,7 +16,14 @@ def get(self, request: HttpRequest) -> HttpResponse: def post(self, request: HttpRequest) -> HttpResponse: form = CreateUserForm(self.request.POST) if form.is_valid(): - form.save() - return redirect("login") + user = form.save() + + login( + request, + user, + backend="django.contrib.auth.backends.ModelBackend", + ) + + return redirect("home") return render(request, self.template_name, {"form": form}) From 44f82fdd6801b396866d45230862cc7c7bd71120 Mon Sep 17 00:00:00 2001 From: kyomi Date: Fri, 17 Nov 2023 22:09:16 -0300 Subject: [PATCH 04/79] docs(apps/users): document the login authentication backend --- apps/users/views.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/users/views.py b/apps/users/views.py index 5695a9a..adabd04 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -18,12 +18,10 @@ def post(self, request: HttpRequest) -> HttpResponse: if form.is_valid(): user = form.save() - login( - request, - user, - backend="django.contrib.auth.backends.ModelBackend", - ) - + # Since we have multiple authentication backends, we need to + # specify which one we want to use. In this case, we want to + # use the :class:`ModelBackend`, which is the default one. + login(request, user, "django.contrib.auth.backends.ModelBackend") return redirect("home") return render(request, self.template_name, {"form": form}) From acdd05296ecab219d770f1bee537de84410e2175 Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Sun, 19 Nov 2023 11:40:48 -0300 Subject: [PATCH 05/79] fix(automatic-login): tests --- .pre-commit-config.yaml | 2 +- apps/users/tests.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c443555..e216ee5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.6.1 hooks: - id: mypy additional_dependencies: diff --git a/apps/users/tests.py b/apps/users/tests.py index b509386..24dd7d4 100644 --- a/apps/users/tests.py +++ b/apps/users/tests.py @@ -1,7 +1,9 @@ from django.test import TestCase +from django.urls import reverse from django.utils.translation import gettext as _ from apps.users.admin import UserAdmin +from apps.users.forms import CreateUserForm from apps.users.models import User @@ -86,3 +88,37 @@ def test_add_fieldsets(self) -> None: ) self.assertEqual(UserAdmin.add_fieldsets, expected_add_fieldsets) + + +class RegisterViewTest(TestCase): + def setUp(self) -> None: + self.url = reverse("users:register") + self.valid_data = { + "username": "testuser", + "email": "test@example.com", + "password1": "TestPassword123", + "password2": "TestPassword123", + } + + def test_register_view_get(self) -> None: + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "registration/register.html") + self.assertIsInstance(response.context["form"], CreateUserForm) + + def test_post_valid_data(self) -> None: + response = self.client.post( + self.url, data=self.valid_data, folloe=True + ) + # Verifica se a página foi redirecionada + # para a página inicial após o registro + self.assertRedirects(response, reverse("home")) + # Verifica se o usuário foi criado no banco de dados + self.assertTrue( + User.objects.filter(username=self.valid_data["username"]).exists() + ) + # Autentica manualmente o usuário + user = User.objects.get(username=self.valid_data["username"]) + self.client.force_login(user) + # Verifica se o usuário está autenticado após o registro + self.assertTrue(self.client.session["_auth_user_id"]) From 14fddfbff23d3c7c08baf30d896afe5501f9317b Mon Sep 17 00:00:00 2001 From: HladczukLe Date: Sun, 19 Nov 2023 19:42:35 -0300 Subject: [PATCH 06/79] feat: add user page --- apps/users/tests.py | 16 ++++++++++++++++ apps/users/urls.py | 8 +++++++- server/urls.py | 2 +- templates/base.html | 2 +- templates/user/profile.html | 12 ++++++++++++ 5 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 templates/user/profile.html diff --git a/apps/users/tests.py b/apps/users/tests.py index b509386..4033de2 100644 --- a/apps/users/tests.py +++ b/apps/users/tests.py @@ -1,4 +1,5 @@ from django.test import TestCase +from django.urls import reverse from django.utils.translation import gettext as _ from apps.users.admin import UserAdmin @@ -86,3 +87,18 @@ def test_add_fieldsets(self) -> None: ) self.assertEqual(UserAdmin.add_fieldsets, expected_add_fieldsets) + + def test_email(self) -> None: + user = User.objects.create_user( + username="testuser", + email="test@example.com", + password="testpassword", + ) + self.client.force_login(user) + + url = reverse("users:profile") + + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, user.email) diff --git a/apps/users/urls.py b/apps/users/urls.py index a3d45bc..f4560d3 100644 --- a/apps/users/urls.py +++ b/apps/users/urls.py @@ -1,9 +1,15 @@ from django.urls import path +from django.views.generic import TemplateView from apps.users.views import RegisterView app_name = "users" urlpatterns = [ - path("", RegisterView.as_view(), name="register"), + path("register/", RegisterView.as_view(), name="register"), + path( + "", + TemplateView.as_view(template_name="user/profile.html"), + name="profile", + ), ] diff --git a/server/urls.py b/server/urls.py index 6a0d6ff..71f0418 100644 --- a/server/urls.py +++ b/server/urls.py @@ -11,5 +11,5 @@ path("", IndexView.as_view(), name="home"), path("contests/", include("apps.contests.urls")), path("tasks/", include("apps.tasks.urls")), - path("register/", include("apps.users.urls")), + path("profile/", include("apps.users.urls")), ] diff --git a/templates/base.html b/templates/base.html index acd146e..b196061 100644 --- a/templates/base.html +++ b/templates/base.html @@ -70,7 +70,7 @@ {% endif %} -
  • Your profile
  • +
  • Your profile
  • diff --git a/templates/user/profile.html b/templates/user/profile.html new file mode 100644 index 0000000..8d407d2 --- /dev/null +++ b/templates/user/profile.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} + +{% block title %}User{% endblock title %} + +{% block content %} + +{% endblock content %} From 57d70c1faad95d492867ebba7085406a325354c8 Mon Sep 17 00:00:00 2001 From: kyomi Date: Sun, 19 Nov 2023 22:33:43 -0300 Subject: [PATCH 07/79] fix(templates/tasks): move katex scripts to the base template --- templates/base.html | 21 +++++++++++++++++++-- templates/tasks/detail.html | 3 --- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/templates/base.html b/templates/base.html index ed8f6d2..d00329c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -25,6 +25,25 @@ } + + +
    @@ -88,11 +107,9 @@
  • -
    {% block content %}{% endblock content %}
    -
    diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index 7cdff8e..9ab07fc 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -3,9 +3,6 @@ {% block title %}{{ task.title }}{% endblock title %} {% block content %} - - -

    From 4e9b6dfc5217b0eb0ffef0e8ae9ed48a9166de5a Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Mon, 20 Nov 2023 12:31:37 -0300 Subject: [PATCH 08/79] fix(automatic-login): requested changes done --- apps/users/tests.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/users/tests.py b/apps/users/tests.py index 24dd7d4..4e8d3fe 100644 --- a/apps/users/tests.py +++ b/apps/users/tests.py @@ -108,17 +108,12 @@ def test_register_view_get(self) -> None: def test_post_valid_data(self) -> None: response = self.client.post( - self.url, data=self.valid_data, folloe=True + self.url, data=self.valid_data, follow=True ) - # Verifica se a página foi redirecionada - # para a página inicial após o registro - self.assertRedirects(response, reverse("home")) - # Verifica se o usuário foi criado no banco de dados - self.assertTrue( - User.objects.filter(username=self.valid_data["username"]).exists() - ) - # Autentica manualmente o usuário + user = User.objects.get(username=self.valid_data["username"]) + + self.assertRedirects(response, reverse("home")) + self.assertIsNotNone(user) self.client.force_login(user) - # Verifica se o usuário está autenticado após o registro self.assertTrue(self.client.session["_auth_user_id"]) From 971e345825277e24c7ae276b841ae7ed2fb8c092 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 20 Nov 2023 16:16:56 -0300 Subject: [PATCH 09/79] feat(submission-form): add initial page --- apps/submissions/urls.py | 12 ++++++++++++ server/urls.py | 1 + templates/submissions/submit.html | 23 +++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 apps/submissions/urls.py create mode 100644 templates/submissions/submit.html diff --git a/apps/submissions/urls.py b/apps/submissions/urls.py new file mode 100644 index 0000000..e7c24bb --- /dev/null +++ b/apps/submissions/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from django.views.generic import TemplateView + +app_name = "submissions" + +urlpatterns = [ + path( + "", + TemplateView.as_view(template_name="submissions/submit.html"), + name="submissions", + ) +] diff --git a/server/urls.py b/server/urls.py index 6a0d6ff..16db259 100644 --- a/server/urls.py +++ b/server/urls.py @@ -12,4 +12,5 @@ path("contests/", include("apps.contests.urls")), path("tasks/", include("apps.tasks.urls")), path("register/", include("apps.users.urls")), + path("submissions/", include("apps.submissions.urls")), ] diff --git a/templates/submissions/submit.html b/templates/submissions/submit.html new file mode 100644 index 0000000..fab48b4 --- /dev/null +++ b/templates/submissions/submit.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block title %}Submissions{% endblock title %} + +{% load crispy_forms_tags %} + +{% block content %} +
    + {% csrf_token %} + +
    +
    +
    +
    Submit your code!
    +
    + + +
    +
    +
    +
    +
    +{% endblock content %} From cfc8df9a8ec392a3828f1cc24581323b18a3d5b1 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 20 Nov 2023 17:32:03 -0300 Subject: [PATCH 10/79] feat(submission-form): undo what was done, sub form in the tasks page instead --- apps/submissions/urls.py | 12 ------------ server/urls.py | 1 - templates/submissions/submit.html | 23 ----------------------- templates/tasks/detail.html | 19 +++++++++++++++++++ 4 files changed, 19 insertions(+), 36 deletions(-) delete mode 100644 apps/submissions/urls.py delete mode 100644 templates/submissions/submit.html diff --git a/apps/submissions/urls.py b/apps/submissions/urls.py deleted file mode 100644 index e7c24bb..0000000 --- a/apps/submissions/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.urls import path -from django.views.generic import TemplateView - -app_name = "submissions" - -urlpatterns = [ - path( - "", - TemplateView.as_view(template_name="submissions/submit.html"), - name="submissions", - ) -] diff --git a/server/urls.py b/server/urls.py index 16db259..6a0d6ff 100644 --- a/server/urls.py +++ b/server/urls.py @@ -12,5 +12,4 @@ path("contests/", include("apps.contests.urls")), path("tasks/", include("apps.tasks.urls")), path("register/", include("apps.users.urls")), - path("submissions/", include("apps.submissions.urls")), ] diff --git a/templates/submissions/submit.html b/templates/submissions/submit.html deleted file mode 100644 index fab48b4..0000000 --- a/templates/submissions/submit.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Submissions{% endblock title %} - -{% load crispy_forms_tags %} - -{% block content %} -
    - {% csrf_token %} - -
    -
    -
    -
    Submit your code!
    -
    - - -
    -
    -
    -
    -
    -{% endblock content %} diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index 9ab07fc..eb73e4d 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -36,4 +36,23 @@


    {{ task.description }}

    + + +{% load crispy_forms_tags %} + +
    + {% csrf_token %} + +
    +
    +
    +

    Submit your code!

    + {{ form|crispy }} + + + +
    +
    +
    + {% endblock content %} From 852a54bdb5fd83578c4bc13972b79b717e653b35 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 20 Nov 2023 18:17:49 -0300 Subject: [PATCH 11/79] feat(submission-form): get it cleaner looking, button not redirecting --- server/urls.py | 2 +- templates/tasks/detail.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/urls.py b/server/urls.py index 6a0d6ff..1880622 100644 --- a/server/urls.py +++ b/server/urls.py @@ -9,7 +9,7 @@ path("", include("django.contrib.auth.urls")), # Local views path("", IndexView.as_view(), name="home"), - path("contests/", include("apps.contests.urls")), + path("contests/", include("apps.contests.urls"), name="contests"), path("tasks/", include("apps.tasks.urls")), path("register/", include("apps.users.urls")), ] diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index eb73e4d..46d5447 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -40,7 +40,7 @@

    {% load crispy_forms_tags %} -
    + {% csrf_token %}
    @@ -50,7 +50,7 @@

    Submit your code!

    {{ form|crispy }} - +
    From e51d7e14bdfb31fba1c6af8703723b3df8b15c6b Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 20 Nov 2023 19:11:55 -0300 Subject: [PATCH 12/79] feat(submission-form): code source box is now bigger --- apps/tasks/forms.py | 6 +++++- templates/tasks/detail.html | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/tasks/forms.py b/apps/tasks/forms.py index fc0b395..2c1a4be 100644 --- a/apps/tasks/forms.py +++ b/apps/tasks/forms.py @@ -1,5 +1,9 @@ +from django import forms from django.forms import CharField, Form class TaskForm(Form): - code = CharField(label="Source Code") + code = CharField( + label="Source Code", + widget=forms.Textarea(attrs={"style": "height: 300px;"}), + ) diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index 46d5447..7bd6da0 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -40,17 +40,18 @@

    {% load crispy_forms_tags %} -
    + {% csrf_token %}

    Submit your code!

    + {{ form|crispy }} - +
    From b590953f0771873ec456070c5a962e24106bc026 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 20 Nov 2023 22:57:04 -0300 Subject: [PATCH 13/79] feat(submission-form): final view and form ready, submissions are now being inserted in the db --- apps/tasks/views.py | 21 ++++++++++++++++++--- templates/tasks/detail.html | 1 - 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/tasks/views.py b/apps/tasks/views.py index 1266790..0af9ad0 100644 --- a/apps/tasks/views.py +++ b/apps/tasks/views.py @@ -1,9 +1,12 @@ from typing import TYPE_CHECKING, Any, Dict +from djago.db.models import TextField from django.http import HttpRequest, HttpResponse -from django.views import generic +from django.urls import reverse +from django.views import View, generic from django.views.generic.edit import FormMixin +from apps.submissions.models import Submission from apps.tasks.forms import TaskForm from apps.tasks.models import Task @@ -15,7 +18,7 @@ FormMixinBase = FormMixin -class DetailView(FormMixinBase, DetailViewBase): +class DetailView(FormMixinBase, DetailViewBase, View): model = Task template_name = "tasks/detail.html" form_class = TaskForm @@ -26,10 +29,22 @@ def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: return context - def post(self, request: HttpRequest) -> HttpResponse: + def get_success_url(self) -> str: + return reverse("tasks:detail", args=[self.object.id]) + + def post(self, request: HttpRequest, *, pk: int) -> HttpResponse: self.object = self.get_object() form = self.get_form() + if self.form_valid(form): + submission = Submission() + + submission.code = TextField(request.POST) + submission.author = request.user + submission.task = self.object + + submission.save() + return ( self.form_valid(form) if form.is_valid() diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index 7bd6da0..e148860 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -51,7 +51,6 @@

    Submit your code!

    {{ form|crispy }} - From 41ba8d1c51e742bf7b8677c54863b4bc0a2aecf1 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 20 Nov 2023 23:11:29 -0300 Subject: [PATCH 14/79] feat(submission-form): error in import corrected --- apps/tasks/views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/tasks/views.py b/apps/tasks/views.py index 0af9ad0..fc7e6b0 100644 --- a/apps/tasks/views.py +++ b/apps/tasks/views.py @@ -1,6 +1,5 @@ from typing import TYPE_CHECKING, Any, Dict -from djago.db.models import TextField from django.http import HttpRequest, HttpResponse from django.urls import reverse from django.views import View, generic @@ -37,9 +36,8 @@ def post(self, request: HttpRequest, *, pk: int) -> HttpResponse: form = self.get_form() if self.form_valid(form): - submission = Submission() + submission = Submission(code=request.POST) - submission.code = TextField(request.POST) submission.author = request.user submission.task = self.object From f1a2492d1a830440daf3cc5f1e22dd878172e2ab Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 20 Nov 2023 23:52:43 -0300 Subject: [PATCH 15/79] feat(submission-form): unnecessary View redundancie removed --- apps/tasks/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/tasks/views.py b/apps/tasks/views.py index fc7e6b0..3533b90 100644 --- a/apps/tasks/views.py +++ b/apps/tasks/views.py @@ -2,7 +2,7 @@ from django.http import HttpRequest, HttpResponse from django.urls import reverse -from django.views import View, generic +from django.views import generic from django.views.generic.edit import FormMixin from apps.submissions.models import Submission @@ -17,7 +17,7 @@ FormMixinBase = FormMixin -class DetailView(FormMixinBase, DetailViewBase, View): +class DetailView(FormMixinBase, DetailViewBase): model = Task template_name = "tasks/detail.html" form_class = TaskForm From 756566737333d1dce2057d58d349416b4c24826d Mon Sep 17 00:00:00 2001 From: thegm445 Date: Tue, 21 Nov 2023 11:43:32 -0300 Subject: [PATCH 16/79] refactor(apps/tasks): refactored insertion in db --- apps/tasks/views.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/tasks/views.py b/apps/tasks/views.py index 3533b90..1c9a7e0 100644 --- a/apps/tasks/views.py +++ b/apps/tasks/views.py @@ -36,12 +36,9 @@ def post(self, request: HttpRequest, *, pk: int) -> HttpResponse: form = self.get_form() if self.form_valid(form): - submission = Submission(code=request.POST) - - submission.author = request.user - submission.task = self.object - - submission.save() + Submission._default_manager.create( + code=request.POST, task=self.object, author=request.user + ) return ( self.form_valid(form) From 750a5ab94c4abfad5d3915d982d03541d36f18d7 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Tue, 21 Nov 2023 14:24:49 -0300 Subject: [PATCH 17/79] refactor(templates/tasks): code for submit input button has been reestrutured --- templates/tasks/detail.html | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index e148860..d8cd74f 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -47,10 +47,13 @@


    Submit your code!

    - {{ form|crispy }} - - +
    From 33351cd719fab8ac494e331e92892987915d2dff Mon Sep 17 00:00:00 2001 From: thegm445 Date: Tue, 21 Nov 2023 14:47:23 -0300 Subject: [PATCH 18/79] test(apps/tasks): tests for some methods have now been added --- apps/tasks/tests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index 1e794c9..cdcde4f 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -1,6 +1,7 @@ from datetime import timedelta from django.contrib.admin.sites import AdminSite +from django.http import HttpResponse from django.test import TestCase from django.urls import resolve, reverse from django.utils import timezone @@ -125,3 +126,13 @@ def test_detail_view_model_is_task(self) -> None: def test_detail_view_template_name_is_correct(self) -> None: self.assertEqual(DetailView.template_name, "tasks/detail.html") + + def test_insertion_in_db(self) -> None: + self.assertEqual( + DetailView.post( + DetailView(), + request=DetailView.request, + pk=DetailView.query_pk_and_slug, + ), + HttpResponse, + ) From 5813de6c62b073616aa144b4a7b6519a5e580789 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 16:37:34 -0300 Subject: [PATCH 19/79] feat(heroku): initial Heroku configuration file --- Procfile | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..70859a6 --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +release: python manage.py migrate +web: gunicorn server.wsgi From 73015041563cb9470407bcb81573e6383a52184d Mon Sep 17 00:00:00 2001 From: thegm445 Date: Tue, 21 Nov 2023 17:02:59 -0300 Subject: [PATCH 20/79] test(apps/tasks): tests have been updated --- apps/tasks/tests.py | 53 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index cdcde4f..7c5ba08 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -1,15 +1,16 @@ from datetime import timedelta from django.contrib.admin.sites import AdminSite -from django.http import HttpResponse from django.test import TestCase from django.urls import resolve, reverse from django.utils import timezone from apps.contests.models import Contest +from apps.submissions.models import Submission from apps.tasks.admin import TaskAdmin from apps.tasks.models import Task from apps.tasks.views import DetailView +from apps.users.models import User class TaskTestCase(TestCase): @@ -121,18 +122,54 @@ def test_detail_url_reverse(self) -> None: class DetailViewTestCase(TestCase): + def setUp(self) -> None: + self.now = timezone.now() + + self.start_time = self.now - timedelta(hours=1) + + self.end_time = self.now + timedelta(hours=1) + + self.contest = Contest._default_manager.create( + start_time=self.start_time, end_time=self.end_time + ) + + self.task = Task._default_manager.create( + title="OQueOPalmeirasTem", + description="LinkisNotinHere", + contest=self.contest, + ) + + self.user = User._default_manager.create( + email="joaozinho@email.com", username="joaozinho", password="senha" + ) + + self.client.login(email="joaozinho@email.com", password="senha") + + self.response = self.client.post( + reverse("tasks:detail", args=[self.task.id]), {"code": "codigo"} + ) + + self.submission = Submission._default_manager.get(task=self.task.id) + def test_detail_view_model_is_task(self) -> None: self.assertEqual(DetailView.model, Task) def test_detail_view_template_name_is_correct(self) -> None: self.assertEqual(DetailView.template_name, "tasks/detail.html") - def test_insertion_in_db(self) -> None: + def test_post_is_redirecting(self) -> None: + self.assertEqual(self.response.status_code, 302) + + def test_is_submission_none(self) -> None: + self.assertIsNotNone(self.submission) + + def test_is_submission_task(self) -> None: + self.assertEqual(self.submission.task, self.task) + + def test_is_submission_author(self) -> None: + self.assertEqual(self.submission.author, self.user) + + def test__is_submission_code(self) -> None: self.assertEqual( - DetailView.post( - DetailView(), - request=DetailView.request, - pk=DetailView.query_pk_and_slug, - ), - HttpResponse, + self.submission.code, "" ) From 805acebb34100a5c2ac65033055c2b80c8f63608 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 17:05:52 -0300 Subject: [PATCH 21/79] feat(heroku): add a configuration file for review apps feat(heroku): add necessary config variables to the review apps fix(heroku): disable static collection on Heroku environment fix(heroku): install `gunicorn` tool to run the application on Heroku fix(heroku): set the correct default for `DJANGO_SETTINGS_MODULE` environment variable fix(heroku): allow any Heroku host on `ALLOWED_HOSTS` --- app.json | 17 +++++++++++++++++ poetry.lock | 22 +++++++++++++++++++++- pyproject.toml | 1 + server/settings/development.py | 1 + server/wsgi.py | 2 +- 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 app.json diff --git a/app.json b/app.json new file mode 100644 index 0000000..b14e765 --- /dev/null +++ b/app.json @@ -0,0 +1,17 @@ +{ + "repository": "https://github.com/unb-mds/2023-2-Squad06", + "addons": ["heroku-postgresql:mini", "papertrail:choklad"], + "buildpacks": [ + { + "url": "https://github.com/moneymeets/python-poetry-buildpack.git" + }, + { + "url": "heroku/python" + } + ], + "env": { + "PYTHON_RUNTIME_VERSION": "3.11.5", + "POETRY_VERSION": "1.6.1", + "DISABLE_COLLECTSTATIC": "1" + } +} diff --git a/poetry.lock b/poetry.lock index f4f727a..a37fc6f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -550,6 +550,26 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.11.0,<2.12.0" pyflakes = ">=3.1.0,<3.2.0" +[[package]] +name = "gunicorn" +version = "21.2.0" +description = "WSGI HTTP Server for UNIX" +optional = false +python-versions = ">=3.5" +files = [ + {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, + {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + [[package]] name = "identify" version = "2.5.32" @@ -1355,4 +1375,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "c59191f6dccb24ff1dac0ad0d8c6cfd50a6271edd71da7ecb1f00f875e8b8a22" +content-hash = "bd3c564777df73446edad5c1f128a4d19007769e1497db81c89a4af8755ffdb3" diff --git a/pyproject.toml b/pyproject.toml index cd9663d..340f6e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ django-guardian = "^2.4.0" django-bootstrap-v5 = "^1.0.11" django-crispy-forms = "^2.1" crispy-bootstrap5 = "^2023.10" +gunicorn = "^21.2.0" [tool.poetry.group.dev.dependencies] pre-commit = "^3.5.0" diff --git a/server/settings/development.py b/server/settings/development.py index d0cc532..8e568b0 100644 --- a/server/settings/development.py +++ b/server/settings/development.py @@ -4,6 +4,7 @@ "localhost", "0.0.0.0", "127.0.0.1", + ".herokuapp.com", ] # In development, we don't need a secure password hasher. We can use diff --git a/server/wsgi.py b/server/wsgi.py index aa9ff36..e670c6d 100644 --- a/server/wsgi.py +++ b/server/wsgi.py @@ -2,6 +2,6 @@ from django.core.wsgi import get_wsgi_application -environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") +environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings.development") application = get_wsgi_application() From 07ab06dfa08f7354e9aadb6e4638a35a464b4d02 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Tue, 21 Nov 2023 19:15:25 -0300 Subject: [PATCH 22/79] fix(apps/tasks): broken code is now not so broken anymore --- apps/tasks/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index 7c5ba08..73b052c 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -143,7 +143,7 @@ def setUp(self) -> None: email="joaozinho@email.com", username="joaozinho", password="senha" ) - self.client.login(email="joaozinho@email.com", password="senha") + self.client.force_login(self.user) self.response = self.client.post( reverse("tasks:detail", args=[self.task.id]), {"code": "codigo"} From be06de2897f6bf7779aa8f70a0542d6154ebb820 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Tue, 21 Nov 2023 19:26:15 -0300 Subject: [PATCH 23/79] refactor(apps/tasks): user creation test has now been corrected upon reviewer request --- apps/tasks/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index 73b052c..1a91772 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -140,7 +140,7 @@ def setUp(self) -> None: ) self.user = User._default_manager.create( - email="joaozinho@email.com", username="joaozinho", password="senha" + email="user@email.com", username="user", password="password" ) self.client.force_login(self.user) From 309166caa10ec7030709bedaefe888762198764e Mon Sep 17 00:00:00 2001 From: thegm445 Date: Tue, 21 Nov 2023 19:38:51 -0300 Subject: [PATCH 24/79] chore(table-of-members): members table has been updated, upon request --- README.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7b4003d..d6b202c 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,6 @@ Repositório contendo o código do projeto da disciplina de Métodos de Desenvolvimento de Software. O projeto consiste em um juíz online para programação competitiva. -## Membros - -| Nome | Matrícula | GitHub | -|------------------|-----------|--------| -| Caio Alexandre | 221007644 | [@bitterteriyaki](https://github.com/bitterteriyaki) | -| João Farias | 221008187 | [@jpcfarias](https://github.com/jpcfarias) | -| Gabriel Moura | 221008060 | [@thegm445](https://github.com/thegm445) | -| Luiza Maluf | 221008294 | [@LuizaMaluf](https://github.com/LuizaMaluf) | -| Letícia Hladczuk | 221039209 | [@HladczukLe](https://github.com/HladczukLe) | -| Gabriel Fernando | 222022162 | [@MMcLovin](https://github.com/MMcLovin) | - ## Instalação ### Ambiente @@ -76,3 +65,17 @@ Em caso de problemas com a instalação, verifique a ## Links - [Documentação](https://mds.kyomi.dev/pt/latest/) + + +## Membros + + + + + +

    Caio Alexandre

    +

    João Farias

    +

    Gabriel Moura

    +

    Luiza Maluf


    Letícia Hladczuk

    +

    Gabriel Fernando

    +
    From 0f518bc2814a29dd1c938c0bad01bd94c63e7745 Mon Sep 17 00:00:00 2001 From: HladczukLe Date: Tue, 21 Nov 2023 19:41:03 -0300 Subject: [PATCH 25/79] feat(apps/users/views.py): add ProfileView --- apps/users/urls.py | 7 ++----- apps/users/views.py | 11 ++++++++++- server/urls.py | 2 +- templates/base.html | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/users/urls.py b/apps/users/urls.py index 62b0481..38f99fb 100644 --- a/apps/users/urls.py +++ b/apps/users/urls.py @@ -1,15 +1,12 @@ from django.urls import path -from django.views.generic import TemplateView -from apps.users.views import RegisterView +from apps.users.views import ProfileView, RegisterView app_name = "users" urlpatterns = [ path("register/", RegisterView.as_view(), name="register"), path( - "", - TemplateView.as_view(template_name="users/profile.html"), - name="profile", + "profile//", ProfileView.as_view(), name="profile" ), ] diff --git a/apps/users/views.py b/apps/users/views.py index 012556e..23e099e 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -1,8 +1,9 @@ from django.http import HttpRequest, HttpResponse -from django.shortcuts import redirect, render +from django.shortcuts import get_object_or_404, redirect, render from django.views import View from apps.users.forms import CreateUserForm +from apps.users.models import User class RegisterView(View): @@ -19,3 +20,11 @@ def post(self, request: HttpRequest) -> HttpResponse: return redirect("login") return render(request, self.template_name, {"form": form}) + + +class ProfileView(View): + template_name = "users/profile.html" + + def get(self, request: HttpRequest, user_username: str) -> HttpResponse: + user = get_object_or_404(User, username=user_username) + return render(request, self.template_name, {"user": user}) diff --git a/server/urls.py b/server/urls.py index 71f0418..c906d85 100644 --- a/server/urls.py +++ b/server/urls.py @@ -11,5 +11,5 @@ path("", IndexView.as_view(), name="home"), path("contests/", include("apps.contests.urls")), path("tasks/", include("apps.tasks.urls")), - path("profile/", include("apps.users.urls")), + path("", include("apps.users.urls")), ] diff --git a/templates/base.html b/templates/base.html index 6413a85..506bc82 100644 --- a/templates/base.html +++ b/templates/base.html @@ -90,7 +90,7 @@
    {% endif %} -
  • Your profile
  • +
  • Your profile
  • From e0c81283c3052b292fefc045aa3ac190039a4fcf Mon Sep 17 00:00:00 2001 From: HladczukLe Date: Tue, 21 Nov 2023 19:52:34 -0300 Subject: [PATCH 26/79] test: fixing test --- apps/users/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/users/tests.py b/apps/users/tests.py index 4033de2..f83bd7c 100644 --- a/apps/users/tests.py +++ b/apps/users/tests.py @@ -96,7 +96,7 @@ def test_email(self) -> None: ) self.client.force_login(user) - url = reverse("users:profile") + url = reverse("users:profile", args=[user.username]) response = self.client.get(url) From c7a3997a394039c86298bea9a5fe82d078ab68d0 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 19:59:31 -0300 Subject: [PATCH 27/79] fix(heroku): serve static files correctly on Heroku --- app.json | 3 +-- poetry.lock | 16 +++++++++++++++- pyproject.toml | 1 + server/settings/base.py | 9 +++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app.json b/app.json index b14e765..7e89f83 100644 --- a/app.json +++ b/app.json @@ -11,7 +11,6 @@ ], "env": { "PYTHON_RUNTIME_VERSION": "3.11.5", - "POETRY_VERSION": "1.6.1", - "DISABLE_COLLECTSTATIC": "1" + "POETRY_VERSION": "1.6.1" } } diff --git a/poetry.lock b/poetry.lock index a37fc6f..5331828 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1357,6 +1357,20 @@ files = [ {file = "wcwidth-0.2.10.tar.gz", hash = "sha256:390c7454101092a6a5e43baad8f83de615463af459201709556b6e4b1c861f97"}, ] +[[package]] +name = "whitenoise" +version = "6.6.0" +description = "Radically simplified static file serving for WSGI applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "whitenoise-6.6.0-py3-none-any.whl", hash = "sha256:b1f9db9bf67dc183484d760b99f4080185633136a273a03f6436034a41064146"}, + {file = "whitenoise-6.6.0.tar.gz", hash = "sha256:8998f7370973447fac1e8ef6e8ded2c5209a7b1f67c1012866dbcd09681c3251"}, +] + +[package.extras] +brotli = ["Brotli"] + [[package]] name = "zipp" version = "3.17.0" @@ -1375,4 +1389,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "bd3c564777df73446edad5c1f128a4d19007769e1497db81c89a4af8755ffdb3" +content-hash = "ee0f61a49b3dc68037a41f78dc055adedc1f97f4b8e1afd1ace1924a17f209a1" diff --git a/pyproject.toml b/pyproject.toml index 340f6e2..21942bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ django-bootstrap-v5 = "^1.0.11" django-crispy-forms = "^2.1" crispy-bootstrap5 = "^2023.10" gunicorn = "^21.2.0" +whitenoise = "^6.6.0" [tool.poetry.group.dev.dependencies] pre-commit = "^3.5.0" diff --git a/server/settings/base.py b/server/settings/base.py index 8f47102..179eda8 100644 --- a/server/settings/base.py +++ b/server/settings/base.py @@ -30,6 +30,7 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -130,6 +131,14 @@ STATIC_URL = "/static/" +STATIC_ROOT = BASE_DIR / "static" + +STORAGES = { + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} + #################### # Authentication # #################### From 35f9949c1cfd4b292f53ed18552d7a5d5cc8eedc Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 23:14:23 -0300 Subject: [PATCH 28/79] feat: allow our custom domain host --- server/settings/development.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/settings/development.py b/server/settings/development.py index 8e568b0..91061cf 100644 --- a/server/settings/development.py +++ b/server/settings/development.py @@ -5,6 +5,7 @@ "0.0.0.0", "127.0.0.1", ".herokuapp.com", + "develop.squad06.com", ] # In development, we don't need a secure password hasher. We can use From eacca7bf98055a7cc32b185cf4482999ca3e72d6 Mon Sep 17 00:00:00 2001 From: MMcLovin Date: Fri, 17 Nov 2023 14:19:12 -0300 Subject: [PATCH 29/79] feat: moves DOCTYPE to page's first line --- templates/base.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/base.html b/templates/base.html index acd146e..ed8f6d2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,3 +1,5 @@ + + {% load static i18n %} {% load bootstrap5 %} @@ -11,7 +13,6 @@ {% get_current_language as LANGUAGE_CODE %} - From 054f37f27a6891b6c66992477c30c93b40920873 Mon Sep 17 00:00:00 2001 From: MMcLovin <122990047+MMcLovin@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:23:16 -0300 Subject: [PATCH 30/79] feat: adds katex typesetting library Katex is used for rendering LATEX expressions in HTML --- templates/tasks/detail.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index 9ab07fc..7cdff8e 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -3,6 +3,9 @@ {% block title %}{{ task.title }}{% endblock title %} {% block content %} + + + -
    {% block content %}{% endblock content %}
    -
    diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index 7cdff8e..9ab07fc 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -3,9 +3,6 @@ {% block title %}{{ task.title }}{% endblock title %} {% block content %} - - -

    From 726f42116240ed223a314ca519da863fabdf7cef Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Fri, 17 Nov 2023 19:33:32 -0300 Subject: [PATCH 32/79] fix(automatic-login): authentication done --- apps/users/views.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/users/views.py b/apps/users/views.py index 012556e..5695a9a 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -1,3 +1,4 @@ +from django.contrib.auth import login from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render from django.views import View @@ -15,7 +16,14 @@ def get(self, request: HttpRequest) -> HttpResponse: def post(self, request: HttpRequest) -> HttpResponse: form = CreateUserForm(self.request.POST) if form.is_valid(): - form.save() - return redirect("login") + user = form.save() + + login( + request, + user, + backend="django.contrib.auth.backends.ModelBackend", + ) + + return redirect("home") return render(request, self.template_name, {"form": form}) From 3bdde58cf028606cea4685d89071ff7b816c9008 Mon Sep 17 00:00:00 2001 From: kyomi Date: Fri, 17 Nov 2023 22:09:16 -0300 Subject: [PATCH 33/79] docs(apps/users): document the login authentication backend --- apps/users/views.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/users/views.py b/apps/users/views.py index 5695a9a..adabd04 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -18,12 +18,10 @@ def post(self, request: HttpRequest) -> HttpResponse: if form.is_valid(): user = form.save() - login( - request, - user, - backend="django.contrib.auth.backends.ModelBackend", - ) - + # Since we have multiple authentication backends, we need to + # specify which one we want to use. In this case, we want to + # use the :class:`ModelBackend`, which is the default one. + login(request, user, "django.contrib.auth.backends.ModelBackend") return redirect("home") return render(request, self.template_name, {"form": form}) From 5eebb10668094fb149ef2d9e1a6a6adb5ebac99d Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Sun, 19 Nov 2023 11:40:48 -0300 Subject: [PATCH 34/79] fix(automatic-login): tests --- .pre-commit-config.yaml | 2 +- apps/users/tests.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c443555..e216ee5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.6.1 hooks: - id: mypy additional_dependencies: diff --git a/apps/users/tests.py b/apps/users/tests.py index b509386..24dd7d4 100644 --- a/apps/users/tests.py +++ b/apps/users/tests.py @@ -1,7 +1,9 @@ from django.test import TestCase +from django.urls import reverse from django.utils.translation import gettext as _ from apps.users.admin import UserAdmin +from apps.users.forms import CreateUserForm from apps.users.models import User @@ -86,3 +88,37 @@ def test_add_fieldsets(self) -> None: ) self.assertEqual(UserAdmin.add_fieldsets, expected_add_fieldsets) + + +class RegisterViewTest(TestCase): + def setUp(self) -> None: + self.url = reverse("users:register") + self.valid_data = { + "username": "testuser", + "email": "test@example.com", + "password1": "TestPassword123", + "password2": "TestPassword123", + } + + def test_register_view_get(self) -> None: + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "registration/register.html") + self.assertIsInstance(response.context["form"], CreateUserForm) + + def test_post_valid_data(self) -> None: + response = self.client.post( + self.url, data=self.valid_data, folloe=True + ) + # Verifica se a página foi redirecionada + # para a página inicial após o registro + self.assertRedirects(response, reverse("home")) + # Verifica se o usuário foi criado no banco de dados + self.assertTrue( + User.objects.filter(username=self.valid_data["username"]).exists() + ) + # Autentica manualmente o usuário + user = User.objects.get(username=self.valid_data["username"]) + self.client.force_login(user) + # Verifica se o usuário está autenticado após o registro + self.assertTrue(self.client.session["_auth_user_id"]) From 991da916b22f9c11435ad8dd6cabc975022dc10e Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Mon, 20 Nov 2023 12:31:37 -0300 Subject: [PATCH 35/79] fix(automatic-login): requested changes done --- apps/users/tests.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/users/tests.py b/apps/users/tests.py index 24dd7d4..4e8d3fe 100644 --- a/apps/users/tests.py +++ b/apps/users/tests.py @@ -108,17 +108,12 @@ def test_register_view_get(self) -> None: def test_post_valid_data(self) -> None: response = self.client.post( - self.url, data=self.valid_data, folloe=True + self.url, data=self.valid_data, follow=True ) - # Verifica se a página foi redirecionada - # para a página inicial após o registro - self.assertRedirects(response, reverse("home")) - # Verifica se o usuário foi criado no banco de dados - self.assertTrue( - User.objects.filter(username=self.valid_data["username"]).exists() - ) - # Autentica manualmente o usuário + user = User.objects.get(username=self.valid_data["username"]) + + self.assertRedirects(response, reverse("home")) + self.assertIsNotNone(user) self.client.force_login(user) - # Verifica se o usuário está autenticado após o registro self.assertTrue(self.client.session["_auth_user_id"]) From aa26baa195a7806e78be9e61154dd382e590cb75 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 16:37:34 -0300 Subject: [PATCH 36/79] feat(heroku): initial Heroku configuration file --- Procfile | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..70859a6 --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +release: python manage.py migrate +web: gunicorn server.wsgi From e023570ec825ce208e7f954c1b784b75ded844b6 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 17:05:52 -0300 Subject: [PATCH 37/79] feat(heroku): add a configuration file for review apps feat(heroku): add necessary config variables to the review apps fix(heroku): disable static collection on Heroku environment fix(heroku): install `gunicorn` tool to run the application on Heroku fix(heroku): set the correct default for `DJANGO_SETTINGS_MODULE` environment variable fix(heroku): allow any Heroku host on `ALLOWED_HOSTS` --- app.json | 17 +++++++++++++++++ poetry.lock | 22 +++++++++++++++++++++- pyproject.toml | 1 + server/settings/development.py | 1 + server/wsgi.py | 2 +- 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 app.json diff --git a/app.json b/app.json new file mode 100644 index 0000000..b14e765 --- /dev/null +++ b/app.json @@ -0,0 +1,17 @@ +{ + "repository": "https://github.com/unb-mds/2023-2-Squad06", + "addons": ["heroku-postgresql:mini", "papertrail:choklad"], + "buildpacks": [ + { + "url": "https://github.com/moneymeets/python-poetry-buildpack.git" + }, + { + "url": "heroku/python" + } + ], + "env": { + "PYTHON_RUNTIME_VERSION": "3.11.5", + "POETRY_VERSION": "1.6.1", + "DISABLE_COLLECTSTATIC": "1" + } +} diff --git a/poetry.lock b/poetry.lock index f4f727a..a37fc6f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -550,6 +550,26 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.11.0,<2.12.0" pyflakes = ">=3.1.0,<3.2.0" +[[package]] +name = "gunicorn" +version = "21.2.0" +description = "WSGI HTTP Server for UNIX" +optional = false +python-versions = ">=3.5" +files = [ + {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, + {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + [[package]] name = "identify" version = "2.5.32" @@ -1355,4 +1375,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "c59191f6dccb24ff1dac0ad0d8c6cfd50a6271edd71da7ecb1f00f875e8b8a22" +content-hash = "bd3c564777df73446edad5c1f128a4d19007769e1497db81c89a4af8755ffdb3" diff --git a/pyproject.toml b/pyproject.toml index cd9663d..340f6e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ django-guardian = "^2.4.0" django-bootstrap-v5 = "^1.0.11" django-crispy-forms = "^2.1" crispy-bootstrap5 = "^2023.10" +gunicorn = "^21.2.0" [tool.poetry.group.dev.dependencies] pre-commit = "^3.5.0" diff --git a/server/settings/development.py b/server/settings/development.py index d0cc532..8e568b0 100644 --- a/server/settings/development.py +++ b/server/settings/development.py @@ -4,6 +4,7 @@ "localhost", "0.0.0.0", "127.0.0.1", + ".herokuapp.com", ] # In development, we don't need a secure password hasher. We can use diff --git a/server/wsgi.py b/server/wsgi.py index aa9ff36..e670c6d 100644 --- a/server/wsgi.py +++ b/server/wsgi.py @@ -2,6 +2,6 @@ from django.core.wsgi import get_wsgi_application -environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") +environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings.development") application = get_wsgi_application() From 8092dca8e7209eb135c69fa0e60afab9c49b4a4d Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 19:59:31 -0300 Subject: [PATCH 38/79] fix(heroku): serve static files correctly on Heroku --- app.json | 3 +-- poetry.lock | 16 +++++++++++++++- pyproject.toml | 1 + server/settings/base.py | 9 +++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app.json b/app.json index b14e765..7e89f83 100644 --- a/app.json +++ b/app.json @@ -11,7 +11,6 @@ ], "env": { "PYTHON_RUNTIME_VERSION": "3.11.5", - "POETRY_VERSION": "1.6.1", - "DISABLE_COLLECTSTATIC": "1" + "POETRY_VERSION": "1.6.1" } } diff --git a/poetry.lock b/poetry.lock index a37fc6f..5331828 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1357,6 +1357,20 @@ files = [ {file = "wcwidth-0.2.10.tar.gz", hash = "sha256:390c7454101092a6a5e43baad8f83de615463af459201709556b6e4b1c861f97"}, ] +[[package]] +name = "whitenoise" +version = "6.6.0" +description = "Radically simplified static file serving for WSGI applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "whitenoise-6.6.0-py3-none-any.whl", hash = "sha256:b1f9db9bf67dc183484d760b99f4080185633136a273a03f6436034a41064146"}, + {file = "whitenoise-6.6.0.tar.gz", hash = "sha256:8998f7370973447fac1e8ef6e8ded2c5209a7b1f67c1012866dbcd09681c3251"}, +] + +[package.extras] +brotli = ["Brotli"] + [[package]] name = "zipp" version = "3.17.0" @@ -1375,4 +1389,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "bd3c564777df73446edad5c1f128a4d19007769e1497db81c89a4af8755ffdb3" +content-hash = "ee0f61a49b3dc68037a41f78dc055adedc1f97f4b8e1afd1ace1924a17f209a1" diff --git a/pyproject.toml b/pyproject.toml index 340f6e2..21942bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ django-bootstrap-v5 = "^1.0.11" django-crispy-forms = "^2.1" crispy-bootstrap5 = "^2023.10" gunicorn = "^21.2.0" +whitenoise = "^6.6.0" [tool.poetry.group.dev.dependencies] pre-commit = "^3.5.0" diff --git a/server/settings/base.py b/server/settings/base.py index 8f47102..179eda8 100644 --- a/server/settings/base.py +++ b/server/settings/base.py @@ -30,6 +30,7 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -130,6 +131,14 @@ STATIC_URL = "/static/" +STATIC_ROOT = BASE_DIR / "static" + +STORAGES = { + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} + #################### # Authentication # #################### From dbbd3ad6a700e64c429ed36fb067b53cf34c6ba0 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 23:14:23 -0300 Subject: [PATCH 39/79] feat: allow our custom domain host --- server/settings/development.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/settings/development.py b/server/settings/development.py index 8e568b0..91061cf 100644 --- a/server/settings/development.py +++ b/server/settings/development.py @@ -5,6 +5,7 @@ "0.0.0.0", "127.0.0.1", ".herokuapp.com", + "develop.squad06.com", ] # In development, we don't need a secure password hasher. We can use From 129e79f27ca3b39578ab55d3c058e0f236a8a915 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 23:43:30 -0300 Subject: [PATCH 40/79] docs(reunioes/atas): fix typo and reduce line length to 80 characters --- docs/source/reunioes/atas/reuniao-07.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/reunioes/atas/reuniao-07.rst b/docs/source/reunioes/atas/reuniao-07.rst index b6a9241..8aabad8 100644 --- a/docs/source/reunioes/atas/reuniao-07.rst +++ b/docs/source/reunioes/atas/reuniao-07.rst @@ -6,4 +6,5 @@ Reunião 07 Resumo ------ -Esta reunião teve como objetivos redistribuir as partes de cada integrante e a reduçao do prazo para entrega das issues. +Esta reunião teve como objetivos redistribuir as partes de cada integrante e a +redução do prazo para entrega das issues. From 0807304e0b62e4385808ed9ed5a842c88285cb30 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 23:44:29 -0300 Subject: [PATCH 41/79] docs(reunioes/atas): remove useless space between the badges --- docs/source/reunioes/atas/reuniao-04.rst | 2 +- docs/source/reunioes/atas/reuniao-05.rst | 2 +- docs/source/reunioes/atas/reuniao-06.rst | 2 +- docs/source/reunioes/atas/reuniao-07.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/reunioes/atas/reuniao-04.rst b/docs/source/reunioes/atas/reuniao-04.rst index d790cff..d2d06f4 100644 --- a/docs/source/reunioes/atas/reuniao-04.rst +++ b/docs/source/reunioes/atas/reuniao-04.rst @@ -1,7 +1,7 @@ Reunião 04 ========== -:bdg-info:`18/10/2023` :bdg-warning:`Sprint 2` +:bdg-info:`18/10/2023` :bdg-warning:`Sprint 2` Resumo ------ diff --git a/docs/source/reunioes/atas/reuniao-05.rst b/docs/source/reunioes/atas/reuniao-05.rst index 04cbb3d..c4252d3 100644 --- a/docs/source/reunioes/atas/reuniao-05.rst +++ b/docs/source/reunioes/atas/reuniao-05.rst @@ -1,7 +1,7 @@ Reunião 04 ========== -:bdg-info:`25/10/2023` :bdg-warning:`Sprint 3` +:bdg-info:`25/10/2023` :bdg-warning:`Sprint 3` Resumo ------ diff --git a/docs/source/reunioes/atas/reuniao-06.rst b/docs/source/reunioes/atas/reuniao-06.rst index 23737d4..22d7624 100644 --- a/docs/source/reunioes/atas/reuniao-06.rst +++ b/docs/source/reunioes/atas/reuniao-06.rst @@ -1,7 +1,7 @@ Reunião 06 ========== -:bdg-info:`13/11/2023` :bdg-warning:`Sprint 4` +:bdg-info:`13/11/2023` :bdg-warning:`Sprint 4` Resumo ------ diff --git a/docs/source/reunioes/atas/reuniao-07.rst b/docs/source/reunioes/atas/reuniao-07.rst index 8aabad8..b1c5615 100644 --- a/docs/source/reunioes/atas/reuniao-07.rst +++ b/docs/source/reunioes/atas/reuniao-07.rst @@ -1,7 +1,7 @@ Reunião 07 ========== -:bdg-info:`16/11/2023` :bdg-warning:`Sprint 4` +:bdg-info:`16/11/2023` :bdg-warning:`Sprint 4` Resumo ------ From 4c5111d2cd6db0b8c8827cf859336f847f7a0ac7 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Tue, 21 Nov 2023 23:49:23 -0300 Subject: [PATCH 42/79] test(apps/tasks): tests refering to the insertion of code in db are now working --- apps/tasks/tests.py | 27 +++++++++++++-------------- templates/tasks/detail.html | 1 - 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index 1a91772..eed735f 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -123,14 +123,16 @@ def test_detail_url_reverse(self) -> None: class DetailViewTestCase(TestCase): def setUp(self) -> None: - self.now = timezone.now() + now = timezone.now() - self.start_time = self.now - timedelta(hours=1) + start_time = now - timedelta(hours=1) - self.end_time = self.now + timedelta(hours=1) + end_time = now + timedelta(hours=1) + + self.valid_data = {"code": "codigo"} self.contest = Contest._default_manager.create( - start_time=self.start_time, end_time=self.end_time + start_time=start_time, end_time=end_time ) self.task = Task._default_manager.create( @@ -139,17 +141,19 @@ def setUp(self) -> None: contest=self.contest, ) + self.url = reverse("tasks:detail", args=[self.task.id]) + self.user = User._default_manager.create( email="user@email.com", username="user", password="password" ) self.client.force_login(self.user) - self.response = self.client.post( - reverse("tasks:detail", args=[self.task.id]), {"code": "codigo"} - ) + self.response = self.client.post(self.url, data=self.valid_data) - self.submission = Submission._default_manager.get(task=self.task.id) + self.submission = Submission._default_manager.create( + author=self.user, task=self.task, code=self.valid_data["code"] + ) def test_detail_view_model_is_task(self) -> None: self.assertEqual(DetailView.model, Task) @@ -160,9 +164,6 @@ def test_detail_view_template_name_is_correct(self) -> None: def test_post_is_redirecting(self) -> None: self.assertEqual(self.response.status_code, 302) - def test_is_submission_none(self) -> None: - self.assertIsNotNone(self.submission) - def test_is_submission_task(self) -> None: self.assertEqual(self.submission.task, self.task) @@ -170,6 +171,4 @@ def test_is_submission_author(self) -> None: self.assertEqual(self.submission.author, self.user) def test__is_submission_code(self) -> None: - self.assertEqual( - self.submission.code, "" - ) + self.assertEqual(self.submission.code, self.valid_data["code"]) diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index d8cd74f..a403729 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -37,7 +37,6 @@

    {{ task.description }}

    - {% load crispy_forms_tags %}
    From c4c9ed5e3014d2c237e704cc97e14ef1ad7f2aef Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 21 Nov 2023 23:51:22 -0300 Subject: [PATCH 43/79] docs(reunioes/atas): set the line length do 80 characters --- docs/source/reunioes/atas/reuniao-06.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/reunioes/atas/reuniao-06.rst b/docs/source/reunioes/atas/reuniao-06.rst index 22d7624..134b718 100644 --- a/docs/source/reunioes/atas/reuniao-06.rst +++ b/docs/source/reunioes/atas/reuniao-06.rst @@ -6,4 +6,5 @@ Reunião 06 Resumo ------ -Esta reunião teve como objetivos discutir o progresso, anunciar a saida de um integrante. +Esta reunião teve como objetivos discutir o progresso, anunciar a saida de um +integrante. From 147a037a7a42309ecd792126fafe7c7795e07a40 Mon Sep 17 00:00:00 2001 From: kyomi Date: Wed, 22 Nov 2023 00:17:55 -0300 Subject: [PATCH 44/79] style: format with `black` style --- apps/users/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/users/tests.py b/apps/users/tests.py index 05d2afb..d7bcf78 100644 --- a/apps/users/tests.py +++ b/apps/users/tests.py @@ -89,6 +89,7 @@ def test_add_fieldsets(self) -> None: self.assertEqual(UserAdmin.add_fieldsets, expected_add_fieldsets) + class RegisterViewTest(TestCase): def setUp(self) -> None: self.url = reverse("users:register") @@ -116,7 +117,7 @@ def test_post_valid_data(self) -> None: self.assertIsNotNone(user) self.client.force_login(user) self.assertTrue(self.client.session["_auth_user_id"]) - + def test_email(self) -> None: user = User.objects.create_user( username="testuser", From ec7fc3de4e550033ecb9ebd43714c39f973170bf Mon Sep 17 00:00:00 2001 From: kyomi Date: Wed, 22 Nov 2023 08:28:58 -0300 Subject: [PATCH 45/79] test(apps/submissions): fix submissions tests --- apps/submissions/forms.py | 10 ++++ apps/submissions/tests.py | 36 ++++++++++----- apps/tasks/forms.py | 9 ---- apps/tasks/tests.py | 53 +++++++++++---------- apps/tasks/views.py | 20 ++++---- static/.keep | 0 templates/tasks/detail.html | 91 ++++++++++++++++++------------------- 7 files changed, 122 insertions(+), 97 deletions(-) create mode 100644 apps/submissions/forms.py delete mode 100644 apps/tasks/forms.py create mode 100644 static/.keep diff --git a/apps/submissions/forms.py b/apps/submissions/forms.py new file mode 100644 index 0000000..b0f91d1 --- /dev/null +++ b/apps/submissions/forms.py @@ -0,0 +1,10 @@ +from django.forms import CharField, Form, Textarea + + +class SubmissionForm(Form): + code = CharField( + label="Source Code", + required=True, + min_length=15, + widget=Textarea(attrs={"rows": 12, "style": "width: 100%;"}), + ) diff --git a/apps/submissions/tests.py b/apps/submissions/tests.py index 649466d..c17c6ec 100644 --- a/apps/submissions/tests.py +++ b/apps/submissions/tests.py @@ -14,9 +14,10 @@ class SubmissionTestCase(TestCase): def setUp(self) -> None: self.user = User.objects.create_user( username="user", - email="user@email.com", + email="user@example", password="password", ) + self.contest = Contest._default_manager.create( title="Test Contest", description="This is a test contest", @@ -24,30 +25,43 @@ def setUp(self) -> None: end_time=timezone.now() + timedelta(hours=1), cancelled=False, ) + self.task = Task._default_manager.create( title="Test Task", description="This is a test task", contest=self.contest, ) - self.submission = Submission( + + self.code = "print('hello world')" + + def test_create_submission(self) -> None: + submission = Submission._default_manager.create( author=self.user, task=self.task, - code="print('hello world')", + code=self.code, ) - def test_submission_representation(self) -> None: - expected = f"#{self.submission.id}" - self.assertEqual(str(self.submission), expected) + self.assertEqual(submission.author, self.user) + self.assertEqual(submission.task, self.task) + self.assertEqual(submission.code, self.code) - def test_submission_has_author_relationship(self) -> None: - self.assertEqual(self.submission.author, self.user) + def test_submission_representation(self) -> None: + submission = Submission._default_manager.create( + author=self.user, + task=self.task, + code=self.code, + ) - def test_submission_has_task_relationship(self) -> None: - self.assertEqual(self.submission.task, self.task) + expected = f"#{submission.id}" + self.assertEqual(str(submission), expected) def test_submission_code_min_length_validator(self) -> None: code = "a" * 14 - submission = Submission(author=self.user, task=self.task, code=code) + submission = Submission( + author=self.user, + task=self.task, + code=code, + ) expected = [ "Ensure this value has at least 15 characters (it has 14)." diff --git a/apps/tasks/forms.py b/apps/tasks/forms.py deleted file mode 100644 index 2c1a4be..0000000 --- a/apps/tasks/forms.py +++ /dev/null @@ -1,9 +0,0 @@ -from django import forms -from django.forms import CharField, Form - - -class TaskForm(Form): - code = CharField( - label="Source Code", - widget=forms.Textarea(attrs={"style": "height: 300px;"}), - ) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index eed735f..ba19a42 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -6,6 +6,7 @@ from django.utils import timezone from apps.contests.models import Contest +from apps.submissions.forms import SubmissionForm from apps.submissions.models import Submission from apps.tasks.admin import TaskAdmin from apps.tasks.models import Task @@ -124,36 +125,41 @@ def test_detail_url_reverse(self) -> None: class DetailViewTestCase(TestCase): def setUp(self) -> None: now = timezone.now() - start_time = now - timedelta(hours=1) - end_time = now + timedelta(hours=1) - self.valid_data = {"code": "codigo"} + self.code = "print('Hello World')" self.contest = Contest._default_manager.create( - start_time=start_time, end_time=end_time + title="Test Contest 1", + description="This is a test contest", + start_time=start_time, + end_time=end_time, ) - self.task = Task._default_manager.create( - title="OQueOPalmeirasTem", - description="LinkisNotinHere", + title="Example task", + description="Some example task", contest=self.contest, ) - - self.url = reverse("tasks:detail", args=[self.task.id]) - self.user = User._default_manager.create( - email="user@email.com", username="user", password="password" + email="user@email.com", + username="user", + password="password", ) + self.url = reverse("tasks:detail", args=[self.task.id]) + + def test_send_submission_successfully(self) -> None: self.client.force_login(self.user) - self.response = self.client.post(self.url, data=self.valid_data) + self.client.post(self.url, data={"code": self.code}) + self.assertEqual(Submission._default_manager.count(), 1) - self.submission = Submission._default_manager.create( - author=self.user, task=self.task, code=self.valid_data["code"] - ) + def test_send_submission_with_short_code(self) -> None: + self.client.force_login(self.user) + + self.client.post(self.url, data={"code": "c"}) + self.assertEqual(Submission._default_manager.count(), 0) def test_detail_view_model_is_task(self) -> None: self.assertEqual(DetailView.model, Task) @@ -161,14 +167,15 @@ def test_detail_view_model_is_task(self) -> None: def test_detail_view_template_name_is_correct(self) -> None: self.assertEqual(DetailView.template_name, "tasks/detail.html") - def test_post_is_redirecting(self) -> None: - self.assertEqual(self.response.status_code, 302) + def test_detail_view_form_class_is_submission_form(self) -> None: + self.assertEqual(DetailView.form_class, SubmissionForm) - def test_is_submission_task(self) -> None: - self.assertEqual(self.submission.task, self.task) + def test_send_submission_is_redirecting(self) -> None: + self.client.force_login(self.user) - def test_is_submission_author(self) -> None: - self.assertEqual(self.submission.author, self.user) + response = self.client.post(self.url, data={"code": self.code}) + self.assertEqual(response.status_code, 302) - def test__is_submission_code(self) -> None: - self.assertEqual(self.submission.code, self.valid_data["code"]) + def test_send_submission_without_authentication(self) -> None: + response = self.client.post(self.url, data={"code": self.code}) + self.assertEqual(response.status_code, 302) diff --git a/apps/tasks/views.py b/apps/tasks/views.py index 1c9a7e0..2296b9b 100644 --- a/apps/tasks/views.py +++ b/apps/tasks/views.py @@ -1,17 +1,18 @@ from typing import TYPE_CHECKING, Any, Dict from django.http import HttpRequest, HttpResponse +from django.shortcuts import redirect from django.urls import reverse from django.views import generic from django.views.generic.edit import FormMixin +from apps.submissions.forms import SubmissionForm from apps.submissions.models import Submission -from apps.tasks.forms import TaskForm from apps.tasks.models import Task if TYPE_CHECKING: DetailViewBase = generic.DetailView[Task] - FormMixinBase = FormMixin[TaskForm] + FormMixinBase = FormMixin[SubmissionForm] else: DetailViewBase = generic.DetailView FormMixinBase = FormMixin @@ -20,11 +21,11 @@ class DetailView(FormMixinBase, DetailViewBase): model = Task template_name = "tasks/detail.html" - form_class = TaskForm + form_class = SubmissionForm def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: context = super().get_context_data(**kwargs) - context["form"] = TaskForm() + context["form"] = SubmissionForm() return context @@ -32,10 +33,16 @@ def get_success_url(self) -> str: return reverse("tasks:detail", args=[self.object.id]) def post(self, request: HttpRequest, *, pk: int) -> HttpResponse: + # Unauthenticated users should not be able to submit + # a submission to a task, so we redirect them to the + # login page. + if not request.user.is_authenticated: + return redirect("login") + self.object = self.get_object() form = self.get_form() - if self.form_valid(form): + if form.is_valid(): Submission._default_manager.create( code=request.POST, task=self.object, author=request.user ) @@ -45,6 +52,3 @@ def post(self, request: HttpRequest, *, pk: int) -> HttpResponse: if form.is_valid() else self.form_invalid(form) ) - - def form_valid(self, form: TaskForm) -> HttpResponse: - return super().form_valid(form) diff --git a/static/.keep b/static/.keep new file mode 100644 index 0000000..e69de29 diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index a403729..e8a9956 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -1,59 +1,58 @@ {% extends "base.html" %} +{% load crispy_forms_tags %} + {% block title %}{{ task.title }}{% endblock title %} {% block content %} -
    -

    - - {{ task.title }} - -

    -
    - -
    -

    - Score: {{ task.score|default:"???" }} -

    -

    - Memory limit: - {% if task.memory_limit %} - {{ task.memory_limit|filesizeformat }} - {% else %} - Unlimited - {% endif %} -

    -

    - Time limit: - {% if task.time_limit %} - {{ task.time_limit }} - second{{ task.time_limit|pluralize:"s" }} - {% else %} - Unlimited - {% endif %} -

    -
    -
    -

    {{ task.description }}

    -
    +

    + + {{ task.title }} + +

    +
    -{% load crispy_forms_tags %} +
    +

    + Score: {{ task.score|default:"???" }} +

    +

    + Memory limit: + {% if task.memory_limit %} + {{ task.memory_limit|filesizeformat }} + {% else %} + Unlimited + {% endif %} +

    +

    + Time limit: + {% if task.time_limit %} + {{ task.time_limit }} + second{{ task.time_limit|pluralize:"s" }} + {% else %} + Unlimited + {% endif %} +

    +
    +
    +

    Description

    +

    {{ task.description }}

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

    Submit your code!

    - {{ form|crispy }} - -
    +
    +

    Submit

    + {{ form|crispy }} +
    From f0f8cc0f987f43b5e6cb12fb4143e2be04b64fe7 Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Thu, 23 Nov 2023 21:02:39 -0300 Subject: [PATCH 46/79] feat(submission_admin_page): admin page --- apps/submissions/admin.py | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 apps/submissions/admin.py diff --git a/apps/submissions/admin.py b/apps/submissions/admin.py new file mode 100644 index 0000000..c0a4ca5 --- /dev/null +++ b/apps/submissions/admin.py @@ -0,0 +1,40 @@ +from typing import TYPE_CHECKING + +from django.contrib.admin import ModelAdmin, register +from django.forms import CharField, ModelForm, Textarea +from django.utils.translation import gettext_lazy as _ + +from apps.submissions.models import Submission + +if TYPE_CHECKING: + SubmissionAdminBase = ModelAdmin[Submission] + SubmissionModelFormBase = ModelForm[Submission] +else: + SubmissionAdminBase = ModelAdmin + SubmissionModelFormBase = ModelForm + + +class SubmissionModelForm(SubmissionModelFormBase): + submission_data = CharField( + widget=Textarea(attrs={"rows": 10, "cols": 80}) + ) + + class Meta: + model = Submission + fields = "__all__" + + +@register(Submission) +class SubmissionAdmin(SubmissionAdminBase): + form = SubmissionModelForm + + list_display = ("author", "task", "status", "created_at") + list_filter = ("status", "task") + search_fields = ("author__username", "task__title") + + fieldsets = [ + ( + _("Submission Details"), + {"fields": ("author", "task", "code", "status", "created_at")}, + ) + ] From 4d200fb6ab2332d408d211f3ee7d1c7ac567fc8b Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Thu, 23 Nov 2023 21:47:53 -0300 Subject: [PATCH 47/79] feat(submission_admin_page): tests --- apps/submissions/admin.py | 6 +++--- apps/submissions/tests.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/apps/submissions/admin.py b/apps/submissions/admin.py index c0a4ca5..d8fb940 100644 --- a/apps/submissions/admin.py +++ b/apps/submissions/admin.py @@ -28,13 +28,13 @@ class Meta: class SubmissionAdmin(SubmissionAdminBase): form = SubmissionModelForm - list_display = ("author", "task", "status", "created_at") - list_filter = ("status", "task") + list_display = ("author", "task", "created_at") + list_filter = ("task",) search_fields = ("author__username", "task__title") fieldsets = [ ( _("Submission Details"), - {"fields": ("author", "task", "code", "status", "created_at")}, + {"fields": ("author", "task", "code", "created_at")}, ) ] diff --git a/apps/submissions/tests.py b/apps/submissions/tests.py index c17c6ec..0eb3bb5 100644 --- a/apps/submissions/tests.py +++ b/apps/submissions/tests.py @@ -1,10 +1,12 @@ from datetime import timedelta +from django.contrib.admin.sites import AdminSite from django.core.exceptions import ValidationError from django.test import TestCase from django.utils import timezone from apps.contests.models import Contest +from apps.submissions.admin import SubmissionAdmin from apps.submissions.models import Submission from apps.tasks.models import Task from apps.users.models import User @@ -71,3 +73,36 @@ def test_submission_code_min_length_validator(self) -> None: submission.full_clean() self.assertEqual(context.exception.messages, expected) + + +class SubmissionAdminTest(TestCase): + def setUp(self) -> None: + self.site = AdminSite() + self.submission_admin = SubmissionAdmin(Submission, self.site) + + def test_list_display(self) -> None: + expected_list_display = ("author", "task", "created_at") + self.assertEqual( + self.submission_admin.list_display, expected_list_display + ) + + def test_list_filter(self) -> None: + expected_list_filter = ("task",) + self.assertEqual( + self.submission_admin.list_filter, expected_list_filter + ) + + def test_search_field(self) -> None: + expected_search_fields = ("author__username", "task__title") + self.assertEqual( + self.submission_admin.search_fields, expected_search_fields + ) + + def test_fieldsets(self) -> None: + expected_fieldsets = [ + ( + ("Submission Details"), + {"fields": ("author", "task", "code", "created_at")}, + ) + ] + self.assertEqual(self.submission_admin.fieldsets, expected_fieldsets) From 74607111e8b102b73c98fa231b4e8777b4fdb705 Mon Sep 17 00:00:00 2001 From: MMcLovin Date: Fri, 24 Nov 2023 13:37:58 -0300 Subject: [PATCH 48/79] fix: removed FINISHED from is_accessible conditions --- apps/tasks/models.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/tasks/models.py b/apps/tasks/models.py index 3443d00..f5e608c 100644 --- a/apps/tasks/models.py +++ b/apps/tasks/models.py @@ -25,7 +25,4 @@ def __str__(self) -> str: @property def is_accessible(self) -> bool: - return self.contest.status in ( - ContestStatus.RUNNING, - ContestStatus.FINISHED, - ) + return self.contest.status in (ContestStatus.RUNNING,) From 73d170041cf2a769f76374a26acbc365a16cf2e1 Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Fri, 24 Nov 2023 14:42:44 -0300 Subject: [PATCH 49/79] feat(submission_admin_page): fixing submission admin --- apps/submissions/admin.py | 2 +- apps/submissions/tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/submissions/admin.py b/apps/submissions/admin.py index d8fb940..ab08bf5 100644 --- a/apps/submissions/admin.py +++ b/apps/submissions/admin.py @@ -35,6 +35,6 @@ class SubmissionAdmin(SubmissionAdminBase): fieldsets = [ ( _("Submission Details"), - {"fields": ("author", "task", "code", "created_at")}, + {"fields": ("author", "task", "code")}, ) ] diff --git a/apps/submissions/tests.py b/apps/submissions/tests.py index 0eb3bb5..6359131 100644 --- a/apps/submissions/tests.py +++ b/apps/submissions/tests.py @@ -102,7 +102,7 @@ def test_fieldsets(self) -> None: expected_fieldsets = [ ( ("Submission Details"), - {"fields": ("author", "task", "code", "created_at")}, + {"fields": ("author", "task", "code")}, ) ] self.assertEqual(self.submission_admin.fieldsets, expected_fieldsets) From 598aef7919799f7dc042ddb664ab172ea89f061e Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Fri, 24 Nov 2023 14:44:14 -0300 Subject: [PATCH 50/79] feat(submission_admin_page): fixing submission admin --- apps/submissions/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/submissions/admin.py b/apps/submissions/admin.py index ab08bf5..c5f1e55 100644 --- a/apps/submissions/admin.py +++ b/apps/submissions/admin.py @@ -28,7 +28,7 @@ class Meta: class SubmissionAdmin(SubmissionAdminBase): form = SubmissionModelForm - list_display = ("author", "task", "created_at") + list_display = ("author", "task") list_filter = ("task",) search_fields = ("author__username", "task__title") From 64c7b4184bfa5630dc448166e04cd2ec25798312 Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Fri, 24 Nov 2023 14:46:02 -0300 Subject: [PATCH 51/79] feat(submission_admin_page): fixing tests --- apps/submissions/tests.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/submissions/tests.py b/apps/submissions/tests.py index 6359131..1757544 100644 --- a/apps/submissions/tests.py +++ b/apps/submissions/tests.py @@ -81,22 +81,16 @@ def setUp(self) -> None: self.submission_admin = SubmissionAdmin(Submission, self.site) def test_list_display(self) -> None: - expected_list_display = ("author", "task", "created_at") - self.assertEqual( - self.submission_admin.list_display, expected_list_display - ) + expected = ("author", "task") + self.assertEqual(self.submission_admin.list_display, expected) def test_list_filter(self) -> None: - expected_list_filter = ("task",) - self.assertEqual( - self.submission_admin.list_filter, expected_list_filter - ) + expected = ("task",) + self.assertEqual(self.submission_admin.list_filter, expected) def test_search_field(self) -> None: - expected_search_fields = ("author__username", "task__title") - self.assertEqual( - self.submission_admin.search_fields, expected_search_fields - ) + expected = ("author__username", "task__title") + self.assertEqual(self.submission_admin.search_fields, expected) def test_fieldsets(self) -> None: expected_fieldsets = [ From be084a2a95a3b94488dbea81ace702e8e117a96b Mon Sep 17 00:00:00 2001 From: kyomi Date: Sun, 26 Nov 2023 17:05:22 -0300 Subject: [PATCH 52/79] fix(apps/submissions): fix submission admin page form --- apps/submissions/admin.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/apps/submissions/admin.py b/apps/submissions/admin.py index c5f1e55..19d5b95 100644 --- a/apps/submissions/admin.py +++ b/apps/submissions/admin.py @@ -15,9 +15,7 @@ class SubmissionModelForm(SubmissionModelFormBase): - submission_data = CharField( - widget=Textarea(attrs={"rows": 10, "cols": 80}) - ) + code = CharField(widget=Textarea(attrs={"rows": 20, "cols": 80})) class Meta: model = Submission @@ -28,13 +26,7 @@ class Meta: class SubmissionAdmin(SubmissionAdminBase): form = SubmissionModelForm - list_display = ("author", "task") - list_filter = ("task",) - search_fields = ("author__username", "task__title") + list_display = ("__str__", "author", "task") + list_filter = ("author", "task", "created_at") - fieldsets = [ - ( - _("Submission Details"), - {"fields": ("author", "task", "code")}, - ) - ] + fieldsets = [(_("Details"), {"fields": ("author", "task", "code")})] From 303d49ca7c626bc49ec23c0d409405c2be381d6c Mon Sep 17 00:00:00 2001 From: kyomi Date: Sun, 26 Nov 2023 17:09:20 -0300 Subject: [PATCH 53/79] test(apps/submissions): change tests to reflect last changes --- apps/submissions/tests.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/apps/submissions/tests.py b/apps/submissions/tests.py index 1757544..4f680f2 100644 --- a/apps/submissions/tests.py +++ b/apps/submissions/tests.py @@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError from django.test import TestCase from django.utils import timezone +from django.utils.translation import gettext as _ from apps.contests.models import Contest from apps.submissions.admin import SubmissionAdmin @@ -81,22 +82,13 @@ def setUp(self) -> None: self.submission_admin = SubmissionAdmin(Submission, self.site) def test_list_display(self) -> None: - expected = ("author", "task") + expected = ("__str__", "author", "task") self.assertEqual(self.submission_admin.list_display, expected) def test_list_filter(self) -> None: - expected = ("task",) + expected = ("author", "task", "created_at") self.assertEqual(self.submission_admin.list_filter, expected) - def test_search_field(self) -> None: - expected = ("author__username", "task__title") - self.assertEqual(self.submission_admin.search_fields, expected) - def test_fieldsets(self) -> None: - expected_fieldsets = [ - ( - ("Submission Details"), - {"fields": ("author", "task", "code")}, - ) - ] - self.assertEqual(self.submission_admin.fieldsets, expected_fieldsets) + expected = [(_("Details"), {"fields": ("author", "task", "code")})] + self.assertEqual(self.submission_admin.fieldsets, expected) From 4bad0513f37888ab2f0a22b16af4575476afb76d Mon Sep 17 00:00:00 2001 From: HladczukLe Date: Sun, 26 Nov 2023 17:26:04 -0300 Subject: [PATCH 54/79] feat: add others users page --- apps/users/models.py | 2 ++ apps/users/views.py | 9 ++++++++- templates/base.html | 6 +++--- templates/users/profile.html | 23 +++++++++++++---------- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/apps/users/models.py b/apps/users/models.py index 542ad76..2de4491 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -19,6 +19,8 @@ class User(AbstractBaseUser, PermissionsMixin, TimestampedModel): email = EmailField(db_index=True, max_length=256, unique=True) username = CharField(db_index=True, max_length=128, unique=True) + score = 0 + contest = 0 # When a user no longer wishes to use our platform, they may try to # delete there account. That's a problem for us because the data we diff --git a/apps/users/views.py b/apps/users/views.py index abadc59..fa23b21 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -33,4 +33,11 @@ class ProfileView(View): def get(self, request: HttpRequest, user_username: str) -> HttpResponse: user = get_object_or_404(User, username=user_username) - return render(request, self.template_name, {"user": user}) + + is_own_profile = user == request.user + + return render( + request, + self.template_name, + {"user": user, "is_own_profile": is_own_profile}, + ) diff --git a/templates/base.html b/templates/base.html index 506bc82..ebbd635 100644 --- a/templates/base.html +++ b/templates/base.html @@ -71,7 +71,7 @@
  • - {% if user.is_authenticated %} + {% if request.user.is_authenticated %}