【问题标题】:Check for multiple values in a M2M relationship in Django在 Django 中检查 M2M 关系中的多个值
【发布时间】:2012-04-08 22:45:45
【问题描述】:

在英语中,我想检查用户的类型是查看者、版主还是管理员。

这是我models.py的相关部分

class UserType( models.Model ) :
    name = models.CharField( max_length = 135 )

    # id |       name
    #----+------------------
    #  1 | tenant
    #  2 | property manager
    #  3 | property owner
    #  4 | vendor manager
    #  5 | vendor
    #  6 | viewer
    #  7 | moderator
    #  8 | administrator

class UserProfile( models.Model ) :
    user       = models.OneToOneField( User )
    user_types = models.ManyToManyField( UserType, null = True, blank = True )

顺便说一句,我将user.profile 设置为与user.get_profile() 相同的东西。

在我的views.py 代码中,我想做检查。我想通了

[ user_type.pk for user_type in user.profile.user_types.all() ]

会给我一个pk 的列表,用于useruser_type,就像[ 1, 2, 6 ]。这意味着该特定用户是租户 (1)、物业经理 (2) 和查看者 (6)。

如果我只想检查一个user_type,那么我可以简单地做

if 6 in [ user_type.pk for user_type in user.profile.user_types.all() ] :
    # This user is a viewer

但是如何检查多个 user_types/pks?我想做类似的事情

# This won't work
if [ 6, 7, 8 ] in [ user_type.pk for user_type in user.profile.user_types.all() ] :
    # This user is either a viewer, moderator, or administrator

我的列表理解方法也是检查user_types Django 方式吗?看起来不像,但我不知道如何在 Django 中干净地查询它。

欢迎任何提示和建议。提前致谢!

编辑:

我刚刚发现我可以使用values_list 以更多 Django 方式列出 pks

user_types = user.profile.user_types.values_list( 'pk', flat = True )
# [ 1, 2, 6 ]

我还发现我可以像这样检查多个值

if len( set( [ 1, 9 ] ).intersection( set( user_types ) ) )
    # True because of 1 is in user_types (don't care about 9)

if len( set( [ 4, 99 ] ).intersection( set( user_types ) ) )
    # False because 4 nor 99 is in user_types

但即使是这种set 方法似乎也不是对 Django 非常友好。一定有更简单的方法吧?

【问题讨论】:

    标签: python django django-models django-views django-queryset


    【解决方案1】:

    简单地做:

    def has_roles(user, roles):
        return user.profile.user_types.filter(pk__in=roles).count() == len(roles)
    
    print has_roles(user, [6,7,8])
    

    附:我会回避使用硬编码的 PK ID 号码作为您的标识符。太多的事情可能会出错。而是在运行时定义映射并按名称引用它们。多方面会有所改善:

    • 您的代码将更容易阅读
    • Django 将卸载模型实例匹配到数据库的第一项
    • 从那时起,您可以缓存 ID 而无需再次询问数据库

    那么你可以这样做:

    class UserType( models.Model ):
        TYPES = (( 'tenant', 'Tenant'),
                 ( 'propman', 'Property Manager'),
                 ( 'propown', 'Property Owner'),
                 ( 'vendman', 'Vendor Manager'),
                 ( 'vendor', 'Vendor'),
                 ( 'viewer', 'Viewer'),
                 ( 'moderator', 'Moderator'),
                 ( 'admin', 'Administrator'))
    
        name = models.CharField( max_length = 135, choices=TYPES )
    
    def has_role(user, role):
        return user.profile.user_types.filter(name=role).count() == 1
    
    def has_roles(user, roles):
        return user.profile.user_types.filter(name__in=roles).count() == len(roles)
    
    print has_roles(user, ['viewer','moderator','admin'])
    

    最后可以将上面的两个函数添加到:

    class UserProfile( models.Model ) :
        user       = models.OneToOneField( User )
        user_types = models.ManyToManyField( UserType, null = True, blank = True )
    
    
        def has_role(self, role):
            return self.user_types.filter(name=role).count() == 1
    
        def has_roles(self, roles):
            return self.user_types.filter(name__in=roles).count() == len(roles)
    

    然后以后这样用:

    u = User.objects.get(username='me')
    if u.userprofile.has_role('admin'):
        print 'I have the powah!'
    

    【讨论】:

    • 很棒的思路!我绝对同意你的方法更干净、更简单。非常感谢!
    • 一条评论:我不确定我是否关注您的has_roles 功能。为什么将其设置为== len( roles )?该逻辑是否意味着,例如,userviewermoderatorAND admin(与所需的 OR 逻辑相反)?
    • 没关系,我决定使用你的函数has_roles() 来处理所有事情,而忽略了== len( roles )。现在它按我想要的方式工作。再次感谢!
    • 这只是对数据库的优化。如果你这样做 .count() DB 只需要计算行数,它就不必获取角色的名称,python 也不必构造 UserType 对象。区别确实是 OR 与 AND。如果没有.count() == len(roles),它相当于 OR 逻辑(用户拥有任何这些角色),而 len(roles) 则相当于用户拥有所有这些角色。干杯
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-17
    • 1970-01-01
    • 2010-10-22
    • 2012-06-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多