Verstehen von _, __ und . im Django ORM

user_id, user.id, user__id – alles auf einen Blick

In Django-Code begegnet man diesen Symbolen schon einmal.

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

Die Mischung aus Unterstrich _, Doppelunterstrich __ und Punkt . kann leicht verwirren. Wenn man sie willkürlich mischt, entstehen nicht nur subtile Funktionsunterschiede, sondern auch Performanceprobleme.

In diesem Beitrag werden wir:

  • erklären, was _, __ und . im Django ORM genau bedeuten;
  • die Unterschiede in Bedeutung und Performance von user_id, user.id und user__id erläutern;
  • praxisnahe Richtlinien geben, wann welcher Ansatz sinnvoll ist.

1. Überblick: Die drei Symbole



_ (ein Unterstrich)

  • Teil des Namens – nichts Besonderes.
  • Beispiel: created_at, user_id.
  • Bei ForeignKey erzeugt Django automatisch eine Spalte <feldname>_id in der Datenbank.

__ (Doppelunterstrich)

  • Spezialzeichen des Django ORM – Lookup‑Separator.
  • Nur innerhalb von filter(), exclude(), order_by(), values() usw. hat es Bedeutung.
  • Beispiele:
  • age__gte=20 (Bedingung)
  • user__email='a@b.com' (Zugriff über Beziehung)
  • created_at__date=... (Transformation + Bedingung)

. (Punkt)

  • Python‑Attribut‑Operator.
  • obj.user, obj.user.id, qs.filter(...).order_by(...) – alles Punkt‑Notation.
  • Im ORM bedeutet es einfach: Zugriff auf ein Attribut eines Python‑Objekts (kann Lazy‑Loading auslösen).

2. _ und _id bei ForeignKey

2.1 Modellerstellung

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)

Datenbank‑Spalten:

  • post‑Tabelle
  • id (PK)
  • user_id (echte FK‑Spalte)
  • title

Django erzeugt:

  • post.user – ein User‑Objekt
  • post.user_id – die PK‑Zahl des Users
post.user    # -> User‑Objekt (möglicherweise zusätzliche Query)
post.user_id # -> PK‑Wert (keine zusätzliche Query)

2.2 Verwendung in Queries

# 1) FK‑Feld mit User‑Objekt
Post.objects.filter(user=request.user)

# 2) FK‑Feld mit PK‑Wert
Post.objects.filter(user=request.user.id)

# 3) Direkte Verwendung der Spalte
Post.objects.filter(user_id=request.user.id)

Alle drei führen zu identischem WHERE‑Klausel.


3. __ – der Lookup‑Separator



__ verbindet Felder, Beziehungen und Transformationen.

3.1 Grundmuster

  1. feld__lookup * age__gte=20, name__icontains='kim'
  2. beziehung__anderes_feld * user__email='a@b.com'
  3. feld__transform__lookup * created_at__date=date.today()

Beispiele:

# 1) einfacher Vergleich
Post.objects.filter(title__icontains="django")

# 2) über Beziehung
Post.objects.filter(user__email__endswith="@example.com")

# 3) nur das Jahr vergleichen
Post.objects.filter(created_at__year=2025)

Bei user__email entsteht ein JOIN in der SQL‑Abfrage.


4. Punkt‑Operator und user.id vs. user_id

4.1 Unterschied in Django‑Instanzen

post = Post.objects.first()

post.user       # User‑Objekt (möglicherweise zusätzliche Query)
post.user.id    # PK des geladenen User‑Objekts
post.user_id    # bereits vorhandener FK‑Wert
  • post.user löst Lazy‑Loading aus, wenn der User noch nicht geladen ist.
  • post.user_id ist sofort verfügbar – keine zusätzliche Query.

4.2 Performance‑Beispiel – Listen‑Ansicht

# views.py
posts = Post.objects.all()   # ohne select_related

for post in posts:
    print(post.user.id)
  • 1 Query für posts.
  • Für jedes post.user wird eine weitere Query ausgeführt → N+1‑Problem.

Mit select_related:

posts = Post.objects.select_related("user")
for post in posts:
    print(post.user.id)  # keine zusätzliche Query

5. Vergleich von user_id, user.id und user__id

5.1 user_id – direkte Spalten‑Nutzung

  • Wo? Modell‑Instanz (post.user_id) oder Query (filter(user_id=1)).
  • Bedeutung: echte FK‑Spalte in der post‑Tabelle.
  • Performance: keine zusätzliche Query.

5.2 user.id – Zugriff über Beziehung

  • Wo? Nur in Python‑Objekten (post.user.id).
  • Bedeutung: lädt zuerst das User‑Objekt, dann liest die PK.
  • Performance: zusätzliche Query, wenn select_related nicht verwendet.

5.3 user__id – Lookup in Query

  • Wo? Nur in Query‑Argumenten (filter(user__id=1)).
  • Bedeutung: folgt der FK‑Beziehung und filtert nach User.id.
  • SQL‑Ausgabe: JOIN user ON post.user_id = user.id + WHERE user.id = 1.

6. Häufige Muster – _ vs. __

# 1) Direkte Spalte
Post.objects.filter(user_id=1)

# 2) FK‑Feld mit PK‑Wert
Post.objects.filter(user=1)

# 3) Über Beziehung
Post.objects.filter(user__id=1)
Post.objects.filter(user__email__icontains="@example.com")

Richtlinie:

  • Für reine PK‑Filter: user_id=... oder user=....
  • Für Filter über andere Felder des Users: user__email=..., user__is_active=True.

7. Praktische Regeln für den Alltag

  1. Nur FK‑Wert filtern python Post.objects.filter(user=request.user) Post.objects.filter(user_id=request.user.id) Beide sind ok – ich bevorzuge die lesbare Variante mit Objekt.

  2. Andere Felder des Users python Post.objects.filter(user__is_active=True) Post.objects.filter(user__email__endswith="@example.com")

  3. Template/Views – nur FK‑Wert nötig django {{ post.user_id }} {# leichter, keine zusätzliche Query #}

  4. Mehrere User‑Felder python posts = Post.objects.select_related("user") for post in posts: print(post.user.username, post.user.email)

  5. __ kann JOINs erzeugen – immer im Hinterkopf behalten.


8. Kurzfassung

  • _ – einfaches Namensmerkmal, z. B. <feld>_id für FK‑Spalte.
  • __ – spezieller Lookup‑Separator des Django ORM.
  • . – Python‑Attribut‑Operator.
  • user_id – FK‑Wert in der post‑Tabelle.
  • user.id – PK‑Wert des geladenen User‑Objekts.
  • user__id – ORM‑Lookup über die FK‑Beziehung.

Durch klare Unterscheidung dieser Symbole wird die Absicht der Queries klarer, unnötige JOINs und N+1‑Probleme reduziert und Code‑Reviews werden einfacher.

image