Jak mogę używać uprawnień Django bez definiowania typu lub modelu zawartości?

84

Chciałbym użyć systemu opartego na uprawnieniach, aby ograniczyć pewne działania w mojej aplikacji Django. Te akcje nie muszą być związane z konkretnym modelem (np. Dostęp do sekcji w aplikacji, wyszukiwanie ...), więc nie mogę bezpośrednio korzystać ze standardowej struktury uprawnień , ponieważ Permissionmodel wymaga odniesienia do zainstalowanego typu zawartości.

Mógłbym napisać własny model uprawnień, ale wtedy musiałbym przepisać wszystkie dodatki zawarte w uprawnieniach Django, takie jak:

Sprawdziłem niektóre aplikacje, takie jak django-authority i django-guardian , ale wydaje się, że zapewniają one uprawnienia jeszcze bardziej powiązane z systemem modelowym, zezwalając na uprawnienia dla poszczególnych obiektów.

Czy istnieje sposób na ponowne użycie tego frameworka bez zdefiniowania żadnego modelu (poza Useri Group) dla projektu?

Chewie
źródło

Odpowiedzi:

57

PermissionModel Django wymaga ContentTypeinstancji .

Myślę, że jednym ze sposobów obejścia tego jest utworzenie atrapy, ContentTypektóra nie jest powiązana z żadnym modelem ( pola app_labeli modelmożna ustawić na dowolną wartość ciągu).

Jeśli chcesz, żeby wszystko było czyste i ładne, możesz stworzyć Permission model proxy, który obsługuje wszystkie brzydkie szczegóły atrapy ContentTypei tworzy "bezmodelowe" instancje uprawnień. Możesz także dodać niestandardowego menedżera, który odfiltrowuje wszystkie Permissionwystąpienia związane z rzeczywistymi modelami.

Gonzalo
źródło
3
Jeśli nie masz nic przeciwko, uzupełnię twoją odpowiedź moją implementacją.
Chewie,
Niestety, nie mogę zatwierdzić, ponieważ nie mam wystarczającej reputacji, aby przejrzeć twoją zmianę (prosi mnie o + 2k). Inni użytkownicy odrzucają Twoje zmiany, więc sugeruję dodanie tej odpowiedzi jako kolejnej odpowiedzi (masz moją opinię!) Jeszcze raz dziękuję.
Gonzalo,
1
To jest dziwne. To naprawdę uzupełnienie Twojej odpowiedzi, więc warto wprowadzić w nim zmiany. W każdym razie umieściłem to w innej odpowiedzi.
Chewie,
139

Dla tych z Was, którzy wciąż szukają:

Możesz utworzyć model pomocniczy bez tabeli bazy danych. Ten model może wnieść do twojego projektu wszelkie potrzebne uprawnienia. Nie ma potrzeby zajmowania się ContentType ani jawnego tworzenia obiektów 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'), 
        )

Zaraz potem manage.py makemigrationsi manage.py migratemożesz korzystać z tych uprawnień jak z każdego innego.

# 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 %}
Dmitry
źródło
2
to genialne, ocal mój dzień!
Reorx
2
Nic się nie zmieniło po uruchomieniu manage.py migrate ... Nie widzę żadnych nowych uprawnień :(
Agey
2
Czy dodałeś aplikację do swojego projektu (INSTALLED_APPS)?
Dmitry
2
Ta odpowiedź jest doskonała. [] Edytuję również default_permissions, zgłaszam błąd NotImplementedError przy funkcji save () modelu i mogę rozważyć, czy polecenie ma _ * _ uprawnienie () zwraca False, jeśli niezarządzany model jest naprawdę TYLKO dla tego uprawnienia.
Douglas Denhartog
4
Proponuję dodanie w klasie Meta następujące: default_permissions = (). Zapobiegnie to automatycznemu tworzeniu przez Django domyślnych uprawnień dodawania / zmiany / usuwania / wyświetlania dla tego modelu, które są najprawdopodobniej niepotrzebne, jeśli używasz tego podejścia.
Jordan
51

Idąc za radą Gonzalo , użyłem modelu proxy i niestandardowego menedżera do obsługi moich „bezmodelowych” uprawnień z fikcyjnym typem zawartości.

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
źródło
10
dziękuję za kod, fajnie byłoby też pokazać przykład jak używać tego kodu.
Ken Cochrane,
2
gdzie powinno mieszkać pozwolenie modelu?
Mirat Can Bayrak
4
Aby utworzyć GlobalPermission: z app.models import GlobalPermission gp = GlobalPermission.objects.create (codename = 'can_do_it', name = 'Can do it'). .
Julien Grenier
3
@JulienGrenier Przerwy kod w Django 1.8: FieldError: Cannot resolve keyword 'name' into field. Choices are: app_label, id, logentry, model, permission.
maciek
2
Ostrzeżenie: nowsze wersje Django (co najmniej 1.10) wymagają zastąpienia metody „get_queryset” (zwróć uwagę na brak znaku _ między słowami „query” i „set).
Lobe
10

Naprawiono odpowiedź Chewiego w Django 1.8, o którą proszono w kilku komentarzach.

W informacjach o wydaniu jest napisane:

Pole nazwy django.contrib.contenttypes.models.ContentType zostało usunięte podczas migracji i zastąpione właściwością. Oznacza to, że nie można już przeszukiwać lub filtrować ContentType za pomocą tego pola.

Więc jest to „nazwa” w odwołaniu w ContentType, której nie używa w GlobalPermissions.

Kiedy to naprawię, otrzymuję następujące informacje:

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)

Klasa GlobalPermissionManager nie uległa zmianie, ale została uwzględniona w celu zapewnienia kompletności.

rgammans
źródło
1
To nadal nie rozwiązuje problemu dla django 1.8, ponieważ w czasie synchronizacji django zapewnia, że ​​pole "name" nie może być puste.
Armita
U mnie zadziałało, ale nie używam migracji, ponieważ w moim projekcie wciąż znajdują się elementy spoza django. Czy aktualizujesz z poprzedniego django, ponieważ w 1.8 nie powinno być pola z nazwą
rgammans
4

To jest alternatywne rozwiązanie. Najpierw zadaj sobie pytanie: dlaczego nie stworzyć manekina, który naprawdę istnieje w DB, ale nigdy nie jest używany, z wyjątkiem posiadania uprawnień? To nie jest miłe, ale myślę, że jest to poprawne i proste rozwiązanie.

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

Powyższe rozwiązanie ma tę zaletę, że możesz użyć zmiennej Permissions.can_search_blue_flowerw swoim kodzie źródłowym zamiast używać literału „my_app.can_search_blue_flower”. Oznacza to mniej literówek i więcej autouzupełniania w IDE.

guettli
źródło
1
Czy używanie managed=Falsenie pozwala ci używać Permissions.can_search_blue_flowerz jakiegoś powodu?
Sam Bobel
@SamBobel tak, możesz mieć rację. Wydaje mi się, że ostatnim razem spróbowałem „abstrakcyjny”.
guettli
1

Możesz użyć proxy modeldo tego z fikcyjnym typem zawartości.

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)

Teraz można utworzyć tylko z pozwolenia namei codenamez pozwoleniem od CustomPermissionmodelu.

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

Możesz też wyszukiwać i wyświetlać tylko uprawnienia niestandardowe w swoich szablonach, takich jak ten.

 CustomPermission.objects.filter(content_type__model='custom permission')
arjun
źródło