代码之家  ›  专栏  ›  技术社区  ›  Adam

Django连锁经理

  •  29
  • Adam  · 技术社区  · 15 年前

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

    我有多个抽象模型类,用于为其他模型提供小的、特定的功能。其中两个模型是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没有globals(这假设我的模型声明是这样的:someModel(deleteMixin,globalMixin)。如果我颠倒顺序,我不会得到错误,但不会过滤掉删除的错误)。如果我将globalMixin更改为将globalManager附加到globals而不是对象(因此新命令将是someModel.globals.globals()),我将得到实例1和2(两个globals),而我的预期结果将是只得到实例1(全局的,未删除的)。

    我不确定是否有人遇到过类似的情况,并得出了结果。无论是一种让它在我目前的想法中发挥作用的方法,还是一种提供我所需要的功能的重新工作,我都会非常感激。我知道这篇文章有点冗长。如果需要更多的解释,我很乐意提供。

    编辑:

    我已经在下面发布了我用来解决这个特定问题的最终解决方案。它基于Simon的自定义QuerySetManager的链接。

    4 回复  |  直到 15 年前
        1
  •  21
  •   jujule    10 年前

    请参见DjangosNippets上的以下片段: http://djangosnippets.org/snippets/734/

    您不必将自定义方法放在管理器中,而是对查询集本身进行子类化。这很容易,而且工作得很好。我唯一遇到的问题是模型继承,您总是必须在模型子类中定义管理器(在子类中只是:“objects=queryset manager()”),即使它们将继承queryset。一旦您使用了querysetmanager,这将更加合理。

        2
  •  8
  •   Adam    15 年前

    下面是使用由Simon定制的QuerySetManager解决我的问题的具体解决方案,它是Scott链接到的。

    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(或者将其放在其继承关系中的某个位置)。每当我试图过滤查询集时,我都会用一个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.\u默认的管理器而不是model.objects,但是任何删除的实例仍然会出现在管理员中(如果你需要取消删除它们)。

        3
  •  2
  •   tghw megawac    15 年前

    我花了一段时间想办法建一个好工厂来做这个,但是我遇到了很多问题。

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

    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)
    

    如果你想要更通用的东西,我能想到的最好办法就是定义一个基 Mixin Manager 重新定义 get_query_set() (我假设您只想这样做一次;否则事情会变得非常复杂),然后通过 混合蛋白 S.

    它看起来像这样(根本不经过测试):

    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() . 如果你想要一个不同类型的经理 全局() 你只要打电话 create_manager 用另一个基地。您可以添加任意多的新方法。对于管理器,您只需不断添加将返回不同查询集的新函数。

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

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

        4
  •  2
  •   asafge    10 年前