📖 Теория: Agile Projects ч.2 — ключевые концепции
⚡ Ключевые концепции
- Generic Views:
ListCreateAPIView,RetrieveDestroyAPIView— наследование вместо ручного кода - Пагинация:
PageNumberPaginationподключается кAPIViewчерез атрибутpaginator - SlugRelatedField: связь по строковому полю (name, email) вместо pk
- AbstractBaseUser: полный контроль над моделью пользователя,
USERNAME_FIELD = "email" - Chunked file save:
file.chunks()— запись по частям, избегаем перегрузки памяти
1. Generic Views в DRF
DRF предоставляет готовые классы-«дженерики» для типовых операций. Вместо написания методов get(), post() вручную — наследуемся от готового класса:
| Класс | HTTP-методы | Применение |
|---|---|---|
ListCreateAPIView | GET (список), POST | Список + создание |
RetrieveDestroyAPIView | GET (объект), DELETE | Получение + удаление |
RetrieveUpdateDestroyAPIView | GET, PUT, PATCH, DELETE | Полный CRUD объекта |
ListAPIView | GET (список) | Только чтение списка |
В проекте «Agile Projects» используется ListCreateAPIView для файлов (список + загрузка) и RetrieveDestroyAPIView для отдельного файла (просмотр + удаление).
2. Пагинация через PageNumberPagination
DRF поддерживает несколько стратегий пагинации. В проекте используется PageNumberPagination — нумерация страниц через query-параметр ?page=N.
При использовании с APIView (не Generic), пагинатор подключается явно:
class StandardResultsSetPagination(PageNumberPagination):
page_size = 5
page_size_query_param = 'page_size'
max_page_size = 15
class TasksListAPIView(APIView):
paginator = StandardResultsSetPagination
def get(self, request):
tasks = Task.objects.all()
paginator = self.paginator()
page = paginator.paginate_queryset(tasks, request, view=self)
if page is not None:
serializer = AllTasksSerializer(page, many=True)
return paginator.get_paginated_response(serializer.data)
serializer = AllTasksSerializer(tasks, many=True)
return Response(serializer.data)
Ответ с пагинацией включает поля count, next, previous, results.
3. SlugRelatedField — связи по строковому полю
SlugRelatedField позволяет отображать и передавать связанные объекты не по pk, а по другому уникальному полю (slug, name, email):
class AllTasksSerializer(serializers.ModelSerializer):
project = serializers.SlugRelatedField(
read_only=True,
slug_field='name' # отображаем project.name вместо project.id
)
assignee = serializers.SlugRelatedField(
read_only=True,
slug_field='email' # отображаем assignee.email
)
Для записи (при создании) убираем read_only=True и передаём queryset:
project = serializers.SlugRelatedField(
slug_field='name',
queryset=Project.objects.all(), # DRF найдёт объект по имени
)
4. Полевая валидация в сериализаторах
Методы validate_<fieldname> должны быть определены на уровне класса сериализатора, а не внутри class Meta. Это частая ошибка в лекции!
class CreateTaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = ('name', 'description', 'priority', 'project', 'tags', 'deadline')
# ПРАВИЛЬНО — на уровне класса, не внутри Meta
def validate_name(self, value: str) -> str:
if len(value) < 10:
raise serializers.ValidationError(
"The name of the task couldn't be less than 10 characters"
)
return value
def validate_deadline(self, value) -> ...:
if timezone.is_naive(value):
value = timezone.make_aware(value, timezone.get_current_timezone())
if value < timezone.now():
raise serializers.ValidationError(
"The deadline of the task couldn't be in the past"
)
return value
5. Файловый pipeline: валидация и chunked save
Загрузка файлов требует нескольких уровней защиты:
- Валидация имени — только ASCII-символы и допустимые расширения (pdf, csv, doc, xlsx)
- Валидация размера — файл ≤ 2 MB
- Создание пути —
documents/{project_name}/{file_name}.{ext} - Chunked save — запись через
file.chunks(), не загружает всё в память
def save_file(file_path, file_content):
os.makedirs(os.path.dirname(Path(file_path)), exist_ok=True)
with open(file_path, 'wb') as f:
for chunk in file_content.chunks(): # по частям — безопасно
f.write(chunk)
return file_path
6. Кастомная модель User
Django позволяет заменить встроенную модель пользователя на свою. Нужно:
- Создать приложение
users - Унаследоваться от
AbstractBaseUser(и опциональноPermissionsMixin) - Указать
USERNAME_FIELD— поле для входа (у насemail) - Указать
REQUIRED_FIELDS— дополнительные поля дляcreatesuperuser - Добавить
AUTH_USER_MODEL = 'users.User'вsettings.pyдо первой миграции - Пересоздать базу (удалить старые миграции), провести миграции заново
UserManager по умолчанию ожидает поле email как USERNAME_FIELD. Если используется нестандартное имя поля для входа — менеджер нужно переопределить.
7. Вложенные сериализаторы
TaskDetailSerializer включает вложенный ProjectShortInfoSerializer для отображения краткой информации о проекте:
class ProjectShortInfoSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('id', 'name')
class TaskDetailSerializer(serializers.ModelSerializer):
project = ProjectShortInfoSerializer() # вложенный сериализатор
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Task
exclude = ('updated_at', 'deleted_at')
Вложенный сериализатор делает поле только для чтения по умолчанию. Для обновления используется отдельный сериализатор (CreateUpdateTaskSerializer со SlugRelatedField).