Понимание символов _, __ и . в Django ORM

Сводка по user_id, user.id и user__id

В коде Django вы, вероятно, уже сталкивались с этими конструкциями.

Post.objects.filter(user_id=1)
Post.objects.filter(user__id=1)
post.user.id
post.user_id

Смешение подчеркивания, двойного подчеркивания и точки может сбивать с толку. Если начать использовать их произвольно, вы столкнетесь с тонкими различиями в поведении и производительности.

В этой статье мы разберём:

  • Что именно означают _, __ и . в Django ORM
  • Различия в значении и производительности user_id, user.id и user__id
  • Когда и какой вариант лучше использовать в реальной работе

1. Общий обзор: роли трёх символов



Кратко:

  • _ (один подчеркивание)
  • Это просто часть имени.
  • Примеры: created_at, user_id.
  • В Django ForeignKey автоматически создаёт поле <имя_поля>_id в базе.

  • __ (двойное подчеркивание)

  • Это разделитель запросов (lookup separator), специфичный для Django ORM.
  • Используется только внутри имен аргументов filter(), exclude(), order_by(), values() и т.д.
  • Примеры:

    • age__gte=20 (поле + условие)
    • user__email='a@b.com' (доступ к полю через связь)
    • created_at__date=... (преобразование + условие)
  • . (точка)

  • Оператор доступа к атрибуту в Python.
  • Примеры: obj.user, obj.user.id, qs.filter(...).order_by(...).
  • В ORM это просто обращение к атрибуту уже загруженного объекта; при необходимости может вызывать дополнительный запрос (ленивая загрузка).

2. _ и _id: значение вокруг ForeignKey

2.1 Пример определения модели

from django.contrib.auth import get_user_model
from django.db import models

User = get_user_model()

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

В базе создаются поля:

  • post таблица
  • id (PK)
  • user_id (реальный FK‑поле)
  • title

Django создаёт:

  • атрибут post.user – объект User
  • атрибут post.user_id – целое число (PK пользователя)
post.user    # -> объект User (может вызвать дополнительный запрос)
post.user_id # -> PK пользователя (уже есть, запрос не нужен)

2.2 Использование _id в запросах

# 1) Передаём объект User
Post.objects.filter(user=request.user)

# 2) Передаём PK
Post.objects.filter(user=request.user.id)

# 3) Используем имя поля напрямую
Post.objects.filter(user_id=request.user.id)

Все три компилируются в один WHERE‑условие. _id – это имя реального столбца в БД.


3. __ – цепочка поиска в Django ORM



__ в Django – это разделитель запросов. Он позволяет:

  • переходить по связям
  • добавлять условия
  • применять преобразования

3.1 Базовый шаблон

  1. поле__условие
  2. связь__другое_поле
  3. поле__преобразование__условие

Примеры:

# 1) Простой фильтр
Post.objects.filter(title__icontains="django")

# 2) Через связь
Post.objects.filter(user__email__endswith="@example.com")

# 3) По дате
Post.objects.filter(created_at__year=2025)

При использовании user__email в БД выполняется JOIN.


4. . и user.id, user_id

4.1 Разница в экземплярах Django

post = Post.objects.first()

post.user       # объект User (может вызвать запрос)
post.user.id    # PK объекта User
post.user_id    # уже загруженное значение FK
  • post.user – при первом обращении может вызвать дополнительный запрос.
  • post.user_id – всегда доступно без запроса.

4.2 Пример производительности

# views.py
posts = Post.objects.all()   # без select_related

for post in posts:
    print(post.user.id)  # 1 запрос + N запросов к User

Если posts содержит 100 записей, будет 101 запрос.

for post in posts:
    print(post.user_id)  # только 1 запрос

Использование select_related("user") позволяет обращаться к post.user без дополнительных запросов.


5. Сравнение user_id, user.id, user__id

5.1 user_id – прямой доступ к столбцу

  • Где? В экземплярах модели и в запросах.
  • Значение: реальный FK‑столбец.
  • Производительность: без дополнительных запросов.

5.2 user.id – доступ через объект

  • Где? Только в Python‑объектах.
  • Значение: PK объекта User.
  • Производительность: может вызвать дополнительный запрос, если не использован select_related.

5.3 user__id – условие через связь

  • Где? Только в аргументах запросов.
  • Значение: условие на поле id связанной модели.
  • Производительность: обычно приводит к JOIN, но в простых случаях может быть оптимизировано.

6. Часто встречающиеся паттерны

# 1) Прямое поле FK
Post.objects.filter(user_id=1)

# 2) Объект FK
Post.objects.filter(user=1)

# 3) Через связь
Post.objects.filter(user__id=1)
Post.objects.filter(user__email__icontains="@example.com")

Рекомендации:

  • Для простого фильтра по PK – user_id или user=request.user.
  • Для фильтра по другим полям связанной модели – user__email, user__is_active.

7. Практические правила

  1. Фильтрация по FK python Post.objects.filter(user=request.user) Post.objects.filter(user_id=request.user.id)
  2. Фильтрация по полям связанной модели python Post.objects.filter(user__is_active=True) Post.objects.filter(user__email__endswith="@example.com")
  3. Только PK в шаблонах django {{ post.user_id }} {# без лишних запросов #}
  4. Частый доступ к полям User python posts = Post.objects.select_related("user") for post in posts: print(post.user.username, post.user.email)
  5. Будьте внимательны с __ – каждый __ может добавить JOIN.

8. Итог

  • _ – обычное имя, например <поле>_id – реальный FK‑столбец.
  • __ – разделитель запросов в Django ORM.
  • . – доступ к атрибуту Python‑объекта.
  • user_id – значение FK в таблице Post.
  • user.id – PK объекта User.
  • user__id – условие на поле id связанной модели в запросе.

Точное понимание этих различий помогает писать более читаемый код, избегать лишних JOIN и проблем N+1.

image