💻 Примеры — Урок 31

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

⚡ Ключевые примеры

# Extract* — прямая фильтрация по дате
books = Book.objects.filter(published_date__year=2023)
books = Book.objects.filter(published_date__month=1)
books = Book.objects.filter(published_date__quarter=2)

# Extract* — через annotate
from django.db.models.functions import ExtractYear
books = Book.objects.annotate(yr=ExtractYear('published_date')).filter(yr=2023)

# APIView + query_params
class BookListView(APIView):
    def get(self, request):
        author = request.query_params.get('author')
        filters = {}
        if author:
            filters['author'] = author
        books = Book.objects.filter(**filters)
        return Response(BookSerializer(books, many=True).data)

Пример 1: Базовая модель

Все примеры используют модель Book. Убедитесь, что модель создана и выполнены миграции.

# models.py
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=8, decimal_places=2, default=0)
    published_date = models.DateField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title
# serializers.py
from rest_framework import serializers
from .models import Book

class BookDetailSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

Пример 2: Прямая фильтрация через lookups

from myapp.models import Book

# Книги, опубликованные в 2023 году
books_2023 = Book.objects.filter(published_date__year=2023)
for book in books_2023:
    print(book.title, book.published_date)

# Книги, опубликованные в январе (любой год)
january_books = Book.objects.filter(published_date__month=1)
for book in january_books:
    print(book.title, book.published_date)

# Книги из второго квартала (апрель–июнь)
q2_books = Book.objects.filter(published_date__quarter=2)
for book in q2_books:
    print(book.title, book.published_date)

# Комбинирование: январь 2023
january_2023 = Book.objects.filter(
    published_date__year=2023,
    published_date__month=1
)
print(f"Книги из января 2023: {january_2023.count()}")

Пример 3: Аннотация через Extract*

from django.db.models.functions import ExtractYear, ExtractMonth, ExtractQuarter
from django.db.models import Count
from myapp.models import Book

# Аннотация годом и фильтрация
books_2023 = Book.objects.annotate(
    year=ExtractYear('published_date')
).filter(year=2023)

print(f"Книг в 2023: {books_2023.count()}")

# Аннотация месяцем и фильтрация
january_books = Book.objects.annotate(
    month=ExtractMonth('published_date')
).filter(month=1)

# Аннотация кварталом и фильтрация
q2_books = Book.objects.annotate(
    quarter=ExtractQuarter('published_date')
).filter(quarter=2)

# Группировка по году (статистика)
by_year = Book.objects.annotate(
    year=ExtractYear('published_date')
).values('year').annotate(
    count=Count('id')
).order_by('year')

for row in by_year:
    print(f"{row['year']}: {row['count']} книг")

Пример 4: APIView с фильтрацией через query_params

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book
from .serializers import BookDetailSerializer

class BookListView(APIView):
    def get(self, request):
        filters = {}

        # Читаем параметры из URL
        author = request.query_params.get('author')
        published_year = request.query_params.get('pub_year')

        # Добавляем в фильтр только если параметр передан
        if author:
            filters['author'] = author

        if published_year:
            filters['published_date__year'] = published_year

        books = Book.objects.filter(**filters)
        serializer = BookDetailSerializer(books, many=True)
        return Response(serializer.data)

Примеры URL-запросов

# Все книги
GET http://127.0.0.1:8000/books/

# Книги автора
GET http://127.0.0.1:8000/books/?author=George%20Orwell

# Книги конкретного года
GET http://127.0.0.1:8000/books/?pub_year=2023

# Комбинированный фильтр
GET http://127.0.0.1:8000/books/?author=Tolkien&pub_year=2022

Пример 5: APIView с сортировкой

# views.py
class BookListView(APIView):
    def get(self, request):
        sort_by = request.query_params.get('sort_by', 'title')
        sort_order = request.query_params.get('sort_order', 'asc')

        books = Book.objects.all()

        if sort_order == 'desc':
            sort_by = f'-{sort_by}'

        books = books.order_by(sort_by)
        serializer = BookDetailSerializer(books, many=True)
        return Response(serializer.data)

Примеры URL-запросов

# По цене по убыванию
GET http://127.0.0.1:8000/books/?sort_by=price&sort_order=desc

# По дате публикации по возрастанию
GET http://127.0.0.1:8000/books/?sort_by=published_date&sort_order=asc

# По заголовку (дефолт)
GET http://127.0.0.1:8000/books/

Пример 6: APIView с пагинацией

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from .models import Book
from .serializers import BookDetailSerializer

