🔖 Справочник: Agile Projects ч.2

К оглавлению урока

⚡ Быстрая шпаргалка

# Команды
python manage.py startapp users
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

# Пагинация
class Paginator(PageNumberPagination):
    page_size = 5

# Generic Views
from rest_framework.generics import ListCreateAPIView, RetrieveDestroyAPIView

# SlugRelatedField
project = serializers.SlugRelatedField(slug_field='name', queryset=Project.objects.all())

# AUTH_USER_MODEL
AUTH_USER_MODEL = 'users.User'  # в settings.py

Generic Views — классы

from rest_framework.generics import (
    ListAPIView,           # GET список
    CreateAPIView,         # POST создание
    ListCreateAPIView,     # GET список + POST создание
    RetrieveAPIView,       # GET объект
    DestroyAPIView,        # DELETE
    UpdateAPIView,         # PUT, PATCH
    RetrieveUpdateAPIView,         # GET + PUT/PATCH
    RetrieveDestroyAPIView,        # GET + DELETE
    RetrieveUpdateDestroyAPIView,  # GET + PUT/PATCH + DELETE
)

PageNumberPagination

from rest_framework.pagination import PageNumberPagination

class StandardResultsSetPagination(PageNumberPagination):
    page_size = 5                        # записей на странице
    page_size_query_param = 'page_size'  # ?page_size=10
    max_page_size = 15                   # максимум

# Использование в APIView
class MyListView(APIView):
    paginator = StandardResultsSetPagination

    def get(self, request):
        qs = MyModel.objects.all()
        paginator = self.paginator()
        page = paginator.paginate_queryset(qs, request, view=self)
        if page is not None:
            s = MySerializer(page, many=True)
            return paginator.get_paginated_response(s.data)
        s = MySerializer(qs, many=True)
        return Response(s.data)

Сериализаторы: SlugRelatedField

from rest_framework import serializers

# Только чтение (отображение)
field = serializers.SlugRelatedField(read_only=True, slug_field='name')

# Чтение и запись (создание/обновление)
field = serializers.SlugRelatedField(
    slug_field='name',
    queryset=MyModel.objects.all()
)

Полевая валидация

class MySerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = (...)

    # validate_<fieldname> — на уровне класса, НЕ внутри Meta
    def validate_name(self, value: str) -> str:
        if len(value) < 10:
            raise serializers.ValidationError("Too short")
        return value

    def validate_deadline(self, value):
        from django.utils import timezone
        if timezone.is_naive(value):
            value = timezone.make_aware(value, timezone.get_current_timezone())
        if value < timezone.now():
            raise serializers.ValidationError("Cannot be in the past")
        return value

Переопределение create() и update()

def create(self, validated_data: dict) -> Task:
    tags = validated_data.pop('tags', [])
    task = Task.objects.create(**validated_data)
    for tag in tags:
        task.tags.add(tag)
    task.save()
    return task

def update(self, instance: Task, validated_data: dict) -> Task:
    tags = validated_data.pop('tags', [])
    for attr, value in validated_data.items():
        setattr(instance, attr, value)
    if tags:
        for tag in tags:
            instance.tags.add(tag)
    instance.save()
    return instance

Утилиты файлов (upload_file_helpers.py)

import os
from pathlib import Path

ALLOWED_EXTENSIONS = ['.csv', '.doc', '.pdf', '.xlsx']

def check_extension(filename: str) -> bool:
    return Path(filename).suffix in ALLOWED_EXTENSIONS

def check_file_size(file, required_size: int = 2) -> bool:
    return (file.size / (1024 * 1024)) <= required_size

def create_file_path(project_name: str, file_name: str) -> str:
    stem, ext = file_name.rsplit('.', 1)
    return "documents/{}/{}.{}".format(
        project_name.replace(' ', '_'),
        stem.replace(' ', '_'),
        ext
    )

def save_file(file_path: str, file_content) -> str:
    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

def delete_file(file_path: str) -> None:
    os.remove(os.path.realpath(file_path))

AbstractBaseUser — кастомная модель User

from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin, UserManager

class User(AbstractBaseUser, PermissionsMixin):
    # ... поля ...
    USERNAME_FIELD = "email"       # поле для входа
    REQUIRED_FIELDS = [            # поля для createsuperuser
        "username", "first_name", "last_name", "position"
    ]
    objects = UserManager()

# settings.py
AUTH_USER_MODEL = 'users.User'

Django-команды в проекте

# Создание нового приложения
python manage.py startapp users

# Миграции
python manage.py makemigrations
python manage.py migrate

# Создание суперпользователя (после кастомного User)
python manage.py createsuperuser

# Запуск сервера
python manage.py runserver

# Git-workflow по каждой задаче
git add .
git commit -m "feat: task N — description"
git push origin <branch_name>
git checkout -b feature/task-N   # новая ветка для задачи
# ... работа ...
# merge request / pull request