💻 Примеры кода проекта Agile Projects

⚡ Ключевые фрагменты

# Модель Tag с MinLengthValidator
from django.core.validators import MinLengthValidator
class Tag(models.Model):
    name = models.CharField(max_length=20, validators=[MinLengthValidator(4)])
    def __str__(self): return self.name

# Deadline = последний день текущего месяца
def calculate_end_of_month():
    now = timezone.now()
    last_day = calendar.monthrange(now.year, now.month)[1]
    return datetime(now.year, now.month, last_day).astimezone()

# APIView: get с фильтрацией
def get(self, request):
    date_from = request.query_params.get('date_from')
    date_to   = request.query_params.get('date_to')
    projects  = self.get_objects(date_from, date_to)
    ...

1. Модель Tag (apps/tasks/models/tag.py)

from django.db import models
from django.core.validators import MinLengthValidator

class Tag(models.Model):
    name = models.CharField(
        max_length=20,
        validators=[MinLengthValidator(4)]
    )

    def __str__(self):
        return self.name

Минимальная длина задаётся через MinLengthValidator — валидатор уровня Django, не базы данных. CHECK CONSTRAINT в БД не создаётся.

2. Enum классы (apps/tasks/choices/)

# statuses.py
from enum import Enum

class Statuses(Enum):
    NEW = "NEW"
    IN_PROGRESS = "IN_PROGRESS"
    PENDING = "PENDING"
    BLOCKED = "BLOCKED"
    TESTING = "TESTING"
    CLOSED = "CLOSED"

    @classmethod
    def choices(cls):
        return [(attr.name, attr.value) for attr in cls]

# priority.py
class Priority(Enum):
    VERY_LOW = (1, 'Very Low')
    LOW = (2, 'Low')
    MEDIUM = (3, 'Medium')
    HIGH = (4, 'High')
    CRITICAL = (5, 'Critical')

    @classmethod
    def choices(cls):
        return [(key.value[0], key.value[1]) for key in cls]

    def __getitem__(self, item):
        return self.value[item]

3. Утилита расчёта дедлайна (apps/tasks/utils/set_end_of_month.py)

from datetime import datetime
import calendar
from django.utils import timezone

def calculate_end_of_month() -> datetime:
    current_date = timezone.now()
    # monthrange(год, месяц) → (день_недели_первого, кол-во_дней)
    amount_of_days = calendar.monthrange(
        current_date.year,
        current_date.month
    )[1]
    date = datetime(
        year=current_date.year,
        month=current_date.month,
        day=amount_of_days,
    )
    return date.astimezone()  # конвертировать в локальный часовой пояс

4. Сериализатор с кастомной валидацией (project_serializers.py)

class CreateProjectSerializer(serializers.ModelSerializer):
    created_at = serializers.DateTimeField(read_only=True)

    class Meta:
        model = Project
        fields = ('name', 'description', 'created_at')

    def validate_description(self, value: str) -> str:
        if len(value) < 30:
            raise serializers.ValidationError(
                "Description must be at least 30 characters long"
            )
        return value

Метод validate_<field_name> вызывается автоматически при is_valid().

5. Фильтрация по диапазону дат (project_views.py)

from django.utils import timezone
from datetime import datetime

class ProjectsListAPIView(APIView):
    def get_objects(self, date_from=None, date_to=None):
        if date_from and date_to:
            # Строки → datetime → aware datetime (с учётом timezone)
            date_from = timezone.make_aware(
                datetime.strptime(date_from, '%Y-%m-%d')
            )
            date_to = timezone.make_aware(
                datetime.strptime(date_to, '%Y-%m-%d')
            )
            return Project.objects.filter(
                created_at__range=[date_from, date_to]
            )
        return Project.objects.all()

    def get(self, request: Request) -> Response:
        date_from = request.query_params.get('date_from')
        date_to   = request.query_params.get('date_to')
        projects  = self.get_objects(date_from, date_to)
        if not projects.exists():
            return Response(data=[], status=status.HTTP_204_NO_CONTENT)
        serializer = AllProjectsSerializer(projects, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

Пример запроса с фильтром: GET /api/v1/projects/?date_from=2024-01-01&date_to=2024-12-31

6. Загрузка файла с chunked-записью

# apps/projects/views/project_file_views.py
def post(self, request: Request) -> Response:
    file_content = request.FILES["file"]
    project_id   = request.data["project_id"]
    project      = get_object_or_404(Project, pk=project_id)

    serializer = CreateProjectFileSerializer(
        data=request.data,
        context={"raw_file": file_content}
    )
    if serializer.is_valid(raise_exception=True):
        project_file = serializer.save()
        project_file.project.set([project])   # M2M связь
        return Response(
            {"message": "File upload successfully"},
            status=status.HTTP_200_OK
        )
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Обратите внимание: файл передаётся в сериализатор через context, а не через data, так как поле file не является полем модели ProjectFile напрямую.