Come posso utilizzare le autorizzazioni Django senza definire un tipo di contenuto o un modello?


86

Vorrei utilizzare un sistema basato su autorizzazioni per limitare determinate azioni all'interno della mia applicazione Django. Queste azioni non devono essere correlate a un modello particolare (ad es. Accesso alle sezioni nell'applicazione, ricerca ...), quindi non posso utilizzare direttamente il framework delle autorizzazioni stock , perché il Permissionmodello richiede un riferimento a un tipo di contenuto installato.

Potrei scrivere il mio modello di autorizzazione ma poi dovrei riscrivere tutte le chicche incluse con le autorizzazioni Django, come ad esempio:

Ho controllato alcune app come django-authority e django-guardian , ma sembrano fornire autorizzazioni ancora più accoppiate al sistema modello, consentendo autorizzazioni per oggetto.

C'è un modo per riutilizzare questo quadro senza aver definito alcun modello (oltre Usere Group) per il progetto?

Risposte:


57

Il Permissionmodello di Django richiede ContentTypeun'istanza .

Penso che un modo per aggirarlo sia creare un dummy ContentTypeche non è correlato a nessun modello (i campi app_labele modelpossono essere impostati su qualsiasi valore di stringa).

Se vuoi che tutto sia pulito e piacevole, puoi creare un Permission modello proxy che gestisca tutti i dettagli orribili del manichino ContentTypee crei istanze di autorizzazione "senza modello". Puoi anche aggiungere un gestore personalizzato che filtra tutte le Permissionistanze relative ai modelli reali.


3
Se non ti dispiace, completerò la tua risposta con la mia implementazione.
Chewie

Purtroppo, non posso approvare perché non ho abbastanza reputazione per rivedere la tua modifica (mi chiede + 2k). Altri utenti stanno rifiutando le tue modifiche, quindi ti suggerisco di aggiungerla come un'altra risposta (hai il mio voto positivo!) Grazie ancora.
Gonzalo

1
Quello è strano. È davvero un completamento per la tua risposta, quindi ha senso renderla una modifica. Comunque, l'ho messo in un'altra risposta.
Chewie

148

Per quelli di voi che stanno ancora cercando:

È possibile creare un modello ausiliario senza tabella di database. Quel modello può portare al tuo progetto qualsiasi autorizzazione di cui hai bisogno. Non è necessario gestire ContentType o creare oggetti Permission in modo esplicito.

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

Subito dopo manage.py makemigrationse manage.py migratepuoi usare questi permessi come qualsiasi altro.

# 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
è geniale, salvami la giornata!
Reorx

2
Non è cambiato nulla dopo aver eseguito la migrazione di manage.py ... Non vedo nuove autorizzazioni :(
Agey

2
Hai aggiunto la tua app al tuo progetto (INSTALLED_APPS)?
Dmitry

2
Questa risposta è perfetta. Ho anche [] ed default_permissions, sollevato NotImplementedError sul save () del modello, e potrei considerare di fare has _ * _ permission () return False se il modello non gestito è veramente SOLO per questa autorizzazione.
Douglas Denhartog

4
Suggerisco di aggiungere nella classe Meta quanto segue: default_permissions = (). Ciò impedirà a Django di creare automaticamente le autorizzazioni di aggiunta / modifica / eliminazione / visualizzazione predefinite per questo modello, che molto probabilmente non sono necessarie se si utilizza questo approccio.
Jordan

51

Seguendo il consiglio di Gonzalo , ho utilizzato un modello proxy e un gestore personalizzato per gestire i miei permessi "senza modello" con un tipo di contenuto fittizio.

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
grazie per il codice, sarebbe bello mostrare anche un esempio su come utilizzare questo codice.
Ken Cochrane

2
dove dovrebbe vivere quel permesso modello?
Mirat Can Bayrak

4
Per creare un GlobalPermission: da app.models import GlobalPermission gp = GlobalPermission.objects.create (codename = 'can_do_it', name = 'Can do it') Una volta eseguito, puoi aggiungere tale autorizzazione a utenti / gruppo come qualsiasi altra autorizzazione .
Julien Grenier

3
@JulienGrenier Le interruzioni codice in Django 1.8: FieldError: Cannot resolve keyword 'name' into field. Choices are: app_label, id, logentry, model, permission.
maciek

2
Attenzione: le versioni più recenti di Django (almeno 1.10) devono sovrascrivere il metodo "get_queryset" (notare l'assenza di _ tra le parole "query" e "set).
Lobe

10

Risolto il problema con la risposta di Chewie in Django 1.8, che come richiesto in alcuni commenti.

Dice nelle note di rilascio:

Il campo del nome di django.contrib.contenttypes.models.ContentType è stato rimosso da una migrazione e sostituito da una proprietà. Ciò significa che non è più possibile interrogare o filtrare un ContentType in questo campo.

Quindi è il "nome" di riferimento in ContentType che non utilizza in GlobalPermissions.

Quando lo risolvo, ottengo quanto segue:

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)

La classe GlobalPermissionManager è rimasta invariata ma inclusa per completezza.


1
Questo ancora non lo risolve per django 1.8 poiché al tempo di syncdb django afferma che il campo "nome" non può essere nullo.
Armita

Ha funzionato per me, ma non sto usando le migrazioni a causa di roba legacy non django ancora nel mio progetto. Stai
eseguendo l'

4

Questa è una soluzione alternativa. Per prima cosa chiediti: perché non creare un modello fittizio che esiste davvero nel DB ma che non viene mai utilizzato, tranne che per mantenere i permessi? Non è bello, ma penso che sia una soluzione valida e diretta.

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

La soluzione sopra ha il vantaggio, che puoi usare la variabile Permissions.can_search_blue_flowernel tuo codice sorgente invece di usare la stringa letterale "my_app.can_search_blue_flower". Ciò significa meno errori di battitura e più completamento automatico nell'IDE.


1
L'uso managed=Falsenon ti consente di utilizzare Permissions.can_search_blue_flowerper qualche motivo?
Sam Bobel

@SamBobel sì, potresti avere ragione. Penso di aver provato "astratto" l'ultima volta.
guettli

1

Puoi usare proxy modelper questo con un tipo di contenuto fittizio.

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)

Ora puoi creare l'autorizzazione con solo namee codenamel'autorizzazione dal CustomPermissionmodello.

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

E puoi interrogare e visualizzare solo le autorizzazioni personalizzate nei tuoi modelli in questo modo.

 CustomPermission.objects.filter(content_type__model='custom permission')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.