'私有'模型,默认查询集和链接方法

时间:2011-06-17 09:31:13

标签: django django-models orm django-queryset

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

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()

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

但是,有时我会想要显示私人模特,所以我在经理上有以下内容:

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(user)不能正常工作,因为mymodel_set返回一个QuerySet对象,而不是管理器。

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

解决此问题的最佳方法是什么?

我没有绑定任何特定的界面,但我希望它可以像djangoy / DRY一样。显然我可以放弃安全性,只需调用一个方法来过滤每次调用的私有任务,但我真的不想这样做。

更新

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

3 个答案:

答案 0 :(得分:2)

定义自定义QuerySet(包含自定义过滤器方法):

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__]):

# 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) 

然后在模型中:

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

    objects = myManager(MyQuerySet)

现在你可以:

¤默认只访问 公开 模型:

    MyModel.objects.filter(..

¤访问 for_user 模型:

    MyModel.objects.for_user(user1).filter(..

由于(use_for_related_fields = True),同一位经理将被用于相关经理。所以你也可以:

¤默认情况下仅访问相关管理员的 公开 模型:

    otherModel.mymodel_set.filter(..

¤从相关经理访问 for_user

    otherModel.mymodel_set.for_user(user).filter(..


更多信息:Subclassing Django QuerySets& Custom managers with chainable filters(django片段)

答案 1 :(得分:0)

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

我不喜欢这个解决方案,但它确实有用。

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)

答案 2 :(得分:0)

如果需要“重置”QuerySet,可以访问查询集的模型并再次调用原始管理器(完全重置)。但是,这可能对您没有用,除非您跟踪以前的过滤/排除等语句并且可以在重置查询集上重播它们。通过一些实际上不会太难做的计划,但可能会有点暴力。

总的来说,manji的答案肯定是正确的方法。

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


from django.db import connection
qn = connection.ops.quote_name
q = myModel.objects.all()
print q.query.where.children[0].as_sql(qn, connection)

哪个应该给你这样的东西:


('"model"."private" = ?', [False])

然而,尝试这样做可能比它的价值更多的努力,并且它正在深入研究可能不是API稳定的Django。

我的建议是使用两位经理。一个可以访问所有东西(排序的逃生舱),另一个可以应用默认过滤。默认管理器是第一个,因此您需要根据需要进行操作。然后重构你的代码以了解使用哪一个 - 所以你没有在那里有额外的private = False子句的问题。