class BookListView(APIView, PageNumberPagination):
    page_size = 5  # по умолчанию 5 объектов на страницу

    def get(self, request):
        books = Book.objects.all()

        # Получаем page_size из запроса или используем дефолт
        page_size = self.get_page_size(request)
        self.page_size = page_size

        results = self.paginate_queryset(books, request, view=self)
        serializer = BookDetailSerializer(results, many=True)
        return self.get_paginated_response(serializer.data)

    def get_page_size(self, request):
        """Переопределение: поддержка параметра page_size."""
        page_size = request.query_params.get('page_size')
        if page_size and page_size.isdigit():
            return int(page_size)
        return self.page_size  # дефолт

Примеры URL-запросов

# Первая страница (5 объектов)
GET http://127.0.0.1:8000/books/

# Вторая страница
GET http://127.0.0.1:8000/books/?page=2

# Вторая страница с 3 объектами
GET http://127.0.0.1:8000/books/?page=2&page_size=3

Пример ответа

{
    "count": 25,
    "next": "http://127.0.0.1:8000/books/?page=3&page_size=3",
    "previous": "http://127.0.0.1:8000/books/?page=1&page_size=3",
    "results": [
        {"id": 4, "title": "Animal Farm", "author": "George Orwell", ...},
        {"id": 5, "title": "Brave New World", "author": "Aldous Huxley", ...},
        {"id": 6, "title": "Fahrenheit 451", "author": "Ray Bradbury", ...}
    ]
}

Пример 7: Полный APIView — фильтрация + сортировка + пагинация

# views.py — объединённый пример
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from .models import Book
from .serializers import BookDetailSerializer

ALLOWED_SORT_FIELDS = {'title', 'price', 'published_date', 'author'}

class BookListView(APIView, PageNumberPagination):
    page_size = 10

    def get(self, request):
        # 1. Фильтрация
        filters = {}
        author = request.query_params.get('author')
        pub_year = request.query_params.get('pub_year')

        if author:
            filters['author__icontains'] = author
        if pub_year and pub_year.isdigit():
            filters['published_date__year'] = int(pub_year)

        # 2. Сортировка
        sort_by = request.query_params.get('sort_by', 'title')
        sort_order = request.query_params.get('sort_order', 'asc')
        if sort_by not in ALLOWED_SORT_FIELDS:
            sort_by = 'title'
        if sort_order == 'desc':
            sort_by = f'-{sort_by}'

        # 3. Применяем фильтры и сортировку
        books = Book.objects.filter(**filters).order_by(sort_by)

        # 4. Пагинация
        page_size = request.query_params.get('page_size')
        if page_size and page_size.isdigit():
            self.page_size = int(page_size)

        results = self.paginate_queryset(books, request, view=self)
        serializer = BookDetailSerializer(results, many=True)
        return self.get_paginated_response(serializer.data)


# urls.py
from django.urls import path
from .views import BookListView

urlpatterns = [
    path('books/', BookListView.as_view(), name='book-list'),
]

Пример 8: ДЗ-ориентированный — фильтрация по дню недели

Аналог задания 1 из ДЗ 14: эндпоинт для получения задач по дню недели.

# models.py
from django.db import models

class Task(models.Model):
    WEEKDAY_CHOICES = [
        (1, 'Понедельник'), (2, 'Вторник'), (3, 'Среда'),
        (4, 'Четверг'), (5, 'Пятница'), (6, 'Суббота'), (7, 'Воскресенье'),
    ]
    title = models.CharField(max_length=200)
    due_date = models.DateField()

    def __str__(self):
        return self.title


# views.py
from django.db.models.functions import ExtractWeekDay
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Task
from .serializers import TaskSerializer

WEEKDAY_MAP = {
    'понедельник': 2, 'вторник': 3, 'среда': 4,
    'четверг': 5, 'пятница': 6, 'суббота': 7, 'воскресенье': 1,
}

class TaskListView(APIView):
    def get(self, request):
        weekday = request.query_params.get('weekday')

        if weekday:
            weekday_num = WEEKDAY_MAP.get(weekday.lower())
            if weekday_num:
                tasks = Task.objects.filter(
                    due_date__week_day=weekday_num
                )
            else:
                tasks = Task.objects.none()
        else:
            tasks = Task.objects.all()

        serializer = TaskSerializer(tasks, many=True)
        return Response(serializer.data)

Примеры запросов

# Все задачи
GET http://127.0.0.1:8000/tasks/

# Задачи на вторник
GET http://127.0.0.1:8000/tasks/?weekday=вторник

# Задачи на пятницу
GET http://127.0.0.1:8000/tasks/?weekday=пятница