mirror of
https://gitlab.com/megazordpobeda/DataRush.git
synced 2026-05-22 22:07:10 +00:00
rewrite migrations; fix comptitions tests
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 20:35
|
||||
# Generated by Django 5.1.6 on 2025-03-01 23:48
|
||||
|
||||
import apps.competition.models
|
||||
import datetime
|
||||
@@ -12,7 +12,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('user', '0002_alter_user_email_alter_user_password_and_more'),
|
||||
('user', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -22,11 +22,11 @@ class Migration(migrations.Migration):
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('title', models.CharField(max_length=100, verbose_name='название')),
|
||||
('description', models.TextField(verbose_name='описание')),
|
||||
('image_url', models.FileField(blank=True, null=True, upload_to=apps.competition.models.Competition.image_url_upload_to, verbose_name='изображение соревнования')),
|
||||
('image_url', models.ImageField(blank=True, null=True, upload_to=apps.competition.models.Competition.image_url_upload_to, verbose_name='изображение соревнования')),
|
||||
('end_date', models.DateTimeField(blank=True, null=True, verbose_name='дедлайн участия')),
|
||||
('start_date', models.DateTimeField(blank=True, null=True, verbose_name='дедлайн участия')),
|
||||
('type', models.CharField(choices=[('solo', 'Индивидуальный')], max_length=10, verbose_name='тип участия')),
|
||||
('participation_type', models.CharField(choices=[('edu', 'Образовательный'), ('competitive', 'Соревновательный')], max_length=11, verbose_name='тип соревнования')),
|
||||
('type', models.CharField(choices=[('edu', 'Образовательный'), ('competitive', 'Соревновательный')], max_length=11, verbose_name='тип участия')),
|
||||
('participation_type', models.CharField(choices=[('solo', 'Индивидуальный')], max_length=11, verbose_name='тип соревнования')),
|
||||
('participants', models.ManyToManyField(blank=True, editable=False, related_name='participants', to='user.user')),
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 22:16
|
||||
|
||||
import apps.competition.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('competition', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='competition',
|
||||
name='image_url',
|
||||
field=models.ImageField(blank=True, null=True, upload_to=apps.competition.models.Competition.image_url_upload_to, verbose_name='изображение соревнования'),
|
||||
),
|
||||
]
|
||||
@@ -21,8 +21,8 @@ class CompetitionEndpointTests(TestCase):
|
||||
self.competition = Competition.objects.create(
|
||||
title="AI Challenge",
|
||||
description="Machine Learning Competition",
|
||||
type="solo",
|
||||
participation_type="edu",
|
||||
type="edu",
|
||||
participation_type="solo",
|
||||
)
|
||||
|
||||
resp = self.client.post(
|
||||
@@ -34,13 +34,10 @@ class CompetitionEndpointTests(TestCase):
|
||||
|
||||
self.valid_headers = {"HTTP_AUTHORIZATION": f"Bearer {token}"}
|
||||
|
||||
# --- Helper methods ---
|
||||
def get_url(self, competition_id):
|
||||
return f"/api/v1/competition/{competition_id}"
|
||||
|
||||
# --- Test Cases ---
|
||||
def test_get_competition_success(self):
|
||||
"""Authenticated user gets competition details (200 OK)"""
|
||||
response = self.client.get(
|
||||
self.get_url(self.competition.id), **self.valid_headers
|
||||
)
|
||||
@@ -51,7 +48,7 @@ class CompetitionEndpointTests(TestCase):
|
||||
# Validate required fields
|
||||
self.assertEqual(data["id"], str(self.competition.id))
|
||||
self.assertEqual(data["title"], "AI Challenge")
|
||||
self.assertEqual(data["type"], "solo")
|
||||
self.assertEqual(data["type"], "edu")
|
||||
|
||||
# Validate optional null fields
|
||||
self.assertIsNone(data["image_url"])
|
||||
@@ -59,20 +56,17 @@ class CompetitionEndpointTests(TestCase):
|
||||
self.assertIsNone(data["end_date"])
|
||||
|
||||
def test_invalid_uuid_format(self):
|
||||
"""Invalid UUID format returns 400 Bad Request"""
|
||||
response = self.client.get(
|
||||
self.get_url("invalid-id"), **self.valid_headers
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_unauthenticated_access(self):
|
||||
"""Missing auth token returns 401 Unauthorized"""
|
||||
response = self.client.get(self.get_url(self.competition.id))
|
||||
self.assertEqual(response.status_code, 401)
|
||||
self.assertEqual(response.json()["detail"], "Unauthorized")
|
||||
|
||||
def test_nonexistent_competition(self):
|
||||
"""Valid UUID but missing competition returns 404"""
|
||||
new_uuid = uuid.uuid4()
|
||||
response = self.client.get(
|
||||
self.get_url(new_uuid), **self.valid_headers
|
||||
@@ -81,7 +75,6 @@ class CompetitionEndpointTests(TestCase):
|
||||
self.assertEqual(response.json()["detail"], "Not Found")
|
||||
|
||||
def test_invalid_auth_token(self):
|
||||
"""Invalid token returns 401 Unauthorized"""
|
||||
response = self.client.get(
|
||||
self.get_url(self.competition.id),
|
||||
HTTP_AUTHORIZATION="Bearer invalid_token",
|
||||
@@ -90,7 +83,6 @@ class CompetitionEndpointTests(TestCase):
|
||||
self.assertEqual(response.json()["detail"], "Unauthorized")
|
||||
|
||||
def test_malformed_auth_header(self):
|
||||
"""Malformed Authorization header returns 401"""
|
||||
cases = [
|
||||
("InvalidScheme valid_token_123", 401),
|
||||
("Bearer", 401), # Missing token
|
||||
@@ -128,11 +120,11 @@ class CompetitionsEndpointTests(TestCase):
|
||||
competition = Competition.objects.create(
|
||||
title=f"Competition {i}",
|
||||
description=f"Description {i}",
|
||||
type=Competition.CompetitionType.SOLO,
|
||||
participation_type=(
|
||||
Competition.CompetitionParticipationType.EDU if i % 2 == 0
|
||||
else Competition.CompetitionParticipationType.COMPETITIVE
|
||||
type=(
|
||||
Competition.CompetitionType.EDU if i % 2 == 0
|
||||
else Competition.CompetitionType.COMPETITIVE
|
||||
),
|
||||
participation_type=Competition.CompetitionParticipationType.SOLO,
|
||||
start_date=(now + timedelta(days=i)).isoformat(),
|
||||
end_date=(now + timedelta(days=i + 7)).isoformat(),
|
||||
)
|
||||
@@ -149,7 +141,6 @@ class CompetitionsEndpointTests(TestCase):
|
||||
return f"{base_url}?{params}" if params else base_url
|
||||
|
||||
def test_get_participating_competitions(self):
|
||||
"""Test filtering competitions where user is participating"""
|
||||
response = self.client.get(
|
||||
self.get_url("is_participating=true"),
|
||||
**self.valid_headers
|
||||
@@ -164,17 +155,15 @@ class CompetitionsEndpointTests(TestCase):
|
||||
)
|
||||
|
||||
def test_competition_type_values(self):
|
||||
"""Test competition type choices are respected"""
|
||||
response = self.client.get(
|
||||
self.get_url("is_participating=true"),
|
||||
**self.valid_headers
|
||||
)
|
||||
|
||||
for item in response.json():
|
||||
self.assertEqual(item["type"], "solo")
|
||||
self.assertEqual(item["type"], "competitive")
|
||||
|
||||
def test_participation_type_values(self):
|
||||
"""Test participation type alternates between edu/competitive"""
|
||||
response = self.client.get(
|
||||
self.get_url("is_participating=false"),
|
||||
**self.valid_headers
|
||||
@@ -183,11 +172,10 @@ class CompetitionsEndpointTests(TestCase):
|
||||
types = [item["participation_type"] for item in response.json()]
|
||||
self.assertCountEqual(
|
||||
types,
|
||||
["competitive", "edu", "competitive"]
|
||||
["solo", "solo", "solo"]
|
||||
)
|
||||
|
||||
def test_datetime_formatting(self):
|
||||
"""Test start/end date ISO formatting"""
|
||||
response = self.client.get(
|
||||
self.get_url("is_participating=true"),
|
||||
**self.valid_headers
|
||||
@@ -206,7 +194,6 @@ class CompetitionsEndpointTests(TestCase):
|
||||
self.fail("Invalid end_date format")
|
||||
|
||||
def test_competition_metadata(self):
|
||||
"""Test competition metadata fields"""
|
||||
response = self.client.get(
|
||||
self.get_url("is_participating=true"),
|
||||
**self.valid_headers
|
||||
@@ -215,27 +202,25 @@ class CompetitionsEndpointTests(TestCase):
|
||||
item = response.json()[0]
|
||||
self.assertEqual(item["title"], "Competition 1")
|
||||
self.assertEqual(item["description"], "Description 1")
|
||||
self.assertEqual(item["type"], "solo")
|
||||
self.assertEqual(item["participation_type"], "competitive")
|
||||
self.assertEqual(item["type"], "competitive")
|
||||
self.assertEqual(item["participation_type"], "solo")
|
||||
|
||||
def test_verbose_name_consistency(self):
|
||||
"""Test model verbose names don't affect API schema"""
|
||||
response = self.client.get(
|
||||
self.get_url("is_participating=true"),
|
||||
**self.valid_headers
|
||||
)
|
||||
|
||||
item = response.json()[0]
|
||||
self.assertNotIn("название", item) # Russian verbose name
|
||||
self.assertIn("title", item) # Actual API field name
|
||||
self.assertNotIn("название", item)
|
||||
self.assertIn("title", item)
|
||||
|
||||
def test_null_dates_handling(self):
|
||||
"""Test competitions with null dates"""
|
||||
competition = Competition.objects.create(
|
||||
title="No Dates Competition",
|
||||
description="Test competition",
|
||||
type=Competition.CompetitionType.SOLO,
|
||||
participation_type=Competition.CompetitionParticipationType.EDU
|
||||
type=Competition.CompetitionType.EDU,
|
||||
participation_type=Competition.CompetitionParticipationType.SOLO
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
@@ -251,7 +236,6 @@ class CompetitionsEndpointTests(TestCase):
|
||||
self.assertIsNone(test_item["end_date"])
|
||||
|
||||
def test_participation_status_filtering(self):
|
||||
"""Test filtering by participation_type"""
|
||||
response = self.client.get(
|
||||
self.get_url("is_participating=false"),
|
||||
**self.valid_headers
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 20:35
|
||||
# Generated by Django 5.1.6 on 2025-03-01 23:48
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -9,20 +10,10 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('task', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Review',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('evaluation', models.JSONField(blank=True, default=list, null=True)),
|
||||
('state', models.CharField(choices=[('not_checked', 'Not Checked'), ('checking', 'Checking'), ('checked', 'Checked')], default='not_checked', max_length=11)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Reviewer',
|
||||
fields=[
|
||||
@@ -35,4 +26,17 @@ class Migration(migrations.Migration):
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Review',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('evaluation', models.JSONField(blank=True, default=list, null=True)),
|
||||
('state', models.CharField(choices=[('not_checked', 'Not Checked'), ('checking', 'Checking'), ('checked', 'Checked')], default='not_checked', max_length=11)),
|
||||
('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='task.competitiontasksubmission')),
|
||||
('reviewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.reviewer')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 20:35
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('review', '0001_initial'),
|
||||
('task', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='review',
|
||||
name='submission',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='task.competitiontasksubmission'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='review',
|
||||
name='reviewer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.reviewer'),
|
||||
),
|
||||
]
|
||||
@@ -1,11 +1,17 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from apps.task.models import CompetitionTask
|
||||
from apps.task.models import CompetitionTask, CompetitionTaskAttachment
|
||||
|
||||
|
||||
class CompletionAttachmentInline(admin.StackedInline):
|
||||
model = CompetitionTaskAttachment
|
||||
extra = 0
|
||||
|
||||
|
||||
@admin.register(CompetitionTask)
|
||||
class CompetitionTaskAdmin(admin.ModelAdmin):
|
||||
list_display = ("title", "type", "points")
|
||||
inlines = [CompletionAttachmentInline]
|
||||
|
||||
|
||||
class CompetitionTaskInline(admin.StackedInline):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 20:35
|
||||
# Generated by Django 5.1.6 on 2025-03-01 23:48
|
||||
|
||||
import apps.task.models
|
||||
import django.db.models.deletion
|
||||
@@ -13,7 +13,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('competition', '0001_initial'),
|
||||
('user', '0002_alter_user_email_alter_user_password_and_more'),
|
||||
('user', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -32,6 +32,7 @@ class Migration(migrations.Migration):
|
||||
('competition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'задание',
|
||||
'verbose_name_plural': 'задания',
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 22:16
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('task', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='competitiontask',
|
||||
options={'verbose_name': 'задание', 'verbose_name_plural': 'задания'},
|
||||
),
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 22:16
|
||||
# Generated by Django 5.1.6 on 2025-03-01 23:48
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
@@ -10,7 +10,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('user', '0002_alter_user_email_alter_user_password_and_more'),
|
||||
('user', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -27,4 +27,16 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'команды',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TeamInvite',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('link', models.UUIDField(verbose_name='инвайт')),
|
||||
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='team.team', verbose_name='команда')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'приглашение',
|
||||
'verbose_name_plural': 'приглашения',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
|
||||
from apps.core.models import BaseModel
|
||||
@@ -17,3 +19,13 @@ class Team(BaseModel):
|
||||
class Meta:
|
||||
verbose_name = "команда"
|
||||
verbose_name_plural = "команды"
|
||||
|
||||
|
||||
class TeamInvite(BaseModel):
|
||||
team = models.ForeignKey(Team, on_delete=models.CASCADE,
|
||||
verbose_name="команда")
|
||||
link = models.UUIDField(verbose_name="инвайт")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "приглашение"
|
||||
verbose_name_plural = "приглашения"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 08:47
|
||||
# Generated by Django 5.1.6 on 2025-03-01 23:48
|
||||
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
@@ -16,9 +16,9 @@ class Migration(migrations.Migration):
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='Почта')),
|
||||
('username', models.SlugField(unique=True, verbose_name='Юзернейм')),
|
||||
('password', models.TextField(verbose_name='Пароль')),
|
||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='почта')),
|
||||
('username', models.SlugField(unique=True, verbose_name='юзернейм')),
|
||||
('password', models.TextField(verbose_name='пароль')),
|
||||
('status', models.CharField(choices=[('student', 'Student'), ('metodist', 'Metodist')], default='student', max_length=10)),
|
||||
],
|
||||
options={
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 14:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='email',
|
||||
field=models.EmailField(max_length=254, unique=True, verbose_name='почта'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='password',
|
||||
field=models.TextField(verbose_name='пароль'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='username',
|
||||
field=models.SlugField(unique=True, verbose_name='юзернейм'),
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user