如何在不定义内容类型或模型的情况下使用Django权限?


84

我想使用基于权限的系统来限制Django应用程序中的某些操作。这些动作不必与特定模型相关(例如,访问应用程序中的各个部分,进行搜索...),因此我不能直接使用库存许可框架,因为该Permission模型需要引用已安装的内容类型。

我可以编写自己的权限模型,但随后必须重写Django权限随附的所有功能,例如:

我已经检查了django-authoritydjango-guardian之类的一些应用程序,但是它们似乎通过允许每个对象的权限来提供与模型系统耦合的权限。

有没有一种方法可以重用此框架,而无需为项目定义任何模型(除了UserGroup)?

Answers:


57

Django的Permission模型需要一个ContentTypeinstance

我认为解决此问题的一种方法是创建一个ContentType与任何模型都不相关的虚拟对象(app_labelandmodel字段可以设置为任何字符串值)。

如果您希望它们整洁美观,则可以创建一个Permission 代理模型来处理虚拟对象的所有丑陋细节,ContentType并创建“无模型”权限实例。您还可以添加一个自定义管理器,以过滤掉Permission与真实模型相关的所有实例。


3
如果您不介意,我将在实施中完成您的回答。
Chewie 2012年

遗憾的是,由于我没有足够的声誉来审核您的修改,我无法批准(它要求我+ 2k)。其他用户正在拒绝您的修改,因此建议您将其添加为另一个答案(您表示我赞成!)再次感谢。
贡萨洛

1
那真是怪了。这确实是您答案的补充,因此对其进行编辑是有意义的。无论如何,我把它放在另一个答案中。
Chewie

138

对于仍在搜索中的您:

您可以创建没有数据库表的辅助模型。该模型可以为您的项目带来所需的任何权限。无需处理ContentType或显式创建Permission对象。

from django.db import models
        
class RightsSupport(models.Model):
            
    class Meta:
        
        managed = False  # No database table creation or deletion  \
                         # operations will be performed for this model. 
                
        default_permissions = () # disable "add", "change", "delete"
                                 # and "view" default permissions

        permissions = ( 
            ('customer_rights', 'Global customer rights'),  
            ('vendor_rights', 'Global vendor rights'), 
            ('any_rights', 'Global any rights'), 
        )

之后manage.py makemigrationsmanage.py migrate您可以像使用其他权限一样使用这些权限。

# Decorator

@permission_required('app.customer_rights')
def my_search_view(request):
    …

# Inside a view

def my_search_view(request):
    request.user.has_perm('app.customer_rights')

# In a template
# The currently logged-in user’s permissions are stored in the template variable {{ perms }}

{% if perms.app.customer_rights %}
    <p>You can do any customer stuff</p>
{% endif %}

2
天才,救救我!
Reorx

2
在我运行manage.py
migration

2
您是否已将应用添加到项目(INSTALLED_APPS)中?
德米特里

2
这个答案是完美的。我还[]编辑了default_permissions,在模型的save()上引发NotImplementedError,并且如果非托管模型确实为此权限所用,则可以考虑使has _ * _ permission()返回False。
Douglas Denhartog '18

4
我建议在Meta类中添加以下内容:default_permissions = ()。这将阻止Django自动为此模型创建默认的添加/更改/删除/查看权限,如果您使用此方法,则很可能不需要。
约旦

51

遵循Gonzalo的建议,我使用了代理模型自定义管理器来处理虚拟内容类型的“无模型”权限。

from django.db import models
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType


class GlobalPermissionManager(models.Manager):
    def get_query_set(self):
        return super(GlobalPermissionManager, self).\
            get_query_set().filter(content_type__name='global_permission')


class GlobalPermission(Permission):
    """A global permission, not attached to a model"""

    objects = GlobalPermissionManager()

    class Meta:
        proxy = True

    def save(self, *args, **kwargs):
        ct, created = ContentType.objects.get_or_create(
            name="global_permission", app_label=self._meta.app_label
        )
        self.content_type = ct
        super(GlobalPermission, self).save(*args, **kwargs)

