QuerySet, filter, exclude, order_by, values, annotate, aggregate
Django ORM предоставляет мощный API для работы с базой данных. QuerySet — это ленивый объект, представляющий набор объектов из БД. Понимание того, как работает QuerySet, критично для написания эффективного кода.
💡 Правило: QuerySet ленивый — SQL выполняется только при обращении к данным. Это позволяет строить сложные запросы цепочкой методов без лишних обращений к БД.
# Все объекты
Post.objects.all()
# Фильтрация
Post.objects.filter(is_published=True)
Post.objects.filter(author__username='john', is_published=True)
# Исключение
Post.objects.exclude(is_published=False)
# Комбинирование
Post.objects.filter(author__username='john').exclude(status='draft').order_by('-created_at')# Создание QuerySet НЕ выполняет запрос
queryset = Post.objects.filter(is_published=True) # Нет SQL
# Запрос выполняется при:
list(queryset) # Преобразование в список
for post in queryset: # Итерация
print(post.title)
len(queryset) # Получение длины
bool(queryset) # Проверка на истинность
queryset[0] # Доступ по индексу
queryset.exists() # Проверка на существование
queryset.first() # Получение первого объекта# Базовая фильтрация
Post.objects.filter(is_published=True)
Post.objects.filter(author_id=5)
# Несколько условий (AND)
Post.objects.filter(is_published=True, author_id=5)
# Цепочка фильтров
Post.objects.filter(is_published=True).filter(author_id=5)
# Исключение
Post.objects.exclude(status='draft')
# Комбинирование
Post.objects.filter(is_published=True).exclude(author_id=5)| Lookup | SQL | Пример |
|---|---|---|
exact | = | id__exact=5 |
iexact | = (case-insensitive) | email__iexact='John@Example.COM' |
contains | LIKE | title__contains='Django' |
icontains | LIKE (case-insensitive) | title__icontains='django' |
gt | > | views__gt=100 |
gte | >= | views__gte=100 |
lt | < | price__lt=1000 |
lte | <= | price__lte=1000 |
in | IN | status__in=['draft', 'published'] |
startswith | LIKE '...' | title__startswith='Django' |
istartswith | LIKE '...' (case-insensitive) | title__istartswith='django' |
endswith | LIKE '%...' | title__endswith='Guide' |
iendswith | LIKE '%...' (case-insensitive) | title__iendswith='guide' |
range | BETWEEN | created_at__range=[start, end] |
isnull | IS NULL | published_at__isnull=True |
regex | REGEXP | title__regex=r'^[A-Z]' |
iregex | REGEXP (case-insensitive) | title__iregex=r'^django' |
# Примеры использования
Post.objects.filter(views__gt=100) # views > 100
Post.objects.filter(status__in=['draft', 'published']) # status IN (...)
Post.objects.filter(published_at__isnull=True) # published_at IS NULL
Post.objects.filter(title__icontains='django') # title ILIKE '%django%'
Post.objects.filter(created_at__range=['2026-01-01', '2026-12-31'])# Сортировка по возрастанию
Post.objects.order_by('created_at')
# Сортировка по убыванию
Post.objects.order_by('-created_at')
# Несколько полей
Post.objects.order_by('-created_at', 'title')
# По связанному полю
Post.objects.order_by('author__username')
# Случайный порядок (неэффективно для больших таблиц!)
Post.objects.order_by('?')
# Сброс сортировки
Post.objects.order_by('created_at').order_by() # Без аргументов сбрасывает# values() — список словарей
Post.objects.values('title', 'author__username')
# [{'title': 'Post 1', 'author__username': 'john'}, ...]
# values_list() — список кортежей
Post.objects.values_list('title', 'author__username')
# [('Post 1', 'john'), ...]
# flat=True для одного поля
Post.objects.filter(is_published=True).values_list('title', flat=True)
# ['Post 1', 'Post 2', ...]
# named=True для именованных кортежей (Python 3.6+)
Post.objects.values_list('title', 'author__username', named=True)
# [Row(title='Post 1', author__username='john'), ...]⚠️ Важно: values() и values_list() возвращают не объекты модели, а словари/кортежи. Нельзя вызывать методы модели на таких объектах.
from django.db.models import Count, Avg, Sum, Min, Max
# Простая агрегация
Post.objects.aggregate(
total=Count('*'),
avg_views=Avg('views'),
total_views=Sum('views'),
min_views=Min('views'),
max_views=Max('views'),
)
# {'total': 100, 'avg_views': 42.5, 'total_views': 4250, 'min_views': 0, 'max_views': 1000}
# Агрегация с фильтром
from django.db.models import Q
Post.objects.aggregate(
published_count=Count('id', filter=Q(is_published=True)),
draft_count=Count('id', filter=Q(status='draft')),
)from django.db.models import Count
# Добавить поле к каждому объекту
Post.objects.annotate(comment_count=Count('comments'))
# Каждый пост имеет атрибут comment_count
# Фильтрация по аннотированному полю
Post.objects.annotate(
comment_count=Count('comments')
).filter(comment_count__gt=10)
# Несколько аннотаций
Post.objects.annotate(
comment_count=Count('comments'),
like_count=Count('likes'),
engagement=Count('comments') + Count('likes') * 2,
).order_by('-engagement')
# Аннотация с группировкой
User.objects.annotate(
post_count=Count('posts'),
total_views=Sum('posts__views')
).filter(post_count__gt=5).order_by('-total_views')from django.db.models import Case, When, IntegerField, Value
Post.objects.annotate(
priority=Case(
When(views__gt=1000, then=Value(1)),
When(views__gt=100, then=Value(2)),
When(likes__gt=50, then=Value(3)),
default=Value(4),
output_field=IntegerField(),
)
).order_by('priority')# Получение по первичному ключу
post = Post.objects.get(pk=1)
# Получение по условиям
post = Post.objects.get(slug='my-post', is_published=True)
# Исключения:
# - DoesNotExist, если объект не найден
# - MultipleObjectsReturned, если найдено >1 объекта
# Безопасная альтернатива
from django.shortcuts import get_object_or_404
post = get_object_or_404(Post, slug='my-post')# Первый объект (или None)
post = Post.objects.filter(is_published=True).order_by('-created_at').first()
# Последний объект
oldest_post = Post.objects.order_by('created_at').first()
# last() — последний объект
latest_post = Post.objects.order_by('created_at').last()# Эффективная проверка (SELECT EXISTS)
if Post.objects.filter(is_published=True).exists():
print("Есть опубликованные посты")
# Неэффективно (загружает все объекты)
if Post.objects.filter(is_published=True):
print("Есть опубликованные посты")
# Неэффективно (SELECT COUNT)
if Post.objects.filter(is_published=True).count() > 0:
print("Есть опубликованные посты")# ForeignKey (прямая связь)
Post.objects.filter(author__username='john')
Post.objects.filter(author__email__icontains='@example.com')
# ManyToMany
Post.objects.filter(tags__name='django')
Post.objects.filter(tags__name__in=['django', 'python'])
# Reverse ForeignKey (через related_name)
Author.objects.filter(posts__is_published=True)
Author.objects.filter(posts__views__gt=1000)
# Цепочка связей
Comment.objects.filter(post__author__username='john')# Count в annotate()
Post.objects.annotate(comment_count=Count('comments'))
# Count с фильтром
from django.db.models import Q
Post.objects.annotate(
approved_comments=Count('comments', filter=Q(comments__is_approved=True))
)
# Distinct для ManyToMany
Author.objects.filter(posts__tags__name='django').distinct()Создадим класс для сложного поиска постов:
# blog/utils.py
from django.db.models import Q, Count
from blog.models import Post
class PostSearch:
"""Класс для поиска и фильтрации постов."""
def __init__(self, request):
self.request = request
self.queryset = Post.objects.filter(is_published=True)
def search(self, query):
"""Поиск по заголовку и содержанию."""
if query:
self.queryset = self.queryset.filter(
Q(title__icontains=query) |
Q(content__icontains=query) |
Q(tags__name__icontains=query)
).distinct()
return self
def filter_by_author(self, username):
"""Фильтр по автору."""
if username:
self.queryset = self.queryset.filter(author__username=username)
return self
def filter_by_category(self, category_slug):
"""Фильтр по категории."""
if category_slug:
self.queryset = self.queryset.filter(category__slug=category_slug)
return self
def filter_by_tags(self, tag_slugs):
"""Фильтр по тегам (AND логика)."""
if tag_slugs:
for slug in tag_slugs:
self.queryset = self.queryset.filter(tags__slug=slug)
self.queryset = self.queryset.distinct()
return self
def filter_by_date_range(self, start_date, end_date):
"""Фильтр по диапазону дат."""
if start_date:
self.queryset = self.queryset.filter(created_at__gte=start_date)
if end_date:
self.queryset = self.queryset.filter(created_at__lte=end_date)
return self
def order_by(self, field):
"""Сортировка по полю."""
order_field = field if field in ['created_at', 'views', 'title'] else '-created_at'
self.queryset = self.queryset.order_by(order_field)
return self
def annotate_popularity(self):
"""Добавить аннотацию популярности."""
self.queryset = self.queryset.annotate(
comment_count=Count('comments'),
like_count=Count('likes'),
)
return self
def paginate(self, page_number, page_size=10):
"""Пагинация."""
from django.core.paginator import Paginator
paginator = Paginator(self.queryset, page_size)
return paginator.get_page(page_number)
def get_queryset(self):
"""Получить итоговый QuerySet."""
return self.queryset
# blog/views.py
from django.shortcuts import render
from .utils import PostSearch
def post_search(request):
query = request.GET.get('q')
author = request.GET.get('author')
category = request.GET.get('category')
tags = request.GET.getlist('tags')
ordering = request.GET.get('order', '-created_at')
page = request.GET.get('page', 1)
search = PostSearch(request)
search = (search
.search(query)
.filter_by_author(author)
.filter_by_category(category)
.filter_by_tags(tags)
.order_by(ordering)
.annotate_popularity())
posts = search.paginate(page, page_size=10)
context = {
'posts': posts,
'query': query,
'filters': request.GET.dict(),
}
return render(request, 'blog/search.html', context)Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.