From 0bcda79f91e9ed90eaa8a404de028d95d698a4c2 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 13:54:49 +0300 Subject: [PATCH 01/20] added backend-staticfiles and minio to nginx --- infrastructure/nginx/nginx.conf | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf index 0286b05..79380d3 100644 --- a/infrastructure/nginx/nginx.conf +++ b/infrastructure/nginx/nginx.conf @@ -109,6 +109,24 @@ http { proxy_read_timeout 600s; } + location /static { + proxy_pass http://backend-staticfiles:80; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_cache_bypass $http_upgrade; + proxy_hide_header X-Powered-By; + + proxy_connect_timeout 75s; + proxy_send_timeout 600s; + proxy_read_timeout 600s; + } + location /api { proxy_pass http://backend:8080; proxy_http_version 1.1; @@ -142,4 +160,56 @@ http { return 444; } } + + server { + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; + server_name prod-team-15-minio-2pc0i3lc.final.prodcontest.ru; + + ssl_certificate /etc/nginx/certs/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/privkey.pem; + + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + + ignore_invalid_headers off; + client_max_body_size 0; + proxy_buffering off; + proxy_request_buffering off; + + location / { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + proxy_pass http://minio:9000; + } + + location /minio/ui/ { + rewrite ^/minio/ui/(.*) /$1 break; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-NginX-Proxy true; + + real_ip_header X-Real-IP; + + proxy_connect_timeout 300; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + chunked_transfer_encoding off; + + proxy_pass http://minio:9001; + } + } } From 3d8260c7bc000e165d56d1edd83c165b16ea5e0f Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 13:56:44 +0300 Subject: [PATCH 02/20] too lazy to explain --- infrastructure/backend/.env.template | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/backend/.env.template b/infrastructure/backend/.env.template index c964cde..8866be3 100644 --- a/infrastructure/backend/.env.template +++ b/infrastructure/backend/.env.template @@ -1,11 +1,11 @@ DJANGO_SECRET_KEY=secretees DJANGO_DEBUG=False DJANGO_ALLOWED_HOSTS=* -DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost,http://127.0.0.1 +DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost,http://127.0.0.1,https://*.final.prodcontest.ru DJANGO_CORS_ALLOWED_ORIGINS=* DJANGO_INTERNAL_IPS=127.0.0.1 -DJANGO_LANGUAGE_CODE=en-us -DJANGO_STATIC_URL=http://localhost:13241/ +DJANGO_LANGUAGE_CODE=ru +DJANGO_STATIC_URL=static/ REDIS_URI=redis://redis:6379 DJANGO_DB_URI=postgresql://postgres:postgres@postgres/postgres @@ -15,6 +15,6 @@ DJANGO_SUPERUSER_EMAIL=admin@mail.com DJANGO_SUPERUSER_PASSWORD=admin MINIO_ENDPOINT=minio:9000 -MINIO_CUSTOM_ENDPOINT_URL=http://127.0.0.1:13244 +MINIO_CUSTOM_ENDPOINT_URL=https://prod-team-15-minio-2pc0i3lc.final.prodcontest.ru MINIO_ACCESS_KEY=admin MINIO_SECRET_KEY=password From 3206e6a68ca30e8ebfe3840fccf6dcd01121b6dd Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 14:05:44 +0300 Subject: [PATCH 03/20] remove built-in user and group managment --- services/backend/apps/core/admin.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 services/backend/apps/core/admin.py diff --git a/services/backend/apps/core/admin.py b/services/backend/apps/core/admin.py new file mode 100644 index 0000000..8a7dbe4 --- /dev/null +++ b/services/backend/apps/core/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from django.contrib.auth.models import Group, User + +admin.site.unregister(Group) +admin.site.unregister(User) From f314d4f1d192f6db3caa12fbf180f8f325ac28d3 Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 14:06:54 +0300 Subject: [PATCH 04/20] change default language to russian --- services/backend/config/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/backend/config/settings.py b/services/backend/config/settings.py index af79c52..c332120 100644 --- a/services/backend/config/settings.py +++ b/services/backend/config/settings.py @@ -227,7 +227,7 @@ FIRST_DAY_OF_WEEK = 1 FORMAT_MODULE_PATH = None -LANGUAGE_CODE = env("DJANGO_LANGUAGE_CODE", default="en-us") +LANGUAGE_CODE = env("DJANGO_LANGUAGE_CODE", default="ru-ru") LANGUAGES = [("en", _("English")), ("ru", _("Russian"))] From e3c2fc078e61a4ea819540fedc16767e19453959 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 14:22:50 +0300 Subject: [PATCH 05/20] (scope): [body] [footer(s)] --- infrastructure/grafana/grafana.ini | 4 ++-- infrastructure/nginx/nginx.conf | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/infrastructure/grafana/grafana.ini b/infrastructure/grafana/grafana.ini index 7f08784..399a5de 100644 --- a/infrastructure/grafana/grafana.ini +++ b/infrastructure/grafana/grafana.ini @@ -48,10 +48,10 @@ domain = localhost enforce_domain = false # The full public facing url -root_url = %(protocol)s://%(domain)s:%(http_port)s/ +root_url = %(protocol)s://%(domain)s:%(http_port)s/admin/grafana/ # Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. -serve_from_sub_path = false +serve_from_sub_path = true # Log web requests router_logging = false diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf index 79380d3..838c1e5 100644 --- a/infrastructure/nginx/nginx.conf +++ b/infrastructure/nginx/nginx.conf @@ -155,6 +155,16 @@ http { client_max_body_size 100M; } + location /admin/grafana/ { + rewrite ^/admin/grafana/(.*) /$1 break; + proxy_pass http://grafana:3000/; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + error_page 404 = @notfound; location @notfound { return 444; From 2a13a2f11d8905b067024284908c417b3b14cbdf Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 14:31:33 +0300 Subject: [PATCH 06/20] (scope): [body] [footer(s)] --- infrastructure/nginx/nginx.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf index 838c1e5..5c5cf8f 100644 --- a/infrastructure/nginx/nginx.conf +++ b/infrastructure/nginx/nginx.conf @@ -172,8 +172,8 @@ http { } server { - listen 443 ssl http2 default_server; - listen [::]:443 ssl http2 default_server; + listen 443 ssl http2; + listen [::]:443 ssl http2; server_name prod-team-15-minio-2pc0i3lc.final.prodcontest.ru; ssl_certificate /etc/nginx/certs/fullchain.pem; From 689179080d3f844f035220ba07239ead7930131e Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 14:34:19 +0300 Subject: [PATCH 07/20] add base admin for conpetitions --- services/backend/apps/competition/admin.py | 10 ++++++++++ services/backend/apps/competition/apps.py | 1 + services/backend/apps/competition/models.py | 12 ++++++++---- services/backend/pyproject.toml | 1 + 4 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 services/backend/apps/competition/admin.py diff --git a/services/backend/apps/competition/admin.py b/services/backend/apps/competition/admin.py new file mode 100644 index 0000000..59e6800 --- /dev/null +++ b/services/backend/apps/competition/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from apps.competition.models import Competition + + +@admin.register(Competition) +class CompetitionAdmin(admin.ModelAdmin): + list_display = ("title", "end_date", "type",) + search_fields = ("title", "description",) + list_filter = ("type", "participation_type",) diff --git a/services/backend/apps/competition/apps.py b/services/backend/apps/competition/apps.py index ff3cb4b..d343cd0 100644 --- a/services/backend/apps/competition/apps.py +++ b/services/backend/apps/competition/apps.py @@ -4,3 +4,4 @@ from django.apps import AppConfig class CompetitionsConfig(AppConfig): name = "apps.competition" label = "competition" + verbose_name = "Соревнование" diff --git a/services/backend/apps/competition/models.py b/services/backend/apps/competition/models.py index 589ce91..0c6bd19 100644 --- a/services/backend/apps/competition/models.py +++ b/services/backend/apps/competition/models.py @@ -8,11 +8,11 @@ from apps.user.models import User class Competition(BaseModel): class CompetitionType(models.TextChoices): - SOLO = "solo" + SOLO = "solo", "Индивидуальный" class CompetitionParticipationType(models.TextChoices): - EDU = "edu" - COMPETITIVE = "competitive" + EDU = "edu", "Образовательный" + COMPETITIVE = "competitive", "Соревновательный" title = models.CharField(max_length=100, verbose_name="Название") description = models.TextField(verbose_name="Описание") @@ -35,7 +35,11 @@ class Competition(BaseModel): choices=CompetitionParticipationType.choices, verbose_name="Тип соревнования", ) - participants = models.ManyToManyField(User, related_name="participants") + participants = models.ManyToManyField(User, related_name="participants", blank=True, + editable=False) + + def __str__(self): + return self.title class Meta: verbose_name = "соревнование" diff --git a/services/backend/pyproject.toml b/services/backend/pyproject.toml index 0aaa38b..b6292e0 100644 --- a/services/backend/pyproject.toml +++ b/services/backend/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "psycopg2-binary>=2.9.10", "pydantic>=2.10.5", "pyjwt>=2.10.1", + "python-gettext>=5.0", "python-json-logger>=3.2.1", "pytz>=2024.2", "redis>=5.2.1", From dd10081764cd20d8fc7457cd6d8b684e6d6fd179 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 14:34:59 +0300 Subject: [PATCH 08/20] (scope): [body] [footer(s)] --- infrastructure/nginx/nginx.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf index 5c5cf8f..a5d9cd3 100644 --- a/infrastructure/nginx/nginx.conf +++ b/infrastructure/nginx/nginx.conf @@ -23,6 +23,7 @@ http { ssl_stapling_verify on; resolver 1.1.1.1 1.0.0.1 8.8.8.8 valid=300s; resolver_timeout 5s; + server_names_hash_bucket_size 128; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; From 1018c0280c4b0bfc3f4836fac4c8e13761d77f35 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 14:37:44 +0300 Subject: [PATCH 09/20] added force recreation to update configs --- compose.yaml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/compose.yaml b/compose.yaml index 87231d1..21501ef 100644 --- a/compose.yaml +++ b/compose.yaml @@ -3,6 +3,8 @@ name: datarush services: backend: image: gitlab.prodcontest.ru:5050/team-15/project/backend:latest + build: + context: ./services/backend depends_on: backend-initdb: restart: false @@ -35,6 +37,8 @@ services: backend-initdb: image: gitlab.prodcontest.ru:5050/team-15/project/backend:latest + build: + context: ./services/backend command: ./scripts/initdb depends_on: postgres: @@ -57,6 +61,9 @@ services: backend-staticfiles: image: gitlab.prodcontest.ru:5050/team-15/project/backend-staticfiles:latest + build: + context: ./services/backend + dockerfile: Dockerfile.staticfiles env_file: - path: ./infrastructure/backend/.env.template required: true @@ -79,6 +86,8 @@ services: backend-celery-worker: image: gitlab.prodcontest.ru:5050/team-15/project/backend:latest + build: + context: ./services/backend command: celery -A config worker -l INFO depends_on: redis: @@ -307,12 +316,12 @@ services: - name: api target: 9000 published: 8005 - host_ip: 127.0.0.1 + host_ip: 0.0.0.0 protocol: tcp - name: console target: 9001 published: 8006 - host_ip: 127.0.0.1 + host_ip: 0.0.0.0 protocol: tcp restart: unless-stopped volumes: @@ -338,7 +347,7 @@ services: - name: web target: 9090 published: 8007 - host_ip: 127.0.0.1 + host_ip: 0.0.0.0 protocol: tcp restart: unless-stopped shm_size: 4mb From 6b2289d6d919b33d1953fae424d49045bec71aab Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 14:41:29 +0300 Subject: [PATCH 10/20] (scope): [body] [footer(s)] --- infrastructure/grafana/grafana.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/grafana/grafana.ini b/infrastructure/grafana/grafana.ini index 399a5de..86cb5f8 100644 --- a/infrastructure/grafana/grafana.ini +++ b/infrastructure/grafana/grafana.ini @@ -48,7 +48,7 @@ domain = localhost enforce_domain = false # The full public facing url -root_url = %(protocol)s://%(domain)s:%(http_port)s/admin/grafana/ +root_url = https://prod-team-15-2pc0i3lc.final.prodcontest.ru/admin/grafana/ # Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. serve_from_sub_path = true From 7a4530acca66a2d89d4a7e86f96464c87514e2f7 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 14:43:16 +0300 Subject: [PATCH 11/20] added django admin --- infrastructure/nginx/nginx.conf | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf index a5d9cd3..b8c8484 100644 --- a/infrastructure/nginx/nginx.conf +++ b/infrastructure/nginx/nginx.conf @@ -156,6 +156,19 @@ http { client_max_body_size 100M; } + location /admin { + proxy_pass http://backend:8080; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_buffering off; + proxy_request_buffering off; + client_max_body_size 100M; + } + location /admin/grafana/ { rewrite ^/admin/grafana/(.*) /$1 break; proxy_pass http://grafana:3000/; From e041ce55c8c683e2b99da74b812da05169a6c57b Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 14:55:08 +0300 Subject: [PATCH 12/20] fix --- infrastructure/nginx/nginx.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf index b8c8484..ba8fad3 100644 --- a/infrastructure/nginx/nginx.conf +++ b/infrastructure/nginx/nginx.conf @@ -111,6 +111,7 @@ http { } location /static { + rewrite ^/static/(.*)$ /$1 break; proxy_pass http://backend-staticfiles:80; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; From 4640d37502a27b39107fd7d6871a8edca2571bbe Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 15:39:58 +0300 Subject: [PATCH 13/20] fix healthcheck --- services/backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/backend/Dockerfile b/services/backend/Dockerfile index b55f660..4611598 100644 --- a/services/backend/Dockerfile +++ b/services/backend/Dockerfile @@ -37,6 +37,6 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ EXPOSE 8080 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:8080/health?format=json || exit 1 + CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/api/health?format=json || exit 1 CMD gunicorn config.wsgi --workers=8 -b 0.0.0.0:8080 --access-logfile - --error-logfile - From 36d1f02d1e006e255a6567ed4446e66a8fd09d05 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 16:13:51 +0300 Subject: [PATCH 14/20] update dfknak --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6060120..29ea8e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -80,7 +80,7 @@ deploy: cd ~/deploy docker compose pull > deploy.log 2>&1 docker compose down >> deploy.log 2>&1 - docker compose up -d --remove-orphans >> 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" From 3f5da3847170919dfe25e4f75f20657d9170eedc Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 16:13:54 +0300 Subject: [PATCH 15/20] (scope): [body] [footer(s)] --- infrastructure/minio/.env.template | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/minio/.env.template b/infrastructure/minio/.env.template index 9d55c3a..7b7c699 100644 --- a/infrastructure/minio/.env.template +++ b/infrastructure/minio/.env.template @@ -1,3 +1,4 @@ MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=password MINIO_VOLUMES=/data +MINIO_BROWSER_REDIRECT_URL=https://prod-team-15-minio-2pc0i3lc.final.prodcontest.ru/minio/ui/ From 1cd6505f7a369b1bf1feef6b1e4cab5aaf6b607b Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 16:15:37 +0300 Subject: [PATCH 16/20] add basic admin for task --- services/backend/apps/competition/admin.py | 2 + ...alter_competition_participants_and_more.py | 35 +++++++++++++++ services/backend/apps/task/apps.py | 1 + ..._alter_competitiontask_options_and_more.py | 45 +++++++++++++++++++ services/backend/apps/task/models.py | 34 ++++++++++---- 5 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 services/backend/apps/competition/migrations/0002_competition_tasks_alter_competition_participants_and_more.py create mode 100644 services/backend/apps/task/migrations/0002_alter_competitiontask_options_and_more.py diff --git a/services/backend/apps/competition/admin.py b/services/backend/apps/competition/admin.py index 59e6800..2aca32c 100644 --- a/services/backend/apps/competition/admin.py +++ b/services/backend/apps/competition/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from apps.competition.models import Competition +from apps.task.admin import CompetitionTaskInline @admin.register(Competition) @@ -8,3 +9,4 @@ class CompetitionAdmin(admin.ModelAdmin): list_display = ("title", "end_date", "type",) search_fields = ("title", "description",) list_filter = ("type", "participation_type",) + inlines = [CompetitionTaskInline] diff --git a/services/backend/apps/competition/migrations/0002_competition_tasks_alter_competition_participants_and_more.py b/services/backend/apps/competition/migrations/0002_competition_tasks_alter_competition_participants_and_more.py new file mode 100644 index 0000000..5cfcfdf --- /dev/null +++ b/services/backend/apps/competition/migrations/0002_competition_tasks_alter_competition_participants_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 5.1.6 on 2025-03-01 12:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('competition', '0001_initial'), + ('task', '0001_initial'), + ('user', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='competition', + name='tasks', + field=models.ManyToManyField(blank=True, related_name='tasks', to='task.competitiontask'), + ), + migrations.AlterField( + model_name='competition', + name='participants', + field=models.ManyToManyField(blank=True, editable=False, related_name='participants', to='user.user'), + ), + migrations.AlterField( + model_name='competition', + name='participation_type', + field=models.CharField(choices=[('edu', 'Образовательный'), ('competitive', 'Соревновательный')], max_length=11, verbose_name='Тип соревнования'), + ), + migrations.AlterField( + model_name='competition', + name='type', + field=models.CharField(choices=[('solo', 'Индивидуальный')], max_length=10, verbose_name='Тип участия'), + ), + ] diff --git a/services/backend/apps/task/apps.py b/services/backend/apps/task/apps.py index 46f853d..4fe9436 100644 --- a/services/backend/apps/task/apps.py +++ b/services/backend/apps/task/apps.py @@ -4,3 +4,4 @@ from django.apps import AppConfig class CompetitionsConfig(AppConfig): name = "apps.task" label = "task" + verbose_name = "Задания" diff --git a/services/backend/apps/task/migrations/0002_alter_competitiontask_options_and_more.py b/services/backend/apps/task/migrations/0002_alter_competitiontask_options_and_more.py new file mode 100644 index 0000000..4a14698 --- /dev/null +++ b/services/backend/apps/task/migrations/0002_alter_competitiontask_options_and_more.py @@ -0,0 +1,45 @@ +# Generated by Django 5.1.6 on 2025-03-01 12:21 + +import apps.task.models +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('competition', '0002_competition_tasks_alter_competition_participants_and_more'), + ('task', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='competitiontask', + options={'verbose_name': 'задание', 'verbose_name_plural': 'задания'}, + ), + migrations.AlterField( + model_name='competitiontask', + name='competition', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition', verbose_name='соревнование'), + ), + migrations.AlterField( + model_name='competitiontask', + name='correct_answer_file', + field=models.FileField(blank=True, null=True, upload_to=apps.task.models.CompetitionTask.answer_file_upload_to, verbose_name='правильный ответ'), + ), + migrations.AlterField( + model_name='competitiontask', + name='criteries', + field=models.JSONField(blank=True, null=True, verbose_name='критерии проверки'), + ), + migrations.AlterField( + model_name='competitiontask', + name='points', + field=models.IntegerField(blank=True, null=True, verbose_name='баллы за задание'), + ), + migrations.AlterField( + model_name='competitiontask', + name='type', + field=models.CharField(choices=[('input', 'Input'), ('checker', 'Checker'), ('review', 'Review')], max_length=8, verbose_name='тип задания'), + ), + ] diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index 08fe998..bd3511e 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -10,33 +10,49 @@ from apps.user.models import User class CompetitionTask(BaseModel): class CompetitionTaskType(models.TextChoices): - INPUT = "input" - CHECKER = "checker" - REVIEW = "review" + INPUT = "input", "Ввод правильного ответа" + CHECKER = "checker", "Вывод кода" + REVIEW = "review", "Ручная" def answer_file_upload_to(instance, filename) -> str: return f"/tasks/{instance.id}/answer/{uuid4()}/filename" competition = models.ForeignKey(Competition, on_delete=models.CASCADE) - title = models.TextField(verbose_name="заголовок", max_length=50) + title = models.CharField(verbose_name="заголовок", max_length=50) description = models.TextField(verbose_name="описание", max_length=300) - type = models.CharField(choices=CompetitionTaskType, max_length=8) + type = models.CharField(choices=CompetitionTaskType, max_length=8, + verbose_name="тип проверки") # only when "input" or "checker" type correct_answer_file = models.FileField( - upload_to=answer_file_upload_to, null=True, blank=True + upload_to=answer_file_upload_to, null=True, blank=True, + verbose_name="файл с правильным ответом" ) - points = models.IntegerField(null=True, blank=True) + points = models.IntegerField(null=True, blank=True, + verbose_name="баллы за задание") # only when "checker" type - answer_file_path = models.TextField(null=True, blank=True) + answer_file_path = models.TextField(null=True, blank=True, + verbose_name="куда сохранять решения", + default="stdout") # only when "review" type - criteries = models.JSONField(blank=True, null=True) + # todo make it more humanize + criteries = models.JSONField(blank=True, null=True, + verbose_name="критерии", + default=lambda: [{"name": "CHANGE ME", "slug": "CHANGE ME", "max_value": 0, "min_value": 0}] + ) def clean(self): ContestTaskCriteriesValidator()(self) + def __str__(self): + return self.title + + class Meta: + verbose_name = "задание" + verbose_name_plural = "задания" + class CompetetionTaskSumbission(BaseModel): class StatusChoices(models.TextChoices): From 5bdad3daefd2958957aab12c2fbc7659a25890aa Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 16:24:06 +0300 Subject: [PATCH 17/20] increased timeout for gunicorn workers --- services/backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/backend/Dockerfile b/services/backend/Dockerfile index 4611598..2678475 100644 --- a/services/backend/Dockerfile +++ b/services/backend/Dockerfile @@ -39,4 +39,4 @@ EXPOSE 8080 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:8080/api/health?format=json || exit 1 -CMD gunicorn config.wsgi --workers=8 -b 0.0.0.0:8080 --access-logfile - --error-logfile - +CMD gunicorn config.wsgi --workers=8 -b 0.0.0.0:8080 --access-logfile - --error-logfile - --timeout=600 From 6ae880778b6f9e0dbbd18ab48bfda4fc3d5ac6dd Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 16:28:03 +0300 Subject: [PATCH 18/20] add task admin file --- services/backend/apps/task/admin.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 services/backend/apps/task/admin.py diff --git a/services/backend/apps/task/admin.py b/services/backend/apps/task/admin.py new file mode 100644 index 0000000..a466334 --- /dev/null +++ b/services/backend/apps/task/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin + +from apps.task.models import CompetitionTask + + +@admin.register(CompetitionTask) +class CompetitionTaskAdmin(admin.ModelAdmin): + list_display = ("title", "type", "points") + + +class CompetitionTaskInline(admin.StackedInline): + model = CompetitionTask + extra = 0 From 57607c7fd40d5c8ac55cfb1c3da362096fd1990c Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 16:39:17 +0300 Subject: [PATCH 19/20] removed cors from nginx --- infrastructure/nginx/nginx.conf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf index ba8fad3..2773428 100644 --- a/infrastructure/nginx/nginx.conf +++ b/infrastructure/nginx/nginx.conf @@ -147,11 +147,6 @@ http { return 204; } - add_header 'Access-Control-Allow-Origin' "$http_origin" always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; - add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - proxy_buffering off; proxy_request_buffering off; client_max_body_size 100M; From 9959b431bea861268b8fc1264725a98310fb3193 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 16:40:05 +0300 Subject: [PATCH 20/20] added max_attemps --- services/backend/apps/task/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index 08fe998..2c7454f 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -21,6 +21,7 @@ class CompetitionTask(BaseModel): title = models.TextField(verbose_name="заголовок", max_length=50) description = models.TextField(verbose_name="описание", max_length=300) type = models.CharField(choices=CompetitionTaskType, max_length=8) + max_attemps = models.PositiveSmallIntegerField() # only when "input" or "checker" type correct_answer_file = models.FileField(