10
感谢您提供的代码,同时还提供了一个有关如何使用此代码的示例,将非常不错。
肯·科克伦

2
该模型许可应该存放在哪里?
2013年

4
要创建GlobalPermission,请执行以下操作:从app.models导入GlobalPermission gp = GlobalPermission.objects.create(codename ='can_do_it',name ='Can do it')一旦运行,您便可以将该权限添加到用户/组中,就像其他权限一样。
Julien Grenier 2014年

3
@JulienGrenier在Django 1.8的代码中断:FieldError: Cannot resolve keyword 'name' into field. Choices are: app_label, id, logentry, model, permission
maciek

2
警告:较新版本的Django(至少1.10版)需要覆盖方法“ get_queryset”(请注意,“ query”和“ set”一词之间没有_)
Lobe

10

修正了在Django 1.8中Chewie的答案,这在一些评论中有所要求。

它在发行说明中说:

django.contrib.contenttypes.models.ContentType的名称字段已通过迁移删除,并由属性替换。这意味着不再可以通过该字段查询或过滤ContentType。

因此,不是在GlobalPermissions中使用的是ContentType中引用的“名称”。

修复后,我得到以下信息:

from django.db import models
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType


class GlobalPermissionManager(models.Manager):
    def get_queryset(self):
        return super(GlobalPermissionManager, self).\
            get_queryset().filter(content_type__model='global_permission')


class GlobalPermission(Permission):
    """A global permission, not attached to a model"""

    objects = GlobalPermissionManager()

    class Meta:
        proxy = True
        verbose_name = "global_permission"

    def save(self, *args, **kwargs):
        ct, created = ContentType.objects.get_or_create(
            model=self._meta.verbose_name, app_label=self._meta.app_label,
        )
        self.content_type = ct
        super(GlobalPermission, self).save(*args)

GlobalPermissionManager类未更改,但为了完整性而包含了该类。


1
对于django 1.8,这仍然不能解决它,因为在syncdb时django断言“名称”字段不能为null。
Armita 2015年

它对我有用,但是由于我项目中仍然存在非Django遗留物,因此我没有使用迁移。您要从以前的django升级吗,因为在1.8中不应该有name字段
rgammans 2015年

4

这是替代解决方案。首先问问自己:为什么不创建数据库中确实存在但从未使用过的虚拟模型(除了持有权限外)?那不是很好,但是我认为这是有效且直接的解决方案。

from django.db import models

class Permissions(models.Model):

    can_search_blue_flower = 'my_app.can_search_blue_flower'

    class Meta:
        permissions = [
            ('can_search_blue_flower', 'Allowed to search for the blue flower'),
        ]

上面的解决方案的好处是,您可以Permissions.can_search_blue_flower在源代码中使用变量,而不是使用文字字符串“ my_app.can_search_blue_flower”。这意味着在IDE中更少的错别字和更多的自动完成功能。


1
不使用managed=False不允许你使用Permissions.can_search_blue_flower某种原因?
山姆·博贝尔

@SamBobel是的,您可能是正确的。我想我上次尝试“抽象”。
guettli

1

您可以将其proxy model用于虚拟内容类型。

from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType


class CustomPermission(Permission):

    class Meta:
        proxy = True

    def save(self, *args, **kwargs):
        ct, created = ContentType.objects.get_or_create(
            model=self._meta.verbose_name, app_label=self._meta.app_label,
        )
        self.content_type = ct
        super(CustomPermission, self).save(*args)

现在,您可以只创建权限name,并codename从许可的CustomPermission模式。

 CustomPermission.objects.create(name='Can do something', codename='can_do_something')

您可以像这样查询和显示模板中的自定义权限。

 CustomPermission.objects.filter(content_type__model='custom permission')
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.