Django 管理器链接

发布于 2024-07-18 14:22:52 字数 1822 浏览 7 评论 0原文

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

我有多个抽象模型类,用于为其他模型提供小型的特定功能。 其中两个模型是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 的链接。

I was wondering if it was possible (and, if so, how) to chain together multiple managers to produce a query set that is affected by both of the individual managers. I'll explain the specific example that I'm working on:

I have multiple abstract model classes that I use to provide small, specific functionality to other models. Two of these models are a DeleteMixin and a GlobalMixin.

The DeleteMixin is defined as such:

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

    class Meta:
        abstract = True

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

Basically it provides a pseudo-delete (the deleted flag) instead of actually deleting the object.

The GlobalMixin is defined as such:

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

    objects = GlobalManager()

    class Meta:
        abstract = True

It allows any object to be defined as either a global object or a private object (such as a public/private blog post).

Both of these have their own managers that affect the queryset that is returned. My DeleteManager filters the queryset to only return results that have the deleted flag set to False, while the GlobalManager filters the queryset to only return results that are marked as global. Here is the declaration for both:

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)

The desired functionality would be to have a model extend both of these abstract models and grant the ability to only return the results that are both non-deleted and global. I ran a test case on a model with 4 instances: one was global and non-deleted, one was global and deleted, one was non-global and non-deleted, and one was non-global and deleted. If I try to get result sets as such: SomeModel.objects.all(), I get instance 1 and 3 (the two non-deleted ones - great!). If I try SomeModel.objects.globals(), I get an error that DeleteManager doesn't have a globals (this is assuming my model declaration is as such: SomeModel(DeleteMixin, GlobalMixin). If I reverse the order, I don't get the error, but it doesn't filter out the deleted ones). If I change GlobalMixin to attach GlobalManager to globals instead of objects (so the new command would be SomeModel.globals.globals()), I get instances 1 and 2 (the two globals), while my intended result would be to only get instance 1 (the global, non-deleted one).

I wasn't sure if anyone had run into any situation similar to this and had come to a result. Either a way to make it work in my current thinking or a re-work that provides the functionality I'm after would be very much appreciated. I know this post has been a little long-winded. If any more explanation is needed, I would be glad to provide it.

Edit:

I have posted the eventual solution I used to this specific problem below. It is based on the link to Simon's custom QuerySetManager.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

征棹 2024-07-25 14:22:52

请在 Djangosnippets 上查看此代码段: http://djangosnippets.org/snippets/734/

而不是将您的在管理器中的自定义方法中,您可以对查询集本身进行子类化。 这非常简单并且运行完美。 我遇到的唯一问题是模型继承,您始终必须在模型子类中定义管理器(只需:子类中的“objects = QuerySetManager()”),即使它们将继承查询集。 一旦您使用 QuerySetManager,这将更有意义。

See this snippet on Djangosnippets: http://djangosnippets.org/snippets/734/

Instead of putting your custom methods in a manager, you subclass the queryset itself. It's very easy and works perfectly. The only issue I've had is with model inheritance, you always have to define the manager in model subclasses (just: "objects = QuerySetManager()" in the subclass), even though they will inherit the queryset. This will make more sense once you are using QuerySetManager.

心碎无痕… 2024-07-25 14:22:52

这是使用 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(或者将其放在其层次结构中的某个位置)即可。 每当我尝试过滤查询集时,我都会将其包装在 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,但任何已删除的实例仍会出现在管理中(以防您需要取消删除它们)。

Here is the specific solution to my problem using the custom QuerySetManager by Simon that Scott linked to.

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

Any mixin in the future that wants to add extra functionality to the query set simply needs to extend BaseMixin (or have it somewhere in its heirarchy). Any time I try to filter the query set down, I wrapped it in a try-catch in case that field doesn't actually exist (ie, it doesn't extend that mixin). The global filter is invoked using globals(), while the delete filter is automatically invoked (if something is deleted, I never want it to show). Using this system allows for the following types of commands:

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

One thing to note is that the delete filter won't affect admin interfaces, because the default Manager is declared first (making it the default). I don't remember when they changed the admin to use Model._default_manager instead of Model.objects, but any deleted instances will still appear in the admin (in case you need to un-delete them).

吻安 2024-07-25 14:22:52

我花了一段时间试图想出一种方法来建造一个漂亮的工厂来做到这一点,但我遇到了很多问题。

我能给你的最好建议就是锁住你的继承权。 它不是很通用,所以我不确定它有多大用处,但你所要做的就是:

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 code> 和 Manager 重新定义 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灵活性的练习。 我还没有尝试使用它,尽管我确实使用了动态扩展类的一些基本原理来使事情更容易访问。

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

I spent a while trying to come up with a way to build a nice factory to do this, but I'm running into a lot of problems with that.

The best I can suggest to you is to chain your inheritance. It's not very generic, so I'm not sure how useful it is, but all you would have to do is:

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)

If you want something more generic, the best I can come up with is to define a base Mixin and Manager that redefines get_query_set() (I'm assuming you only want to do this once; things get pretty complicated otherwise) and then pass a list of fields you'd want added via Mixins.

It would look something like this (not tested at all):

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

Ok, so this is ugly, but what does it get you? Essentially, it's the same solution, but much more dynamic, and a little more DRY, though more complex to read.

First you create your manager dynamically:

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

GlobalDeleteManager = create_manager(DeleteManager, globals=globals)

This creates a new manager which is a subclass of DeleteManager and has a method called globals.

Next, you create your mixin model:

GlobalDeleteMixin = create_mixin(DeleteMixin,
                                 is_global=models.BooleanField(default=False),
                                 objects = GlobalDeleteManager())

Like I said, it's ugly. But it means you don't have to redefine globals(). If you want a different type of manager to have globals(), you just call create_manager again with a different base. And you can add as many new methods as you like. Same for the manager, you just keep adding new functions that will return different querysets.

So, is this really practical? Maybe not. This answer is more an exercise in (ab)using Python's flexibility. I haven't tried using this, though I do use some of the underlying principals of dynamically extending classes to make things easier to access.

Let me know if anything is unclear and I'll update the answer.

墨小墨 2024-07-25 14:22:52

您应该使用 QuerySet 而不是 Manager

请参阅此处的文档。< /a>

You should use QuerySet instead of Manager.

See Documentation here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文