¿Cómo puedo usar los permisos de Django sin definir un tipo o modelo de contenido?

84

Me gustaría usar un sistema basado en permisos para restringir ciertas acciones dentro de mi aplicación Django. Estas acciones no necesitan estar relacionadas con un modelo en particular (por ejemplo, acceso a secciones en la aplicación, búsqueda ...), por lo que no puedo usar el marco de permisos de stock directamente, porque el Permissionmodelo requiere una referencia a un tipo de contenido instalado.

Podría escribir mi propio modelo de permisos, pero luego tendría que volver a escribir todas las ventajas incluidas con los permisos de Django, como:

Revisé algunas aplicaciones como django-Authority y django-guardian , pero parecen proporcionar permisos aún más acoplados al sistema modelo, al permitir permisos por objeto.

¿Hay alguna forma de reutilizar este marco sin haber definido ningún modelo (además de Usery Group) para el proyecto?

Chewie
fuente

Respuestas:

57

El Permissionmodelo de Django requiere una ContentTypeinstancia .

Creo que una forma de evitarlo es crear un maniquí ContentTypeque no esté relacionado con ningún modelo (los campos app_labely modelse pueden establecer en cualquier valor de cadena).

Si lo desea todo limpio y agradable, puede crear un Permission modelo de proxy que maneje todos los detalles desagradables del muñeco ContentTypey cree instancias de permisos "sin modelo". También puede agregar un administrador personalizado que filtre todas las Permissioninstancias relacionadas con modelos reales.

Gonzalo
fuente
3
Si no le importa, completaré su respuesta con mi implementación.
Chewie
Lamentablemente, no puedo aprobarlo porque no tengo la reputación suficiente para revisar tu edición (me pide + 2k). Otros usuarios están rechazando sus ediciones, por lo que le sugiero que la agregue como otra respuesta (¡tiene mi voto a favor!) Gracias nuevamente.
Gonzalo
1
Eso es raro. Realmente es un final para tu respuesta, por lo que tiene sentido editarlo. De todos modos, lo pongo en otra respuesta.
Chewie
138

Para aquellos de ustedes que todavía están buscando:

Puede crear un modelo auxiliar sin tabla de base de datos. Ese modelo puede aportar a su proyecto cualquier permiso que necesite. No es necesario tratar con ContentType o crear objetos de permiso de forma explícita.

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

Inmediatamente después manage.py makemigrationsy manage.py migratepuede usar estos permisos como cualquier otro.

# 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
fuente
2
eso es genial, salva mi día!
Reorx
2
Nada cambió después de ejecutar manage.py migrate ... No veo ningún permiso nuevo :(
Edad
2
¿Agregaste tu aplicación a tu proyecto (INSTALLED_APPS)?
Dmitry
2
Esta respuesta es perfecta. También [] edité default_permissions, genere NotImplementedError en el save () del modelo, y podría considerar hacer que has _ * _ allow () return False si el modelo no administrado es realmente SOLO para este permiso.
Douglas Denhartog
4
Yo sugiero agregar en la clase Meta el siguiente: default_permissions = (). Esto evitará que Django cree automáticamente los permisos predeterminados de agregar / cambiar / eliminar / ver para este modelo, que probablemente sean innecesarios si está utilizando este enfoque.
Jordan
51

Siguiendo el consejo de Gonzalo , usé un modelo de proxy y un administrador personalizado para manejar mis permisos "sin modelo" con un tipo de contenido ficticio.

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
fuente
10
gracias por el código, sería bueno mostrar también un ejemplo sobre cómo usar este código.
Ken Cochrane
2
¿Dónde debería vivir ese permiso modelo?
Mirat Can Bayrak
4
Para crear un Permiso Global: desde app.models importar Permiso Global gp = Permiso Global.objects.create (nombre en clave = 'can_do_it', nombre = 'Puede hacerlo') Una vez que esto se ejecuta, puede agregar ese permiso a los usuarios / grupo como cualquier otro permiso .
Julien Grenier
3
@JulienGrenier Los descansos de código en Django 1.8: FieldError: Cannot resolve keyword 'name' into field. Choices are: app_label, id, logentry, model, permission.
maciek
2
Advertencia: las versiones más recientes de Django (al menos 1.10) deben anular el método "get_queryset" (tenga en cuenta la falta de _ entre las palabras "query" y "set).
Lobe
10

Arreglo para la respuesta de Chewie en Django 1.8, que como se solicitó en algunos comentarios.

Dice en las notas de la versión:

El campo de nombre de django.contrib.contenttypes.models.ContentType ha sido eliminado por una migración y reemplazado por una propiedad. Eso significa que ya no es posible consultar o filtrar un ContentType por este campo.

Entonces, es el 'nombre' en referencia en ContentType que no usa en GlobalPermissions.

Cuando lo arreglo, obtengo lo siguiente:

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 clase GlobalPermissionManager no se modifica, pero se incluye para completar.

rgammans
fuente
1
Esto todavía no lo soluciona para django 1.8 ya que en el tiempo de syncdb django afirma que el campo "nombre" no puede ser nulo.
Armita
Funcionó para mí, pero no estoy usando migraciones debido a cosas heredadas que no son de django que aún están en mi proyecto. Estás actualizando desde una Django anterior, debido a que no se supone que es un campo de nombre en 1,8
rgammans
4

Esta es una solución alternativa. Primero pregúntese: ¿Por qué no crear un modelo ficticio que realmente exista en la base de datos pero que nunca se use, excepto para tener permisos? Eso no es agradable, pero creo que es una solución válida y sencilla.

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 solución anterior tiene la ventaja de que puede usar la variable Permissions.can_search_blue_floweren su código fuente en lugar de usar la cadena literal "my_app.can_search_blue_flower". Esto significa menos errores tipográficos y más autocompletado en IDE.

guettli
fuente
1
¿ managed=FalseUsar no te deja usar Permissions.can_search_blue_flowerpor alguna razón?
Sam Bobel
@SamBobel sí, podrías tener razón. Supongo que intenté "abstracto" la última vez.
guettli
1

Puede utilizar proxy modelpara esto con un tipo de contenido ficticio.

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)

Ahora puede crear el permiso con solo namey codenamedel permiso del CustomPermissionmodelo.

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

Y puede consultar y mostrar solo los permisos personalizados en sus plantillas de esta manera.

 CustomPermission.objects.filter(content_type__model='custom permission')
arjun
fuente