Creando búsquedas de campo personalizadas en Django

¿Cómo crear búsquedas de campo personalizadas en Django?

Al filtrar conjuntos de consultas, django proporciona un conjunto de búsquedas que puede utilizar: __contains , __iexact , __in , etc. Quiero poder proporcionar una nueva búsqueda para mi gerente, así que, por ejemplo, alguien podría decir:

 twentysomethings = Person.objects.filter(age__within5=25) 

y recupere todos los objetos Person con una edad entre 20 y 30 años. ¿Debo subclasificar la clase QuerySet o Manager para hacer esto? ¿Cómo se implementaría?

A partir de Django 1.7, hay una forma sencilla de implementarlo. Su ejemplo es en realidad muy similar al de la documentación :

 from django.db.models import Lookup class AbsoluteValueLessThan(Lookup): lookup_name = 'lt' def as_sql(self, qn, connection): lhs, lhs_params = qn.compile(self.lhs.lhs) rhs, rhs_params = self.process_rhs(qn, 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) 

Mientras se registra, puede usar Field.register_lookup(AbsoluteValueLessThan) lugar.

Una forma más flexible de hacer esto es escribir un QuerySet personalizado así como un administrador personalizado. Trabajando desde el código de ozan:

 class PersonQuerySet(models.query.QuerySet): def in_age_range(self, min, max): return self.filter(age__gte=min, age__lt=max) class PersonManager(models.Manager): def get_query_set(self): return PersonQuerySet(self.model) def __getattr__(self, name): return getattr(self.get_query_set(), name) class Person(models.Model): age = #... objects = PersonManager() 

Esto le permite encadenar su consulta personalizada. Entonces ambas consultas serían válidas:

 Person.objects.in_age_range(20,30) Person.objects.exclude(somefield = some_value).in_age_range(20, 30) 

En lugar de crear una búsqueda de campo, la mejor práctica sería crear un método de administrador, que podría verse un poco así:

 class PersonManger(models.Manager): def in_age_range(self, min, max): return self.filter(age__gte=min, age__lt=max) class Person(models.Model): age = #... objects = PersonManager() 

entonces el uso sería así:

 twentysomethings = Person.objects.in_age_range(20, 30) 

Primero, permítame decir que no hay ninguna maquinaria Django en su lugar que tenga la intención de facilitar públicamente lo que le gustaría.

(Editar – en realidad desde Django 1.7 hay: https://docs.djangoproject.com/en/1.7/howto/custom-lookups/ )

Dicho esto, si realmente desea lograr esto, QuerySet subclase de QuerySet y anule el método _filter_or_exclude() . Luego cree un administrador personalizado que solo devuelva su QuerySet personalizado (o el parche de Django’s QuerySet , yuck). Hacemos esto en neo4django para reutilizar la mayor cantidad posible de código Queryset de Django ORM al crear objetos de Query específicos de Neo4j.

Intenta algo (aproximadamente) como este, adaptado de la respuesta de Zach. He dejado el manejo real de errores para el análisis de búsqueda de campo como un ejercicio para el lector 🙂

 class PersonQuerySet(models.query.QuerySet): def _filter_or_exclude(self, negate, *args, **kwargs): cust_lookups = filter(lambda s: s[0].endswith('__within5'), kwargs.items()) for lookup in cust_lookups: kwargs.pop(lookup[0]) lookup_prefix = lookup[0].rsplit('__',1)[0] kwargs.update({lookup_prefix + '__gte':lookup[1]-5, lookup_prefix + '__lt':lookup[1]+5}) return super(PersonQuerySet, self)._filter_or_exclude(negate, *args, **kwargs) class PersonManager(models.Manager): def get_query_set(self): return PersonQuerySet(self.model) class Person(models.Model): age = #... objects = PersonManager() 

Comentarios finales: claro, si quieres encadenar búsquedas de campo personalizadas, esto va a ser bastante peludo. Además, normalmente escribo esto un poco más funcionalmente y uso itertools para el rendimiento, pero pensé que era más claro dejarlo de lado. ¡Que te diviertas!