自定义QuerySet和pipe理器没有打破DRY?

我试图find一种方法来实现一个自定义的QuerySet和一个自定义Manager没有打破DRY。 这是我迄今为止:

 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() 

这工作正常,直到我做这样的事情:

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

由于QuerySetManager没有相同的方法,因此会立即中断一切。 我试过创build一个自定义的QuerySet类,并在MyInquiryManager实现它,但是最终我复制了所有的方法定义。

我也发现这个代码片段可以工作,但是我需要将额外的parameter passing给for_user所以它会中断,因为它很大程度上依赖于重新定义get_query_set

有没有办法做到这一点,而无需在QuerySetManager子类中重新定义我的所有方法?

Django已经改变了! 在2009年编写的答案中使用代码之前,请务必查看其余答案和Django文档,看看是否有更合适的解决scheme。


我实现这个的方法是通过添加实际的get_active_for_account作为自定义QuerySet的方法。 然后,为了让pipe理员工作,您可以简单地将__getattr__陷入并相应地返回

为了使这个模式可以重复使用,我已经把Manager位提取出来给一个单独的模型pipe理器:

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) 

一旦你有了这些,在你的模型上,你所需要做的就是将一个QuerySet定义为一个自定义的内部类,并将pipe理器设置为你的自定义pipe理器:

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) 

有了这个模式,其中的任何一个都可以工作:

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

Django 1.7发布了一种创build联合查询集和模型pipe理器的简单方法:

 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() 

有关更多详细信息,请参阅使用QuerySet方法创buildManager 。

您可以使用mixin在pipe理器和查询集上提供这些方法。 看到下面的技术:

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

这也避免了__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) 

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() 

类装饰器使用简单如下:

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

更新:支持非标准的Manager和QuerySet基类,例如@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) 

以下为我工作。

 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) 

这是在默认pipe理器上; 所以我曾经这样做过:

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

但没有理由不应该为二级经理工作。