在不破坏 DRY 的情况下自定义 QuerySet 和 Manager? [英] Custom QuerySet and Manager without breaking DRY?

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

问题描述

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

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

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

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.

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

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.

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

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

推荐答案

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

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.

我实现这一点的方法是将实际的 get_active_for_account 添加为自定义 QuerySet 的方法.然后,要使其脱离管理器,您可以简单地捕获 __getattr__ 并相应地返回它

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

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

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)

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

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 如果您使用自定义用户(AbstractUser),则需要更改
来自

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

class CustomQuerySetManager(models.Manager):

from django.contrib.auth.models import UserManager

class CustomQuerySetManager(UserManager):
    ***

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

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