¿QuerySet y Manager personalizados sin romper DRY?

Estoy tratando de encontrar una forma de implementar tanto un QuerySet personalizado como un Manager personalizado sin romper DRY. Esto es lo que tengo hasta ahora:

 class MyInquiryManager(models.Manager): def for_user(self, user): return self.get_query_set().filter( Q(assigned_to_user=user) | Q(assigned_to_group__in=user.groups.all()) ) class Inquiry(models.Model): ts = models.DateTimeField(auto_now_add=True) status = models.ForeignKey(InquiryStatus) assigned_to_user = models.ForeignKey(User, blank=True, null=True) assigned_to_group = models.ForeignKey(Group, blank=True, null=True) objects = MyInquiryManager() 

Esto funciona bien, hasta que haga algo como esto:

 inquiries = Inquiry.objects.filter(status=some_status) my_inquiry_count = inquiries.for_user(request.user).count() 

Esto rompe rápidamente todo porque QuerySet no tiene los mismos métodos que el Manager . Intenté crear una clase QuerySet personalizada e implementarla en MyInquiryManager , pero termino replicando todas las definiciones de mis métodos.

También encontré este fragmento que funciona, pero necesito pasar el argumento adicional a for_user para que se rompa porque depende en gran medida de la redefinición de get_query_set .

¿Hay alguna forma de hacerlo sin redefinir todos mis métodos en las QuerySet y Manager ?

Django ha cambiado! Antes de usar el código en esta respuesta, que fue escrita en 2009, asegúrese de revisar el rest de las respuestas y la documentación de Django para ver si hay una solución más adecuada.


La forma en que lo implementé es agregando la get_active_for_account real como método de un QuerySet personalizado. Entonces, para que funcione el administrador, simplemente puede atrapar el __getattr__ y devolverlo en consecuencia

Para que este patrón sea reutilizable, extraje los bits de Manager en un administrador de modelo independiente:

custom_queryset / models.py

 from django.db import models from django.db.models.query import QuerySet class CustomQuerySetManager(models.Manager): """A re-usable Manager to access a custom QuerySet""" def __getattr__(self, attr, *args): try: return getattr(self.__class__, attr, *args) except AttributeError: # don't delegate internal methods to the queryset if attr.startswith('__') and attr.endswith('__'): raise return getattr(self.get_query_set(), attr, *args) def get_query_set(self): return self.model.QuerySet(self.model, using=self._db) 

Una vez que tenga eso, en sus modelos todo lo que necesita hacer es definir un QuerySet como una clase interna personalizada y configurar el administrador en su administrador personalizado:

your_app / models.py

 from custom_queryset.models import CustomQuerySetManager from django.db.models.query import QuerySet class Inquiry(models.Model): objects = CustomQuerySetManager() class QuerySet(QuerySet): def active_for_account(self, account, *args, **kwargs): return self.filter(account=account, deleted=False, *args, **kwargs) 

Con este patrón, cualquiera de estos funcionará:

 >>> Inquiry.objects.active_for_account(user) >>> Inquiry.objects.all().active_for_account(user) >>> Inquiry.objects.filter(first_name='John').active_for_account(user) 

El Django 1.7 lanzó una forma nueva y sencilla de crear un conjunto de consultas combinado y un administrador de modelos:

 class InquiryQuerySet(models.QuerySet): def for_user(self): return self.filter( Q(assigned_to_user=user) | Q(assigned_to_group__in=user.groups.all()) ) class Inquiry(models.Model): objects = InqueryQuerySet.as_manager() 

Consulte Crear administrador con los métodos QuerySet para obtener más detalles.

Puede proporcionar los métodos en el administrador y el conjunto de consultas utilizando un mixin. Ver la siguiente técnica:

http://hunterford.me/django-custom-model-manager-chaining/

Esto también evita el uso de un __getattr__() .

 from django.db.models.query import QuerySet class PostMixin(object): def by_author(self, user): return self.filter(user=user) def published(self): return self.filter(published__lte=datetime.now()) class PostQuerySet(QuerySet, PostMixin): pass class PostManager(models.Manager, PostMixin): def get_query_set(self): return PostQuerySet(self.model, using=self._db) 

Una versión ligeramente mejorada del enfoque de T. Stone:

 def objects_extra(mixin_class): class MixinManager(models.Manager, mixin_class): class MixinQuerySet(QuerySet, mixin_class): pass def get_query_set(self): return self.MixinQuerySet(self.model, using=self._db) return MixinManager() 

Los decoradores de clase hacen el uso tan simple como:

 class SomeModel(models.Model): ... @objects_extra class objects: def filter_by_something_complex(self, whatever parameters): return self.extra(...) ... 

Actualización: soporte para clases base no estándar y de QuerySet, por ejemplo @objects_extra (django.contrib.gis.db.models.GeoManager, django.contrib.gis.db.models.query.GeoQuerySet):

 def objects_extra(Manager=django.db.models.Manager, QuerySet=django.db.models.query.QuerySet): def oe_inner(Mixin, Manager=django.db.models.Manager, QuerySet=django.db.models.query.QuerySet): class MixinManager(Manager, Mixin): class MixinQuerySet(QuerySet, Mixin): pass def get_query_set(self): return self.MixinQuerySet(self.model, using=self._db) return MixinManager() if issubclass(Manager, django.db.models.Manager): return lambda Mixin: oe_inner(Mixin, Manager, QuerySet) else: return oe_inner(Mixin=Manager) 

El siguiente funciona para mi.

 def get_active_for_account(self,account,*args,**kwargs): """Returns a queryset that is Not deleted For the specified account """ return self.filter(account = account,deleted=False,*args,**kwargs) 

Esto está en el administrador predeterminado; entonces solía hacer algo como:

 Model.objects.get_active_for_account(account).filter() 

Pero no hay ninguna razón por la que no debería funcionar para un administrador secundario.