Django经理Chaining

时间:2009-04-30 21:33:29

标签: python django django-models django-managers

我想知道是否有可能(以及如果是这样,如何)将多个经理链接在一起以生成受两个经理影响的查询集。我将解释我正在研究的具体例子:

我有多个抽象模型类,用于为其他模型提供小的特定功能。其中两个模型是DeleteMixin和GlobalMixin。

DeleteMixin定义如下:

class DeleteMixin(models.Model):
    deleted = models.BooleanField(default=False)
    objects = DeleteManager()

    class Meta:
        abstract = True

    def delete(self):
        self.deleted = True
        self.save()

基本上它提供了伪删除(已删除的标志),而不是实际删除对象。

GlobalMixin定义如下:

class GlobalMixin(models.Model):
    is_global = models.BooleanField(default=True)

    objects = GlobalManager()

    class Meta:
        abstract = True

它允许将任何对象定义为全局对象或私有对象(例如公共/私人博客文章)。

这两个都有自己的管理器,会影响返回的查询集。我的DeleteManager过滤查询集只返回已删除标志设置为False的结果,而GlobalManager过滤查询集只返回标记为全局的结果。以下是两者的声明:

class DeleteManager(models.Manager):
    def get_query_set(self):
        return super(DeleteManager, self).get_query_set().filter(deleted=False)

class GlobalManager(models.Manager):
    def globals(self):
        return self.get_query_set().filter(is_global=1)

所需的功能是让模型扩展这两个抽象模型,并授予仅返回非删除和全局结果的能力。我在一个包含4个实例的模型上运行了一个测试用例:一个是全局的,未删除的,一个是全局的,已删除,一个是非全局的,未删除,一个是非全局的,已删除。如果我尝试获取结果集:SomeModel.objects.all(),我得到实例1和3(两个未删除的 - 很棒!)。如果我尝试SomeModel.objects.globals(),我会得到一个错误,即DeleteManager没有全局变量(假设我的模型声明是这样的:SomeModel(DeleteMixin,GlobalMixin)。如果我颠倒顺序,我不会得到错误,但它不会过滤掉已删除的错误)。如果我更改GlobalMixin以将GlobalManager连接到全局而不是对象(因此新命令将是SomeModel.globals.globals()),我得到实例1和2(两个全局变量),而我的预期结果只是获取实例1(全局的,未删除的)。

我不确定是否有人遇到任何与此类似的情况并且已经得出结果。无论是在我当前的想法中使其工作的方式还是提供我所追求的功能的重新工作都将非常感激。我知道这篇文章有点啰嗦。如果需要更多解释,我很乐意提供。

修改

我已经发布了以下针对此特定问题的最终解决方案。它基于Simon自定义QuerySetManager的链接。

4 个答案:

答案 0 :(得分:21)

请参阅Djangosnippets上的这个片段:http://djangosnippets.org/snippets/734/

不是将自定义方法放在管理器中,而是将查询集本身子类化。它非常简单,工作完美。我唯一的问题是模型继承,你总是必须在模型子类中定义管理器(只是:子类中的“objects = QuerySetManager()”),即使它们将继承查询集。一旦使用QuerySetManager,这将更有意义。

答案 1 :(得分:8)

以下是使用Scott链接到的Simon自定义QuerySetManager的问题的具体解决方案。

from django.db import models
from django.contrib import admin
from django.db.models.query import QuerySet
from django.core.exceptions import FieldError

class MixinManager(models.Manager):    
    def get_query_set(self):
        try:
            return self.model.MixinQuerySet(self.model).filter(deleted=False)
        except FieldError:
            return self.model.MixinQuerySet(self.model)

class BaseMixin(models.Model):
    admin = models.Manager()
    objects = MixinManager()

    class MixinQuerySet(QuerySet):

        def globals(self):
            try:
                return self.filter(is_global=True)
            except FieldError:
                return self.all()

    class Meta:
        abstract = True

