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

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

我可以编写我自己的权限模型,但是我必须重写包含在 Django 权限中的所有好东西,比如:

我已经检查了一些像 姜戈权威姜戈,守护者这样的应用程序,但是它们似乎通过允许每个对象的权限来提供与模型系统更耦合的权限。

有没有一种方法可以在没有为项目定义任何模型(除了 UserGroup之外)的情况下重用这个框架?

32990 次浏览

Django's Permission model requires a ContentType instance.

I think one way around it is creating a dummy ContentType that isn't related to any model (the app_label and model fields can be set to any string value).

If you want it all clean and nice, you can create a Permission proxy model that handles all the ugly details of the dummy ContentType and creates "modelless" permission instances. You can also add a custom manager that filters out all Permission instances related to real models.

Following Gonzalo's advice, I used a proxy model and a custom manager to handle my "modelless" permissions with a dummy content type.

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)

Fix for Chewie's answer in Django 1.8, which as been requested in a few comments.

It says in the release notes:

The name field of django.contrib.contenttypes.models.ContentType has been removed by a migration and replaced by a property. That means it’s not possible to query or filter a ContentType by this field any longer.

So it's the 'name' in reference in ContentType that the uses not in GlobalPermissions.

When I fix it I get the following:

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)

The GlobalPermissionManager class is unchanged but included for completeness.

For those of you, who are still searching:

You can create an auxiliary model with no database table. That model can bring to your project any permission you need. There is no need to deal with ContentType or create Permission objects explicitly.

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'),
)

Right after manage.py makemigrations and manage.py migrate you can use these permissions like any other.

# 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 %}

This is alternative solution. First ask yourself: Why not create a Dummy-Model which really exists in DB but never ever gets used, except for holding permissions? That's not nice, but I think it is valid and straight forward solution.

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'),
]

Above solution has the benefit, that you can use the variable Permissions.can_search_blue_flower in your source code instead of using the literal string "my_app.can_search_blue_flower". This means less typos and more autocomplete in IDE.

You can use the proxy model for this with a dummy content type.

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)

Now you can create the permission with just name and codename of the permission from the CustomPermission model.

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

And you can query and display only the custom permissions in your templates like this.

 CustomPermission.objects.filter(content_type__model='custom permission')

For my part, for any larger project, I find it useful to have a generic app that isn't really part of my project's data model per se - I typically call it "projectlibs". It's a simple django app where I put things like fixtures for imports, templatetags that can be reused for multiple apps, etc. Some of it is template stuff I find myself re-using often, so the added benefit of having that type of stuff in an app is that it's reusable for other projects.

So inside that projectlibs/models.py, you could:

You could create that "meta app", in essence, and assign the content_type to some dummy class:

class UserRightsSupport(models.Model):
class Meta:
default_permissions = ()  # disable defaults add, delete, view, change perms
permissions = (
("perm_name", "Verbose description"),
)

All answers are bad for me except this:

content_type = ContentType.objects.get_for_model(Permission)


Permission.objects.create(
content_type=content_type,
name='...', codename='...',
)

which handles model-less permissions without adding new models, but by adding new values.

I think that User model substitution do the trick (see Django documentation: https://docs.djangoproject.com/en/4.1/topics/auth/customizing/#substituting-a-custom-user-model)

Next on that model we can add Meta class with permissions and at the end we need to add our User model in settings.py.

Here is my example of user.py file:

from django.contrib.auth.models import AbstractUser


class User(AbstractUser):


class Meta:
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'),
]

and in settings.py:

AUTH_USER_MODEL = 'myapp.User'