From 08b44f9410b7dd9e6d082b9622c425ee11e6bef3 Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Tue, 28 Nov 2023 10:47:00 -0300 Subject: [PATCH 01/24] feat(submission_page): arquivos --- apps/submissions/urls.py | 0 apps/submissions/views.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/submissions/urls.py create mode 100644 apps/submissions/views.py diff --git a/apps/submissions/urls.py b/apps/submissions/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/submissions/views.py b/apps/submissions/views.py new file mode 100644 index 0000000..e69de29 From ce0314eb0f8a20a14fa445f77e140d8016ef9564 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 28 Nov 2023 15:17:59 -0300 Subject: [PATCH 02/24] feat(celery): add initial integration with RabbitMQ --- .pre-commit-config.yaml | 2 + app.json | 6 +- .../0004_task_input_file_task_output_file.py | 4 +- apps/tasks/models.py | 4 +- apps/tasks/views.py | 48 ++-- config/.env.example | 14 +- docker-compose.yml | 43 +++- docker/django/entrypoint.sh | 7 +- poetry.lock | 213 +++++++++++++++++- pyproject.toml | 2 + server/__init__.py | 3 + server/celery.py | 20 ++ 12 files changed, 334 insertions(+), 32 deletions(-) create mode 100644 server/celery.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e216ee5..c250cbb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,3 +39,5 @@ repos: additional_dependencies: - 'django-stubs>=4.2.4,<5.0' - 'django-environ>=0.11.2,<1.0' + - 'celery[librabbitmq]>=5.3.6,<6.0' + - 'celery-types>=0.20.0,<1.0' diff --git a/app.json b/app.json index 7e89f83..1332cb7 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,10 @@ { "repository": "https://github.com/unb-mds/2023-2-Squad06", - "addons": ["heroku-postgresql:mini", "papertrail:choklad"], + "addons": [ + "heroku-postgresql:mini", + "papertrail:choklad", + "cloudamqp:lemur" + ], "buildpacks": [ { "url": "https://github.com/moneymeets/python-poetry-buildpack.git" diff --git a/apps/tasks/migrations/0004_task_input_file_task_output_file.py b/apps/tasks/migrations/0004_task_input_file_task_output_file.py index c02435c..6411c59 100644 --- a/apps/tasks/migrations/0004_task_input_file_task_output_file.py +++ b/apps/tasks/migrations/0004_task_input_file_task_output_file.py @@ -12,11 +12,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name="task", name="input_file", - field=models.TextField(), + field=models.TextField(default=""), ), migrations.AddField( model_name="task", name="output_file", - field=models.TextField(), + field=models.TextField(default=""), ), ] diff --git a/apps/tasks/models.py b/apps/tasks/models.py index c9d4c84..4b1068b 100644 --- a/apps/tasks/models.py +++ b/apps/tasks/models.py @@ -23,8 +23,8 @@ class Task(TimestampedModel): memory_limit = IntegerField(null=True) time_limit = IntegerField(null=True) - input_file = TextField() - output_file = TextField() + input_file = TextField(default="") + output_file = TextField(default="") class Meta: db_table = "tasks" diff --git a/apps/tasks/views.py b/apps/tasks/views.py index dff5fbe..2e0f441 100644 --- a/apps/tasks/views.py +++ b/apps/tasks/views.py @@ -1,6 +1,5 @@ import sys from io import StringIO -from traceback import format_exc from typing import TYPE_CHECKING, Any, Dict from django.http import HttpRequest, HttpResponse @@ -12,6 +11,7 @@ from apps.submissions.forms import SubmissionForm from apps.submissions.models import Submission from apps.tasks.models import Task +from server import celery if TYPE_CHECKING: DetailViewBase = generic.DetailView[Task] @@ -21,12 +21,10 @@ FormMixinBase = FormMixin -def handle_submission(request: HttpRequest, task: Task) -> HttpResponse: - submission = Submission._default_manager.create( - code=request.POST["code"], - task=task, - author=request.user, - ) +@celery.task(ignore_result=True) +def handle_submission(code: str, task_id: int, submission_id: int) -> None: + task = Task._default_manager.get(id=task_id) + submission = Submission._default_manager.get(id=submission_id) input_data = StringIO(task.input_file) @@ -37,26 +35,19 @@ def handle_submission(request: HttpRequest, task: Task) -> HttpResponse: sys.stdout = stdout = StringIO() try: - eval(compile(request.POST["code"], "", "exec")) - except Exception as exc: + eval(compile(code, "", "exec")) + except Exception: submission.status = "RE" submission.save() - - return HttpResponse(f"Error: {exc} {format_exc()}") + return finally: sys.stdout = old_stdout sys.stdin = old_stdin output = stdout.getvalue() - if output == task.output_file: - submission.status = "AC" - submission.save() - return HttpResponse("Correct!") - else: - submission.status = "WA" - submission.save() - return HttpResponse("Incorrect!") + submission.status = "AC" if output == task.output_file else "WA" + submission.save() class DetailView(FormMixinBase, DetailViewBase): @@ -74,7 +65,10 @@ def get_success_url(self) -> str: return reverse("tasks:detail", args=[self.object.id]) def get( - self, request: HttpRequest, *args: Any, **kwargs: Any + self, + request: HttpRequest, + *args: Any, + **kwargs: Any, ) -> HttpResponse: self.object = self.get_object() @@ -97,4 +91,16 @@ def post(self, request: HttpRequest, *, pk: int) -> HttpResponse: if not form.is_valid(): return self.form_invalid(form) - return handle_submission(request, self.object) + submission = Submission._default_manager.create( + code=form.cleaned_data["code"], + task=self.object, + author=request.user, + ) + + handle_submission.delay( + form.cleaned_data["code"], + self.object.id, + submission.id, + ) + + return redirect(self.get_success_url()) diff --git a/config/.env.example b/config/.env.example index aabbc8e..1620897 100644 --- a/config/.env.example +++ b/config/.env.example @@ -1,4 +1,3 @@ - ############ # Django # ############ @@ -15,3 +14,16 @@ POSTGRES_PORT=5432 POSTGRES_USER=virtualjudge POSTGRES_PASSWORD=virtualjudge POSTGRES_DB=virtualjudge + +############## +# RabbitMQ # +############## + +RABBITMQ_USER=virtualjudge +RABBITMQ_PASSWORD=virtualjudge +RABBITMQ_HOST=rabbitmq +RABBITMQ_PORT=5672 +RABBITMQ_VHOST=/ + +RABBITMQ_DEFAULT_USER=virtualjudge +RABBITMQ_DEFAULT_PASS=virtualjudge diff --git a/docker-compose.yml b/docker-compose.yml index d321115..90e9f6f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,25 +18,62 @@ services: ports: - 8000:8000 networks: - - main + - database + - message-broker env_file: - config/.env depends_on: - postgres command: python -Wd manage.py runserver 0.0.0.0:8000 + celery: + image: virtual-judge:dev + build: + context: . + dockerfile: docker/django/Dockerfile + target: development-build + args: + - DJANGO_ENV=development + - UID=${UID:-1000} + - GID=${GID:-1000} + cache_from: + - "virtual-judge:dev" + - "virtual-judge:latest" + - "*" + volumes: + - .:/app + networks: + - database + - message-broker + env_file: + - config/.env + depends_on: + - postgres + command: celery -A server worker -l INFO + postgres: image: postgres:16.0-alpine volumes: - data:/var/lib/postgresql/data networks: - - main + - database env_file: - config/.env restart: unless-stopped + rabbitmq: + image: rabbitmq:3.12.9-management-alpine + ports: + - 5672:5672 + - 15672:15672 + networks: + - message-broker + env_file: + - config/.env + volumes: data: networks: - main: + database: + message-broker: diff --git a/docker/django/entrypoint.sh b/docker/django/entrypoint.sh index 0fecb0e..f2d8351 100755 --- a/docker/django/entrypoint.sh +++ b/docker/django/entrypoint.sh @@ -10,12 +10,17 @@ readonly cmd="$*" : "${POSTGRES_PORT:=5432}" export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" +: "${RABBITMQ_HOST:=rabbitmq}" +: "${RABBITMQ_PORT:=5672}" +export CELERY_BROKER_URL="amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@${RABBITMQ_HOST}:${RABBITMQ_PORT}/${RABBITMQ_VHOST}" + # We need this line to make sure that this container is started after the one # with PostgreSQL: dockerize \ -wait "tcp://${POSTGRES_HOST}:${POSTGRES_PORT}" \ + -wait "tcp://${RABBITMQ_HOST}:${RABBITMQ_PORT}" \ -timeout 90s ->&2 echo 'PostgreSQL is up -- continuing...' +>&2 echo 'PostgreSQL and RabbitMQ is up -- continuing...' exec $cmd diff --git a/poetry.lock b/poetry.lock index 5331828..aec3a78 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,6 +11,20 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] +[[package]] +name = "amqp" +version = "5.2.0" +description = "Low-level AMQP client for Python (fork of amqplib)." +optional = false +python-versions = ">=3.6" +files = [ + {file = "amqp-5.2.0-py3-none-any.whl", hash = "sha256:827cb12fb0baa892aad844fd95258143bce4027fdac4fccddbc43330fd281637"}, + {file = "amqp-5.2.0.tar.gz", hash = "sha256:a1ecff425ad063ad42a486c902807d1482311481c8ad95a72694b2975e75f7fd"}, +] + +[package.dependencies] +vine = ">=5.0.0,<6.0.0" + [[package]] name = "argcomplete" version = "3.1.6" @@ -74,6 +88,17 @@ soupsieve = ">1.2" html5lib = ["html5lib"] lxml = ["lxml"] +[[package]] +name = "billiard" +version = "4.2.0" +description = "Python multiprocessing fork with improvements and bugfixes" +optional = false +python-versions = ">=3.7" +files = [ + {file = "billiard-4.2.0-py3-none-any.whl", hash = "sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d"}, + {file = "billiard-4.2.0.tar.gz", hash = "sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c"}, +] + [[package]] name = "black" version = "23.11.0" @@ -114,6 +139,75 @@ d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "celery" +version = "5.3.6" +description = "Distributed Task Queue." +optional = false +python-versions = ">=3.8" +files = [ + {file = "celery-5.3.6-py3-none-any.whl", hash = "sha256:9da4ea0118d232ce97dff5ed4974587fb1c0ff5c10042eb15278487cdd27d1af"}, + {file = "celery-5.3.6.tar.gz", hash = "sha256:870cc71d737c0200c397290d730344cc991d13a057534353d124c9380267aab9"}, +] + +[package.dependencies] +billiard = ">=4.2.0,<5.0" +click = ">=8.1.2,<9.0" +click-didyoumean = ">=0.3.0" +click-plugins = ">=1.1.1" +click-repl = ">=0.2.0" +kombu = ">=5.3.4,<6.0" +python-dateutil = ">=2.8.2" +tzdata = ">=2022.7" +vine = ">=5.1.0,<6.0" + +[package.extras] +arangodb = ["pyArango (>=2.0.2)"] +auth = ["cryptography (==41.0.5)"] +azureblockblob = ["azure-storage-blob (>=12.15.0)"] +brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] +cassandra = ["cassandra-driver (>=3.25.0,<4)"] +consul = ["python-consul2 (==0.1.5)"] +cosmosdbsql = ["pydocumentdb (==2.3.5)"] +couchbase = ["couchbase (>=3.0.0)"] +couchdb = ["pycouchdb (==1.14.2)"] +django = ["Django (>=2.2.28)"] +dynamodb = ["boto3 (>=1.26.143)"] +elasticsearch = ["elastic-transport (<=8.10.0)", "elasticsearch (<=8.11.0)"] +eventlet = ["eventlet (>=0.32.0)"] +gevent = ["gevent (>=1.5.0)"] +librabbitmq = ["librabbitmq (>=2.0.0)"] +memcache = ["pylibmc (==1.6.3)"] +mongodb = ["pymongo[srv] (>=4.0.2)"] +msgpack = ["msgpack (==1.0.7)"] +pymemcache = ["python-memcached (==1.59)"] +pyro = ["pyro4 (==4.82)"] +pytest = ["pytest-celery (==0.0.0)"] +redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"] +s3 = ["boto3 (>=1.26.143)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +solar = ["ephem (==4.1.5)"] +sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] +sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.0)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] +tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=1.3.1)"] +zstd = ["zstandard (==0.22.0)"] + +[[package]] +name = "celery-types" +version = "0.20.0" +description = "Type stubs for Celery and its related packages" +optional = false +python-versions = ">=3.10,<4.0" +files = [ + {file = "celery-types-0.20.0.tar.gz", hash = "sha256:e5c762555605ed0592baed9d519230046ce8e7a11c67a821555c08b7cece1960"}, + {file = "celery_types-0.20.0-py3-none-any.whl", hash = "sha256:5ebf858a4bf73ca610652d82940dc3a2e4c86afed0421ab1becbff66b49feea4"}, +] + +[package.dependencies] +typing-extensions = ">=3.10.0,<5.0.0" + [[package]] name = "certifi" version = "2023.11.17" @@ -249,6 +343,55 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "click-didyoumean" +version = "0.3.0" +description = "Enables git-like *did-you-mean* feature in click" +optional = false +python-versions = ">=3.6.2,<4.0.0" +files = [ + {file = "click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"}, + {file = "click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"}, +] + +[package.dependencies] +click = ">=7" + +[[package]] +name = "click-plugins" +version = "1.1.1" +description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +optional = false +python-versions = "*" +files = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] + +[[package]] +name = "click-repl" +version = "0.3.0" +description = "REPL plugin for Click" +optional = false +python-versions = ">=3.6" +files = [ + {file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, + {file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, +] + +[package.dependencies] +click = ">=7.0" +prompt-toolkit = ">=3.0.36" + +[package.extras] +testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] + [[package]] name = "colorama" version = "0.4.6" @@ -659,6 +802,38 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "kombu" +version = "5.3.4" +description = "Messaging library for Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "kombu-5.3.4-py3-none-any.whl", hash = "sha256:63bb093fc9bb80cfb3a0972336a5cec1fa7ac5f9ef7e8237c6bf8dda9469313e"}, + {file = "kombu-5.3.4.tar.gz", hash = "sha256:0bb2e278644d11dea6272c17974a3dbb9688a949f3bb60aeb5b791329c44fadc"}, +] + +[package.dependencies] +amqp = ">=5.1.1,<6.0.0" +vine = "*" + +[package.extras] +azureservicebus = ["azure-servicebus (>=7.10.0)"] +azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"] +confluentkafka = ["confluent-kafka (>=2.2.0)"] +consul = ["python-consul2"] +librabbitmq = ["librabbitmq (>=2.0.0)"] +mongodb = ["pymongo (>=4.1.1)"] +msgpack = ["msgpack"] +pyro = ["pyro4"] +qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] +redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] +sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=2.8.0)"] + [[package]] name = "markupsafe" version = "2.1.3" @@ -927,6 +1102,20 @@ files = [ plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "pyyaml" version = "6.0.1" @@ -1027,6 +1216,17 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + [[package]] name = "snowballstemmer" version = "2.2.0" @@ -1326,6 +1526,17 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "vine" +version = "5.1.0" +description = "Python promises." +optional = false +python-versions = ">=3.6" +files = [ + {file = "vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc"}, + {file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"}, +] + [[package]] name = "virtualenv" version = "20.24.6" @@ -1389,4 +1600,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "ee0f61a49b3dc68037a41f78dc055adedc1f97f4b8e1afd1ace1924a17f209a1" +content-hash = "993537e69dee02102d9f10abc28bfabb79a43412e14d1cd49f3f17a559da4cc3" diff --git a/pyproject.toml b/pyproject.toml index 21942bb..db7285d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ django-crispy-forms = "^2.1" crispy-bootstrap5 = "^2023.10" gunicorn = "^21.2.0" whitenoise = "^6.6.0" +celery = {extras = ["librabbitmq"], version = "^5.3.6"} [tool.poetry.group.dev.dependencies] pre-commit = "^3.5.0" @@ -27,6 +28,7 @@ flake8 = "^6.1.0" mypy = "^1.6.1" django-stubs = {extras = ["compatible-mypy"], version = "^4.2.6"} coverage = "^7.3.2" +celery-types = "^0.20.0" [tool.poetry.group.docs] optional = true diff --git a/server/__init__.py b/server/__init__.py index e69de29..5419bc8 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -0,0 +1,3 @@ +from server.celery import app as celery + +__all__ = ("celery",) diff --git a/server/celery.py b/server/celery.py new file mode 100644 index 0000000..dc4c066 --- /dev/null +++ b/server/celery.py @@ -0,0 +1,20 @@ +from os import environ + +from celery import Celery +from celery.app.task import Task + +environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings.development") + +app = Celery("virtualjudge", broker_connection_retry_on_startup=False) + +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() + +# We are monkey-patching to add type hints to Celery. This is not a good +# practice of course, but it is a workaround for now, since Celery does +# not support type hints yet and this project is just an academic +# project. +# If you don't know what "monkey-patching" is, please read this: +# https://en.wikipedia.org/wiki/Monkey_patch +class_getitem = classmethod(lambda cls, *args, **kwargs: cls) +Task.__class_getitem__ = class_getitem # type: ignore[attr-defined] From d545cb9668c2e08437a1853a089ad6a93c0d79d0 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 28 Nov 2023 15:33:40 -0300 Subject: [PATCH 03/24] ci: add RabbitMQ service on GitHub workflow --- .github/workflows/ci.yml | 12 +++++++++++- .github/workflows/test.yml | 13 +++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2527e4a..54ac29f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,15 +6,25 @@ on: types: - reopened +env: + CELERY_BROKER_URL: "amqp://virtualjudge:virtualjudge@localhost:5672//" + jobs: upload: runs-on: ubuntu-latest + services: + rabbitmq: + image: rabbitmq:3.12.9-alpine + env: + RABBITMQ_DEFAULT_USER: virtualjudge + RABBITMQ_DEFAULT_PASS: virtualjudge + ports: + - 5672:5672 strategy: max-parallel: 4 matrix: python-version: ["3.11"] poetry-version: ["1.6.1"] - env: DATABASE_URL: "sqlite:///db.sqlite3" CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 29ed65f..ac21ce1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,18 +6,27 @@ on: types: - reopened +env: + CELERY_BROKER_URL: "amqp://virtualjudge:virtualjudge@localhost:5672//" + jobs: build: runs-on: ubuntu-latest + services: + rabbitmq: + image: rabbitmq:3.12.9-alpine + env: + RABBITMQ_DEFAULT_USER: virtualjudge + RABBITMQ_DEFAULT_PASS: virtualjudge + ports: + - 5672:5672 strategy: max-parallel: 4 matrix: python-version: ["3.11"] poetry-version: ["1.6.1"] - env: DATABASE_URL: "sqlite:///db.sqlite3" - steps: - name: Checkout repository uses: actions/checkout@v3 From 6d3f1045eda486df11cb2669da45287b7699d117 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 28 Nov 2023 15:42:19 -0300 Subject: [PATCH 04/24] test(apps/tasks): fix tests to reflect changes --- apps/tasks/tests.py | 56 +++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index 37eb078..983c84b 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -238,13 +238,18 @@ def test_detail_view_template_name_is_correct(self) -> None: def test_detail_view_form_class_is_submission_form(self) -> None: self.assertEqual(DetailView.form_class, SubmissionForm) - def test_send_submission_is_redirecting(self) -> None: - response = self.client.post(self.url, data={"code": self.code}) - self.assertEqual(response.status_code, 302) - def test_send_submission_without_authentication(self) -> None: response = self.client.post(self.url, data={"code": self.code}) + expected_target = reverse("login") + self.assertEqual(response.status_code, 302) + self.assertRedirects( + response, + expected_target, + status_code=302, + target_status_code=200, + fetch_redirect_response=True, + ) def test_access_task_that_is_accessible(self) -> None: response = self.client.get(self.url) @@ -255,18 +260,31 @@ def test_access_task_that_is_not_accessible(self) -> None: self.task.contest.save() response = self.client.get(self.url) + expected_target = reverse("home") + self.assertEqual(response.status_code, 302) + self.assertRedirects( + response, + expected_target, + status_code=302, + target_status_code=200, + fetch_redirect_response=True, + ) def test_handle_submission_with_exception(self) -> None: self.client.force_login(self.user) code = "raise Exception('Test exception')" - expected = "Exception: Test exception" - response = self.client.post(self.url, data={"code": code}) - self.assertEqual(response.status_code, 200) - self.assertInHTML(expected, response.content.decode()) + self.assertEqual(response.status_code, 302) + self.assertRedirects( + response, + self.url, + status_code=302, + target_status_code=200, + fetch_redirect_response=True, + ) def test_handle_submission_with_correct_output(self) -> None: self.client.force_login(self.user) @@ -275,10 +293,15 @@ def test_handle_submission_with_correct_output(self) -> None: self.task.save() response = self.client.post(self.url, data={"code": self.code}) - expected = "Correct!" - self.assertEqual(response.status_code, 200) - self.assertHTMLEqual(response.content.decode(), expected) + self.assertEqual(response.status_code, 302) + self.assertRedirects( + response, + self.url, + status_code=302, + target_status_code=200, + fetch_redirect_response=True, + ) def test_handle_submission_with_wrong_output(self) -> None: self.client.force_login(self.user) @@ -287,10 +310,15 @@ def test_handle_submission_with_wrong_output(self) -> None: self.task.save() response = self.client.post(self.url, data={"code": self.code}) - expected = "Incorrect!" - self.assertEqual(response.status_code, 200) - self.assertHTMLEqual(response.content.decode(), expected) + self.assertEqual(response.status_code, 302) + self.assertRedirects( + response, + self.url, + status_code=302, + target_status_code=200, + fetch_redirect_response=True, + ) def test_form_success_url(self) -> None: self.assertEqual(self.view.get_success_url(), self.url) From 76fe64fb9ad359e431f6803f6371e24b0a6614a0 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 28 Nov 2023 21:14:41 -0300 Subject: [PATCH 05/24] test(apps/tasks): test Celery tasks --- apps/tasks/tests.py | 69 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index 983c84b..fd33cdb 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -13,7 +13,7 @@ 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 +from apps.tasks.views import DetailView, handle_submission from apps.users.models import User @@ -322,3 +322,70 @@ def test_handle_submission_with_wrong_output(self) -> None: def test_form_success_url(self) -> None: self.assertEqual(self.view.get_success_url(), self.url) + + +class BackgroundJobTaskTest(TestCase): + def setUp(self) -> None: + now = timezone.now() + start_time = now - timedelta(hours=1) + end_time = now + timedelta(hours=1) + + self.code = "print('Hello, World!')" + + self.contest = Contest._default_manager.create( + 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="Example task", + description="Some example task", + contest=self.contest, + ) + self.user = User._default_manager.create( + email="user@email.com", + username="user", + password="password", + ) + self.submission = Submission._default_manager.create( + author=self.user, + task=self.task, + code=self.code, + status=SubmissionStatus.ACCEPTED, + ) + + def test_handle_submission_with_correct_output(self) -> None: + self.task.output_file = "Hello, World!\n" + self.task.save() + + handle_submission.apply( + args=(self.code, self.task.id, self.submission.id) + ) + + self.submission.refresh_from_db() + expected = SubmissionStatus.ACCEPTED + + self.assertEqual(self.submission.status, expected) + + def test_handle_submission_with_wrong_output(self) -> None: + self.task.output_file = "Hello, World!" + self.task.save() + + handle_submission.apply( + args=(self.code, self.task.id, self.submission.id) + ) + + self.submission.refresh_from_db() + expected = SubmissionStatus.WRONG_ANSWER + + self.assertEqual(self.submission.status, expected) + + def test_handle_submission_with_exception(self) -> None: + code = "raise Exception('Test exception')" + handle_submission.apply(args=(code, self.task.id, self.submission.id)) + + self.submission.refresh_from_db() + expected = SubmissionStatus.RUNTIME_ERROR + + self.assertEqual(self.submission.status, expected) From 37893287e4c7da09b40a69e5906f3d34650bce70 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 28 Nov 2023 21:47:02 -0300 Subject: [PATCH 06/24] build(heroku): specify the command for workers --- Procfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Procfile b/Procfile index 70859a6..9487609 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,3 @@ release: python manage.py migrate web: gunicorn server.wsgi +worker: celery -A server worker -l info From d7e05bcb040d092a4cf6fe8922715e28cdd0800f Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 28 Nov 2023 22:00:56 -0300 Subject: [PATCH 07/24] build(heroku): specify the formation for review apps --- app.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app.json b/app.json index 1332cb7..288629a 100644 --- a/app.json +++ b/app.json @@ -16,5 +16,15 @@ "env": { "PYTHON_RUNTIME_VERSION": "3.11.5", "POETRY_VERSION": "1.6.1" + }, + "formation": { + "web": { + "quantity": 1, + "size": "basic" + }, + "worker": { + "quantity": 1, + "size": "basic" + } } } From 93b380b2eec59c04c2f8065ed5ff730aa71b3eaa Mon Sep 17 00:00:00 2001 From: jpcfarias Date: Tue, 28 Nov 2023 22:09:46 -0300 Subject: [PATCH 08/24] docs(readme): chenge readme add resumo and motivacao --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 7b4003d..6f617c5 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,15 @@ programação competitiva. | Letícia Hladczuk | 221039209 | [@HladczukLe](https://github.com/HladczukLe) | | Gabriel Fernando | 222022162 | [@MMcLovin](https://github.com/MMcLovin) | +## Resumo + +Um juiz online representa uma plataforma essencial em competições de programação, desempenhando o papel crucial de avaliar e classificar as soluções submetidas pelos participantes. Além de ser uma ferramenta valiosa para competições, também serve como um ambiente propício para a exploração e aprendizado de novas linguagens de programação. Nessa plataforma, os participantes enfrentam desafios por meio de questões específicas, e ao submeterem seus códigos, essas submissões são sujeitas a limites rigorosos de tempo e memória, adicionando uma dimensão adicional de desafio e eficiência à avaliação. + + +## Motivação + +Nossa motivação é desenvolver um juiz online que adira estritamente aos princípios do Software Livre. + ## Instalação ### Ambiente From 334e06c437f2866c4744f3d62677a8412811284a Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 28 Nov 2023 22:21:36 -0300 Subject: [PATCH 09/24] build(docker): change the broker URL environment variable in the application --- docker/django/entrypoint.sh | 2 +- server/celery.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docker/django/entrypoint.sh b/docker/django/entrypoint.sh index f2d8351..9376512 100755 --- a/docker/django/entrypoint.sh +++ b/docker/django/entrypoint.sh @@ -12,7 +12,7 @@ export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES : "${RABBITMQ_HOST:=rabbitmq}" : "${RABBITMQ_PORT:=5672}" -export CELERY_BROKER_URL="amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@${RABBITMQ_HOST}:${RABBITMQ_PORT}/${RABBITMQ_VHOST}" +export CLOUDAMQP_URL="amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@${RABBITMQ_HOST}:${RABBITMQ_PORT}/${RABBITMQ_VHOST}" # We need this line to make sure that this container is started after the one # with PostgreSQL: diff --git a/server/celery.py b/server/celery.py index dc4c066..1650f7e 100644 --- a/server/celery.py +++ b/server/celery.py @@ -3,9 +3,14 @@ from celery import Celery from celery.app.task import Task +from server.settings import env + environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings.development") -app = Celery("virtualjudge", broker_connection_retry_on_startup=False) +broker = env("CLOUDAMQP_URL", default="") +app = Celery( + "virtualjudge", broker=broker, broker_connection_retry_on_startup=False +) app.config_from_object("django.conf:settings", namespace="CELERY") app.autodiscover_tasks() From a47836db06d3c0c93aefce2397978c2c0d0d1a91 Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Wed, 29 Nov 2023 08:46:56 -0300 Subject: [PATCH 10/24] feat(submission_page): submissions list --- apps/submissions/urls.py | 7 ++++++ apps/submissions/views.py | 9 +++++++ templates/submission/submission_list.html | 30 +++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 templates/submission/submission_list.html diff --git a/apps/submissions/urls.py b/apps/submissions/urls.py index e69de29..5070325 100644 --- a/apps/submissions/urls.py +++ b/apps/submissions/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from apps.submissions.views import SubmissionListView + +urlpatterns = [ + path("submissions/", SubmissionListView.as_view(), name="submission_list") +] diff --git a/apps/submissions/views.py b/apps/submissions/views.py index e69de29..964f1f2 100644 --- a/apps/submissions/views.py +++ b/apps/submissions/views.py @@ -0,0 +1,9 @@ +from django.views.generic import ListView + +from apps.submissions.models import Submission + + +class SubmissionListView(ListView[Submission]): + model = Submission + template_name = "submission_list.html" + context_object_name = "submissions" diff --git a/templates/submission/submission_list.html b/templates/submission/submission_list.html new file mode 100644 index 0000000..67dcb99 --- /dev/null +++ b/templates/submission/submission_list.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block title %}Lista de Submissões{% endblock title %} + +{% block content %} +
+

Lista de Submissões

+ +
+ {% for submission in submissions %} +
+
+ + Submissão #{{ submission.id }} + + + Autor: {{ submission.author.username }} + + + Status: {{ submission.get_status_display }} + +
+
+

Tarefa: {{ submission.task.title }}

+
+
+ {% endfor %} +
+
+{% endblock content %} From bee9204d49f365afef2c13b627726a00b6b5a95d Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Wed, 29 Nov 2023 10:23:16 -0300 Subject: [PATCH 11/24] feat(submission_page): submissions list tests --- apps/submissions/tests.py | 41 ++++++++++++++++++++++ apps/submissions/views.py | 9 ++++- server/urls.py | 1 + templates/submission/submission_list.html | 30 ---------------- templates/submissions/submission_list.html | 0 5 files changed, 50 insertions(+), 31 deletions(-) delete mode 100644 templates/submission/submission_list.html create mode 100644 templates/submissions/submission_list.html diff --git a/apps/submissions/tests.py b/apps/submissions/tests.py index fe42958..9fedc94 100644 --- a/apps/submissions/tests.py +++ b/apps/submissions/tests.py @@ -3,6 +3,7 @@ from django.contrib.admin.sites import AdminSite from django.core.exceptions import ValidationError from django.test import TestCase +from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext as _ @@ -94,3 +95,43 @@ def test_fieldsets(self) -> None: (_("Details"), {"fields": ("author", "task", "code", "status")}) ] self.assertEqual(self.submission_admin.fieldsets, expected) + + +class SubmissionListView(TestCase): + def setUp(self) -> None: + self.user = User.objects.create_user( + username="testuser", + email="testuser@example", + password="testpassword", + ) + + self.contest = Contest._default_manager.create( + title="Test Contest", + description="This is a test contest", + start_time=timezone.now(), + 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._default_manager.create( + author=self.user, + task=self.task, + code="test code", + ) + + def test_submission_list_view(self) -> None: + self.client.login(email="testuser@example", password="testpassword") + + url = reverse("submission_list") + + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertTrue("submissions" in response.context) + self.assertIn(self.submission, response.context["submissions"]) diff --git a/apps/submissions/views.py b/apps/submissions/views.py index 964f1f2..e715b2d 100644 --- a/apps/submissions/views.py +++ b/apps/submissions/views.py @@ -1,9 +1,16 @@ +from typing import TYPE_CHECKING + from django.views.generic import ListView from apps.submissions.models import Submission +if TYPE_CHECKING: + SubmissionViewBase = ListView[Submission] +else: + SubmissionViewBase = ListView + -class SubmissionListView(ListView[Submission]): +class SubmissionListView(SubmissionViewBase): model = Submission template_name = "submission_list.html" context_object_name = "submissions" diff --git a/server/urls.py b/server/urls.py index c9f993e..2570b89 100644 --- a/server/urls.py +++ b/server/urls.py @@ -12,4 +12,5 @@ path("contests/", include("apps.contests.urls"), name="contests"), path("tasks/", include("apps.tasks.urls")), path("", include("apps.users.urls")), + path("submissions/", include("apps.submissions.urls")), ] diff --git a/templates/submission/submission_list.html b/templates/submission/submission_list.html deleted file mode 100644 index 67dcb99..0000000 --- a/templates/submission/submission_list.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Lista de Submissões{% endblock title %} - -{% block content %} -
-

