Entendiendo correctamente _, __ y . en el ORM de Django
Resumen de user_id, user.id y user__id
Probablemente ya hayas visto estos elementos en tu código Django.
Post.objects.filter(user_id=1)
Post.objects.filter(user__id=1)
post.user.id
post.user_id
La mezcla de guiones bajos, dobles guiones bajos y puntos puede resultar confusa. Si empiezas a usarlos sin distinguirlos, aparecerán diferencias sutiles de comportamiento y rendimiento.
En este artículo cubriremos:
- ¿Qué significa exactamente cada símbolo en el ORM de Django?
- Diferencias de significado y rendimiento entre user_id, user.id y user__id
- Recomendaciones prácticas sobre cuándo usar cada uno
1. Visión general: el papel de los tres símbolos
1.1 _ (un guion bajo)
- Es simplemente parte del nombre.
- Ejemplos:
created_at,user_id. - En Django, una
ForeignKeycrea automáticamente una columna<campo>_iden la base de datos.
1.2 __ (doble guion bajo)
- Es el separador de búsqueda exclusivo del ORM de Django.
- Solo tiene significado dentro de los nombres de argumentos de
filter(),exclude(),order_by(),values(), etc. - Ejemplos:
age__gte=20(campo + condición)user__email='a@b.com'(acceder a un campo de un modelo relacionado)created_at__date=...(transformar y filtrar)
1.3 . (punto)
- Es el operador de acceso a atributos de Python.
- Ejemplos:
obj.user,obj.user.id,qs.filter(...).order_by(...). - En el ORM, simplemente accede a un atributo de un objeto Python que ya está en memoria (puede disparar una consulta adicional si el objeto relacionado no está cargado).
2. _ y _id: significado alrededor de ForeignKey
2.1 Definición del modelo
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)
Esto crea la tabla post con columnas:
id(PK)user_id(columna FK real)title
Django crea dos atributos en el objeto Python:
post.user→ instancia deUser(puede disparar una consulta)post.user_id→ entero (valor PK, sin consulta adicional)
2.2 Usar _id en consultas
# 1) Pasar la instancia de User
Post.objects.filter(user=request.user)
# 2) Pasar el PK de User
Post.objects.filter(user=request.user.id)
# 3) Usar directamente el nombre de columna
Post.objects.filter(user_id=request.user.id)
Las tres formas se compilan en la misma condición WHERE "post"."user_id" = 1.
3. __ en la cadena de búsqueda: el separador de búsqueda del ORM
El separador __ permite encadenar relaciones, transformaciones y condiciones.
3.1 Patrones comunes
campo__lookup→age__gte=20relacion__campo→user__email='a@b.com'campo__transform__lookup→created_at__date=date.today()
Ejemplo:
# 1) Comparación simple
Post.objects.filter(title__icontains="django")
# 2) Condición a través de una relación
Post.objects.filter(user__email__endswith="@example.com")
# 3) Comparar solo el año de una fecha
Post.objects.filter(created_at__year=2025)
Cuando se atraviesa una relación, el ORM genera un JOIN.
4. . y los atributos user.id vs user_id
4.1 Diferencia en instancias de Django
post = Post.objects.first()
post.user # instancia de User (puede disparar una consulta)
post.user.id # PK de la instancia de User
post.user_id # valor FK ya presente en `post`
post.userpuede disparar una consulta adicional si el objeto no está cargado.post.user_idno dispara ninguna consulta adicional.
4.2 Ejemplo de rendimiento en una lista
# views.py
posts = Post.objects.all() # sin select_related
for post in posts:
print(post.user.id) # 1 consulta por post (N+1)
En cambio:
for post in posts:
print(post.user_id) # sin consultas adicionales
Si usas select_related("user"), post.user ya está cargado y no habrá consultas adicionales.
5. Comparación clara entre user_id, user.id y user__id
5.1 user_id – usar la columna FK directamente
- En instancias:
post.user_id. - En consultas:
filter(user_id=1),order_by("user_id"). - No dispara consultas adicionales.
5.2 user.id – acceder al PK a través del objeto relacionado
- Solo en Python:
post.user.id. - Puede disparar una consulta si
post.userno está cargado. - Con
select_related("user"), no hay consultas adicionales.
5.3 user__id – usar el separador de búsqueda en consultas
- Solo en argumentos de consulta:
Post.objects.filter(user__id=1). - Genera un
JOINy una condiciónWHERE user.id = 1. - En la práctica, el ORM puede optimizar a
user_id = 1cuando sea posible, pero conceptualmente es una consulta que atraviesa la relación.
6. Resumen de patrones confusos
# 1) Usar la columna FK directamente
Post.objects.filter(user_id=1)
# 2) Pasar la instancia o el PK
Post.objects.filter(user=1)
# 3) Usar la relación en la búsqueda
Post.objects.filter(user__id=1)
Post.objects.filter(user__email__icontains="@example.com")
Regla práctica:
- Para filtrar por el PK de la FK:
user_id=...ouser=.... - Para filtrar por otro campo del modelo relacionado:
user__campo=....
7. Recomendaciones para la práctica
-
Filtrar por PK de la FK
python Post.objects.filter(user=request.user) Post.objects.filter(user_id=request.user.id)Ambos son aceptables; la elección depende de la legibilidad. -
Filtrar por otro campo del modelo relacionado
python Post.objects.filter(user__is_active=True) Post.objects.filter(user__email__endswith="@example.com") -
En plantillas o vistas cuando solo necesitas el PK
django {{ post.user_id }} {# sin consultas adicionales #} {{ post.user.id }} {# puede disparar una consulta #} -
Si usas
select_related("user")python posts = Post.objects.select_related("user") for post in posts: print(post.user.username)Evita el problema N+1. -
Ten en cuenta los
JOIN* Cada__que atraviesa una relación puede generar unJOINadicional. * En consultas complejas, revisa el plan de ejecución para evitar sobrecarga.
8. Conclusión
_→ parte del nombre, especialmente<campo>_idpara FK.__→ separador de búsqueda del ORM; encadena campos, relaciones y transformaciones..→ acceso a atributos de Python; puede disparar consultas adicionales.user_id→ valor FK en la tablapost.user.id→ PK de la instanciaUser(puede requerir consulta).user__id→ condición en la consulta que atraviesa la relaciónuser.
Conocer estas diferencias te permite escribir consultas más claras, evitar consultas N+1 y optimizar el rendimiento de tu aplicación Django.

No hay comentarios.