📖 Теория: Agile Projects ч.3 — ключевые концепции

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

  • get_serializer_class — разный сериализатор для GET и POST в одном view
  • AbstractBaseUser — полная замена User с произвольным USERNAME_FIELD
  • FileResponse — стриминг файла с заголовком Content-Disposition: attachment
  • validate_password — встроенная проверка надёжности пароля от Django
  • SlugRelatedField — связь по email вместо pk

1. get_serializer_class — динамический выбор сериализатора

Generic Views в DRF поддерживают метод get_serializer_class(), который вызывается каждый раз перед сериализацией. Это позволяет использовать разные сериализаторы в зависимости от метода HTTP:

class ProjectFileListGenericView(ListCreateAPIView):
    def get_serializer_class(self):
        if self.request.method == 'GET':
            return AllProjectFilesSerializer
        return CreateProjectFileSerializer

Паттерн особенно полезен когда GET-ответ должен содержать больше данных (вложенные объекты), а POST-запрос — минимальный набор полей для создания.

2. AbstractBaseUser — кастомная модель пользователя

Django позволяет полностью заменить встроенную модель пользователя через AbstractBaseUser. Это нужно когда стандартные поля (username как логин, first_name/last_name ограниченной длины) не подходят.

Ключевые атрибуты:

  • USERNAME_FIELD = "email" — поле для аутентификации (вместо username)
  • REQUIRED_FIELDS = [...] — поля, запрашиваемые при createsuperuser
  • objects = UserManager() — стандартный менеджер
  • AUTH_USER_MODEL = 'users.User' в settings.py — должно быть установлено ДО первой миграции
class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["username", "first_name", "last_name"]
    objects = UserManager()

PermissionsMixin добавляет поля is_superuser, groups, user_permissions для работы со стандартной системой прав Django.

3. Валидация пароля в сериализаторе

Django предоставляет встроенные валидаторы паролей через validate_password:

from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError

def validate(self, data):
    password = data.get("password")
    re_password = data.get("re_password")
    if password != re_password:
        raise serializers.ValidationError({"password": "Passwords don't match"})
    try:
        validate_password(password)
    except ValidationError as err:
        raise serializers.ValidationError({"password": err.messages})
    return data

Валидаторы по умолчанию: минимальная длина 8 символов, не слишком похоже на другие поля пользователя, не общеупотребительный пароль, не полностью числовой.

4. Regex-валидация полей

Модуль re позволяет проверять формат строковых полей:

import re

# Только латиница, цифры, подчёркивание
if not re.match('^[a-zA-Z0-9_]*$', username):
    raise serializers.ValidationError("Invalid username format")

# Только буквы латиницы
if not re.match('^[a-zA-Z]*$', first_name):
    raise serializers.ValidationError("Only latin letters allowed")
⚠️ Проверить по документации: в актуальном DRF 3.15+ можно также использовать RegexValidator из Django как поле-уровневый валидатор вместо метода validate(). Подход с методом validate работает корректно в обеих версиях.

5. FileResponse — скачивание файлов

FileResponse — специальный класс DRF/Django для стриминга файлов. Позволяет избежать загрузки всего файла в память:

from django.http import FileResponse

class DownloadProjectFileView(APIView):
    def get(self, request, *args, **kwargs):
        project_file = get_object_or_404(ProjectFile, pk=self.kwargs['pk'])
        file_handle = project_file.file_path.open()
        response = FileResponse(file_handle, content_type='application/octet-stream')
        response['Content-Disposition'] = f'attachment; filename="{project_file.file_name}"'
        return response

Заголовок Content-Disposition: attachment; filename="..." сигнализирует браузеру/клиенту, что файл нужно скачать, а не открыть инлайн.

6. SlugRelatedField — связь по человекочитаемому полю

SlugRelatedField позволяет задавать связанный объект не по pk, а по любому уникальному полю:

assignee = serializers.SlugRelatedField(
    slug_field='email',
    queryset=User.objects.all(),
    required=False
)

При этом в API передаётся email пользователя, а DRF сам найдёт соответствующий объект User.

7. Структура проекта на этом этапе

agile_project/
├── apps/
│   ├── projects/
│   │   ├── models.py           (Project, ProjectFile)
│   │   ├── serializers/
│   │   │   ├── project_serializers.py
│   │   │   └── project_file_serializers.py
│   │   ├── views/
│   │   │   ├── project_views.py
│   │   │   └── project_file_views.py   # Задачи 1, 2, 7
│   │   └── utils/
│   │       └── upload_file_helpers.py
│   ├── tasks/
│   │   ├── serializers/
│   │   │   └── task_serializers.py     # Задача 6 (assignee)
│   │   └── views/
│   └── users/                          # Задачи 3, 4, 5
│       ├── models.py
│       ├── choices/
│       │   └── positions.py
│       ├── serializers.py
│       ├── views.py
│       └── urls.py
└── settings.py  (AUTH_USER_MODEL)