📖 Теория — Урок 31
⚡ Ключевые концепции урока 31
- Extract* (Django ORM): функции для извлечения компонентов даты — год, месяц, квартал, день, день недели, час, минута, секунда, номер недели
- Прямая фильтрация:
filter(date__year=2023)— без явного импорта функций - Аннотация + Extract:
annotate(year=ExtractYear('date')).filter(year=2023)— мощнее, поддерживает агрегации - request.query_params: словарь GET-параметров в DRF APIView
- Фильтрация: передаём параметры в
filter(**filters)только при наличии значений - Сортировка:
sort_by + sort_orderчерезorder_by(), префикс-для убывания - Пагинация:
PageNumberPagination,paginate_queryset(),get_paginated_response()
Часть 1: Функции извлечения (Extract*)
В Django существуют специальные функции для работы с компонентами полей типа DateField, DateTimeField и TimeField. Эти функции позволяют фильтровать, аннотировать и агрегировать записи на основе отдельных частей даты или времени.
Функции Extract* находятся в модуле django.db.models.functions и выполняются на уровне базы данных — они не загружают все объекты в память Python.
Основные функции Extract*
| Функция | Что извлекает | Пример значения | Тип поля |
|---|---|---|---|
ExtractYear |
Год из даты или времени | 2023 | Date, DateTime |
ExtractMonth |
Месяц из даты или времени | 1–12 | Date, DateTime |
ExtractDay |
День из даты или времени | 1–31 | Date, DateTime |
ExtractWeekDay |
День недели (1=воскресенье, 7=суббота) | 1–7 | Date, DateTime |
ExtractWeek |
Номер недели в году (ISO) | 1–53 | Date, DateTime |
ExtractQuarter |
Квартал года | 1–4 | Date, DateTime |
ExtractHour |
Час из времени | 0–23 | Time, DateTime |
ExtractMinute |
Минута из времени | 0–59 | Time, DateTime |
ExtractSecond |
Секунда из времени | 0–59 | Time, DateTime |
from django.db.models.functions import (
ExtractYear, ExtractMonth, ExtractDay,
ExtractWeekDay, ExtractWeek, ExtractQuarter,
ExtractHour, ExtractMinute, ExtractSecond
)
Два способа фильтрации по дате
1. Прямая фильтрация через встроенные lookups
Самый простой способ — использовать встроенные Django lookup-суффиксы: __year, __month, __day, __week, __quarter, __week_day, __hour, __minute, __second. Дополнительный импорт не нужен.
# Прямая фильтрация по году публикации
books_2023 = Book.objects.filter(published_date__year=2023)
# Прямая фильтрация по месяцу (январь = 1)
january_books = Book.objects.filter(published_date__month=1)
# Прямая фильтрация по кварталу (2-й квартал)
q2_books = Book.objects.filter(published_date__quarter=2)
2. Фильтрация и аннотация через функции Extract*
Более гибкий способ — аннотировать queryset вычисляемым полем через annotate(), а затем фильтровать по нему. Это позволяет использовать аннотированные значения в order_by(), values() и агрегациях.
from django.db.models.functions import ExtractYear, ExtractMonth, ExtractQuarter
from myapp.models import Book
# Аннотирование и фильтрация по году
books_2023 = Book.objects.annotate(
year=ExtractYear('published_date')
).filter(year=2023)
# Аннотирование и фильтрация по месяцу
january_books = Book.objects.annotate(
month=ExtractMonth('published_date')
).filter(month=1)
# Аннотирование и фильтрация по кварталу
q2_books = Book.objects.annotate(
quarter=ExtractQuarter('published_date')
).filter(quarter=2)
- Прямые lookups (
__year) — для простой фильтрации. Короче, не требует импорта. annotate() + Extract*— когда нужно извлечённое значение использовать в GROUP BY, сортировке или других вычислениях.
Часть 2: Query Parameters с APIView
Query parameters (параметры запроса) — это дополнительные данные, передаваемые в URL после символа ?. Они разделяются символом &, а ключ и значение разделяются символом =.
Пример URL с query parameters
https://127.0.0.1:8000/books/?author=John&published_year=2023
Здесь author и published_year — параметры запроса. Параметр author имеет значение John, параметр published_year — значение 2023.
Основные применения query parameters
- Фильтрация / поиск: ограничение результатов по значениям параметров
- Сортировка: определение порядка отображения данных
- Пагинация: разделение результатов на страницы
request.query_params в DRF
В Django REST Framework доступ к query parameters осуществляется через атрибут request.query_params. Это объект типа QueryDict — аналог request.GET в стандартном Django, но предпочтительный в DRF.
class BookListView(APIView):
def get(self, request):
# Получить значение параметра или None, если не передан
author = request.query_params.get('author')
year = request.query_params.get('pub_year')
# Получить с дефолтным значением
page_size = request.query_params.get('page_size', '10')
request.query_params — это более явное и читаемое имя, соответствующее терминологии HTTP. Оба атрибута работают одинаково.
1. Фильтрация данных через query_params
Типичный паттерн: собираем непустые параметры в словарь filters и передаём его в filter(**filters). Это позволяет легко комбинировать несколько фильтров.
# 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 = {}
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)
Пример запроса
GET http://127.0.0.1:8000/books/?author=George%20Orwell
Возвращает список книг автора George Orwell. Если параметры не переданы — возвращает все книги.
None перед добавлением в filters. Иначе filter(author=None) вернёт пустой queryset вместо ожидаемых всех записей.
2. Сортировка данных через query_params
Параметры sort_by и sort_order позволяют клиенту управлять сортировкой. В Django префикс - у имени поля означает сортировку по убыванию.
# 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)
Пример запроса
GET http://127.0.0.1:8000/books/?sort_by=price&sort_order=desc
Возвращает книги, отсортированные по цене в порядке убывания.
sort_by напрямую передаётся в order_by(). Без валидации допустимых полей клиент может передать произвольное имя поля. Рекомендуется белый список разрешённых полей:
ALLOWED_SORT_FIELDS = {'title', 'price', 'published_date', 'author'}
if sort_by not in ALLOWED_SORT_FIELDS:
sort_by = 'title' # дефолт
⚠️ Проверить по документации: для промышленного кода используйте OrderingFilter из DRF.
3. Пагинация через PageNumberPagination
Пагинация разделяет большие наборы данных на страницы. PageNumberPagination — стандартный класс DRF. Параметры page и page_size передаются через URL.
# 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 # значение по умолчанию
def get(self, request):
books = Book.objects.all()
page_size = self.get_page_size(request) # читает page_size из query_params
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 # дефолт
Пример запроса
GET http://127.0.0.1:8000/books/?page=2&page_size=3
Возвращает вторую страницу списка книг с тремя элементами на странице.
Структура ответа с пагинацией
{
"count": 25, // всего объектов
"next": "http://.../?page=3",
"previous": "http://.../?page=1",
"results": [...] // объекты текущей страницы
}
paginate_queryset(queryset, request, view=None)— возвращает срез queryset для текущей страницыget_paginated_response(data)— оборачивает данные в стандартный ответ с метаданнымиget_page_size(request)— определяет размер страницы (можно переопределить)
pagination_class на уровне Generic Views или глобально в settings.py, а не через множественное наследование от PageNumberPagination в APIView. Паттерн из лекции работает, но не является предпочтительным.