class DeleteMixin(BaseMixin):
    deleted = models.BooleanField(default=False)

    class Meta:
        abstract = True

    def delete(self):
        self.deleted = True
        self.save()

class GlobalMixin(BaseMixin):
    is_global = models.BooleanField(default=True)

    class Meta:
        abstract = True

将来想要为查询集添加额外功能的任何mixin只需要扩展BaseMixin(或者将它放在其heirarchy中的某个地方)。每当我尝试过滤查询设置时,我将其包装在try-catch中,以防该字段实际上不存在(即,它不会扩展该mixin)。使用globals()调用全局过滤器,同时自动调用删除过滤器(如果删除某些内容,我绝不希望它显示)。使用此系统允许以下类型的命令:

TemporaryModel.objects.all() # If extending DeleteMixin, no deleted instances are returned
TemporaryModel.objects.all().globals() # Filter out the private instances (non-global)
TemporaryModel.objects.filter(...) # Ditto about excluding deleteds

需要注意的一点是,删除过滤器不会影响管理界面,因为首先声明默认管理器(使其成为默认值)。我不记得他们何时更改管理员以使用Model._default_manager而不是Model.objects,但任何已删除的实例仍将出现在管理员中(如果您需要取消删除它们)。

答案 2 :(得分:2)

我花了一段时间试图想出一个建立一个好工厂来做这件事的方法,但是我遇到了很多问题。

我建议你做的最好的事情就是把你的遗产链接起来。它不是很通用,所以我不确定它有多么有用,但你所要做的就是:

class GlobalMixin(DeleteMixin):
    is_global = models.BooleanField(default=True)

    objects = GlobalManager()

    class Meta:
        abstract = True

class GlobalManager(DeleteManager):
    def globals(self):
        return self.get_query_set().filter(is_global=1)

如果你想要更通用的东西,我能想到的最好的方法是定义一个基础MixinManager重新定义get_query_set()(我假设你只想做这一次;事情变得相当复杂,然后传递一个你想通过Mixin添加的字段列表。

它看起来像这样(根本没有测试):

class DeleteMixin(models.Model):
    deleted = models.BooleanField(default=False)

    class Meta:
        abstract = True

def create_mixin(base_mixin, **kwargs):
    class wrapper(base_mixin):
        class Meta:
            abstract = True
    for k in kwargs.keys():
        setattr(wrapper, k, kwargs[k])
    return wrapper

class DeleteManager(models.Manager):
    def get_query_set(self):
        return super(DeleteManager, self).get_query_set().filter(deleted=False)

def create_manager(base_manager, **kwargs):
    class wrapper(base_manager):
        pass
    for k in kwargs.keys():
        setattr(wrapper, k, kwargs[k])
    return wrapper

好的,所以这很难看,但它能带给你什么?从本质上讲,它是相同的解决方案,但更具动态性,更干一点,但阅读更复杂。

首先动态创建经理:

def globals(inst):
    return inst.get_query_set().filter(is_global=1)

GlobalDeleteManager = create_manager(DeleteManager, globals=globals)

这会创建一个新的管理器,它是DeleteManager的子类,并且有一个名为globals的方法。

接下来,您将创建mixin模型:

GlobalDeleteMixin = create_mixin(DeleteMixin,
                                 is_global=models.BooleanField(default=False),
                                 objects = GlobalDeleteManager())
像我说的那样,它很难看。但这意味着您无需重新定义globals()。如果您希望其他类型的管理员拥有globals(),则只需使用不同的基础再次调用create_manager即可。您可以添加任意数量的新方法。对于管理员来说,您只需继续添加将返回不同查询集的新函数。

那么,这真的很实用吗?也许不吧。这个答案更像是(ab)使用Python灵活性的练习。我没有尝试过使用它,虽然我确实使用了动态扩展类的一些底层原则来使事情更容易访问。

如果有任何不清楚的地方,请告诉我,我会更新答案。

答案 3 :(得分:2)

值得考虑的另一个选择是PassThroughManager:

https://django-model-utils.readthedocs.org/en/latest/managers.html#passthroughmanager