Coffee bytes

Blog de desarrollo web con Python y Javascript

Eduardo Zepeda

¿Cómo funcionan los permisos y grupos en Django?

El sábado, 17 de abril de 2021

por Eduardo Zepeda

Tiempo de lectura: 6 minutos

La primera vez que me enteré de que Django tenía un sistema de permisos, hace ya muchos años, me pareció algo bastante esotérico, sin mucha utilidad y fácil de replicar, que equivocado estaba en aquel entonces. Después me di cuenta de que el sistema de permisos integrado era una maravilla y ahorraba muchísimo código, además de ser bastante sólido y puesto a prueba por algunas de las empresas más grandes del mundo.

Si aún no te decides a usar Django y estás investigando sus características revisa mi entrada sobre las ventajas y desventajas del framework de desarrollo web Django.

Por otro lado, si ya tienes un poco de práctica con Django quizá te convenga profundizar lo que sabes con este libro gratuito, y en español, llamado la guía definitiva de Django

¿Cómo se crean los permisos en Django?

Cada que creas un modelo y corres las migraciones se crean automáticamente 4 permisos (add, edit, delete y view) en django.contrib.auth para ese objeto.

Por otro lado, el modelo usuario tiene una relación ManyToMany con el modelo Permissions (que guarda los permisos anteriores) y el modelo Groups. Por lo que ya de primeras contamos con una relación entre usuarios, grupos y permisos que podemos aprovechar.

Los permisos que crea Django vienen en la forma de <app>.<accion>_<modelo> o app.accion_modelo

app.add_modelo # Para añadir modelos
app.edit_modelo # Para editar modelos
app.delete_modelo # Para borrar modelos
app.view_modelo # Para ver modelos
# por ejemplo: 
# streaming.add_pelicula
# tienda.edit_articulo
# encuestas.delete_encuesta
# vapor.view_videojuego

Agregar o remover permisos a un usuario

Para agregar o remover los permisos a un usuario haremos uso de los métodos que nos provee Django

Para agregar permisos uno por uno los pasamos al método add().

user.permissions.add(permiso1, permiso2, ...)

Para remover permisos le pasamos el permiso que queremos remover al método remove().

user.permissions.remove(permiso1, permiso2, ...)

Si queremos establecer una lista de permisos simplemente igualamos el atributo permissions a la lista que queremos que tenga.

user.permissions = [lista_de_permisos]

Para eliminar todos los permisos de un usuario usamos el método clear().

user.permissions.clear()

Comprobando los permisos para un usuario

Ya que hemos asignado permisos, podemos limitar el comportamiento de un usuario a los permisos que tenga. Por ejemplo, Patreon solo muestra su contenido a usuarios que donan periódicamente a un creador de contenido e incluso dentro de una sola cuenta hay diferentes permisos que están en función de la cantidad de dinero que dones.

Comprobar que permisos tiene un usuario

El método get_all_permissions() nos devuelve una lista de todos los permisos con los que cuenta un usuario.

user.get_all_permissions()

Comprobar los permisos de los grupos a los que pertenece un usuario

Esto nos devolverá los permisos que obtiene un usuario por los grupos a los que pertenece.

user.user.get_group_permissions()

Comprobar si un usuario tiene un permiso

Podemos corroborar si un usuario tiene un permiso único con el método has_perm(). Devolverá True si el usuario tiene el permiso. Si el usuario tiene los permisos pero su instancia de usuario tiene la propiedad active igual a False, devolverá False.

user.has_perm("app.accion_modelo") 

Comprobar si un usuario tiene una serie de permisos

has_perms es bastante útil si queremos comprobar si un usuario cuenta con una serie de permisos. Devolverá True, solamente si cuenta con todos los permisos. Igual que el anterior, devolverá False si el usuario no está activo, incluso si cuenta con los permisos.

user.has_perms(["app.edit_modelo", "app.delete_modelo"]) # por ejemplo videogame_store.edit_videogame, videogame_store.delete_videogame

Comprobar si un usuario cuenta con algún permiso

Quizás queremos solo corroborar la existencia de cualquier permiso, para eso sirve has_module_perms(). Devuelve True si el usuario cuenta con algún permiso para la etiqueta de la aplicación que le pasemos. De la misma manera, devuelve False para usuarios inactivos.

user.has_module_perms('etiqueta_de_la_app') # por ejemplo videogame.view_videogame

# models.py
class tuModelo(models.Model):
    class Meta:
        app_label = 'etiqueta_de_la_app'

Aplicando los permisos para limitar acciones

Podemos aplicar los permisos para proteger nuestras vistas envolviéndolas en un decorador

Comprobando con user_passes_test

