From fcbd19351916c64a8272afd0c42e8444a25637be Mon Sep 17 00:00:00 2001 From: HladczukLe Date: Sun, 10 Dec 2023 18:02:45 -0300 Subject: [PATCH 1/9] feat(file_upload): add file upload for tasks --- apps/submissions/forms.py | 7 ++++++- apps/tasks/tests.py | 32 ++++++++++++++++++++++++++++++-- apps/tasks/views.py | 35 +++++++++++++++++++++++++++++++---- server/settings/base.py | 7 +++++++ templates/tasks/detail.html | 12 +++++------- 5 files changed, 79 insertions(+), 14 deletions(-) diff --git a/apps/submissions/forms.py b/apps/submissions/forms.py index 1918102..df46f97 100644 --- a/apps/submissions/forms.py +++ b/apps/submissions/forms.py @@ -1,4 +1,4 @@ -from django.forms import Form +from django.forms import CharField, FileField, Form from djangocodemirror.fields import CodeMirrorField @@ -9,3 +9,8 @@ class SubmissionForm(Form): required=True, config_name="python", ) + + +class UploadFileForm(Form): + title = CharField(max_length=255) + file = FileField() diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index bd7a818..280d5ba 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -1,8 +1,12 @@ +import os from datetime import timedelta from io import BytesIO from django.contrib.admin.sites import AdminSite -from django.core.files.uploadedfile import InMemoryUploadedFile +from django.core.files.uploadedfile import ( + InMemoryUploadedFile, + SimpleUploadedFile, +) from django.test import TestCase from django.test.client import RequestFactory from django.urls import resolve, reverse @@ -13,7 +17,11 @@ from apps.submissions.models import Submission, SubmissionStatus from apps.tasks.admin import TaskAdmin, TaskModelForm from apps.tasks.models import Task -from apps.tasks.views import DetailView, handle_submission +from apps.tasks.views import ( + DetailView, + handle_submission, + handle_uploaded_file, +) from apps.users.models import User @@ -348,6 +356,7 @@ def setUp(self) -> None: self.url = reverse("tasks:detail", args=[self.task.id]) self.view = DetailView() self.view.object = self.task + self.factory = RequestFactory() def test_send_submission_successfully(self) -> None: self.client.force_login(self.user) @@ -510,6 +519,25 @@ def test_invalid_form_submission(self) -> None: self.assertTemplateUsed(response, "tasks/detail.html") self.assertFalse(response.context["form"].is_valid()) + def test_handle_uploaded_file(self) -> None: + file_content = b"Test file content" + uploaded_file = SimpleUploadedFile("test_file.txt", file_content) + + request = self.factory.post(self.url, {"file": uploaded_file}) + request.user = self.user + + handle_uploaded_file(request, self.task.id, self.submission.id) + + self.submission.refresh_from_db() + self.assertEqual(self.submission.status, "WJ") + + file_path = os.path.join("apps/tasks/uploads/", "test_file.txt") + self.assertTrue(os.path.exists(file_path)) + + with open(file_path, "rb") as file: + content = file.read() + self.assertEqual(content, file_content) + class BackgroundJobTaskTest(TestCase): def setUp(self) -> None: diff --git a/apps/tasks/views.py b/apps/tasks/views.py index 0238fd4..4471597 100644 --- a/apps/tasks/views.py +++ b/apps/tasks/views.py @@ -1,3 +1,4 @@ +import os import sys from io import StringIO from typing import TYPE_CHECKING, Any, Dict @@ -8,7 +9,7 @@ from django.views import generic from django.views.generic.edit import FormMixin -from apps.submissions.forms import SubmissionForm +from apps.submissions.forms import SubmissionForm, UploadFileForm from apps.submissions.models import Submission, SubmissionStatus from apps.tasks.models import Task from server import celery @@ -66,6 +67,26 @@ def handle_submission(code: str, task_id: int, submission_id: int) -> None: submission.author.save() +def handle_uploaded_file( + request: HttpRequest, task_id: int, submission_id: int +) -> None: + submission: Submission = Submission._default_manager.get(id=submission_id) + destination_dir = "apps/tasks/uploads/" + os.makedirs(destination_dir, exist_ok=True) + + uploaded_file = request.FILES.get("file") + + if uploaded_file: + with open( + os.path.join(destination_dir, uploaded_file.name or ""), "wb+" + ) as destination: + for chunk in uploaded_file.chunks(): + destination.write(chunk) + + submission.status = SubmissionStatus.WAITING_JUDGE + submission.save() + + class DetailView(FormMixinBase, DetailViewBase): model = Task template_name = "tasks/detail.html" @@ -103,20 +124,26 @@ def post(self, request: HttpRequest, *, pk: int) -> HttpResponse: self.object = self.get_object() form = self.get_form() + uploaded_file = request.FILES.get("file") - if not form.is_valid(): + if not form.is_valid() and not uploaded_file: return self.form_invalid(form) submission = Submission._default_manager.create( - code=form.cleaned_data["code"], + code=form.cleaned_data.get("code", ""), task=self.object, author=request.user, ) handle_submission.delay( - form.cleaned_data["code"], + form.cleaned_data.get("code", ""), self.object.id, submission.id, ) + if uploaded_file: + upload_form = UploadFileForm(request.POST, request.FILES) + if upload_form.is_valid(): + handle_uploaded_file(request, self.object.id, submission.id) + return redirect(self.get_success_url()) diff --git a/server/settings/base.py b/server/settings/base.py index 684bf98..289405b 100644 --- a/server/settings/base.py +++ b/server/settings/base.py @@ -177,3 +177,10 @@ CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" CRISPY_TEMPLATE_PACK = "bootstrap5" + +#################### +# Media Settings # +#################### + +MEDIA_URL = "/media/" +MEDIA_ROOT = BASE_DIR / "media" diff --git a/templates/tasks/detail.html b/templates/tasks/detail.html index a760c90..12f5796 100644 --- a/templates/tasks/detail.html +++ b/templates/tasks/detail.html @@ -8,12 +8,6 @@ {% block content %} - - - - - - @@ -68,11 +62,15 @@

Constraints


-
+ {% csrf_token %}

Submit

+

+ File: +

+