Wie kann ich Django-Berechtigungen verwenden, ohne einen Inhaltstyp oder ein Modell zu definieren?

83

Ich möchte ein auf Berechtigungen basierendes System verwenden, um bestimmte Aktionen in meiner Django-Anwendung einzuschränken. Diese Aktionen müssen sich nicht auf ein bestimmtes Modell beziehen (z. B. Zugriff auf Abschnitte in der Anwendung, Suchen ...), daher kann ich das Framework für Bestandsberechtigungen nicht direkt verwenden, da für das PermissionModell ein Verweis auf einen installierten Inhaltstyp erforderlich ist.

Ich könnte mein eigenes Berechtigungsmodell schreiben, aber dann müsste ich alle in den Django-Berechtigungen enthaltenen Goodies neu schreiben, wie z.

Ich habe einige Apps wie Django-Authority und Django-Guardian überprüft , aber sie scheinen Berechtigungen bereitzustellen, die noch stärker an das Modellsystem gekoppelt sind, indem sie Berechtigungen pro Objekt zulassen.

Gibt es eine Möglichkeit, dieses Framework wiederzuverwenden, ohne ein Modell (außer Userund Group) für das Projekt definiert zu haben?

Chewie
quelle

Antworten:

56

Djangos PermissionModell erfordert eine ContentTypeInstanz .

Ich denke, eine Möglichkeit, dies zu umgehen ContentType, besteht darin, einen Dummy zu erstellen , der keinem Modell zugeordnet ist (die Felder app_labelund modelkönnen auf einen beliebigen Zeichenfolgenwert gesetzt werden).

Wenn Sie möchten, dass alles sauber und schön ist, können Sie ein Permission Proxy-Modell erstellen , das alle hässlichen Details des Dummys verarbeitet ContentTypeund "modelllose" Berechtigungsinstanzen erstellt. Sie können auch einen benutzerdefinierten Manager hinzufügen, der alle PermissionInstanzen herausfiltert, die sich auf reale Modelle beziehen.

Gonzalo
quelle
3
Wenn es Ihnen nichts ausmacht, werde ich Ihre Antwort mit meiner Implementierung vervollständigen.
Chewie
Leider kann ich nicht zustimmen, da ich nicht genug Ruf habe, um Ihre Bearbeitung zu überprüfen (ich werde um +2k gebeten). Andere Benutzer lehnen Ihre Änderungen ab, daher schlage ich vor, dass Sie sie als weitere Antwort hinzufügen (Sie haben meine positive Bewertung!). Nochmals vielen Dank.
Gonzalo
Das ist komisch. Es ist wirklich eine Vervollständigung für Ihre Antwort, daher ist es sinnvoll, sie zu bearbeiten. Wie auch immer, ich habe es in eine andere Antwort geschrieben.
Chewie
131

Für diejenigen von Ihnen, die noch suchen:

Sie können ein Hilfsmodell ohne Datenbanktabelle erstellen. Dieses Modell kann jede Erlaubnis, die Sie benötigen, in Ihr Projekt einbringen. Es ist nicht erforderlich, sich mit ContentType zu befassen oder Berechtigungsobjekte explizit zu erstellen.

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

Gleich danach manage.py makemigrationsund manage.py migrateSie können diese Berechtigungen wie alle anderen verwenden.

# 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
quelle
2
Das ist Genie, rette meinen Tag!
Reorx
2
Nichts hat sich geändert, nachdem ich manage.py migrate ausgeführt habe ... Ich sehe keine neuen Berechtigungen :(
Agey
2
Haben Sie Ihre App zu Ihrem Projekt hinzugefügt (INSTALLED_APPS)?
Dmitry
2
Diese Antwort ist perfekt. Ich habe auch default_permissions [] bearbeitet, NotImplementedError für save () des Modells ausgelöst und möglicherweise in Betracht gezogen, _ * _ erlaube () False zurückzugeben, wenn das nicht verwaltete Modell wirklich NUR für diese Berechtigung ist.
Douglas Denhartog
4
Ich schlage vor, in der Meta-Klasse Folgendes hinzuzufügen : default_permissions = (). Dadurch wird verhindert, dass Django automatisch die Standardberechtigungen zum Hinzufügen / Ändern / Löschen / Anzeigen für dieses Modell erstellt, die bei Verwendung dieses Ansatzes höchstwahrscheinlich nicht erforderlich sind.
Jordanien
51

Nach Gonzalos Rat habe ich ein Proxy-Modell und einen benutzerdefinierten Manager verwendet , um meine "modelllosen" Berechtigungen mit einem Dummy-Inhaltstyp zu verarbeiten.

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
quelle
10
Vielen Dank für den Code. Es wäre schön, auch ein Beispiel für die Verwendung dieses Codes zu zeigen.
Ken Cochrane
2
Wo soll diese Mustererlaubnis leben?
Mirat Can Bayrak
4
So erstellen Sie eine GlobalPermission: Importieren Sie aus app.models GlobalPermission gp = GlobalPermission.objects.create (codename = 'can_do_it', name = 'Can do it'). Sobald dies ausgeführt wird, können Sie diese Berechtigung wie jede andere Berechtigung Benutzern / Gruppen hinzufügen .
Julien Grenier
3
@ JulienGrenier Der Code bricht in Django 1.8 : FieldError: Cannot resolve keyword 'name' into field. Choices are: app_label, id, logentry, model, permission.
Maciek
2
Warnung: Neuere Versionen von Django (mindestens 1.10) müssen die Methode "get_queryset" überschreiben (beachten Sie das Fehlen von _ zwischen den Wörtern "query" und "set").
Lobe
10

Fix für Chewies Antwort in Django 1.8, die in einigen Kommentaren angefordert wurde.

In den Versionshinweisen heißt es:

Das Namensfeld von django.contrib.contenttypes.models.ContentType wurde durch eine Migration entfernt und durch eine Eigenschaft ersetzt. Das heißt, es ist nicht mehr möglich, einen ContentType nach diesem Feld abzufragen oder zu filtern.

Es ist also der 'Name', auf den in ContentType verwiesen wird, der nicht in GlobalPermissions verwendet wird.

Wenn ich es behebe, bekomme ich folgendes:

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)