Lista de Submissões

- -
- {% for submission in submissions %} -
-
- - Submissão #{{ submission.id }} - - - Autor: {{ submission.author.username }} - - - Status: {{ submission.get_status_display }} - -
-
-

Tarefa: {{ submission.task.title }}

-
-
- {% endfor %} -
-
-{% endblock content %} diff --git a/templates/submissions/submission_list.html b/templates/submissions/submission_list.html new file mode 100644 index 0000000..e69de29 From 11103bb4960eead358fd4fff126854164997be7a Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Wed, 29 Nov 2023 10:27:57 -0300 Subject: [PATCH 12/24] feat(submission_page): submissions list tests --- templates/base.html | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/base.html b/templates/base.html index ebbd635..0e159cb 100644 --- a/templates/base.html +++ b/templates/base.html @@ -69,6 +69,7 @@ Ranking + Submissions {% if request.user.is_authenticated %} From d8c680cefe5f0af2809a38cb293c12b764af0c95 Mon Sep 17 00:00:00 2001 From: kyomi Date: Wed, 29 Nov 2023 10:34:40 -0300 Subject: [PATCH 13/24] ci: update the RabbitMQ connection URL --- .github/workflows/ci.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54ac29f..3491f8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: - reopened env: - CELERY_BROKER_URL: "amqp://virtualjudge:virtualjudge@localhost:5672//" + CLOUDAMQP_URL: "amqp://virtualjudge:virtualjudge@localhost:5672//" jobs: upload: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ac21ce1..35003b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ on: - reopened env: - CELERY_BROKER_URL: "amqp://virtualjudge:virtualjudge@localhost:5672//" + CLOUDAMQP_URL: "amqp://virtualjudge:virtualjudge@localhost:5672//" jobs: build: From 48d650dfee8272e24fda0b367b3c68a4738d5c5a Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Wed, 29 Nov 2023 11:20:32 -0300 Subject: [PATCH 14/24] feat(submission_page): template of submissions incomplete --- apps/submissions/tests.py | 2 +- apps/submissions/urls.py | 6 +++--- templates/submissions/submission_list.html | 23 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/submissions/tests.py b/apps/submissions/tests.py index 9fedc94..85703b3 100644 --- a/apps/submissions/tests.py +++ b/apps/submissions/tests.py @@ -128,7 +128,7 @@ def setUp(self) -> None: def test_submission_list_view(self) -> None: self.client.login(email="testuser@example", password="testpassword") - url = reverse("submission_list") + url = reverse("submissions:list") response = self.client.get(url) diff --git a/apps/submissions/urls.py b/apps/submissions/urls.py index 5070325..6ee5e84 100644 --- a/apps/submissions/urls.py +++ b/apps/submissions/urls.py @@ -2,6 +2,6 @@ from apps.submissions.views import SubmissionListView -urlpatterns = [ - path("submissions/", SubmissionListView.as_view(), name="submission_list") -] +app_name = "submissions" + +urlpatterns = [path("", SubmissionListView.as_view(), name="list")] diff --git a/templates/submissions/submission_list.html b/templates/submissions/submission_list.html index e69de29..af9d08d 100644 --- a/templates/submissions/submission_list.html +++ b/templates/submissions/submission_list.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block title %}Submissions{% endblock title %} + +{% block content %} + +

