Restreignez des aspects de la fonctionnalité du site avec les permissions
Lorsque vous développez un site, vous serez souvent dans le cas où différents utilisateurs ont besoin d’interagir de façon différente avec certaines parties du site. Vous devrez peut-être restreindre certaines fonctionnalités à des utilisateurs spécifiques. Pour cela, utilisez des permissions.
Pour notre site web, nous allons restreindre l’accès à la création, la modification, et la suppression des modèles Photo
et Blog
aux utilisateurs de type CREATOR
.
Chaque modèle créé en Django a quatre permissions qui sont générées en parallèle. Pour le modèle Photo
, ce sont les suivantes :
blog.add_photo
— ou, plus généralement<app>.add_<model>
blog.change_photo
— ou<app>.change_<model>
blog.delete_photo
— ou<app>.delete_<model>
blog.view_photo
— ou<app>.view_<model>
Django utilise ces permissions en interne pour gérer l’accès au site administrateur, mais vous pouvez également les utiliser dans votre code. Découvrez en vidéo comment restreindre les fonctionnalités du site selon les permissions de l'utilisateur.
Étape 1 : Restreignez l’accès dans la vue
Tout d’abord, voyons comment restreindre l’accès à la vue en utilisant les permissions.
Tout comme vous utilisez le décorateur @login_required
pour restreindre l’accès aux utilisateurs en fonction de s’ils sont ou non connectés, vous pouvez utiliser le décorateur @permission_required
pour limiter l’accès en fonction de la permission. Dans ce cas, la seule différence est que vous spécifiez la permission requise comme argument au décorateur.
Restreignons l’accès à la vue photo_upload
aux seuls utilisateurs qui ont la permission blog.add_photo
:
from django.contrib.auth.decorators import login_required, permission_required
@login_required
@permission_required('blog.add_photo', raise_exception=True)
def photo_upload(request):
...
Maintenant, essayez d’accéder à http://localhost:8000/photo/upload en étant connecté. Vous verrez que vous allez recevoir une réponse 403 forbidden (interdit).
Étape 2 : Restreignez l’accès dans le gabarit
Vous pouvez aussi vérifier si un utilisateur a des permissions dans un gabarit. Cela peut vous servir pour montrer ou cacher du contenu en fonction de ses droits d’accès.
Ne montrons le lien Télécharger une photo
que si l’utilisateur a la permission blog.add_photo
. Pour cela, utilisez l’attribut perms
, qui est automatiquement chargé dans le contexte du gabarit :
# blog/templates/blog/base.html
…
{% if perms.blog.add_photo %}
<a href="{% url 'photo_upload' %}">Upload a Photo</a>
{% endif %}
Et maintenant, si vous naviguez jusqu’à la page d’accueil :
Le lien a disparu.
L’accès à cette page est maintenant restreint en fonction des permissions.
Comment est-ce que je peux donner ces permissions aux utilisateurs ?
Avec du code, dans le shell Django.
Étape 3 : Donnez des permissions à un utilisateur
Vous pouvez utiliser la méthode user_permissions.add()
pour ajouter des permissions.
>>> from authentication.models import User
>>> user = User.objects.get(username='toto')
>>> from django.contrib.auth.models import Permission
>>> permission = Permission.objects.get(codename='add_photo')
>>> user.user_permissions.add(permission)
Si vous retournez au site, vous devriez voir réapparaître le lien Télécharger une photo
, et pouvoir accéder à la page de mise en ligne de photos.
Ça fonctionne ! Un utilisateur a maintenant l’autorisation. Néanmoins, cette approche ne peut pas être mise à l’échelle si vous voulez l’appliquer à de nombreux utilisateurs. L’utilisation des groupes permet de résoudre ce problème.
Attribuez des permissions à plusieurs utilisateurs grâce aux groupes
À présent que le site a des parties restreintes en fonction des permissions, vous allez devoir en permettre l’accès à différents utilisateurs selon des spécifications.
Pour cela, répartissez les utilisateurs en groupes. Cela permet de regrouper un sous-ensemble d’utilisateurs. Ce groupement existe sous forme de tableau dans la base de données.
Pour notre site, nous aurons deux groupes :Creator (Créateur
etSubscriber (Abonné)
. Nous voulons que ces groupes soient créés automatiquement. Ainsi, si quelqu’un récupérait une copie du projet et le paramétrait sur son ordinateur, il n’aurait pas besoin de créer ces groupes et de leur attribuer les permissions appropriées manuellement.
Pour ce faire, nous devons écrire une migration personnalisée.
Qu’est-ce que c’est, une migration personnalisée ?
Les migrations que vous avez rencontrées jusqu’à présent étaient liées à des changements du schéma dans la base de données, et étaient automatiquement générées par Django.
Les migrations personnalisées vous permettent de manipuler des données déjà contenues dans la base de données, ou même de créer de nouvelles instances de modèle fondées sur des critères spécifiques. Elles sont utiles si vous devez migrer des données vers un nouveau type de données, sans perdre aucune information.
Comme ce sont des migrations, elles peuvent être stockées dans votre historique de contrôle de version. Elles peuvent être récupérées et exécutées par toute personne ayant accès au projet. Cela permet à l’application d’être reproductible dans différents environnements. Elles seront également exécutées dès que quelqu’un configure le projet initialement.
Tout d’abord, nous allons écrire une migration personnalisée pour créer les groupes creators
et subscribers
. Nous ajouterons ensuite les utilisateurs dans leur groupe approprié, en fonction de leur attribut role
. Découvrez plus de détails dans la vidéo qui suit :
Pour créer une migration personnalisée, vous devez générer une migration vide grâce au drapeau --empty
. Ensuite, spécifiez l’application où la migration sera générée. Dans notre cas, c’est authentication
:
python manage.py makemigrations --empty authentication
Et maintenant, si vous regardez le répertoiremigrations
de l’application authentication
, vous verrez qu’une nouvelle migration a été générée. Voyons ce qu’elle contient :
# Generated by Django 3.2.1 on 2021-04-06 01:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('authentication', '0001_initial'),
]
operations = [
]
La propriété dependencies
liste les migrations qui doivent être exécutées avant celle-ci, tandis qu’ operations
constitue une liste des opérations que la migration va effectuer.
Pour écrire le code sur mesure à exécuter dans la migration, vous devez l’écrire en tant que fonction prenant deux arguments, apps
et schema_editor
. Vous ne pouvez pas accéder aux modèles directement depuis les imports, vous devez donc utiliser la fonction apps.get_model()
pour les récupérer.
Écrivons une fonction create_groups
, qui va créer les groupes creators
et subscribers
, puis leur attribuer correctement les utilisateurs existants dans la base de données avec la fonction Group.user_set.add()
:
def create_groups(apps, schema_migration):
User = apps.get_model('authentication', 'User')
Group = apps.get_model('auth', 'Group')
Permission = apps.get_model('auth', 'Permission')
add_photo = Permission.objects.get(codename='add_photo')
change_photo = Permission.objects.get(codename='change_photo')
delete_photo = Permission.objects.get(codename='delete_photo')
view_photo = Permission.objects.get(codename='view_photo')
creator_permissions = [
add_photo,
change_photo,
delete_photo,
view_photo,
]
creators = Group(name='creators')
creators.save()
creators.permissions.set(creator_permissions)
subscribers = Group(name='subscribers')
subscribers.save()
subscribers.permissions.add(view_photo)
for user in User.objects.all():
if user.role == 'CREATOR':
creators.user_set.add(user)
if user.role == 'SUBSCRIBER':
subscribers.user_set.add(user)
Ensuite, ajoutez la migration aux operations
en spécifiant la fonction create_groups
comme argument de la classe migrations.RunPython
:
class Migration(migrations.Migration):
dependencies = [
('authentication', '0001_initial'),
]
operations = [
migrations.RunPython(create_groups)
]
Et maintenant, exécutez la migration :
(fotoblog) ~/fotoblog (master)
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, authentication, blog, contenttypes, sessions
Running migrations:
Applying authentication.0002_auto_20210406_0135... OK
Ça a l’air d’avoir marché !
N’hésitez pas à aller sur votre site en tant que créateur connecté. Vous devriez voir la fonctionnalité Upload Photo
apparaître à nouveau. Puis, visualisez la page en tant qu’abonné. Vous devriez constater que le bouton a disparu.
Maintenant que vous savez comment ajouter ces permissions, à vous de jouer pour construire le reste de la gestion des permissions !
C'est à vous ! Menez à bien la gestion des permissions
Maintenant que vous savez comment configurer les permissions pour la fonctionnalité Photo Upload
, il est temps de construire les permissions restantes.
Quatre tâches vous attendent :
Utilisez le décorateur
permission_required
pour restreindre l’accès aux vuescreate_multiple_photos
,edit_blog
etblog_and_photo_upload
.Utilisez l’attribut
perms
dans un gabarit pour afficher de façon optionnelle les liensTélécharger plusieurs photos
,Écrire un billet
, etModifier un billet
.Mettez à jour la méthode
save()
deUser
pour ajouter l’utilisateur au bon groupe.Créez une migration personnalisée pour attribuer les bonnes permissions aux groupes pour le modèle
Blog
. Ce seront les mêmes permissions que pourPhoto
.
Si vous bloquez sur un point ou que vous voulez vérifier votre implémentation, vous trouverez une solution dans le dépôt GitHub.
Configurez des accès à granularité fine avec les permissions personnalisées
Les quatre autorisations par défaut créées par Django sont utiles, mais que faire si vous avez besoin de permissions à granularité plus fine ? Vous pourriez vouloir, par exemple, qu’un type d’utilisateur soit uniquement capable de modifier un champ particulier dans un modèle, comme le champ title
de Blog
.
Comment faire ? En créant des permissions personnalisées ! La façon la plus simple de les créer est de les définir comme faisant partie du modèle.
Spécifiez des permissions personnalisées en configurant l’attribut permissions
dans une classe Meta
d’un modèle, comme ceci :
class Blog(models.Model):
...
class Meta:
permissions = [
('change_blog_title', 'Peut changer le titre d’un billet de blog')
]
Vous pouvez ensuite attribuer cette permission à des groupes ou des utilisateurs, exactement comme pour les permissions de base.
Les permissions par objet ne sont pas bien gérées par Django tel qu’il est conçu. Si vous avez besoin d’utiliser des permissions de cette façon, certaines bibliothèques tierces bien faites peuvent vous aider. Django Guardian et Rules sont deux solutions populaires à ce problème, et vous pouvez toujours aller voir la section sur les permissions de Django Packages pour d’autres propositions (les trois sont des ressources en anglais).
En résumé
Django fournit quatre permissions par défaut pour chaque modèle, qui correspondent aux quatre opérations CRUD.
Vous pouvez utiliser le décorateur
permission_required
pour restreindre l’accès à une vue en fonction d’une permission.Vous pouvez attribuer des permissions à des utilisateurs individuels, ou à plusieurs utilisateurs en utilisant les groupes.
Les migrations personnalisées vous permettent d’apporter des changements sur mesure à la base de données, qu’on peut répéter sur différents environnements..
Les permissions personnalisées peuvent être spécifiées dans une classe de modèle
Meta
.Il vaut mieux utiliser un package tiers pour les permissions au niveau de l’objet.
Maintenant que vous savez restreindre l’accès en fonction des permissions et écrire des migrations personnalisées, vous pouvez aller découvrir les champs plusieurs-à-plusieurs (many-to-many en anglais).