En Django, ¿puedes agregar un método a querysets?

En Django, si tengo una clase modelo, por ejemplo

from django.db import models class Transaction(models.Model): ... 

luego, si quiero agregar métodos al modelo, para almacenar, por ejemplo, filtros razonablemente complejos, puedo agregar un administrador de modelos personalizado, por ejemplo

 class TransactionManager(models.Manager): def reasonably_complex_filter(self): return self.get_query_set().filter(...) class Transaction(models.Model): objects = TransactionManager() 

Y luego puedo hacer:

 >>> Transaction.objects.reasonably_complex_filter() 

¿Hay alguna manera en que pueda agregar un método personalizado que pueda ser encadenado al final de un conjunto de consultas desde el modelo?

es decir, agregue el método personalizado de tal manera que pueda hacer esto:

 >>> Transaction.objects.filter(...).reasonably_complex_filter() 

QuerySet agregar métodos al QuerySet que eventualmente terminará. Por lo tanto, debe crear y utilizar una subclase QuerySet que tenga los métodos que defina donde quiera que desee esta funcionalidad.

Encontré este tutorial que explica cómo hacerlo y los motivos por los que podría querer:

http://adam.gomaa.us/blog/2009/feb/16/subclassing-django-querysets/index.html

A partir de django 1.7, se agregó la capacidad de usar un conjunto de consultas como administrador :

 class PersonQuerySet(models.QuerySet): def authors(self): return self.filter(role='A') def editors(self): return self.filter(role='E') class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor')))) people = PersonQuerySet.as_manager() 

Resultando lo siguiente:

 Person.people.authors(last_name='Dahl') 

Además, también se agregó la capacidad de agregar búsquedas personalizadas .

Esta es una solución completa que se sabe que funciona en Django 1.3, cortesía de Zach Smith y Ben.

 class Entry(models.Model): objects = EntryManager() # don't forget this is_public = models.BooleanField() owner = models.ForeignKey(User) class EntryManager(models.Manager): '''Use this class to define methods just on Entry.objects.''' def get_query_set(self): return EntryQuerySet(self.model) def __getattr__(self, name, *args): if name.startswith("_"): raise AttributeError return getattr(self.get_query_set(), name, *args) def get_stats(self): '''A sample custom Manager method.''' return { 'public_count': self.get_query_set().public().count() } class EntryQuerySet(models.query.QuerySet): '''Use this class to define methods on queryset itself.''' def public(self): return self.filter(is_public=True) def by(self, owner): return self.filter(owner=owner) stats = Entry.objects.get_stats() my_entries = Entry.objects.by(request.user).public() 

Nota: el método get_query_set() ahora está en desuso en Django 1.6 ; En este caso, se debe usar get_queryset() .

Puede modificar el método get_query_set() para devolver un QuerySet personalizado, agregando los métodos que necesite. En tu caso, utilizarías:

 class TransactionManager(models.Manager): def get_query_set(self): return TransactionQuerySet(self.model) class TransactionQuerySet(models.query.QuerySet): def reasonably_complex_filter(self): return self.filter(...) 

He visto ejemplos subclasificando TransactionQuerySet en el modelo de Transaction , o en el Manager relacionado, pero eso depende completamente de usted.

edición : parece que he pasado por alto el hecho de que los objects refieren primero al TransactionManager y, por lo tanto, a Transaction.objects.reasonably_complex_filter() no es posible en mi implementación. Esto se puede arreglar de tres maneras:

  • Implemente el filtro_complex_comonablemente tanto en el Administrador como en el QuerySet;
  • Use Transaction.objects.all().reasonably_complex_filter() cuando ese sea el único filtro requerido;
  • Consulte la respuesta de Marcus Whybrow para obtener una solución que implementará el método tanto en QuerySet como en Manager sin duplicación de código.

Depende de la aplicación que opción sea la más deseable, aunque recomendaría encarecidamente la duplicación de código (aunque podría usar un método global para superar esto). Sin embargo, la última opción podría ser demasiado costosa en términos de gastos generales si solo requiere este tipo de práctica una vez, o si solo pretende usar el filtro complejo en combinación con otro filtro.

De hecho, terminé yendo con otro método. Resultó que solo necesitaba encadenar filter llamadas de filter al final de mi método personalizado, así que modifiqué mi método para tomar argumentos de palabras clave arbitrarios y pasarlos a una llamada de filter() al final de mi consulta razonablemente compleja:

 class TransactionManager(models.Manager): def reasonably_complex_filter(self, **kwargs): return self.get_query_set().filter(...).filter(**kwargs) 

Parece funcionar bien para mis propósitos, y es un poco más simple que hacer subclases de QuerySet .

Si necesita métodos personalizados de Manager y métodos personalizados QuerySet, puede usar from_queryset .

 class BaseManager(models.Manager): def manager_only_method(self): return class CustomQuerySet(models.QuerySet): def manager_and_queryset_method(self): return class MyModel(models.Model): objects = BaseManager.from_queryset(CustomQuerySet)() 

https://docs.djangoproject.com/en/2.1/topics/db/managers/#from-queryset