Как я могу использовать разрешения Django без определения типа или модели контента?

86

Я хотел бы использовать систему на основе разрешений, чтобы ограничить определенные действия в моем приложении Django. Эти действия не обязательно должны быть связаны с конкретной моделью (например, доступ к разделам в приложении, поиск ...), поэтому я не могу использовать структуру разрешений акций напрямую, потому что Permissionмодель требует ссылки на установленный тип контента.

Я мог бы написать свою собственную модель разрешений, но тогда мне пришлось бы переписать все полезности, включенные в разрешения Django, такие как:

Я проверил некоторые приложения, такие как django-Authority и django-guardian , но они, похоже, предоставляют разрешения, даже более связанные с системой модели, разрешая разрешения для каждого объекта.

Есть ли способ повторно использовать эту структуру без определения какой-либо модели (кроме Userи Group) для проекта?

Chewie
источник

Ответы:

57

Для Permissionмодели Django требуется ContentTypeэкземпляр .

Я думаю , что один из способов вокруг него создает соску ContentType, которая не связана с какой - либо модели ( app_labelи modelполя могут быть установлены в любое строковое значение).

Если вы хотите, чтобы все было чисто и красиво, вы можете создать Permission прокси-модель, которая обрабатывает все уродливые детали манекена ContentTypeи создает «немодельные» экземпляры разрешений. Вы также можете добавить настраиваемый менеджер, который отфильтровывает все Permissionэкземпляры, относящиеся к реальным моделям.

Гонсало
источник
3
Если вы не возражаете, я дополню ваш ответ своей реализацией.
Chewie
К сожалению, я не могу одобрить, так как у меня недостаточно репутации, чтобы просмотреть вашу правку (мне нужно +2k). Другие пользователи отклоняют ваши правки, поэтому я предлагаю вам добавить его в качестве еще одного ответа (у вас есть мой голос за!) Еще раз спасибо.
Gonzalo
1
Это странно. Это действительно завершение вашего ответа, поэтому имеет смысл внести изменения. Во всяком случае, я вставил это в другой ответ.
Chewie
148

Для тех из вас, кто все еще ищет:

Вы можете создать вспомогательную модель без таблицы базы данных. Эта модель может дать вашему проекту любое необходимое разрешение. Нет необходимости иметь дело с ContentType или явно создавать объекты разрешений.

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 makemigrationsи manage.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 03
2
Ничего не изменилось после того, как я запустил manage.py migrate ... Я не вижу новых разрешений :(
Agey
2
Вы добавили свое приложение в свой проект (INSTALLED_APPS)?
Дмитрий
2
Этот ответ идеален. Я также [] редактировал default_permissions, вызываю NotImplementedError в методе save () модели и мог бы подумать о том, чтобы сделать has _ * _ permission () return False, если неуправляемая модель действительно ТОЛЬКО для этого разрешения.
Дуглас Денхартог 08
4
Я предлагаю добавить в классе Meta следующее: default_permissions = (). Это предотвратит автоматическое создание Django разрешений на добавление / изменение / удаление / просмотр по умолчанию для этой модели, которые, скорее всего, не нужны, если вы используете этот подход.
Иордания
51

Следуя совету Гонсало , я использовал прокси-модель и настраиваемый менеджер для обработки моих «немодельных» разрешений с фиктивным типом контента.

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)
Chewie
источник
10
спасибо за код, было бы неплохо также показать пример того, как использовать этот код.
Кен Кокрейн
2
где должно находиться разрешение этой модели?
Mirat Can Bayrak
4
Чтобы создать GlobalPermission: from app.models import GlobalPermission gp = GlobalPermission.objects.create (codename = 'can_do_it', name = 'Can do it') После его запуска вы можете добавить это разрешение пользователям / группе, как любое другое разрешение .
Жюльен Гренье
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, о котором просили в нескольких комментариях.

В примечаниях к выпуску говорится:

Поле имени django.contrib.contenttypes.models.ContentType было удалено путем миграции и заменено свойством. Это означает, что больше невозможно запрашивать или фильтровать ContentType по этому полю.

Таким образом, это «имя» в ссылке в ContentType, которое используется не в GlobalPermissions.

Когда исправляю, получаю следующее:

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 не изменился, но включен для полноты картины.

rgammans
источник
1
Это по-прежнему не исправляет это для django 1.8, поскольку во время syncdb django утверждает, что поле «name» не может быть нулевым.
Armita
У меня это сработало, но я не использую миграции из-за того, что в моем проекте все еще есть устаревшие вещи, не относящиеся к django. Вы обновляетесь с предыдущего django, потому что в 1.8 не должно быть поля имени?
rgammans
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')
Арджун
источник