← Назад к вопросам

Как Django понимает, какие данные нужно получить?

2.2 Middle🔥 151 комментариев
#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как Django понимает, какие данные нужно получить

Django использует ORM (Object-Relational Mapping) для преобразования Python кода в SQL запросы. Процесс довольно интеллектуален, и разберусь, как это работает под капотом.

Базовый механизм: QuerySet

Всё начинается с QuerySet — ленивого объекта, который описывает запрос, но не выполняет его сразу:

# Django НЕ выполняет запрос здесь
query = User.objects.filter(age__gte=18)
print(type(query))  # <class 'django.db.models.query.QuerySet'>

# Запрос выполняется только когда мы его "материализуем"
users = list(query)  # СЕЙЧАС выполняется SQL
for user in query:   # ИЛИ здесь
    print(user)
user = query.first()  # ИЛИ здесь

Как Django строит SQL

Django преобразует вызовы методов в SQL WHERE условия:

# Этот Python код:
User.objects.filter(
    age__gte=18,
    country="USA",
    is_active=True
).exclude(
    username__startswith="test_"
).order_by("-created_at")

# Превращается в SQL:
SELECT * FROM users
WHERE age >= 18
  AND country = 'USA'
  AND is_active = true
  AND username NOT LIKE 'test_%'
ORDER BY created_at DESC

Джанго анализирует имена параметров с двойным подчёркиванием (__):

# Filter lookups
User.objects.filter(age__gte=18)        # age >= 18
User.objects.filter(name__icontains="john")  # name ILIKE '%john%'
User.objects.filter(created__year=2024)  # YEAR(created) = 2024
User.objects.filter(email__isnull=True)  # email IS NULL

# Relationships
User.objects.filter(posts__title__contains="Django")  # JOIN posts
User.objects.filter(profile__age__gte=18)  # JOIN profile

Парсинг query expressions

Джанго разбирает эти выражения на части:

class User(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add=True)

# Django видит: "age__gte=18"
# Разбирает как:
# - model field: "age" -> IntegerField
# - lookup type: "gte" -> ">=" в SQL
# - value: 18

print(User._meta.get_field('age'))  # <DjangoField: IntegerField>

Внутренняя структура:

from django.db.models import Q

# Внутри Django создаёт граф условий
query_condition = Q(age__gte=18) & Q(country="USA") | Q(is_vip=True)

# Это преобразуется в WHERE clause:
# (age >= 18 AND country = 'USA') OR is_vip = true

JOIN'ы для связанных моделей

Когда фильтруем по связанной таблице, Django автоматически добавляет JOIN:

class User(models.Model):
    name = models.CharField(max_length=100)

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)

# Django видит это:
User.objects.filter(posts__title__contains="Django")

# И генерирует SQL с JOIN:
SELECT DISTINCT users.*
FROM users
INNER JOIN posts ON (users.id = posts.user_id)
WHERE posts.title LIKE '%Django%'

Джанго интеллектуально понимает отношения через ForeignKey, ManyToMany, OneToOne:

class Author(models.Model):
    name = models.CharField(max_length=100)

class Post(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)

class Tag(models.Model):
    posts = models.ManyToManyField(Post)

# Django понимает эти связи и строит нужные JOIN'ы
Author.objects.filter(post__title="Hello")  # INNER JOIN posts
Post.objects.filter(tag__name="python")     # INNER JOIN tags
Post.objects.filter(author__name="John")    # INNER JOIN authors

select_related и prefetch_related

Джанго может выполнить неопределённо много запросов (N+1 problem):

# ❌ Плохо — N+1 запросов
for post in Post.objects.all():
    print(post.author.name)  # Доп. запрос для каждого автора!
# 1 запрос на посты + N запросов на авторов = N+1

# ✅ Хорошо — 2 запроса
for post in Post.objects.select_related('author'):  # 1 JOIN
    print(post.author.name)  # Данные уже в памяти

# ✅ Для ManyToMany
for post in Post.objects.prefetch_related('tags'):
    for tag in post.tags.all():  # Уже загружено
        print(tag.name)

Внутри Django:

# select_related использует JOIN
SELECT posts.*, authors.* FROM posts
LEFT OUTER JOIN authors ON posts.author_id = authors.id

# prefetch_related использует IN clause
SELECT * FROM posts WHERE id IN (1, 2, 3, ...)
SELECT * FROM tags WHERE posts_id IN (1, 2, 3, ...)

SQL compilation

Джанго компилирует QuerySet в SQL через compiler:

from django.db.models.sql import compiler

query = User.objects.filter(age__gte=18)

# Получить SQL
sql, params = query.query.get_compiler(query.db).as_sql()
print(sql)     # SELECT * FROM users WHERE age >= %s
print(params)  # [18]

Aggregation и annotation

Джанго может вычислять агрегаты прямо в SQL:

from django.db.models import Count, Avg, Q

# Генерирует GROUP BY
User.objects.annotate(
    post_count=Count('posts'),
    avg_rating=Avg('posts__rating')
).filter(post_count__gte=5)

# SQL:
SELECT users.*,
       COUNT(posts.id) as post_count,
       AVG(posts.rating) as avg_rating
FROM users
LEFT JOIN posts ON users.id = posts.user_id
GROUP BY users.id
HAVING COUNT(posts.id) >= 5

Raw SQL fallback

Джанго может выполнить raw SQL, если ORM недостаточно:

# Когда ORM не справляется
users = User.objects.raw("""
    SELECT u.*, COUNT(p.id) as post_count
    FROM users u
    LEFT JOIN posts p ON u.id = p.user_id
    GROUP BY u.id
    HAVING COUNT(p.id) > 5
""")

# Или прямой запрос
from django.db import connection
cursor = connection.cursor()
cursor.execute("SELECT * FROM users WHERE age > %s", [18])
rows = cursor.fetchall()

Оптимизация query

Джанго может анализировать запросы и предлагать оптимизации:

# Debug mode показывает SQL запросы
DEBUG = True

from django.db import connection
from django.test.utils import override_settings

# Смотреть SQL
for query in connection.queries:
    print(query['sql'])
    print(query['time'])

Query expression objects

Джанго использует специальные объекты для представления выражений:

from django.db.models import Q, F, Value, Case, When

# F — обращение к полю
User.objects.filter(age__gte=F('min_age'))

# Case/When — условная логика
User.objects.annotate(
    status=Case(
        When(age__lt=18, then=Value('minor')),
        When(age__gte=18, then=Value('adult')),
        default=Value('unknown')
    )
)

# Q — логические операции
User.objects.filter(
    Q(first_name__startswith='John') | Q(last_name__startswith='Smith')
)

Выводы

Джанго ORM работает так:

  1. Парсирует Python выражения — разбирает фильтры на части
  2. Анализирует модели — понимает типы полей и связи
  3. Строит SQL AST — создаёт абстрактное дерево синтаксиса
  4. Компилирует в SQL — конкретный диалект БД
  5. Выполняет с параметризацией — безопасно от SQL injection
  6. Кеширует результаты — select_related, prefetch_related

Это делает Django ORM мощным инструментом для безопасного и эффективного доступа к данным.