Merge branch 'master' of gitlab.prodcontest.ru:team-15/project

This commit is contained in:
ITQ
2025-03-01 14:15:36 +03:00
14 changed files with 191 additions and 57 deletions
+4 -5
View File
@@ -1,26 +1,25 @@
from abc import ABC
from typing import Optional
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.urls import resolve
from ninja.errors import AuthenticationError
from ninja.security import APIKeyQuery
from ninja.security.apikey import APIKeyBase
from apps.review.models import Reviewer
class APIKeyPath(APIKeyBase, ABC):
openapi_in: str = "path"
def _get_key(self, request: HttpRequest) -> Optional[str]:
def _get_key(self, request: HttpRequest) -> str | None:
func, args, kwargs = resolve(request.path)
return kwargs.get(self.param_name)
class ReviewerAuth(APIKeyPath):
param_name = "token"
def authenticate(self, request, token):
if not (reviewer := Reviewer.objects.filter(token=token).first()):
raise AuthenticationError
return reviewer
return reviewer
+8 -8
View File
@@ -1,8 +1,8 @@
from typing import List, Literal
from typing import Literal
from uuid import UUID
from django.http import HttpRequest
from ninja import Schema, ModelSchema
from ninja import ModelSchema, Schema
from apps.review.models import Reviewer
from apps.task.models import CompetetionTaskSumbission
@@ -11,6 +11,7 @@ from apps.task.models import CompetetionTaskSumbission
class PingOut(Schema):
status: str = "ok"
class ReviewerOut(ModelSchema):
id: UUID
@@ -18,20 +19,19 @@ class ReviewerOut(ModelSchema):
model = Reviewer
exclude = ("token",)
class SubmissionOut(ModelSchema):
id: UUID
status: Literal["sent", "checking", "checked"]
class Meta:
model = CompetetionTaskSumbission
exclude = (
"user",
)
exclude = ("user",)
class SubmissionsOut(Schema):
submissions: list[SubmissionOut] = []
@staticmethod
def resolve_submissions(self, context: HttpRequest) -> List[SubmissionOut]:
print(CompetetionTaskSumbission.objects.all())
return list(CompetetionTaskSumbission.objects.all())
def resolve_submissions(self, context: HttpRequest) -> list[SubmissionOut]:
return list(CompetetionTaskSumbission.objects.all())
+8 -10
View File
@@ -3,19 +3,20 @@ from http import HTTPStatus as status
from django.http import HttpRequest
from ninja import Router
from api.v1.review import schemas
from api.v1 import schemas as global_schemas
from api.v1.review import schemas
router = Router(tags=["review"])
@router.get(
"{token}/tasks",
"{token}/submissions",
response={
status.OK: schemas.SubmissionsOut,
},
description="Список отправок, на проверку которых назначен ревьюер"
)
def ping(request: HttpRequest, token) -> tuple[status, schemas.SubmissionsOut]:
def get_submissions(request: HttpRequest, token) -> tuple[status, schemas.SubmissionsOut]:
return status.OK, schemas.SubmissionsOut()
@@ -23,12 +24,9 @@ def ping(request: HttpRequest, token) -> tuple[status, schemas.SubmissionsOut]:
"{token}",
response={
status.OK: schemas.ReviewerOut,
status.UNAUTHORIZED: global_schemas.UnauthorizedError
status.UNAUTHORIZED: global_schemas.UnauthorizedError,
},
description="token есть и в сваггер авторизации, но оно не работает, не верьте. подставляйте токен вручную в query"
description="token есть и в сваггер авторизации, но оно не работает, не верьте. подставляйте токен вручную в query",
)
def get_reviewer(
request: HttpRequest,
token: str
):
return status.OK, request.auth
def get_reviewer_profile(request: HttpRequest, token: str):
return status.OK, request.auth
+1 -2
View File
@@ -7,8 +7,8 @@ from api.v1.auth import BearerAuth
from api.v1.competition.views import router as competition_router
from api.v1.ping.views import router as ping_router
from api.v1.review.auth import ReviewerAuth
from api.v1.user.views import router as user_router
from api.v1.review.views import router as review_router
from api.v1.user.views import router as user_router
router = NinjaAPI(
title="DataRush API",
@@ -39,6 +39,5 @@ router.add_router(
)
for exception, handler in handlers.exception_handlers:
router.add_exception_handler(exception, partial(handler, router=router))
+1 -1
View File
@@ -1,8 +1,8 @@
from typing import Literal
from uuid import UUID
from ninja import ModelSchema, Schema
from apps.competition.models import State
from apps.task.models import CompetitionTask
+13 -10
View File
@@ -3,21 +3,20 @@ from uuid import UUID
from django.shortcuts import get_object_or_404
from ninja import Router
from django.shortcuts import get_object_or_404
from api.v1.schemas import NotFoundError, UnauthorizedError, ForbiddenError
from api.v1.ping.schemas import PingOut
from api.v1.schemas import ForbiddenError, NotFoundError, UnauthorizedError
from api.v1.task.schemas import (
TaskOutSchema,
TaskSubmissionOut,
TaskSubmissionIn,
)
from apps.task.models import (
Competition,
CompetitionTask,
CompetetionTaskSumbission,
TaskSubmissionOut,
)
from apps.competition.models import State
from apps.task.models import (
CompetetionTaskSumbission,
Competition,
CompetitionTask,
)
router = Router(tags=["competition"])
@@ -49,7 +48,9 @@ def start_competition(request, competition_id: UUID) -> PingOut:
status.NOT_FOUND: NotFoundError,
},
)
def get_competition_tasks(request, competition_id: UUID) -> list[TaskOutSchema]:
def get_competition_tasks(
request, competition_id: UUID
) -> list[TaskOutSchema]:
competition = get_object_or_404(Competition, pk=competition_id)
state = State.objects.filter(
user=request.auth, competition=competition, state="started"
@@ -57,7 +58,9 @@ def get_competition_tasks(request, competition_id: UUID) -> list[TaskOutSchema]:
if not state:
return 403, ForbiddenError()
return status.OK, CompetitionTask.objects.filter(competition=competition).all()
return status.OK, CompetitionTask.objects.filter(
competition=competition
).all()
@router.get(
+1
View File
@@ -64,6 +64,7 @@ def sign_in(request, data: LoginSchema):
def get_me(request):
return 200, request.auth
@router.get(
path="/user/{user_id}",
response={
@@ -0,0 +1,136 @@
import random
import uuid
from datetime import timedelta
from django.contrib.auth.hashers import make_password
from django.core.files.base import ContentFile
from django.core.management.base import BaseCommand
from django.utils import timezone
from apps.competition.models import Competition, State
from apps.task.models import CompetetionTaskSumbission, CompetitionTask
from apps.user.models import User, UserRole
class Command(BaseCommand):
help = "Generate sample data for Users, Competitions, Tasks, Submissions, and States."
def handle(self, *args, **options):
self.stdout.write("Starting data generation...")
users = self.create_users(5)
competitions = self.create_competitions(2, users)
tasks = self.create_tasks(competitions)
self.create_submissions(tasks, users)
self.create_states(competitions, users)
self.stdout.write("Data generation completed.")
def create_users(self, count):
users = []
for i in range(1, count + 1):
email = f"user{i}@example.com"
username = f"user{i}"
password = (
"password123" # In production, use proper password handling.
)
role = random.choice(
[UserRole.STUDENT.value, UserRole.METODIST.value]
)
user, created = User.objects.get_or_create(
email=email,
defaults={
"username": username,
"password": make_password(password),
"status": role,
},
)
users.append(user)
self.stdout.write(f"Created user: {username}")
return users
def create_competitions(self, count, users):
competitions = []
now = timezone.now()
for i in range(1, count + 1):
title = f"Competition {i}"
description = f"Description for competition {i}"
start_date = now - timedelta(days=random.randint(1, 10))
end_date = now + timedelta(days=random.randint(1, 10))
competition = Competition.objects.create(
title=title,
description=description,
start_date=start_date,
end_date=end_date,
type="solo", # assuming only one type for now
participation_type=random.choice(["edu", "competitive"]),
)
# Add random participants
selected_users = random.sample(
users, k=min(len(users), random.randint(1, len(users)))
)
competition.participants.add(*selected_users)
competitions.append(competition)
self.stdout.write(f"Created competition: {title}")
return competitions
def create_tasks(self, competitions):
tasks = []
task_types = [
CompetitionTask.CompetitionTaskType.INPUT.value,
]
for comp in competitions:
# Create 3 tasks per competition
for i in range(1, 4):
task_type = random.choice(task_types)
title = f"Task {i} for {comp.title}"
description = f"Task description for task {i} in {comp.title}"
task = CompetitionTask.objects.create(
competition=comp,
title=title,
description=description,
type=task_type,
points=random.randint(1, 10),
)
tasks.append(task)
self.stdout.write(f"Created task: {title} (type: {task_type})")
return tasks
def create_submissions(self, tasks, users):
for task in tasks:
# Each task will get between 1 and 3 submissions
num_submissions = random.randint(1, 3)
for _ in range(num_submissions):
user = random.choice(users)
# Create a dummy content file
dummy_content = ContentFile(
b"Submission content",
name=f"submission_{uuid.uuid4().hex}.txt",
)
submission = CompetetionTaskSumbission.objects.create(
user=user,
task=task,
earned_points=random.randint(
0, task.points if task.points else 10
),
content=dummy_content,
)
submission.save()
self.stdout.write(
f"Created submission for task '{task.title}' by user '{user.username}'"
)
def create_states(self, competitions, users):
# For each competition, create a State for some of its participants
state_choices = [choice for choice in State.StateChoices.values]
for comp in competitions:
for user in comp.participants.all():
state_obj, created = State.objects.get_or_create(
user=user,
competition=comp,
defaults={
"state": random.choice(state_choices),
"changed_at": timezone.now(),
},
)
self.stdout.write(
f"Created state '{state_obj.state}' for user '{user.username}' in competition '{comp.title}'"
)
+1 -1
View File
@@ -7,4 +7,4 @@ class Reviewer(BaseModel):
name = models.CharField(max_length=100)
surname = models.CharField(max_length=100)
token = models.CharField(max_length=100)
token = models.CharField(max_length=100)
+3 -3
View File
@@ -1,13 +1,13 @@
from random import choice
from uuid import uuid4
from django.db import models
from apps.task.validators import ContestTaskCriteriesValidator
from apps.competition.models import Competition
from apps.core.models import BaseModel
from apps.task.validators import ContestTaskCriteriesValidator
from apps.user.models import User
class CompetitionTask(BaseModel):
class CompetitionTaskType(models.TextChoices):
INPUT = "input"
@@ -45,7 +45,7 @@ class CompetetionTaskSumbission(BaseModel):
CHECKED = "checked"
def submission_content_upload_to(instance, filename) -> str:
return f"/submissions/{instance.id}/content"
return f"submissions/{instance.id}/content"
def submission_stdout_upload_to(instance, filename) -> str:
return f"/submissions/{instance.id}/stdout"
+11 -10
View File
@@ -1,9 +1,10 @@
import tempfile
import ast
import hashlib
import os
import sys
import ast
import tempfile
from io import StringIO
import hashlib
from config.celery import app
ALLOWED_MODULES = {
@@ -29,7 +30,7 @@ def validate_code(code_str):
try:
tree = ast.parse(code_str)
except SyntaxError as e:
raise SecurityException(f"Syntax error: {str(e)}")
raise SecurityException(f"Syntax error: {e!s}")
class ImportVisitor(ast.NodeVisitor):
def visit_Import(self, node):
@@ -56,10 +57,10 @@ def validate_code(code_str):
try:
ImportVisitor().visit(tree)
SecurityVisitor().visit(tree)
except SecurityException as e:
except SecurityException:
raise
except Exception as e:
raise SecurityException(f"Security check failed: {str(e)}")
raise SecurityException(f"Security check failed: {e!s}")
def secure_exec(code_str, result_path):
@@ -95,7 +96,7 @@ def secure_exec(code_str, result_path):
result_content = f.read()
except Exception as e:
raise RuntimeError(f"Execution error: {str(e)}")
raise RuntimeError(f"Execution error: {e!s}")
finally:
os.chdir(original_dir)
sys.stdout = original_stdout
@@ -121,8 +122,8 @@ def analyze_data_task(self, code_str, result_path, expected_bytes):
}
except SecurityException as e:
return {"success": False, "error": f"Security violation: {str(e)}"}
return {"success": False, "error": f"Security violation: {e!s}"}
except RuntimeError as e:
return {"success": False, "error": f"Execution error: {str(e)}"}
return {"success": False, "error": f"Execution error: {e!s}"}
except Exception as e:
return {"success": False, "error": f"Unexpected error: {str(e)}"}
return {"success": False, "error": f"Unexpected error: {e!s}"}
+2 -2
View File
@@ -12,12 +12,12 @@ class Criteria(BaseModel):
class ContestTaskCriteriesValidator:
def __call__(self, instance):
if instance.criterties and not isinstance(instance.criterties, list):
if instance.criteries and not isinstance(instance.criteries, list):
err = "criteries must be a valid dictionary"
raise ValidationError(err)
try:
for criteria in instance.criterties:
for criteria in instance.criteries if instance.criteries else []:
Criteria(**criteria)
except PydanticValidationError:
err = "invalid criteries data"
+1 -4
View File
@@ -24,9 +24,6 @@ class TestSignUp(TestCase):
user.full_clean()
def test_missing_params(self):
user = User(
password="123123",
username="132131232131"
)
user = User(password="123123", username="132131232131")
with self.assertRaises(ValidationError):
user.full_clean()
+1 -1
View File
@@ -446,7 +446,7 @@ INSTALLED_APPS = [
"apps.user",
"apps.competition",
"apps.review",
"apps.task"
"apps.task",
]
# GUID