如何在链查询中使用自定义管理器?

Posted

技术标签:

【中文标题】如何在链查询中使用自定义管理器?【英文标题】:How to use custom managers in chain queries? 【发布时间】:2011-11-19 15:15:02 【问题描述】:

我创建了一个自定义管理器,它必须随机化我的查询:

class RandomManager(models.Manager):

    def randomize(self):        
        count = self.aggregate(count=Count('id'))['count']
        random_index = random.randint(0, count - 1)
        return self.all()[random_index]

当我首先使用经理中定义的方法时,它可以正常工作:

>>> PostPages.random_objects.randomize()
>>> <PostPages: post 3>

我需要随机化已经过滤的查询。当我尝试使用管理器和链中的方法时出现错误:

PostPages.random_objects.filter(image_gallary__isnull=False).randomize()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/home/i159/workspace/shivaroot/shivablog/<ipython-input-9-98f654c77896> in <module>()
----> 1 PostPages.random_objects.filter(image_gallary__isnull=False).randomize()

AttributeError: 'QuerySet' object has no attribute 'randomize'

过滤的结果不是模型类的实例,而是django.db.models.query.QuerySet,所以分别没有我的管理器和方法。 有没有办法在链式查询中使用自定义管理器?

【问题讨论】:

【参考方案1】:

鉴于您有一个现有的models.Manager,并且您不想将某些管理器方法公开给可链接的查询集,您可以使用Manager.from_queryset(QuerySet)()

因此,您仍然可以将所有可链接的查询集方法分别放在 QuerySet 和管理器方法中。

官网给出的例子。

来自 Django 文档的片段

class BaseManager(models.Manager):
    # Available only on Manager.
    def manager_only_method(self):
        return

class CustomQuerySet(models.QuerySet):
    # Available on both Manager and QuerySet.
    def manager_and_queryset_method(self):
        return

    # Available only on QuerySet.
    def _private_method(self):
        return

CustomManager = BaseManager.from_queryset(CustomQuerySet)

class MyModel(models.Model):
    objects = CustomManager()

【讨论】:

【参考方案2】:

只是一个使用新 as_manager() 方法的代码示例(请参阅来自@zzart 的更新信息。

class MyQuerySet(models.query.QuerySet):
    def randomize(self):        
        count = self.aggregate(count=Count('id'))['count']
        random_index = random.randint(0, count - 1)
        return self.all()[random_index]

class MyModel(models.Model):
    .....
    .....
    objects = MyQuerySet.as_manager()
    .....
    .....

然后你就可以在你的代码中使用这样的东西了:

MyModel.objects.filter(age__gt=16).randomize()

如你所见,新的 as_manager() 非常简洁:)

【讨论】:

刚刚使用.as_manager() 并验证这是这样做的方法。谢谢。【参考方案3】:

这是您在自定义管理器上链接自定义方法的方式,即:Post.objects.by_author(user=request.user).published()

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)

链接在这里:django-custom-model-manager-chaining

注意:

在 Django 1.7 中,您可以立即使用它。查看QuerySet.as_manager

【讨论】:

这真是太好了。您可以在 ManagerQueryset 中访问所有自定义方法,并且它们都定义在一个类中。为了进一步推动它,可以定义装饰器以使代码库更小。虽然,我不知道如何实现它。 非常好的方法 +1 表示 Django 1.7 的方式,但不应该是 QuerySet.as_manager() 吗?在我看来,这也应该被标记为正确答案.. 指定继承时mixin类不应该在基类的左边吗?【参考方案4】:

如何动态创建自定义 QuerySet 并允许我们将自定义查询“移植”到返回的 QuerySet 实例上:

class OfferManager(models.Manager):
    """
    Additional methods / constants to Offer's objects manager
    """
    ### Model (db table) wide constants - we put these and 
    ### not in model definition to avoid circular imports.
    ### One can access these constants through like
    <foo>.objects.STATUS_DISABLED or ImageManager.STATUS_DISABLED

    STATUS_DISABLED = 0
    ...
    STATUS_CHOICES = (
        (STATUS_DISABLED, "Disabled"),
        (STATUS_ENABLED, "Enabled"),
        (STATUS_NEGOTIATED, "Negotiated"),
        (STATUS_ARCHIVED, "Archived"),
    )
    ...

    # we keep status and filters naming a little different as
    # it is not one-to-one mapping in all situations
    QUERYSET_PUBLIC_KWARGS = 'status__gte': STATUS_ENABLED
    QUERYSET_ACTIVE_KWARGS = 'status': STATUS_ENABLED

    def get_query_set(self):
        """ our customized method which transpalats manager methods
        as per get_query_set.<method_name> = <method> definitions """
        CustomizedQuerySet = QuerySet
        for name, function in self.get_query_set.__dict__.items():
            setattr(CustomizedQuerySet, name, function)
        return CustomizedQuerySet(self.model, using=self._db)

    def public(self):
        """ Returns all entries accessible through front end site"""
        return self.all().filter(**OfferManager.QUERYSET_PUBLIC_KWARGS)
    get_query_set.public = public # will tranplat the function onto the 
                                  # returned QuerySet instance which 
                                  # means 'self' changes depending on context.

    def active(self):
        """ returns offers that are open to negotiation """
        return self.public().filter(**OfferManager.QUERYSET_ACTIVE_KWARGS)
    get_query_set.active = active
    ...

此方法的更完善版本和 django 票在这里:https://code.djangoproject.com/ticket/20625。

【讨论】:

【参考方案5】:

看起来这个 sn-p 为您的情况提供了解决方案:Custom managers with chainable filters。

【讨论】:

如果您使用的是 Django 1.7+,请参阅下面的答案,因为该功能是开箱即用的。

以上是关于如何在链查询中使用自定义管理器?的主要内容,如果未能解决你的问题,请参考以下文章

如何覆盖自定义管理器类中的 .update() 方法

JPA,实体管理器,选择多列并获取结果列表自定义对象

如何使用 Windows 身份验证并注册自定义身份验证管理器

如何在 Wildfly 中提供自定义身份验证/授权管理器

使用 select_related 时,如何使用相关模型的自定义管理器?

VS Code 自定义文件资源管理器窗口颜色主题