Django ofrece una amplia variedad de consultas integradas para filtrar (por ejemplo, exact y icontains). Esta documentación explica cómo escribir consultas personalizadas y cómo alterar el funcionamiento de las consultas existentes. Para las referencias de la API de consultas, vea la Referencia de la API de búsqueda.
Let’s start with a small custom lookup. We will write a custom lookup ne
which works opposite to exact. Author.objects.filter(name__ne='Jack')
will translate to the SQL:
"author"."name" <> 'Jack'
Esta consulta es independiente del motor de base de datos, por lo que no necesitamos preocuparnos por diferentes bases de datos.
Hay dos pasos para hacer que esto funcione. Primero debemos implementar la búsqueda personalizada, luego debemos decirle a Django sobre ella:
from django.db.models import Lookup
class NotEqual(Lookup):
lookup_name = "ne"
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return "%s <> %s" % (lhs, rhs), params
Para registrar la búsqueda NotEqual necesitaremos llamar a register_lookup en la clase de campo al que queramos que esté disponible la búsqueda. En este caso, la búsqueda tiene sentido para todos los subclases de Field, por lo que registramos directamente con Field:
from django.db.models import Field
Field.register_lookup(NotEqual)
La inscripción de búsquedas también se puede hacer utilizando un patrón de decorador:
from django.db.models import Field
@Field.register_lookup
class NotEqualLookup(Lookup): ...
Ahora podemos usar foo__ne para cualquier campo foo. Debes asegurarte de que esta inscripción suceda antes de intentar crear cualquier conjunto de consultas usando ella. Podrías colocar la implementación en un archivo models.py, o registrar la búsqueda en el método ready() de una clase AppConfig.
Tomando una mirada más cercana a la implementación, el primer atributo requerido es lookup_name. Esto permite que el ORM entienda cómo interpretar name__ne y use NotEqual para generar la consulta SQL. Por convención, estos nombres siempre son cadenas de minúsculas conteniendo solo letras, pero la única exigencia dura es que no contenga la cadena __.
Luego debemos definir el método as_sql. Este método toma un objeto SQLCompiler, llamado compiler, y la conexión de base de datos activa en ese momento. Los objetos SQLCompiler no están documentados, pero lo único que necesitamos saber sobre ellos es que tienen un método compile() que devuelve una tupla conteniendo una cadena SQL y los parámetros para ser interpolados en esa cadena. En la mayoría de los casos, no necesitas usarlo directamente y puedes pasar el objeto a process_lhs() y process_rhs().
Una búsqueda funciona contra dos valores, lhs y rhs, que representan lado izquierdo y derecho respectivamente. El lado izquierdo es normalmente una referencia de campo, pero puede ser cualquier cosa que implemente la API de expresiones de consulta. El lado derecho es el valor dado por el usuario. En el ejemplo Author.objects.filter(name__ne='Jack'), el lado izquierdo es una referencia al campo name del modelo Author, y 'Jack' es el lado derecho.
Llamamos a process_lhs y process_rhs para convertirlos en los valores que necesitamos para SQL utilizando el objeto compiler descrito antes. Estos métodos devuelven tuplas conteniendo alguna SQL y los parámetros para ser interpolados en esa SQL, exactamente como debemos devolver desde nuestro método as_sql. En el ejemplo anterior, process_lhs devuelve ('"author"."name"', []) y process_rhs devuelve ('"%s"', ['Jack']). En este ejemplo no había parámetros para el lado izquierdo, pero esto dependería del objeto que tengamos, por lo que todavía debemos incluirlos en los parámetros que devolvemos.
Finalmente combinamos las partes en una expresión SQL con <>, y suministramos todos los parámetros para la consulta. Luego devolvemos un tupla conteniendo la cadena SQL generada y los parámetros.
La búsqueda personalizada anterior es genial, pero en algunos casos puede que desee poder encadenar las búsquedas entre sí. Por ejemplo, supongamos que estamos construyendo una aplicación donde queremos aprovechar el operador abs(). Tenemos un modelo Experiment que registra un valor de inicio, un valor final y la diferencia (inicio - fin). Queremos encontrar todos los experimentos donde la diferencia era igual a cierta cantidad (Experiment.objects.filter(change__abs=27)), o donde no superaba cierta cantidad (Experiment.objects.filter(change__abs__lt=27)).
Nota
Este ejemplo es algo forzado, pero muestra con claridad el rango de funcionalidades posibles en una base de datos independiente del backend y sin duplicar la funcionalidad ya presente en Django.
Empezaremos escribiendo un transformador AbsoluteValue. Esto utilizará la función SQL ABS() para transformar el valor antes de la comparación:
from django.db.models import Transform
class AbsoluteValue(Transform):
lookup_name = "abs"
function = "ABS"
A continuación, registremoslo para IntegerField:
from django.db.models import IntegerField
IntegerField.register_lookup(AbsoluteValue)
Ahora podemos ejecutar las consultas que teníamos antes. Experiment.objects.filter(change__abs=27) generará la siguiente SQL:
SELECT ... WHERE ABS("experiments"."change") = 27
Al utilizar Transform en lugar de Lookup significa que podemos encadenar búsquedas adicionales después. Así, Experiment.objects.filter(change__abs__lt=27) generará la siguiente SQL:
SELECT ... WHERE ABS("experiments"."change") < 27
Tenga en cuenta que si no se especifica ninguna otra búsqueda, Django interpreta change__abs=27 como change__abs__exact=27.
Esto también permite utilizar el resultado en cláusulas ORDER BY y DISTINCT ON. Por ejemplo Experiment.objects.order_by('change__abs') genera:
SELECT ... ORDER BY ABS("experiments"."change") ASC
Y en bases de datos que admiten distinct on campos (como PostgreSQL), Experiment.objects.distinct('change__abs') genera:
SELECT ... DISTINCT ON ABS("experiments"."change")
Cuando se busca determinar qué consultas son admisibles después de que se ha aplicado la Transform, Django utiliza el atributo output_field. No era necesario especificarlo aquí, ya que no cambió, pero suponiendo que estuviéramos aplicando AbsoluteValue a algún campo que representa un tipo más complejo (por ejemplo, un punto relativo a un origen o un número complejo) entonces podríamos haber querido especificar que la transformación devuelve un tipo de campo FloatField para consultas posteriores. Esto se puede hacer agregando el atributo output_field a la transformación:
from django.db.models import FloatField, Transform
class AbsoluteValue(Transform):
lookup_name = "abs"
function = "ABS"
@property
def output_field(self):
return FloatField()
Esto garantiza que las consultas posteriores como abs__lte se comporten como lo harían para un FloatField.
abs__lt¶Cuando se utiliza la búsqueda anterior escrita abs, el SQL producido no utilizará los índices de manera eficiente en algunos casos. En particular, cuando utilizamos change__abs__lt=27, esto es equivalente a change__gt=-27 Y change__lt=27. (Para el caso lte podríamos usar la SQL BETWEEN).
Queremos que la consulta Experiment.objects.filter(change__abs__lt=27) genere el siguiente SQL:
SELECT .. WHERE "experiments"."change" < 27 AND "experiments"."change" > -27
La implementación es:
from django.db.models import Lookup
class AbsoluteValueLessThan(Lookup):
lookup_name = "lt"
def as_sql(self, compiler, connection):
lhs, lhs_params = compiler.compile(self.lhs.lhs)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params + lhs_params + rhs_params
return "%s < %s AND %s > -%s" % (lhs, rhs, lhs, rhs), params
AbsoluteValue.register_lookup(AbsoluteValueLessThan)
Hay un par de cosas notables sucediendo. Primero, AbsoluteValueLessThan no llama a process_lhs(). En su lugar, omite la transformación del lhs realizada por AbsoluteValue y utiliza el lhs original. Es decir, queremos obtener "experiments"."change" en lugar de ABS("experiments"."change"). Se puede referirse directamente a self.lhs.lhs ya que AbsoluteValueLessThan solo se puede acceder desde la búsqueda de AbsoluteValue, es decir, el lhs siempre es una instancia de AbsoluteValue.
También ten en cuenta que, ya que ambos lados se utilizan múltiples veces en la consulta, los parámetros deben contener lhs_params y rhs_params múltiples veces.
La consulta final realiza la inversión (27 a -27) directamente en la base de datos. La razón para hacer esto es que si self.rhs es algo diferente a un valor entero plano (por ejemplo, una referencia F()), no podemos realizar las transformaciones en Python.
Nota
De hecho, la mayoría de las consultas con __abs podrían implementarse como consultas de rango como esta, y en la mayoría de los backends de bases de datos es probable que sea más sensato hacerlo ya que puedes aprovechar los índices. Sin embargo, con PostgreSQL podrías querer agregar un índice sobre abs(change) lo que permitiría a estas consultas ser muy eficientes.
La AbsoluteValue ejemplo que discutimos anteriormente es una transformación que se aplica al lado izquierdo de la búsqueda. Es posible que haya algunos casos en los que desee aplicar la transformación a ambos lados izquierdo y derecho. Por ejemplo, si desea filtrar un conjunto de resultados basado en la igualdad del lado izquierdo y el lado derecho sin sensibilidad hacia alguna función SQL.
Examinemos las transformaciones no sensibles al caso aquí. Esta transformación no es muy útil en la práctica ya que Django ya viene con un conjunto de consultas predefinidas no sensibles al caso, pero servirá como una demostración agradable de transformaciones bilaterales de manera independiente del motor de base de datos.
Definimos un transformador UpperCase que utiliza la función SQL UPPER() para transformar los valores antes de la comparación. Definimos bilateral = True para indicar que esta transformación debe aplicarse tanto a lhs como a rhs:
from django.db.models import Transform
class UpperCase(Transform):
lookup_name = "upper"
function = "UPPER"
bilateral = True
Próximo, registremoslo:
from django.db.models import CharField, TextField
CharField.register_lookup(UpperCase)
TextField.register_lookup(UpperCase)
Ahora, la consulta de conjunto Author.objects.filter(name__upper="doe") generará una consulta insensible al caso como esta:
SELECT ... WHERE UPPER("author"."name") = UPPER('doe')
A veces diferentes proveedores de bases de datos requieren SQL diferente para la misma operación. Para este ejemplo reescribiremos una implementación personalizada para MySQL para el operador NotEqual. En lugar de <> usaremos el operador !=. (Nota que en realidad casi todos los motores de base de datos admiten ambos, incluidos todos los motores oficiales soportados por Django).
Podemos cambiar el comportamiento en un back-end específico creando una subclase de NotEqual con un método as_mysql:
class MySQLNotEqual(NotEqual):
def as_mysql(self, compiler, connection, **extra_context):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return "%s != %s" % (lhs, rhs), params
Field.register_lookup(MySQLNotEqual)
We podemos registrarlo con Field. Reemplaza a la clase original NotEqual ya que tiene el mismo lookup_name.
Cuando se compila una consulta, Django busca primero los métodos as_%s % connection.vendor, y luego cae en as_sql. Los nombres de proveedor para las bases de datos integradas son sqlite, postgresql, oracle y mysql.
En algunos casos, puede desear cambiar dinámicamente cuál Transform o Lookup se devuelve en función del nombre pasado en lugar de fijarlo. Como ejemplo, podría tener un campo que almacena coordenadas o una dimensión arbitraria, y desea permitir una sintaxis como .filter(coords__x7=4) para devolver los objetos donde la 7ª coordenada tiene valor 4. Para hacer esto, debería sobrescribir get_lookup con algo como:
class CoordinatesField(Field):
def get_lookup(self, lookup_name):
if lookup_name.startswith("x"):
try:
dimension = int(lookup_name.removeprefix("x"))
except ValueError:
pass
else:
return get_coordinate_lookup(dimension)
return super().get_lookup(lookup_name)
Luego definiría get_coordinate_lookup apropiadamente para devolver una clase de subclase Lookup que maneje el valor relevante de dimension.
Hay un método llamado similarmente nombrado get_transform(). get_lookup() siempre debe devolver una subclase Lookup, y get_transform() una subclase Transform. Es importante recordar que los objetos Transform pueden ser filtrados aún más, mientras que los objetos Lookup no.
Al filtrar, si solo hay un nombre de lookup restante para resolver, buscamos un Lookup. Si hay múltiples nombres, buscamos un Transform. En la situación donde solo hay un nombre y no se encuentra un Lookup, buscamos un Transform y luego el lookup exacto en ese Transform. Todas las secuencias de llamadas siempre terminan con un Lookup. Para aclarar:
.filter(myfield__mylookup) llamará a myfield.get_lookup('mylookup').
.filter(myfield__mytransform__mylookup) llamará a myfield.get_transform('mytransform'), y luego mytransform.get_lookup('mylookup').
.filter(myfield__mytransform) primero llamará a myfield.get_lookup('mytransform'), que fallará, por lo que caerá en llamar a myfield.get_transform('mytransform') y luego mytransform.get_lookup('exact').
may 31, 2026