Submissions

+
    + {% for submission in submissions %} +
  • + Submission ID: {{ submission.id }} - + + - {{ task.title }} + + - Status: {{ submission.status }} + - Data: {{ submission.created_at }} +
  • + {% endfor %} +
+ +{% endblock content %} From 85e7c58506b5198ac6069e959de5efb8c017da7c Mon Sep 17 00:00:00 2001 From: kyomi Date: Wed, 29 Nov 2023 12:01:58 -0300 Subject: [PATCH 15/24] chore: add links to the project on README --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9876011..8251153 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,14 @@ avaliação. Nossa motivação é desenvolver um juiz online que adira estritamente aos princípios do software livre. +## Showcase + +Você pode olhar um showcase do nosso projeto através desses links: + +- [Ambiente de desenvolvimento](https://develop.squad06.com) +- [Ambiente de homologação](https://staging.squad06.com) +- [Ambiente de produção](https://squad06.com) + ## Instalação ### Ambiente @@ -80,11 +88,12 @@ $ docker compose down -v ``` Em caso de problemas com a instalação, verifique a -[documentação](https://mds-squad-06.readthedocs.io/pt/latest/installation.html). +[documentação](https://docs.squad06.com/pt/latest/installation.html). ## Links -- [Documentação](https://mds.kyomi.dev/pt/latest/) +- [Documentação](https://docs.squad06.com/pt/latest/) +- [Link para o projeto](https://squad06.com) ## Membros From 297cd3cc9fb0a7fe24a9b58bbcb32b9845d18de5 Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Thu, 30 Nov 2023 20:45:15 -0300 Subject: [PATCH 16/24] feat(submission_page): template of submissions complete --- apps/submissions/views.py | 2 +- templates/base.html | 1 - templates/submissions/list.html | 57 ++++++++++++++++++++++ templates/submissions/submission_list.html | 23 --------- 4 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 templates/submissions/list.html delete mode 100644 templates/submissions/submission_list.html diff --git a/apps/submissions/views.py b/apps/submissions/views.py index e715b2d..6a2dc04 100644 --- a/apps/submissions/views.py +++ b/apps/submissions/views.py @@ -12,5 +12,5 @@ class SubmissionListView(SubmissionViewBase): model = Submission - template_name = "submission_list.html" + template_name = "submissions/list.html" context_object_name = "submissions" diff --git a/templates/base.html b/templates/base.html index 0e159cb..ebbd635 100644 --- a/templates/base.html +++ b/templates/base.html @@ -69,7 +69,6 @@ Ranking - Submissions {% if request.user.is_authenticated %} diff --git a/templates/submissions/list.html b/templates/submissions/list.html new file mode 100644 index 0000000..bd5869a --- /dev/null +++ b/templates/submissions/list.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block title %}Submissions{% endblock title %} + +{% block content %} +

