Як я можу використовувати дозволи Django, не визначаючи тип вмісту чи модель?


84

Я хотів би використовувати систему на основі дозволів, щоб обмежити певні дії в моїй програмі Django. Ці дії не повинні бути пов'язані з конкретною моделлю (наприклад, доступ до розділів у програмі, пошук ...), тому я не можу використовувати безпосередньо структуру дозволів на запас , оскільки Permissionмодель вимагає посилання на встановлений тип вмісту.

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

Я перевірив деякі додатки, такі як django-Authority і django-guardian , але, схоже, вони надають дозволи, ще більше пов'язані з модельною системою, дозволяючи дозволи для кожного об'єкта.

Чи існує спосіб повторного використання цього фреймворку, не визначивши жодної моделі (крім Userі Group) для проекту?

Відповіді:


57

PermissionМодель Django вимагає ContentTypeекземпляра .

Я думаю, що одним із шляхів є створення манекена, ContentTypeякий не пов’язаний з жодною моделлю (для полів app_labeland modelможна встановити будь-яке значення рядка).

Якщо ви хочете, щоб все було чисто та приємно, ви можете створити Permission проксі-модель, яка обробляє всі потворні деталі манекена ContentTypeта створює екземпляри дозволів без моделей. Ви також можете додати власний менеджер, який фільтрує всі Permissionекземпляри, пов’язані з реальними моделями.


3
Якщо ви не проти, я доповню вашу відповідь своїм введенням.
Chewie

На жаль, я не можу схвалити, оскільки у мене недостатньо репутації для перегляду вашого редагування (воно вимагає від мене 2 тисячі). Інші користувачі відхиляють ваші зміни, тому я пропоную додати це як чергову відповідь (ви маєте мою підтримку!) Ще раз спасибі.
Гонсало,

1
Це дивно. Це справді доповнення вашої відповіді, тому має сенс внести її в редагування. У всякому разі, я вкладаю це в іншу відповідь.
Chewie

138

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

Ви можете створити допоміжну модель без таблиці бази даних. Ця модель може принести до вашого проекту будь-який необхідний дозвіл. Немає необхідності мати справу з 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

2
Нічого не змінилося після того, як я запустив manage.py migrate ... Я не бачу нових дозволів :(
Agey

2
Ви додали свою програму у свій проект (INSTALLED_APPS)?
Дмитро

2
Ця відповідь ідеальна. Я також [] видав дозволи за замовчуванням, піднімаю NotImplementedError під час збереження моделі (), і можу розглянути можливість створення має _ * _ дозволу () повернути False, якщо некерована модель справді ПРОСТО для цього дозволу.
Дуглас Денхартог,

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)

10
дякую за код, було б непогано також показати приклад того, як використовувати цей код.
Кен Кокрейн

2
де повинен жити цей типовий дозвіл?
Мірат Кан Байрак

4
Щоб створити GlobalPermission: з app.models імпортуйте GlobalPermission gp = GlobalPermission.objects.create (codename = 'can_do_it', name = 'Can do it') Після запуску ви можете додати цей дозвіл користувачам / групі, як і будь-який інший дозвіл .
Julien Grenier

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" (зверніть увагу на відсутність _ між словами "запит" і "встановити).
Лобе

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


1
Це все ще не виправляє це для django 1.8, оскільки під час синхронізації django стверджує, що поле "ім'я" не може бути нульовим.
Armita

Це спрацювало для мене, але я не використовую міграції через застарілі матеріали, не пов’язані з джанго, як і раніше в моєму проекті. Ви оновлюєтесь з попереднього джанго, тому що в 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')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.