From cf8e9169d5199709167a673a18067d578ebe12a9 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 01:55:40 +0300 Subject: [PATCH 01/23] (scope): [body] [footer(s)] --- services/backend/scripts/initdb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/backend/scripts/initdb b/services/backend/scripts/initdb index f2d64eb..09683e6 100755 --- a/services/backend/scripts/initdb +++ b/services/backend/scripts/initdb @@ -9,3 +9,5 @@ fi if [ "$DJANGO_CREATE_SUPERUSER" = "True" ]; then python manage.py createsuperuser --noinput --username "$DJANGO_SUPERUSER_USERNAME" --email "$DJANGO_SUPERUSER_EMAIL" || true fi + +python manage.py init_achivements \ No newline at end of file From 43eec235ccac73fbb107013775cee9e1f59ad311 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 02:09:28 +0300 Subject: [PATCH 02/23] hehe --- compose.yaml | 2 +- services/checker/main.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compose.yaml b/compose.yaml index 1c471f1..c79a296 100644 --- a/compose.yaml +++ b/compose.yaml @@ -373,7 +373,7 @@ services: custom_python: image: gitlab.prodcontest.ru:5050/team-15/project/custom-python:latest entrypoint: [""] - command: echo "Image pulled." + command: exit 0 checker: image: gitlab.prodcontest.ru:5050/team-15/project/checker:latest diff --git a/services/checker/main.py b/services/checker/main.py index 9e370b1..4d13a05 100644 --- a/services/checker/main.py +++ b/services/checker/main.py @@ -158,12 +158,12 @@ def run_container_safely( "stderr": f"Container error: {str(e)}", "status": -1, } - finally: - if container: - try: - container.remove(force=True) - except docker.errors.DockerException: - pass + # finally: + # if container: + # try: + # container.remove(force=True) + # except docker.errors.DockerException: + # pass def validate_file_path(path: str) -> bool: From e412627d6b075322cc7459ac86588611d8650c54 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 02:16:06 +0300 Subject: [PATCH 03/23] (scope): [body] [footer(s)] --- compose.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compose.yaml b/compose.yaml index c79a296..7dad6e9 100644 --- a/compose.yaml +++ b/compose.yaml @@ -372,8 +372,7 @@ services: custom_python: image: gitlab.prodcontest.ru:5050/team-15/project/custom-python:latest - entrypoint: [""] - command: exit 0 + entrypoint: ["exit 0"] checker: image: gitlab.prodcontest.ru:5050/team-15/project/checker:latest From aaa720ad014126965e6df02e8a9716eae866f9b2 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 08:47:39 +0300 Subject: [PATCH 04/23] (scope): [body] [footer(s)] --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index 7dad6e9..53771a3 100644 --- a/compose.yaml +++ b/compose.yaml @@ -372,7 +372,7 @@ services: custom_python: image: gitlab.prodcontest.ru:5050/team-15/project/custom-python:latest - entrypoint: ["exit 0"] + entrypoint: ["sh", "-c", "exit 0"] checker: image: gitlab.prodcontest.ru:5050/team-15/project/checker:latest From 5d3c75ab0ae8f1f51d93a61d1978437fdbe93783 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 08:59:27 +0300 Subject: [PATCH 05/23] (scope): [body] [footer(s)] --- .gitlab-ci.yml | 2 +- services/checker/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8897e31..bf5f487 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -142,9 +142,9 @@ reset-compose: - | ssh $SSH_ADDRESS <<'EOF' cd ~/deploy + docker system prune -a --force docker compose down -v > deploy.log 2>&1 docker compose up -d --remove-orphans --force-recreate >> deploy.log 2>&1 docker compose ps >> deploy.log 2>&1 EOF - - ssh $SSH_ADDRESS "docker system prune -a --force" retry: 2 diff --git a/services/checker/Dockerfile b/services/checker/Dockerfile index d4266d1..d0f7ea4 100644 --- a/services/checker/Dockerfile +++ b/services/checker/Dockerfile @@ -30,7 +30,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONOPTIMIZE=2 \ PATH="/opt/venv/bin:$PATH" -EXPOSE 8080 +EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --start-interval=2s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8000/health || exit 1 From f2863d89bb79455cefe872e6495232d9551b7774 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 09:07:15 +0300 Subject: [PATCH 06/23] (scope): [body] [footer(s)] --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bf5f487..457741d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -107,12 +107,12 @@ deploy: - | ssh $SSH_ADDRESS <<'EOF' cd ~/deploy + docker system prune -a --force docker compose pull > deploy.log 2>&1 docker compose down >> deploy.log 2>&1 docker compose up -d --remove-orphans --force-recreate >> deploy.log 2>&1 docker compose ps >> deploy.log 2>&1 EOF - - ssh $SSH_ADDRESS "docker system prune -a --force" retry: 2 @@ -142,7 +142,6 @@ reset-compose: - | ssh $SSH_ADDRESS <<'EOF' cd ~/deploy - docker system prune -a --force docker compose down -v > deploy.log 2>&1 docker compose up -d --remove-orphans --force-recreate >> deploy.log 2>&1 docker compose ps >> deploy.log 2>&1 From abfd10fc4c15317fb9e05015931eff9bbfa49e39 Mon Sep 17 00:00:00 2001 From: Timur Date: Mon, 3 Mar 2025 10:21:04 +0300 Subject: [PATCH 07/23] rewrite migrations --- services/backend/apps/achievement/migrations/0001_initial.py | 2 +- services/backend/apps/achievement/migrations/0002_initial.py | 2 +- services/backend/apps/competition/migrations/0001_initial.py | 2 +- services/backend/apps/review/migrations/0001_initial.py | 2 +- services/backend/apps/review/migrations/0002_initial.py | 2 +- services/backend/apps/task/migrations/0001_initial.py | 2 +- services/backend/apps/team/migrations/0001_initial.py | 2 +- services/backend/apps/user/migrations/0001_initial.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/services/backend/apps/achievement/migrations/0001_initial.py b/services/backend/apps/achievement/migrations/0001_initial.py index 5f7eb43..03300ea 100644 --- a/services/backend/apps/achievement/migrations/0001_initial.py +++ b/services/backend/apps/achievement/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 22:53 +# Generated by Django 5.1.6 on 2025-03-03 07:20 import apps.achievement.models import django.db.models.deletion diff --git a/services/backend/apps/achievement/migrations/0002_initial.py b/services/backend/apps/achievement/migrations/0002_initial.py index 6c4d57b..1d03338 100644 --- a/services/backend/apps/achievement/migrations/0002_initial.py +++ b/services/backend/apps/achievement/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 22:53 +# Generated by Django 5.1.6 on 2025-03-03 07:20 import django.db.models.deletion from django.db import migrations, models diff --git a/services/backend/apps/competition/migrations/0001_initial.py b/services/backend/apps/competition/migrations/0001_initial.py index 58d3335..428a78a 100644 --- a/services/backend/apps/competition/migrations/0001_initial.py +++ b/services/backend/apps/competition/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 22:53 +# Generated by Django 5.1.6 on 2025-03-03 07:20 import apps.competition.models import datetime diff --git a/services/backend/apps/review/migrations/0001_initial.py b/services/backend/apps/review/migrations/0001_initial.py index 26cf907..afe281e 100644 --- a/services/backend/apps/review/migrations/0001_initial.py +++ b/services/backend/apps/review/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 22:53 +# Generated by Django 5.1.6 on 2025-03-03 07:20 import uuid from django.db import migrations, models diff --git a/services/backend/apps/review/migrations/0002_initial.py b/services/backend/apps/review/migrations/0002_initial.py index c35e191..5a5d3cc 100644 --- a/services/backend/apps/review/migrations/0002_initial.py +++ b/services/backend/apps/review/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 22:53 +# Generated by Django 5.1.6 on 2025-03-03 07:20 import django.db.models.deletion from django.db import migrations, models diff --git a/services/backend/apps/task/migrations/0001_initial.py b/services/backend/apps/task/migrations/0001_initial.py index f6604a4..9cfeeae 100644 --- a/services/backend/apps/task/migrations/0001_initial.py +++ b/services/backend/apps/task/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 22:53 +# Generated by Django 5.1.6 on 2025-03-03 07:20 import apps.task.models import django.db.models.deletion diff --git a/services/backend/apps/team/migrations/0001_initial.py b/services/backend/apps/team/migrations/0001_initial.py index f6bcf2e..1924dc3 100644 --- a/services/backend/apps/team/migrations/0001_initial.py +++ b/services/backend/apps/team/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 22:53 +# Generated by Django 5.1.6 on 2025-03-03 07:20 import django.db.models.deletion import uuid diff --git a/services/backend/apps/user/migrations/0001_initial.py b/services/backend/apps/user/migrations/0001_initial.py index 34d87e9..4ade297 100644 --- a/services/backend/apps/user/migrations/0001_initial.py +++ b/services/backend/apps/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 22:53 +# Generated by Django 5.1.6 on 2025-03-03 07:20 import uuid from django.db import migrations, models From 9c80bb86fb97e39fddf83bca09f8355a5ecfa7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=A1=D1=83=D0=BC?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Mon, 3 Mar 2025 07:31:58 +0000 Subject: [PATCH 08/23] Edit initdb --- services/backend/scripts/initdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/backend/scripts/initdb b/services/backend/scripts/initdb index 09683e6..3943ba9 100755 --- a/services/backend/scripts/initdb +++ b/services/backend/scripts/initdb @@ -10,4 +10,4 @@ if [ "$DJANGO_CREATE_SUPERUSER" = "True" ]; then python manage.py createsuperuser --noinput --username "$DJANGO_SUPERUSER_USERNAME" --email "$DJANGO_SUPERUSER_EMAIL" || true fi -python manage.py init_achivements \ No newline at end of file +python manage.py init_achievements \ No newline at end of file From 151d997eb5428fd809a10354777a1df5b59be781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=A1=D1=83=D0=BC?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Mon, 3 Mar 2025 07:44:29 +0000 Subject: [PATCH 09/23] Edit initdb --- services/backend/scripts/initdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/backend/scripts/initdb b/services/backend/scripts/initdb index 3943ba9..146732a 100755 --- a/services/backend/scripts/initdb +++ b/services/backend/scripts/initdb @@ -10,4 +10,4 @@ if [ "$DJANGO_CREATE_SUPERUSER" = "True" ]; then python manage.py createsuperuser --noinput --username "$DJANGO_SUPERUSER_USERNAME" --email "$DJANGO_SUPERUSER_EMAIL" || true fi -python manage.py init_achievements \ No newline at end of file +python manage.py init_achievments \ No newline at end of file From 07ec22881ea26b690c797bbd028f5a5635755f5b Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 10:54:28 +0300 Subject: [PATCH 10/23] (scope): [body] [footer(s)] --- services/checker/main.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/services/checker/main.py b/services/checker/main.py index 4d13a05..a1d05fa 100644 --- a/services/checker/main.py +++ b/services/checker/main.py @@ -1,4 +1,3 @@ -import docker.errors from fastapi import FastAPI, HTTPException, status from pydantic import BaseModel, Field, HttpUrl import aiohttp @@ -158,12 +157,12 @@ def run_container_safely( "stderr": f"Container error: {str(e)}", "status": -1, } - # finally: - # if container: - # try: - # container.remove(force=True) - # except docker.errors.DockerException: - # pass + finally: + if container: + try: + container.remove(force=True) + except docker.errors.DockerException: + pass def validate_file_path(path: str) -> bool: From 1e4ddd2b9cdd3cebd046fe780df6610c1ddd280c Mon Sep 17 00:00:00 2001 From: rngsurrounded Date: Mon, 3 Mar 2025 17:11:00 +0900 Subject: [PATCH 11/23] work test --- .../src/pages/CompetitionSession/index.tsx | 3 - .../components/SolutionHistorySheet/index.tsx | 1 + .../modules/TaskSolution/index.tsx | 56 +++++++++++-------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/services/frontend/src/pages/CompetitionSession/index.tsx b/services/frontend/src/pages/CompetitionSession/index.tsx index 4d0c5f8..5c8e7c1 100644 --- a/services/frontend/src/pages/CompetitionSession/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/index.tsx @@ -44,9 +44,6 @@ const CompetitionSession = () => { queryClient.invalidateQueries({ queryKey: ['solutionHistory', competitionId, taskId] }); - - setAnswer(""); - setSelectedFile(null); }, onError: (error) => { console.error("Error submitting solution:", error); diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx index 3f181bd..83d8cda 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx @@ -22,6 +22,7 @@ const SolutionHistorySheet: React.FC = ({ onSolutionSelect, currentSolutionId }) => { + return ( diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx index 20fdda1..a463cde 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx @@ -32,7 +32,7 @@ const TaskSolution: React.FC = ({ const [selectedSolutionUrl, setSelectedSolutionUrl] = useState(null); const [currentSolution, setCurrentSolution] = useState(null); const { id: competitionId } = useParams<{ id: string }>(); - const previousTaskIdRef = useRef(null); + const taskIdRef = useRef(null); const solutionsQuery = useQuery({ queryKey: ['solutionHistory', competitionId, task.id], @@ -40,37 +40,42 @@ const TaskSolution: React.FC = ({ enabled: !!(competitionId && task.id), }); + // Get the solution history - already sorted from oldest to newest const solutionHistory = solutionsQuery.data || []; - + + // Handle task changes useEffect(() => { - if (solutionHistory.length > 0 && !currentSolution) { - setCurrentSolution(solutionHistory[solutionHistory.length - 1]); - } - }, [solutionHistory, currentSolution]); - - useEffect(() => { - if (solutionHistory.length > 0 && currentSolution && - solutionHistory[0].id !== currentSolution.id) { - setCurrentSolution(solutionHistory[solutionHistory.length - 1]); - } - }, [solutionHistory, currentSolution]); - - useEffect(() => { - if (previousTaskIdRef.current !== task.id) { + // If task changed, reset everything and load the latest solution + if (taskIdRef.current !== task.id) { setCurrentSolution(null); setSelectedSolutionUrl(null); - setAnswer(""); setSelectedFile(null); + taskIdRef.current = task.id; - if (solutionHistory.length > 0 && !solutionsQuery.isLoading) { - setCurrentSolution(solutionHistory[solutionHistory.length - 1]); + // Wait for the query to complete + if (!solutionsQuery.isLoading && solutionHistory.length > 0) { + // Get the most recent solution (last in the array) + const latestSolution = solutionHistory[solutionHistory.length - 1]; + setCurrentSolution(latestSolution); } - - previousTaskIdRef.current = task.id; } }, [task.id, solutionHistory, solutionsQuery.isLoading, setAnswer, setSelectedFile]); + // Refresh current solution when the solution history changes (after a new submission) + useEffect(() => { + if (!solutionsQuery.isLoading && solutionHistory.length > 0) { + // If we don't have a current solution or there's a new submission + // (which would be the last item in the array) + if (!currentSolution || + currentSolution.id !== solutionHistory[solutionHistory.length - 1].id) { + // Set to the latest solution (last in the array) + setCurrentSolution(solutionHistory[solutionHistory.length - 1]); + } + } + }, [solutionHistory, currentSolution, solutionsQuery.isLoading]); + + // Load solution content when current solution changes useEffect(() => { const loadSolutionContent = async () => { if (!currentSolution || !currentSolution.content) return; @@ -108,6 +113,13 @@ const TaskSolution: React.FC = ({ setSelectedSolutionUrl(null); }; + const handleSubmitWrapper = () => { + onSubmit(); + setAnswer(""); + setSelectedFile(null); + setSelectedSolutionUrl(null); + }; + return (
{currentSolution ? ( @@ -143,7 +155,7 @@ const TaskSolution: React.FC = ({ )} From 8f7111a99869469225b479dda77189e6c8541bec Mon Sep 17 00:00:00 2001 From: Timur Date: Mon, 3 Mar 2025 11:15:20 +0300 Subject: [PATCH 12/23] add missing verbose names to admin --- services/backend/apps/task/models.py | 28 ++++++++++++++++++++-------- services/backend/apps/user/models.py | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index 0c77556..a5e725b 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -19,11 +19,15 @@ class CompetitionTask(BaseModel): def answer_file_upload_to(instance, filename) -> str: return f"tasks/{instance.id}/answer/{uuid4()}/{filename}" - in_competition_position = models.PositiveSmallIntegerField() - competition = models.ForeignKey(Competition, on_delete=models.CASCADE) + in_competition_position = models.PositiveSmallIntegerField( + verbose_name="позиция в соревновании" + ) + competition = models.ForeignKey(Competition, on_delete=models.CASCADE, + verbose_name="привязанное соревнование") title = models.CharField(verbose_name="заголовок", max_length=50) description = MartorField(verbose_name="описание") - max_attempts = models.PositiveSmallIntegerField(null=True, blank=True) + max_attempts = models.PositiveSmallIntegerField(null=True, blank=True, + verbose_name="максимальное кол-во попыток") type = models.CharField( choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки" ) @@ -56,7 +60,7 @@ class CompetitionTask(BaseModel): help_text="Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему", ) submission_reviewers_count = models.PositiveSmallIntegerField( - default=1, null=True, blank=True + default=1, null=True, blank=True, verbose_name="кол-во проверяющих для зачета задачи" ) def __str__(self): @@ -72,10 +76,18 @@ class CompetitionTaskCriteria(BaseModel): CompetitionTask, on_delete=models.CASCADE, related_name="criteries" ) - name = models.TextField() - slug = models.SlugField() - description = models.TextField() - max_value = models.PositiveSmallIntegerField() + name = models.TextField( + verbose_name="название" + ) + slug = models.SlugField( + verbose_name="техническое название" + ) + description = models.TextField( + verbose_name="описание критерии" + ) + max_value = models.PositiveSmallIntegerField( + verbose_name="максимальное кол-во баллов" + ) class CompetitionTaskAttachment(BaseModel): diff --git a/services/backend/apps/user/models.py b/services/backend/apps/user/models.py index aaa0ec0..c1df2f1 100644 --- a/services/backend/apps/user/models.py +++ b/services/backend/apps/user/models.py @@ -15,7 +15,7 @@ class User(BaseModel): username = models.SlugField(unique=True, verbose_name="юзернейм") password = models.TextField(verbose_name="пароль") - created_at = models.DateTimeField(auto_now=True) + created_at = models.DateTimeField(auto_now=True, verbose_name="дата создания") achievements = models.ManyToManyField( Achievement, blank=True, verbose_name="ачивки пользователя" From 8064eb9cbaa893720c7050fd07b29d4b2785e052 Mon Sep 17 00:00:00 2001 From: Timur Date: Mon, 3 Mar 2025 11:21:53 +0300 Subject: [PATCH 13/23] add verbose names to user roles and user status --- services/backend/apps/user/models.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/services/backend/apps/user/models.py b/services/backend/apps/user/models.py index c1df2f1..598abb6 100644 --- a/services/backend/apps/user/models.py +++ b/services/backend/apps/user/models.py @@ -5,9 +5,9 @@ from apps.achievement.models import Achievement from apps.core.models import BaseModel -class UserRole(models.Choices): - STUDENT = "student" - METODIST = "metodist" +class UserRole(models.TextChoices): + STUDENT = "student", "Участник соревнований" + METODIST = "metodist", "Методист (составитель заданий)" class User(BaseModel): @@ -29,7 +29,8 @@ class User(BaseModel): return check_password(self.password, password) status = models.CharField( - max_length=10, choices=UserRole, default="student" + max_length=10, choices=UserRole.choices, default="student", + verbose_name="роль участника" ) def __str__(self) -> str: From 7231fc1baeb7e7bee78b5787b4bc92e9c07b3c1d Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 11:30:47 +0300 Subject: [PATCH 14/23] nepobeda --- services/backend/apps/task/models.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index 0c77556..094a782 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -34,9 +34,10 @@ class CompetitionTask(BaseModel): null=True, blank=True, verbose_name="файл с правильным ответом", + help_text="Имеет смысл только при автоматической (ввод ответа или кода) проверке.", ) points = models.IntegerField( - null=True, blank=True, verbose_name="баллы за задание" + null=True, blank=True, verbose_name="общий балл за задание" ) # only when "checker" type @@ -44,7 +45,10 @@ class CompetitionTask(BaseModel): null=True, blank=True, verbose_name="куда сделать вывод программы участнику", - help_text="Путь до файла в котором ожидается результат. Пример: stdout или ./output.txt", + help_text=( + "Путь до файла в котором ожидается результат. " + "Пример: stdout или ./output.txt. Имеет смысл только при автоматическом типе проверки." + ), default="stdout", ) @@ -53,7 +57,11 @@ class CompetitionTask(BaseModel): Reviewer, blank=True, verbose_name="ревьюверы", - help_text="Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему", + help_text=( + "Справа отображаются действующие проверяющие, слева - доступные для выбора. " + "Для перемещения можно кликнуть 2 раза по проверяющему. Имеет смысл только" + " при ручном типе проверки." + ), ) submission_reviewers_count = models.PositiveSmallIntegerField( default=1, null=True, blank=True From c06cd72627c319c1086700e9feb87b1a74a128b4 Mon Sep 17 00:00:00 2001 From: Timur Date: Mon, 3 Mar 2025 11:31:15 +0300 Subject: [PATCH 15/23] change markdown editor in admin (now I use https://github.com/pylixm/django-mdeditor) --- services/backend/apps/task/admin.py | 8 +++++++- services/backend/apps/task/models.py | 10 +++++++++- services/backend/config/settings.py | 3 +++ services/backend/config/urls.py | 2 ++ services/backend/pyproject.toml | 1 + 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/services/backend/apps/task/admin.py b/services/backend/apps/task/admin.py index 7b6fbfa..10f3c67 100644 --- a/services/backend/apps/task/admin.py +++ b/services/backend/apps/task/admin.py @@ -4,6 +4,7 @@ from apps.task.models import ( CompetitionTask, CompetitionTaskAttachment, CompetitionTaskSubmission, + CompetitionTaskCriteria ) @@ -12,12 +13,17 @@ class CompletionAttachmentInline(admin.StackedInline): extra = 0 +class CompetitionCriteriaInline(admin.StackedInline): + model = CompetitionTaskCriteria + extra = 0 + + @admin.register(CompetitionTask) class CompetitionTaskAdmin(admin.ModelAdmin): list_display = ("title", "type", "points") filter_horizontal = ("reviewers",) list_filter = ("type",) - inlines = (CompletionAttachmentInline,) + inlines = (CompletionAttachmentInline, CompetitionCriteriaInline,) @admin.register(CompetitionTaskSubmission) diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index a5e725b..9947fb3 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -3,6 +3,7 @@ from uuid import uuid4 from django.db import models from django.db.models import Count, Q from martor.models import MartorField +from mdeditor.fields import MDTextField from apps.competition.models import Competition from apps.core.models import BaseModel @@ -25,7 +26,7 @@ class CompetitionTask(BaseModel): competition = models.ForeignKey(Competition, on_delete=models.CASCADE, verbose_name="привязанное соревнование") title = models.CharField(verbose_name="заголовок", max_length=50) - description = MartorField(verbose_name="описание") + description = MDTextField(verbose_name="описание") max_attempts = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name="максимальное кол-во попыток") type = models.CharField( @@ -89,6 +90,13 @@ class CompetitionTaskCriteria(BaseModel): verbose_name="максимальное кол-во баллов" ) + def __str__(self): + return self.name + + class Meta: + verbose_name = "критерий" + verbose_name_plural = "критерии" + class CompetitionTaskAttachment(BaseModel): def file_upload_at(instance, filename) -> str: diff --git a/services/backend/config/settings.py b/services/backend/config/settings.py index 3987361..da2215b 100644 --- a/services/backend/config/settings.py +++ b/services/backend/config/settings.py @@ -271,6 +271,8 @@ DEFAULT_CHARSET = "utf-8" FORCE_SCRIPT_NAME = None +X_FRAME_OPTIONS = "SAMEORIGIN" + INTERNAL_IPS = env( "DJANGO_INTERNAL_IPS", list, @@ -440,6 +442,7 @@ INSTALLED_APPS = [ "minio_storage", "tinymce", "martor", + "mdeditor", # Internal apps "apps.core", "apps.user", diff --git a/services/backend/config/urls.py b/services/backend/config/urls.py index 6fe96c2..0fb5044 100644 --- a/services/backend/config/urls.py +++ b/services/backend/config/urls.py @@ -16,6 +16,8 @@ urlpatterns = [ path("tinymce/", include("tinymce.urls")), # martor path("martor/", include("martor.urls")), + # mdeditor + path(r'mdeditor/', include('mdeditor.urls')), # Admin urls path("admin/", admin.site.urls), # API urls diff --git a/services/backend/pyproject.toml b/services/backend/pyproject.toml index d3b2371..bb39e9e 100644 --- a/services/backend/pyproject.toml +++ b/services/backend/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "django-extensions>=3.2.3", "django-guid>=3.5.0", "django-health-check>=3.18.3", + "django-mdeditor>=0.1.20", "django-minio-storage>=0.5.7", "django-ninja>=1.3.0", "django-pagedown>=2.2.1", From e8311d7c14117749d9b994241ecf83ac9d40873e Mon Sep 17 00:00:00 2001 From: Timur Date: Mon, 3 Mar 2025 11:32:10 +0300 Subject: [PATCH 16/23] remove old markdown editors --- services/backend/apps/task/models.py | 1 - services/backend/config/settings.py | 61 ---------------------------- services/backend/pyproject.toml | 2 - 3 files changed, 64 deletions(-) diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index 9947fb3..ef0016d 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -2,7 +2,6 @@ from uuid import uuid4 from django.db import models from django.db.models import Count, Q -from martor.models import MartorField from mdeditor.fields import MDTextField from apps.competition.models import Competition diff --git a/services/backend/config/settings.py b/services/backend/config/settings.py index da2215b..cb0d18e 100644 --- a/services/backend/config/settings.py +++ b/services/backend/config/settings.py @@ -440,8 +440,6 @@ INSTALLED_APPS = [ "django_guid", "ninja", "minio_storage", - "tinymce", - "martor", "mdeditor", # Internal apps "apps.core", @@ -453,65 +451,6 @@ INSTALLED_APPS = [ "apps.achievement", ] -# tinymce -TINYMCE_DEFAULT_CONFIG = { - "theme": "silver", - "height": 500, - "menubar": False, - "plugins": "advlist,autolink,lists,link,image,charmap,print,preview,anchor," - "searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste," - "code,help,wordcount,markdown", - "toolbar": "undo redo | formatselect | " - "bold italic backcolor | alignleft aligncenter " - "alignright alignjustify | bullist numlist outdent indent | " - "removeformat | help", - "skin": "oxide-dark", - "content_css": "dark", - "textpattern_patterns": [ - {"start": "*", "end": "*", "format": "italic"}, - {"start": "**", "end": "**", "format": "bold"}, - {"start": "#", "format": "h1"}, - {"start": "##", "format": "h2"}, - {"start": "###", "format": "h3"}, - {"start": "####", "format": "h4"}, - {"start": "#####", "format": "h5"}, - {"start": "######", "format": "h6"}, - {"start": "1. ", "cmd": "InsertOrderedList"}, - {"start": "* ", "cmd": "InsertUnorderedList"}, - {"start": "- ", "cmd": "InsertUnorderedList"}, - ], -} - -# martor - -MARTOR_THEME = "bootstrap" - -MARTOR_ENABLE_CONFIGS = { - "emoji": "true", # to enable/disable emoji icons. - "imgur": "true", # to enable/disable imgur/custom uploader. - "mention": "false", # to enable/disable mention - "jquery": "true", # to include/revoke jquery (require for admin default django) - "living": "false", # to enable/disable live updates in preview - "spellcheck": "false", # to enable/disable spellcheck in form textareas - "hljs": "true", # to enable/disable hljs highlighting in preview -} - -MARTOR_TOOLBAR_BUTTONS = [ - "bold", - "italic", - "horizontal", - "heading", - "pre-code", - "blockquote", - "unordered-list", - "ordered-list", - "link", - "emoji", - "direct-mention", - "toggle-maximize", - "help", -] - # GUID DJANGO_GUID = { diff --git a/services/backend/pyproject.toml b/services/backend/pyproject.toml index bb39e9e..23fefd0 100644 --- a/services/backend/pyproject.toml +++ b/services/backend/pyproject.toml @@ -17,10 +17,8 @@ dependencies = [ "django-ninja>=1.3.0", "django-pagedown>=2.2.1", "django-stubs-ext>=5.1.3", - "django-tinymce>=4.1.0", "gunicorn>=23.0.0", "httpx>=0.28.1", - "martor>=1.6.45", "pillow>=11.1.0", "psycopg2-binary>=2.9.10", "pydantic>=2.10.5", From 889d49f95542b0515f9e4a69fd6a49d3b5b1b6ca Mon Sep 17 00:00:00 2001 From: rngsurrounded Date: Mon, 3 Mar 2025 17:36:54 +0900 Subject: [PATCH 17/23] input hotfix --- services/frontend/src/pages/CompetitionSession/index.tsx | 3 +++ .../pages/CompetitionSession/modules/TaskSolution/index.tsx | 6 ------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/services/frontend/src/pages/CompetitionSession/index.tsx b/services/frontend/src/pages/CompetitionSession/index.tsx index 5c8e7c1..4d0c5f8 100644 --- a/services/frontend/src/pages/CompetitionSession/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/index.tsx @@ -44,6 +44,9 @@ const CompetitionSession = () => { queryClient.invalidateQueries({ queryKey: ['solutionHistory', competitionId, taskId] }); + + setAnswer(""); + setSelectedFile(null); }, onError: (error) => { console.error("Error submitting solution:", error); diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx index a463cde..06cebdd 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx @@ -40,12 +40,9 @@ const TaskSolution: React.FC = ({ enabled: !!(competitionId && task.id), }); - // Get the solution history - already sorted from oldest to newest const solutionHistory = solutionsQuery.data || []; - // Handle task changes useEffect(() => { - // If task changed, reset everything and load the latest solution if (taskIdRef.current !== task.id) { setCurrentSolution(null); setSelectedSolutionUrl(null); @@ -115,9 +112,6 @@ const TaskSolution: React.FC = ({ const handleSubmitWrapper = () => { onSubmit(); - setAnswer(""); - setSelectedFile(null); - setSelectedSolutionUrl(null); }; return ( From 91e1c2842cb103a54463712efadb170f4f06ad99 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 11:38:59 +0300 Subject: [PATCH 18/23] (scope): [body] [footer(s)] --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 457741d..62653ee 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -87,6 +87,7 @@ deploy: stage: deploy rules: - if: '$CI_COMMIT_REF_NAME == "master"' + when: manual variables: SSH_HOST: "158.160.172.23" SSH_USER: "ubuntu" @@ -109,7 +110,6 @@ deploy: cd ~/deploy docker system prune -a --force docker compose pull > deploy.log 2>&1 - docker compose down >> deploy.log 2>&1 docker compose up -d --remove-orphans --force-recreate >> deploy.log 2>&1 docker compose ps >> deploy.log 2>&1 EOF From a98b63a781f14cb5778c04de615352c2182f3b6b Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 11:49:38 +0300 Subject: [PATCH 19/23] aboba --- services/backend/apps/task/tasks.py | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/services/backend/apps/task/tasks.py b/services/backend/apps/task/tasks.py index 9001a6d..f9598dd 100644 --- a/services/backend/apps/task/tasks.py +++ b/services/backend/apps/task/tasks.py @@ -2,27 +2,38 @@ import httpx from celery import shared_task from django.conf import settings from django.core.files.base import ContentFile +import hashlib + +from apps.task.models import CompetitionTaskSubmission @shared_task(bind=True, max_retries=3) def analyze_data_task(self, submission_id): - from .models import CompetitionTaskSubmission - submission = CompetitionTaskSubmission.objects.get(id=submission_id) try: - code = submission.content.read().decode() + code_url = ( + f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}{submission.path}" + ) files = [ - (f.name, f.file.open("rb")) - for f in submission.task.attachments.filter(public=True) + { + "url": f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}{attachment.path}", + "bind_path": attachment.bind_at, + } + for attachment in submission.task.attachments.filter( + bind_path__isnull=False + ) ] response = httpx.post( f"{settings.CHECKER_API_ENDPOINT}/execute", - files=[("files", (f.name, f)) for f in files] - + [ - ("code", code), - ("expected_hash", submission.task.correct_answer_hash), - ], + json={ + "files": files, + "code_url": code_url, + "answer_file_path": submission.task.answer_file_path, + "expected_hash": hashlib.sha256( + submission.task.correct_answer_file.read().encode() + ).hexdigest(), + }, timeout=30, ) response.raise_for_status() From c9b3abe4854b23bc404f07fec565a4721608491a Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 11:57:46 +0300 Subject: [PATCH 20/23] some lol n keks --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62653ee..f0afffb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -108,7 +108,7 @@ deploy: - | ssh $SSH_ADDRESS <<'EOF' cd ~/deploy - docker system prune -a --force + docker system prune -a docker compose pull > deploy.log 2>&1 docker compose up -d --remove-orphans --force-recreate >> deploy.log 2>&1 docker compose ps >> deploy.log 2>&1 From be76fe0864a491ec011572adc6f883e6ded5f781 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 12:03:34 +0300 Subject: [PATCH 21/23] (scope): [body] [footer(s)] --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f0afffb..62653ee 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -108,7 +108,7 @@ deploy: - | ssh $SSH_ADDRESS <<'EOF' cd ~/deploy - docker system prune -a + docker system prune -a --force docker compose pull > deploy.log 2>&1 docker compose up -d --remove-orphans --force-recreate >> deploy.log 2>&1 docker compose ps >> deploy.log 2>&1 From 792f4ab6944223f5d741271200b1527525e3b902 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 12:09:12 +0300 Subject: [PATCH 22/23] abobas and abemes --- services/checker/main.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/checker/main.py b/services/checker/main.py index a1d05fa..64107cc 100644 --- a/services/checker/main.py +++ b/services/checker/main.py @@ -157,12 +157,12 @@ def run_container_safely( "stderr": f"Container error: {str(e)}", "status": -1, } - finally: - if container: - try: - container.remove(force=True) - except docker.errors.DockerException: - pass + # finally: + # if container: + # try: + # container.remove(force=True) + # except docker.errors.DockerException: + # pass def validate_file_path(path: str) -> bool: From 19f52a11ef0f63c82821125cbae3421f5742e453 Mon Sep 17 00:00:00 2001 From: Timur Date: Mon, 3 Mar 2025 12:14:55 +0300 Subject: [PATCH 23/23] fixes --- services/backend/apps/task/admin.py | 3 --- services/backend/config/urls.py | 4 ---- 2 files changed, 7 deletions(-) diff --git a/services/backend/apps/task/admin.py b/services/backend/apps/task/admin.py index 10f3c67..24e9fb9 100644 --- a/services/backend/apps/task/admin.py +++ b/services/backend/apps/task/admin.py @@ -45,9 +45,6 @@ class CompetitionTaskSubmissionAdmin(admin.ModelAdmin): def has_add_permission(self, request, obj=None): return False - def has_delete_permission(self, request, obj=None): - return False - class CompetitionTaskInline(admin.StackedInline): model = CompetitionTask diff --git a/services/backend/config/urls.py b/services/backend/config/urls.py index 0fb5044..423dfe6 100644 --- a/services/backend/config/urls.py +++ b/services/backend/config/urls.py @@ -12,10 +12,6 @@ admin.site.index_title = "DataRush" urlpatterns = [ - # tinymce - path("tinymce/", include("tinymce.urls")), - # martor - path("martor/", include("martor.urls")), # mdeditor path(r'mdeditor/', include('mdeditor.urls')), # Admin urls