Begrijpen van _, __ en . in Django ORM
Een overzicht van user_id, user.id en user__id in één keer
Je hebt deze constructies waarschijnlijk al gezien in Django-code.
Post.objects.filter(user_id=1)
Post.objects.filter(user__id=1)
post.user.id
post.user_id
Met de onderstrepingstekens _, dubbele onderstrepingstekens __ en het punt . die allemaal door elkaar heen lopen, is het makkelijk om te verdwalen. Als je ze zomaar door elkaar gebruikt, ontstaan subtiele gedragsverschillen én prestatieproblemen.
In dit artikel behandelen we:
- Wat
_,__en.precies betekenen in Django ORM - De betekenis en prestatieverschillen tussen
user_id,user.idenuser__id - Praktische richtlijnen voor wanneer je welke syntaxis moet gebruiken
1. Het grote plaatje: de rol van de drie symbolen
Kort samengevat:
_(één onderstrepingsteken)- Het is slechts een onderdeel van de naam.
- Voorbeelden:
created_at,user_id. -
In Django maakt een
ForeignKeyautomatisch een kolom<veldnaam>_idin de database. -
__(twee onderstrepingstekens) - De "lookup separator" van Django ORM.
- Alleen binnen de argumentnamen van
filter(),exclude(),order_by(),values()etc. heeft het betekenis. -
Voorbeelden:
age__gte=20(veld + voorwaarde)user__email='a@b.com'(toegang tot een veld via een relatie)created_at__date=...(transformatie + voorwaarde)
-
.(punt) - De attribuuttoegang in Python.
- Voorbeelden:
obj.user,obj.user.id,qs.filter(...).order_by(...). - Vanuit het ORM‑perspectief is dit simpelweg toegang tot een attribuut van een Python‑object. (Eventueel kan er een extra query worden uitgevoerd – Lazy Loading.)
Laten we nu dieper ingaan op elk van deze symbolen vanuit het Django‑ORM‑perspectief.
2. _ en _id: betekenis rond ForeignKey
2.1 Voorbeeld van een modeldefinitie
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)
Dit resulteert in de volgende kolommen in de database:
posttabelid(PK)user_id(de echte FK‑kolom)title
Django creëert:
- Een attribuut
post.user(eenUser‑instantie) - Een attribuut
post.user_id(de integer‑waarde van de PK)
post.user # -> User object (kan een extra query triggeren)
post.user_id # -> PK‑waarde van User (al aanwezig, geen extra query)
2.2 Gebruik van _id in queries
Voor een ForeignKey kun je op verschillende manieren filteren:
# 1) Een User‑object als waarde
Post.objects.filter(user=request.user)
# 2) De PK‑waarde van de User
Post.objects.filter(user=request.user.id)
# 3) Direct de kolomnaam gebruiken
Post.objects.filter(user_id=request.user.id)
Deze drie benaderingen resulteren uiteindelijk in dezelfde WHERE‑clausule (voor een eenvoudige PK‑vergelijking).
Samenvatting:
_idis de daadwerkelijke kolomnaam in de database en wordt in het ORM gebruikt wanneer je die kolom rechtstreeks wilt aanspreken.
3. __ dubbele onderstreping: de lookup separator van Django ORM
Dit is het unieke Django‑specifieke concept.
In de officiële documentatie wordt __ aangeduid als de "ORM lookup separator". Het wordt gebruikt om velden te koppelen, relaties te traverseren of voorwaarden/transformaties toe te voegen.
3.1 Basispatroon
Er zijn drie hoofdpatronen:
veldnaam__lookup* Voorbeeld:age__gte=20,name__icontains='kim'relatieveld__ander_veld* Voorbeeld:user__email='a@b.com',profile__company__name='...'veld__transform__lookup* Voorbeeld:created_at__date=date.today(),created_at__year__gte=2024
Voorbeeld:
# 1) Eenvoudige vergelijking
Post.objects.filter(title__icontains="django")
# 2) Via een relatie
Post.objects.filter(user__email__endswith="@example.com")
# 3) Alleen het jaar van een datumveld
Post.objects.filter(created_at__year=2025)
Wanneer je een relatie traverses zoals user__email, wordt er een JOIN uitgevoerd in de database.
Kernpunt: elke keer dat
__voorkomt, doet het ORM iets complexer (JOIN, functie, voorwaarde).
4. . puntoperator en user.id, user_id
Laten we nu user.id en user_id naast elkaar bekijken.
4.1 Verschillen in een Django‑instantie
post = Post.objects.first()
post.user # User object (kan een extra SELECT triggeren)
post.user.id # PK van het User object
post.user_id # De FK‑waarde die al in de Post‑instantie staat
Belangrijk:
- Het eerste gebruik van
post.userkan een extra query veroorzaken (Lazy Loading). post.user_idis al aanwezig, dus geen extra query.- Als je alleen de PK nodig hebt, is
user_idlichter.
4.2 Prestatievoorbeeld: lijstpagina
# views.py
posts = Post.objects.all() # zonder select_related
for post in posts:
print(post.user.id)
- De initiële query voor
posts: 1 keer. - Bij elke iteratie die
post.useraanspreekt, wordt er een extra query uitgevoerd. → Bij 100 posts: 100 extra queries (N+1 probleem).
In plaats daarvan:
for post in posts:
print(post.user_id)
user_idis al geladen, dus geen extra query.
Of gebruik select_related:
posts = Post.objects.select_related("user")
for post in posts:
print(post.user.id) # geen extra query
5. user_id, user.id, user__id – een nauwkeurige vergelijking
5.1 user_id – directe DB‑kolom
- Waar? Model‑instantie (
post.user_id) en ORM‑query (filter(user_id=1),order_by("user_id")). - Betekenis: de echte FK‑kolom in de
post‑tabel. - Prestatie: geen extra query bij lezen; in een query resulteert in een eenvoudige WHERE‑clausule.
5.2 user.id – via relatie
- Waar? Alleen in Python‑objecten (
post.user.id). - Betekenis: eerst het
User‑object laden, dan deid‑attribuut lezen. - Prestatie: kan een extra query triggeren tenzij
select_related("user")is gebruikt.
Samenvatting: gebruik
user_idals je alleen de waarde nodig hebt; gebruikuser.<attribuut>als je ook andere velden vanUsernodig hebt.
5.3 user__id – ORM‑lookup via relatie
- Waar? Alleen in de argumentnamen van ORM‑methoden (
filter(user__id=1)). - Betekenis: een JOIN naar de
User‑tabel en een voorwaarde opid. - Prestatie: kan een JOIN veroorzaken; in eenvoudige gevallen kan het worden geoptimaliseerd tot
user_id = 1.
6. _ vs __ – verwarrende patronen overzichtelijk maken
6.1 Veelvoorkomende combinaties in filters
# 1) Directe kolomnaam (_id)
Post.objects.filter(user_id=1)
# 2) FK‑veld met PK‑waarde
Post.objects.filter(user=1)
# 3) Relatie traversen met lookup (__)
Post.objects.filter(user__id=1)
Post.objects.filter(user__email__icontains="@example.com")
In de praktijk geldt:
- Voor een eenvoudige PK‑filter:
user_id=...ofuser=request.user. - Voor een ander veld van de gerelateerde model:
user__email=...,user__is_active=True.
6.2 _ is "naamdeel", __ is "ORM‑operator"
_(underscore) verschijnt overal: variabelen, model‑velden, DB‑kolommen.__(double underscore) is uitsluitend een syntactische constructie binnen ORM‑queries.
7. Praktische regels voor de praktijk
-
Filteren op FK‑waarde
python Post.objects.filter(user=request.user) Post.objects.filter(user_id=request.user.id)Beide zijn acceptabel; kies voor leesbaarheid. -
Filteren op een ander veld van de gerelateerde model
python Post.objects.filter(user__is_active=True) Post.objects.filter(user__email__endswith="@example.com") -
In templates of views alleen de FK‑waarde nodig
django {{ post.user_id }} {# lichtgewicht, geen extra query #} {{ post.user.id }} {# kan extra query triggeren #} -
Veelgebruikte User‑velden
python posts = Post.objects.select_related("user") for post in posts: print(post.user.username, post.user.email) -
Wees bewust van JOIN‑overhead bij
__* Vooral bij omgekeerde FK‑relaties of meerderefilter()‑ketens.
8. Samenvatting in één regel
_: gewoon een naamdeel;<veld>_idis de echte FK‑kolom.__: de ORM‑lookup separator; koppelt velden, relaties, transformaties en voorwaarden..: Python‑attribuuttoegang;user.idlaadt eerst het User‑object.user_id: de FK‑waarde in depost‑tabel.user.id: de PK‑waarde van het User‑object.user__id: een ORM‑lookup die via de relatie naarUser.idfiltert.
Door deze onderscheidingen te kennen, kun je:
- Je query‑intentie verduidelijken
- Onnodige JOINs en N+1‑queries vermijden
- Code‑reviews soepeler laten verlopen

댓글이 없습니다.