📖 Теория: архитектура проекта Agile Projects

⚡ Ключевые концепции

  • Модуль apps/ — все приложения вынесены в одну папку для чистоты архитектуры
  • Модуль models/ — каждая модель в отдельном файле (tag.py, project.py)
  • Enum choices — статусы и приоритеты через Python Enum, а не строки
  • APIView — базовый класс DRF для ручного управления методами HTTP
  • Agile git-flow — каждая задача = один коммит + pull request

Концепция проекта

«Agile Projects» — веб-платформа для управления проектами, задачами и сотрудниками. Платформа обеспечивает:

  • Отслеживание статусов задач, их приоритетности и исполнителей
  • Лёгкое добавление файлов к проектам
  • Аналитику: количество задач, скорость закрытия, нагруженность проекта

Технологический стек: Django 5.x + DRF 3.15+. Все взаимодействия через REST API.

Архитектура: модуль apps/

Для чистоты структуры все Django-приложения помещаются в папку apps/, которая является Python-пакетом (содержит __init__.py).

agile_projects/          ← корневой пакет Django
  apps/
    __init__.py
    routers.py           ← общий URL-маршрутизатор
    tasks/               ← приложение задач и тегов
      models/
        __init__.py
        tag.py
        task.py
      choices/
        statuses.py
        priority.py
      serializers/
        tag_serializers.py
      views/
        tag_views.py
      utils/
        set_end_of_month.py
      urls.py
    projects/            ← приложение проектов и файлов
      models/
        __init__.py
        project.py
      serializers/
        project_serializers.py
        project_file_serializers.py
      views/
        project_views.py
        project_file_views.py
      utils/
        upload_file_helpers.py
      urls.py

При регистрации в INSTALLED_APPS указывается полный путь через точку:

INSTALLED_APPS = [
    ...
    'apps.tasks.apps.TasksConfig',
    'apps.projects.apps.ProjectsConfig',
]

Модуль models/ вместо models.py

Вместо одного файла models.py создаётся пакет models/, где каждая модель живёт в отдельном файле. В models/__init__.py нужно явно экспортировать классы:

# apps/tasks/models/__init__.py
from apps.tasks.models.tag import Tag
from apps.tasks.models.task import Task

Enum choices для Django-полей

Вместо хардкода строк используют Python Enum-классы. Это обеспечивает автодополнение IDE, защиту от опечаток, единое место изменения значений.

# 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]
⚠️ Проверить по документации: в Django 3.0+ есть встроенный models.TextChoices и models.IntegerChoices. Они предпочтительнее кастомного Enum в продакшн-коде.

Agile git-workflow в учебном проекте

Каждая задача практикума завершается циклом:

  1. Написать код
  2. python manage.py makemigrations + python manage.py migrate
  3. Запустить сервер, проверить работу
  4. git add . && git commit -m "feat: описание" && git push origin <branch>
  5. Открыть pull request для код-ревью

Это имитирует реальный рабочий процесс в Agile-команде с частыми небольшими коммитами.

APIView: ручное управление HTTP-методами

APIView — базовый класс DRF, аналог Django View, но с автоматической обработкой Content-Type, парсингом тела запроса и рендерингом ответа.

from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status

class TagListAPIView(APIView):
    def get(self, request: Request) -> Response:
        ...
    def post(self, request: Request) -> Response:
        ...

Каждый HTTP-метод реализуется как метод класса с тем же именем в нижнем регистре.

Работа с файлами в DRF

Для загрузки файлов запрос должен иметь Content-Type: multipart/form-data. Файл доступен через request.FILES["key"], текстовые поля — через request.data["key"].

Хорошая практика — писать по частям (chunked write), чтобы не держать весь файл в памяти:

with open(file_path, 'wb') as f:
    for chunk in file_content.chunks():
        f.write(chunk)