Esta función requiere obligatoriamente un objeto como argumento y este objeto debe aceptar al objeto user como su argumento. Dejando de lado eso, este test puede contener lo que quieras, desde comprobar un campo del usuario, permisos, corroborar una fecha límite, etc.

Existe un segundo parámetro opcional, la dirección url a redireccionar para usuarios que no están autenticados, que toma el valor de settings.LOGIN_URL (sí, el que especifiques en tu archivo de configuración) si no especificamos ninguno.

def can_delete_and_edit(user):
return user.has_perm("app.edit_modelo") and user.has_perm("app.delete_modelo")

@user_passes_test(can_delete_and_edit, login_url="/login/")
def manage_videogame(request):
    pass

Comprobando con permissions_required

De igual manera, cuenta con un segundo parámetro opcional, la dirección url a redireccionar para usuarios que no están autenticados, que adopta el valor de settings.LOGIN_URL si no especificamos ninguno.

from django.contrib.auth.decorators import permission_required

@permission_required('app.edit_videogame', login_url="/login/")
def edit_videogame(request):
# ...

Aplicando los permisos en plantillas

Las plantillas de Django ya no son tan populares como antes, debido al auge de los microservicios y frameworks de frontend como React, Vue, Angular, etc. Aún así, si deseas usarlas en plantillas puedes acceder a los permisos de la siguiente manera:

Actualización: Htmx puede darle vida, nuevamente, a los permisos en el sistema de plantillas de Django.

{% if perms.app.action_model %}
Para comprobar si el usuario tiene un permiso en específico
{% endif %}
{% if perms.app %}
Para comprobar si el usuario tiene algún permiso para esa app
{% endif %}

Grupos de permisos en Django

Como ya sabes, usar grupos es una manera bastante cómoda de asignar una serie de permisos. Quizás tengas una aplicación de pago y quieras que todos los usuarios con el plan básico tengan una serie de permisos, mientas que los usuarios premium tengan permisos extra. Para organizar los permisos y asignarlos de manera más sencilla podemos usar grupos. Puedes acceder a estos grupos y asignarles permisos desde el panel de administración o desde la terminal de Python.

Crear grupos de permisos

También podemos crearlos directo de la terminal de Python

from django.contrib.auth.models import Group
Group.objects.create(name="Premium")
premium = Group.objects.get(name="Premium")

Asignar permisos a un grupo

Si ya tenemos un grupo y queremos asignarle permisos usamos prácticamente el mismo set de métodos que usamos para un usuario.

Para establecer una lista de permisos

premium.permissions.set([lista_de_permisos])

Para añadir permisos, ya sea una lista o uno por uno.

premium.permissions.add(permiso1, permiso2, ...)

Para remover permisos, ya sea una lista o uno por uno

premium.permissions.remove(permiso1, permiso2, ...)

Para eliminar todos los permisos de un grupo

premium.permissions.clear()

Añadir o remover usuarios de un grupo

Para añadir un grupo de permisos usamos los mismos métodos que en los ejemplos anteriores: add, remove, clear

# agregar a un usuario a varios grupos
user.groups = group_list
# Agregar un usuario a uno o varios grupos
user.groups.add(grupo1, grupo2,...)
# Removemos un usuario de uno o varios grupos
user.groups.remove(grupo1, grupo2,...)
# Eliminamos a un usuario de todos los grupos
user.groups.clear()

Crear permisos personalizados en Django

Podemos agregar permisos personalizados a un modelo, usaremos la sub clase Meta del nuestro modelo y asignar la propiedad permissions a una tupla de tuplas, donde cada tupla cuenta con el nombre del permiso y su descripción.

permissions = (('permiso1', 'descripción 1'), ('permiso 2', 'descripción 2'))

Podemos nombrar estos permisos como deseemos, posteriormente dotar de ese permiso a ciertos usuarios y luego comprobar si un usuario cuenta alguno de nuestros permisos usando los métodos vistos con anterioridad.

class usuarioPersonalizado(AbstractUser):
    # ...

    class Meta:
        permissions = (
            ('puede_ver_contenido_premium', 'Puede ver contenido para usuarios premium'),
            ('puede_ver_contenido_básico', 'Puede ver contenido para usuarios básico'))

Si ahora ejecutamos las migraciones, notaremos que ya podemos agregar los permisos que creamos desde el panel de administración y desde la terminal de Python.

python manage.py makemigrations
python manage.py migrate

Recuerda revisar la documentación de Django sobre permisos para profundizar la publicación.

Presume lo que aprendiste en redes

Únete a mi comunidad de lectores

Recibe contenido como este por correo electrónico, una vez por semana, de manera totalmente gratuita.

* Campo obligatorio