自定义 QuerySet 和 Manager 而不破坏 DRY? [英] Custom QuerySet and Manager without breaking DRY?

查看:22
本文介绍了自定义 QuerySet 和 Manager 而不破坏 DRY?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图找到一种方法来实现自定义 QuerySet 和自定义 Manager 而不会破坏 DRY.这是我目前所拥有的:

class MyInquiryManager(models.Manager):def for_user(self, user):返回 self.get_query_set().filter(Q(assigned_to_user=user) |Q(assigned_to_group__in=user.groups.all()))类查询(模型.模型):ts = models.DateTimeField(auto_now_add=True)status = models.ForeignKey(InquiryStatus)assigned_to_user = models.ForeignKey(用户,空白=真,空=真)assigned_to_group = models.ForeignKey(组,空白=真,空=真)对象 = MyInquiryManager()

这很好用,直到我做这样的事情:

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

这会立即破坏一切,因为 QuerySet 没有与 Manager 相同的方法.我已经尝试创建一个自定义的 QuerySet 类,并在 MyInquiryManager 中实现它,但我最终复制了我的所有方法定义.

我还发现 这个片段 有效,但我需要将额外的参数传递给 for_user 所以它会崩溃,因为它严重依赖于重新定义 get_query_set.

有没有办法在不重新定义 QuerySetManager 子类中的所有方法的情况下做到这一点?

解决方案

Django 发生了变化! 在使用此答案中的代码之前,该答案是 2009 年编写的,请务必查看其余部分答案和 Django 文档,看看是否有更合适的解决方案.

<小时>

我实现的方法是添加实际的get_active_for_account 作为自定义QuerySet 的方法.然后,为了让它在经理之外工作,你可以简单地捕获 __getattr__ 并相应地返回它

为了使这种模式可重用,我将 Manager 位提取到单独的模型管理器中:

custom_queryset/models.py

from django.db 导入模型从 django.db.models.query 导入 QuerySet类 CustomQuerySetManager(models.Manager):"""访问自定义查询集的可重用管理器"""def __getattr__(self, attr, *args):尝试:返回 getattr(self.__class__, attr, *args)除了属性错误:# 不要将内部方法委托给查询集如果 attr.startswith('__') 和 attr.endswith('__'):增加返回 getattr(self.get_query_set(), attr, *args)def get_query_set(self):返回 self.model.QuerySet(self.model, using=self._db)

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

your_app/models.py

from custom_queryset.models import CustomQuerySetManager从 django.db.models.query 导入 QuerySet类查询(模型.模型):对象 = CustomQuerySetManager()类查询集(查询集):def active_for_account(self, account, *args, **kwargs):return self.filter(account=account,deleted=False,*args,**kwargs)

使用这种模式,其中任何一个都可以:

<预><代码>>>>Inquiry.objects.active_for_account(用户)>>>Inquiry.objects.all().active_for_account(user)>>>Inquiry.objects.filter(first_name='John').active_for_account(user)

UPD 如果您使用自定义用户(AbstractUser),则需要更改
来自

class CustomQuerySetManager(models.Manager):

 from django.contrib.auth.models import UserManager类 CustomQuerySetManager(UserManager):***

I'm trying to find a way to implement both a custom QuerySet and a custom Manager without breaking DRY. This is what I have so far:

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

This works fine, until I do something like this:

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

This promptly breaks everything because the QuerySet doesn't have the same methods as the Manager. I've tried creating a custom QuerySet class, and implementing it in MyInquiryManager, but I end up replicating all of my method definitions.

I also found this snippet which works, but I need to pass in the extra argument to for_user so it breaks down because it relies heavily on redefining get_query_set.

Is there a way to do this without redefining all of my methods in both the QuerySet and the Manager subclasses?

解决方案

Django has changed! Before using the code in this answer, which was written in 2009, be sure to check out the rest of the answers and the Django documentation to see if there is a more appropriate solution.


The way I've implemented this is by adding the actual get_active_for_account as a method of a custom QuerySet. Then, to make it work off the manager, you can simply trap the __getattr__ and return it accordingly

To make this pattern re-usable, I've extracted out the Manager bits to a separate model manager:

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)

Once you've got that, on your models all you need to do is define a QuerySet as a custom inner class and set the manager to your custom manager:

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)

With this pattern, any of these will work:

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

UPD if you are using it with custom user(AbstractUser), you need to change
from

class CustomQuerySetManager(models.Manager):

to

from django.contrib.auth.models import UserManager

class CustomQuerySetManager(UserManager):
    ***

这篇关于自定义 QuerySet 和 Manager 而不破坏 DRY?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