首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >“私有”模型、默认查询集和链接方法

“私有”模型、默认查询集和链接方法
EN

Stack Overflow用户
提问于 2011-06-17 09:31:13
回答 3查看 834关注 0票数 3

我的模型上有一个private布尔标志,还有一个自定义管理器,它用过滤器覆盖get_query_set方法,删除private=True:

代码语言:javascript
运行
复制
class myManager(models.Manager):
    def get_query_set(self):
        qs = super(myManager, self).get_query_set()
        qs = qs.filter(private=False)
        return qs

class myModel(models.Model):
    private = models.BooleanField(default=False)
    owner = models.ForeignKey('Profile', related_name="owned")
    #...etc...

    objects = myManager()

我希望默认查询集将私有模型排除为默认的安全措施,从而防止意外使用显示私有模型的模型。

然而,有时我会想要展示私人模型,所以我有以下关于经理的信息:

代码语言:javascript
运行
复制
def for_user(self, user):
    if user and not user.is_authenticated():
        return self.get_query_set()
    qs = super(myManager, self).get_query_set()
    qs = qs.filter(Q(owner=user, private=True) | Q(private=False))
    return qs

这是很好的工作,但有一个限制,我不能连锁过滤器。当我有一个fk指向myModel并使用otherModel.mymodel_set时,这就成了一个问题。OtherModel.mymodel_set.for_user(用户)无法工作,因为mymodel_set返回一个QuerySet对象,而不是管理器。

现在,真正的问题开始了,因为我看不到使for_user()方法在QuerySet子类上工作的方法,因为我无法访问来自queryset子类的完整、未经过滤的查询集(基本上是覆盖get_query_set),就像在管理器中那样(使用super()来获取基本的查询集)。

解决这个问题的最好方法是什么?

我没有绑定到任何特定的界面,但我希望它尽可能地变得单调/干燥。显然,我可以放弃安全性,只需调用一个方法来筛选出每个调用上的私有任务,但我真的不想这样做。

更新

manji下面的答案非常接近,但是当我想要的queryset不是默认queryset的子集时,它就失败了。我想这里真正的问题是如何从链式查询中删除特定的筛选器?

EN

回答 3

Stack Overflow用户

发布于 2011-06-17 12:44:57

定义自定义QuerySet (包含自定义筛选器方法):

代码语言:javascript
运行
复制
class MyQuerySet(models.query.QuerySet):

    def public(self):
        return self.filter(private=False)

    def for_user(self, user):
        if user and not user.is_authenticated():
            return self.public()
        return self.filter(Q(owner=user, private=True) | Q(private=False))

定义一个使用MyQuerySet的自定义管理器(MyQuerySet自定义过滤器可以访问,就好像它们是通过覆盖__getattr__在管理器中定义的一样):

代码语言:javascript
运行
复制
# A Custom Manager accepting custom QuerySet
class MyManager(models.Manager):

    use_for_related_fields = True

    def __init__(self, qs_class=models.query.QuerySet):
        self.queryset_class = qs_class
        super(QuerySetManager, self).__init__()

    def get_query_set(self):
        return self.queryset_class(self.model).public()

    def __getattr__(self, attr, *args):
        try:
            return getattr(self.__class__, attr, *args)
        except AttributeError:
            return getattr(self.get_query_set(), attr, *args) 

然后在模型中:

代码语言:javascript
运行
复制
class MyModel(models.Model):
    private = models.BooleanField(default=False)
    owner = models.ForeignKey('Profile', related_name="owned")
    #...etc...

    objects = myManager(MyQuerySet)

现在你可以:

默认情况下,只访问公共模型:

代码语言:javascript
运行
复制
    MyModel.objects.filter(..

access for_user模型:

代码语言:javascript
运行
复制
    MyModel.objects.for_user(user1).filter(..

由于(字段=真),这个管理器将用于相关经理。所以你也可以:

默认情况下,只访问来自相关管理人员的公共模型:

代码语言:javascript
运行
复制
    otherModel.mymodel_set.filter(..

来自相关经理的 access for_user:

代码语言:javascript
运行
复制
    otherModel.mymodel_set.for_user(user).filter(..

更多信息:子类Django QuerySets & 具有可链式过滤器的自定义管理器 (django片段)

票数 2
EN

Stack Overflow用户

发布于 2011-06-17 11:27:42

要使用这个链,您应该覆盖管理器中的get_query_set,并将for_user放在您的自定义QuerySet中。

我不喜欢这个解决方案,但有效。

代码语言:javascript
运行
复制
class CustomQuerySet(models.query.QuerySet):
    def for_user(self):
        return super(CustomQuerySet, self).filter(*args, **kwargs).filter(private=False)

class CustomManager(models.Manager):
    def get_query_set(self):
        return CustomQuerySet(self.model, using=self._db)
票数 0
EN

Stack Overflow用户

发布于 2011-06-24 18:21:53

如果您需要“重置”QuerySet,您可以访问查询集的模型并再次调用原始管理器(完全重置)。但是,这对您可能不是很有用,除非您正在跟踪前面的筛选器/排除etc语句,并且可以在重置查询集上再次重放它们。有了一些计划,这实际上不会太难做,但可能有点野蛮的力量。

总的来说,曼吉的回答肯定是正确的。

因此,修改manji的答案,您需要用("model"."owner_id" = 2 AND "model"."private" = True ) OR "model"."private" = False )替换现有的("model"."owner_id" = 2 AND "model"."private" = True ) OR "model"."private" = False )。要做到这一点,您需要遍历queryset的where对象上的query对象,以找到要删除的相关位。查询对象有一个WhereNode对象,它表示where子句的树,每个节点都有多个子节点。您必须调用节点上的as_sql来确定它是否是您想要的:

代码语言:javascript
运行
复制
from django.db import connection
qn = connection.ops.quote_name
q = myModel.objects.all()
print q.query.where.children[0].as_sql(qn, connection)

这应该会给你这样的东西:

代码语言:javascript
运行
复制
('"model"."private" = ?', [False])

然而,尝试这样做可能比它的价值要大得多,而且它正在钻研一些可能不是API稳定的Django。

我的建议是使用两个经理。一个可以访问所有东西(排序的转义口),另一个应用了默认的过滤。默认的管理器是第一个,因此您需要根据您需要做的事情来处理顺序。然后重构您的代码,知道使用哪一个-这样您就不会有问题,在那里已经有额外的private=False子句。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6383860

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档