Submissions

+ + + + + + + +
+ + + + + + + + + + + {% for submission in submissions.all %} + + + + + + + {% endfor %} + +
Submission IDTaskStatusCreated at
{{ submission.id }}{{ submission.task.title }}{{ submission.status }}{{ submission.created_at}}
+
+ +{% endblock content %} diff --git a/templates/submissions/submission_list.html b/templates/submissions/submission_list.html deleted file mode 100644 index af9d08d..0000000 --- a/templates/submissions/submission_list.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base.html" %} - -{% load crispy_forms_tags %} - -{% block title %}Submissions{% endblock title %} - -{% block content %} - -

Submissions

-
    - {% for submission in submissions %} -
  • - Submission ID: {{ submission.id }} - - - - {{ task.title }} - - - Status: {{ submission.status }} - - Data: {{ submission.created_at }} -
  • - {% endfor %} -
- -{% endblock content %} From b2c54246349202e0f317cc8c30ee46c11faba09e Mon Sep 17 00:00:00 2001 From: LuizaMaluf Date: Thu, 30 Nov 2023 20:48:23 -0300 Subject: [PATCH 17/24] feat(submission_page): add submissions to nav bar --- templates/base.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/templates/base.html b/templates/base.html index ebbd635..8d0e4de 100644 --- a/templates/base.html +++ b/templates/base.html @@ -70,6 +70,11 @@ Ranking + {% if request.user.is_authenticated %}