自定义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()
由于QuerySet
与Manager
没有相同的方法,因此会立即中断一切。 我试过创build一个自定义的QuerySet
类,并在MyInquiryManager
实现它,但是最终我复制了所有的方法定义。
我也发现这个代码片段可以工作,但是我需要将额外的parameter passing给for_user
所以它会中断,因为它很大程度上依赖于重新定义get_query_set
。
有没有办法做到这一点,而无需在QuerySet
和Manager
子类中重新定义我的所有方法?
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()
但没有理由不应该为二级经理工作。