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.idunduser__iderlä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>_idin 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‑Tabelleid(PK)user_id(echte FK‑Spalte)title
Django erzeugt:
post.user– einUser‑Objektpost.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
feld__lookup*age__gte=20,name__icontains='kim'beziehung__anderes_feld*user__email='a@b.com'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.userlöst Lazy‑Loading aus, wenn der User noch nicht geladen ist.post.user_idist 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.userwird 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_relatednicht 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=...oderuser=.... - Für Filter über andere Felder des Users:
user__email=...,user__is_active=True.
7. Praktische Regeln für den Alltag
-
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. -
Andere Felder des Users
python Post.objects.filter(user__is_active=True) Post.objects.filter(user__email__endswith="@example.com") -
Template/Views – nur FK‑Wert nötig
django {{ post.user_id }} {# leichter, keine zusätzliche Query #} -
Mehrere User‑Felder
python posts = Post.objects.select_related("user") for post in posts: print(post.user.username, post.user.email) -
__kann JOINs erzeugen – immer im Hinterkopf behalten.
8. Kurzfassung
_– einfaches Namensmerkmal, z. B.<feld>_idfür FK‑Spalte.__– spezieller Lookup‑Separator des Django ORM..– Python‑Attribut‑Operator.user_id– FK‑Wert in derpost‑Tabelle.user.id– PK‑Wert des geladenenUser‑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.

Es sind keine Kommentare vorhanden.