Django模型 - 传递额外信息给管理器
我正在尝试为Django模型实现基于行的安全检查。我的想法是,当我访问模型管理器时,指定一些额外的信息,这些信息会在数据库查询中使用,从而只获取允许的实例。
举个例子,我们可以有两个模型:用户(Users)和物品(Items)。每个物品属于某个用户,而一个用户可以关联多个物品。假设有一些限制,根据这些限制,用户可能会看到或看不到其他用户的物品。我想把这些限制和其他查询元素分开,写成类似这样的:
items = Item.scoped.forceRule('user1').all() # all items visible for 'user1'
或者
# show all items of 'user2' visible by 'user1'
items = Item.scoped.forceRule('user1').filter(author__username__exact = 'user2')
为了实现这个目标,我做了以下工作:
class SecurityManager(models.Manager):
def forceRule(self, onBehalf) :
modelSecurityScope = getattr(self.model, 'securityScope', None)
if modelSecurityScope :
return super(SecurityManager, self).get_query_set().filter(self.model.securityScope(onBehalf))
else :
return super(SecurityManager, self).get_query_set()
def get_query_set(self) :
#
# I need to know that 'onBehalf' parameter here
#
return super(SecurityManager, self).get_query_set()
class User(models.Model) :
username = models.CharField(max_length=32, unique=True)
class Item(models.Model) :
author = models.ForeignKey(User)
private = models.BooleanField()
name = models.CharField(max_length=32)
scoped = SecurityManager()
@staticmethod
def securityScope(onBehalf) :
return Q(author__username__exact = onBehalf) | Q(bookmark__private__exact = False)
对于上面的例子来说,这样做是有效的,但在以下情况下就不行了:
items = Item.scoped.forceRule('user1').filter(author__username__exact = 'user2') # (*)
items2 = items[0].author.item_set.all() # (**)
显然,items2
包含了所有属于'user2'的物品,而不仅仅是符合规则的那些。这是因为当执行all()时,SecurityManager.get_query_set()
并不知道限制条件是什么。其实它是可以知道的。比如,在forceRule()中,我可以为每个实例添加一个字段,然后,如果我能从管理器访问这个字段,就可以应用需要的规则。
所以,我的问题是 - 有没有办法把传递给forceRule()
的参数,在语句(*)
中传递给在语句(**)
中调用的管理器?
或者另一个问题是 - 我这样做是不是有点奇怪,根本不应该这样做?
谢谢。
1 个回答
4
根据我对文档的理解,我觉得有两个问题:
- 安全管理器(SecurityManager)不会被用于相关对象,而是会使用django.db.models.Manager的实例。
- 你可以解决上面的问题,但文档特别强调,get_query_set()这个方法不应该过滤掉任何与查询相关的行。
我建议你创建一个函数,这个函数接收一个查询集(QuerySet),然后对它应用你需要的过滤条件。这样的话,每当你得到一个物品的查询集,并想进一步处理时,就可以使用这个函数。