Die GlobalPermissionManager-Klasse ist unverändert, der Vollständigkeit halber jedoch enthalten.

rgammans
quelle
1
Dies behebt es immer noch nicht für Django 1.8, da Django zum Zeitpunkt der Synchronisierung behauptet, dass das Feld "Name" nicht null sein darf.
Armita
Es hat bei mir funktioniert, aber ich verwende keine Migrationen aufgrund von Nicht-Django-Legacy-Inhalten, die sich noch in meinem Projekt befinden. Aktualisieren Sie von einem früheren Django, weil es in 1.8
rgammans
4

Dies ist eine alternative Lösung. Fragen Sie sich zunächst: Warum nicht ein Dummy-Modell erstellen, das tatsächlich in der Datenbank vorhanden ist, aber nie verwendet wird, außer zum Halten von Berechtigungen? Das ist nicht schön, aber ich denke, es ist eine gültige und einfache Lösung.

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

Die obige Lösung hat den Vorteil, dass Sie die Variable Permissions.can_search_blue_flowerin Ihrem Quellcode verwenden können, anstatt die Literalzeichenfolge "my_app.can_search_blue_flower" zu verwenden. Dies bedeutet weniger Tippfehler und mehr Autovervollständigung in der IDE.

guettli
quelle
1
Lässt managed=FalseSie die Verwendung Permissions.can_search_blue_floweraus irgendeinem Grund nicht zu?
Sam Bobel
@ SamBobel ja, du könntest recht haben. Ich denke, ich habe gerade das letzte Mal "abstrakt" versucht.
Guettli
1

Sie können das proxy modelhierfür mit einem Dummy-Inhaltstyp verwenden.

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)

Jetzt können Sie die Berechtigung mit nur nameund codenamemit der Berechtigung aus dem CustomPermissionModell erstellen .

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

Und Sie können nur die benutzerdefinierten Berechtigungen in Ihren Vorlagen wie folgt abfragen und anzeigen.

 CustomPermission.objects.filter(content_type__model='custom permission')
Arjun
quelle
0

Anstatt diesen Code zu schreiben und auszuführen, der Datensätze in die Datenbank einfügt, können Sie die Datensätze einfach in Ihre Datenbank einfügen (offensichtlich den Primär- und den Fremdschlüssel nach Bedarf bearbeiten).

insert into django_content_type(id,name,app_label,model) values (22,'app_permission','myapp','app_permission');
insert into auth_permission(id,name,content_type_id,codename) values (64,'Is Staff Member',22,'staff_member');

Und dann haben Sie in Ihrem Anwendungsadministrator die Möglichkeit, Ihren Benutzern oder Gruppen "Ist Mitarbeiter" zuzuweisen. Um diese Berechtigung in Ihrer Klasse zu überprüfen, würden Sie schreiben

from django.contrib.auth.decorators import permission_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

class MyClass(TemplateView):
    template_name = myapp/index.html'

    @method_decorator(permission_required(['myapp.staff_member'],raise_exception=True))
    def dispatch(self, *args, **kwargs):
        return super(MyClass, self).dispatch(*args, **kwargs)
Lehrian
quelle
1
Dies sollte nicht der ideale Weg sein. Die oben vorgeschlagenen Möglichkeiten sind hilfreicher.
Shrey
1
Zumindest sollten Sie eine Migrationsdatei erstellen und eine ordnungsgemäße Migration durchführen
Guival