在不破坏 DRY 的情况下自定义 QuerySet 和 Manager? [英] Custom QuerySet and Manager without breaking DRY?
问题描述
我正在尝试找到一种方法来实现自定义 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
.
有没有一种方法可以在不重新定义 QuerySet
和 Manager
子类中的所有方法的情况下做到这一点?
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屋!