Catégorie : développement

Tout ce qui concerne le développement en général, que ce soit des choses générales, ou des choses bien précises. Cela va de la gestion de projet à la recherche du fonctionnement de pointeurs en C.

Python : faire un « beep » sur Debian

Le code est simple, il fautt avoir paplay d’installé (je ne sais pas si c’est installé par défaut dans Debian).

A partir de là voici un exemple de code qui joue un fichier ogg :

# python3
>>> import subprocess
>>> subprocess.Popen([
... "paplay",
... "/home/olivier/.local/blah/Mission_Failed.ogg"
... ]).poll()
>>>

CI / git / gitlab / github

gitlab hints / aide

Git

Workflow Gittrunkbaseddevelopment.com
Semantic versioningsemver.org
Commit messages, règles de base gist.github.com/robertpainsi
Conventional commits conventionalcommits.org
Full refresh à partir du develop (si problème ou autre) git reset --hard origin/develop
git pull
Rafraîchir (à la main) une branche locale (si problème ou autre) git checkout master
git branch -D develop
git fetch
git checkout develop
git pull
Synchroniser le local avec le remote git fetch
Pour faire une nouvelle branche git checkout -b [nom de la branche]
Pour changer de branche git checkout [nom de la branche]
git pull
Sous-module : ajouter un git qui vient d'un autre repo git submodule add [nom du repo] [dossier dest]
Exemple concret qui a fonctionné pour moi : git submodule add https://github.com/goldfire/howler.js.git static/vendors/howler.js

Si erreur git qui parle de répertoire, demander à supprimer le dossier du cache : git rm --cached [dest. ex: static/vendors/howler.js]
Sous-module : ajouter un git qui vient d'un autre repo git submodule add [nom du repo] [dossier dest]
Exemple concret qui a fonctionné pour moi : git submodule add https://github.com/goldfire/howler.js.git static/vendors/howler.js

Si erreur git qui parle de répertoire, demander à supprimer le dossier du cache : git rm --cached [dest. ex: static/vendors/howler.js]
Sous-module : ajouter un git qui vient d'un autre repo git submodule add [nom du repo] [dossier dest]
Exemple concret qui a fonctionné pour moi : git submodule add https://github.com/goldfire/howler.js.git static/vendors/howler.js

Si erreur git qui parle de répertoire, demander à supprimer le dossier du cache : git rm --cached [dest. ex: static/vendors/howler.js]
Sous-module : : sur un autre PC mettre à jour avec tous les sous-modules Si git pull ne met pas à jour les sous-modules, c'est que ce qui gère les sous-modules sous git n'est pas initialisé :
git submodule update --init
Pour changer de branche git checkout [nom de la branche]
git pull
Pour changer de branche git checkout [nom de la branche]
git pull
Git global setup
Sur PC dev
git config --global user.name "Olivier Pons"
git config --global user.email "monmail@mail.com"
Clone gitlab » dev
Push dev » gitlab
git clone git@gitlab.com:olivier.pons/ajeter.git
cd ajeter
touch README.md
git add README.md
git commit -m "add README"
git push -u origin master
Init. projet pas vide:
Clone dev » gitlab
cd existing_folder
git init
git remote add origin git@gitlab.com:olivier.pons/ajeter.git
git add .
git commit -m "Initial commit"
git push -u origin master
Git existant sur dev
Clone dev » gitlab
cd existing_repo
git remote rename origin old-origin
git remote add origin git@gitlab.com:olivier.pons/ajeter.git
git push -u origin --all
git push -u origin --tags
Autre Git existant sur gitlab
Clone gitlab » dev
git clone git@gitlab.com:adresse/repo/sur/gitlab/monprojet.git
Cloning into 'monprojet'...
remote: Enumerating objects: 308, done.
remote: Counting objects: 100% (308/308), done.
remote: Compressing objects: 100% (192/192), done.
Receiving objects: 63% (195/308)
Receiving objects: 100% (308/308), 93.34 KiB | 463.00 KiB/s, done.
Resolving deltas: 100% (169/169), done.
cd monprojet
git checkout -b nom-de-la-branche remotes/origin/nom-de-la-branche
Donc en local, on a en local nom-de-la-branche liée à la branche distante remotes/origin/nom-de-la-branche.
A partir de là commit, push et pull fonctionnent.
Si des branches ont été ajoutées au remote :
Synchroniser le git local avec le remote
 git remote update origin --prune

Requirements

requirements

Le résultat du pip freeze permet d'avoir un "snapshot" des versions exactes utilisées, cela peut éviter certains bugs. Dans tous les cas, il faut prendre en compte les dépendances dès le début du projet et tout au long de son cycle de vie.
Une bonne pratique quand on utilise pip est de structurer ses dépendances de la manière suivante : ​
  • Un fichier requirements_base.txt avec les dépendances communes, par exemple : pynsq==0.8.2
    requests==2.19.1
    xlrd==1.1.0
    openpyxl==2.5.7
    python-datauri==0.2.8
  • Un fichier requirements_dev.txt dépendant de requirements_base.txt ne contenant les dépendances uniquement nécessaires pour le développement, test, CI, par exemple :
    -r requirements_base.txt
    pylint
  • Un fichier requirements_prod.txt dépendant de requirements_base.txt contenant les dépendances uniquement nécessaire à la production : -r requirements_base.txt
    gunicorn==19.7.1
  • Et un fichier requirements.txt qui ne sert que de lien à celui de prod (certaines plateformes style heroku utilisent celui-ci par défaut) -r requirements_prod.txt
Le développeur installe requirements_dev.txt, le déploiement en prod utilise requirement_prod.txt ou requirements.txt. ​ Tout ceci permet d'avoir des environnements reproductibles avec des versions figées, et de ne pas avoir à installer des dépendances supplémentaires à la main (par exemple gunicorn en prod).
Commentaires fermés sur CI / git / gitlab / github Publié dans Mots-clé , , ,

Sublime Text : licence. Oui. Licence.

Oui.

TAX INVOICE

TAX INVOICE? One image:

Run away!

Je l’ai fait. OUI.
Sublime Text licence bought

J’ai même choisi toutes les catégories dans lesquelles il peut entrer :

  • développement
  • développement divers
  • développement Internet
  • développement Django
  • Php
  • programmation C
  • programmation JavaScript
  • Python
  • geek
  • gestion de projet
  • euh j’arrête, il peut entrer… partout ! (aucune digression, merci !)

Je lutte activement contre la mentalité Française latine : j’essaie d’expliquer à tout le monde qu’il faut arrêter de scier la branche sur laquelle on (= les développeurs) est assise : si vous pouvez avoir quelque chose de gratuit, mais que vous vous en servez souvent, faites le calcul : s’il vous fait économiser du temps, donc de l’argent, bah la conclusion est simple : il faut que cela continue. Pour que cela continue, payez (moins que ce qu’il vous a fait économiser, sinon c’est pas intéressant) mais payez, bon sang !

J’ai évolué de ce côté et je fais tout pour que les personnes que je fréquente – et étudiants – aillent dans ce sens.

Donc oui, Sublime Text me fait gagner du temps tous les jours depuis plusieurs années, et aujourd’hui, la conclusion est évidente : il m’a fait gagner bien plus que 80 €.

Donc… je résume, une image vaut mille mots. Et même si pour certains c’est vieux, le principe reste éternel :

Anastacia - I paid my dues

PS : pour ceux qui ne savent pas ce que « OMFG » signifie, ne le dites à personne, et allez vite chercher sur Internet. Promis on ne le dira à personne. C’est comme « RTFM ». Promis.

python, flask, https et wsgi howto

Je ne vais pas dire le temps que j’ai passé à réussir à mettre ça en oeuvre si quelqu’un fait ça en quelques minutes j’aurais honte… mais disons que j’ai passé beaucoup de temps à le faire tourner, mais je suis vraiment impressionné par sa rapidité. Voici – via ma petite expérience – comment faire tourner un serveur Web très optimisé, en mode production  :
  • flask
  • wsgi
  • https

https / certbot

Je vous donne des pistes, à vous de finir.
L’idée, c’est que je veux paramétrer pour la n-ième fois un nouveau nom de domaine via certbot, et je ne veux pas qu’il touche à la conf de nginx. L’idée c’est que :
(1) Je le fais simplement via certbot-auto puis après,
(2) toutes les autres fois j’utilise « -d » pour qu’il ne touche pas à la conf, et je le fais à la main, en m’inspirant de la conf que cert-bot a fait dans le (1)
Donc, pour faire le (1), je demande à certbot de me créer uniquement le certificat : ./certbot-auto certonly -d monsite.fr
Pour le https, regardez ici.

Python

Code exemple, qui lit un fichier JSON, lance le serveur et sert le fichier lu en tant que JSON :

import json

from flask import Flask, jsonify

app = Flask(__name__)

with open("mon fichier JSON", 'r') as f:
    json_levels_full_description = json.load(f)


@app.route('/')
def index():
    return jsonify(json_levels_full_description)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8004)
else:
    application = app

Attention : surtout ne faites pas le classique if __name__ == '__main__' car uwsgi inclut ce fichier, donc ce fichier n’est jamais un __main__, par contre la configuration au lancement a besoin d’une variable globale nommée application.

Nginx

Je copie colle la configuration d’un site qui tourne déjà en https, en voici un extrait diminué à mort pour ne pas polluer avec des règles personnelles, regardez bien le texte en gras, c’est le plus important pour faire tourner Nginx en collaboration avec wsgi :

server {
  server_name "~(www\.)?monsite\.(com|fr|org|biz|be)$";

  index index.html index.htm;

  access_log /blah/proxy-access.monsite.log proxylog;
  error_log /blah/proxy-error.monsite.log error;

  listen 443 ssl;
  ssl_certificate /blah/fullchain.pem;
  ssl_certificate_key /blah/privkey.pem;
  include /blah/options-ssl-nginx.conf;
  ssl_dhparam /blah/ssl-dhparams.pem;

  location / {
    include denied_clients;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Server $host;

    # (!) here it's uwsgi:
    include uwsgi_params;
    uwsgi_pass 127.0.0.1:8002;

  }
}

# redirect http -> https:
server {
  listen *:80;
  server_name "~^monsite\..*$";
  return 302 https://$host$request_uri;
}

Flask

Pour installer flask, et uwsgi, faites un environnement virtuel puis installez les deux :

$ python3 -m venv venvpython.3.6.6
$ source venvpython.3.6.6/bin/activate
$ pip install --upgrade pip
$ pip install flask

uwsgi

Il faut être dans le venv activé (voir paragraphe précédent), puis l’installer via pip :

$ pip install uwsgi

Enfin, le script de lancement !

uwsgi --chdir=/web/htdocs/blah \
    --module=flask_server:application \
    --master --pidfile=/tmp/uwsgi.blah.pid \
    --socket=127.0.0.1:8004 \
    --processes=5 \
    --uid=1000 --gid=2000 \
    --harakiri=20 \
    --max-requests=5000 \
    --vacuum \
    --home=/web/htdocs/blah/venvpython.3.6.6

flask_server correspond au fichier python flask_server.pydécrit dans la section python au début et application est le nom de la variable globale (lisez le code de la section python)

Lorsque vous le lancerez, tout se passera bien, mais l’exécutable ne sera pas en tâche de fond. Cela a un avantage : un CTRL-C et vous l’arrêtez ! L’inconvénient, c’est que vous n’avez pas la main.

Si vous voulez le lancer en tâche de fond et qu’il écrive les logs dans un fichier, ajoutez l’option     --daemonize=/var/log/uwsgi/blah.uwsgi.log


Notes d’amélioration :
– Le port 8004 peut être passé en tant que variable d’environnement à l’application flask via le paramètre `env` de la commande uwsgi, ce qui rend le code plus portable ;
– Les paramètres uwsgi peuvent être consignés dans un fichier my_app.ini et uwsgi serait alors lancé en faisant uwsgi my_app.ini

Python » PyPI

Contribuer à une librairie Python

Principes

  • Faire un dossier (ex : source)
  • Dans source, cloner la librairie à laquelle on veut contribuer via
    git clone [repo concerné]
  • Faire un dossier pour l’environnement pip (ex : pip_env_lib)
  • Dans pip_env_lib, installer l’environnement pip via pipenv install
  • Dans pip_env_lib, lancer le « shell pip » via pip shell
  • Rarement documenté et important :
    – aller dans le dossier source
    – faire pip install -e .

pip install -e . (n’oubliez pas le . qui change tout, cela indique le répertoire à utiliser) signifie « si jamais le shell Python en cours essaie d’accéder à la librairie à laquelle on veut contribuer, aller la chercher dans source ». En fait, concrètement parlant, pip fait simplement un lien symbolique entre le dossier source d’installation de pip (ici c’est pip_env_lib), et le dossier source. N’oubliez pas que cela ne concerne le shell Python en cours (ici pip_env_lib).

L’avantage pratique de ce « lien symbolique », c’est qu’il est directement relié au code source. Donc, lorsqu’on modifie le code source de la librairie elle-même (dossier source), elle est immédiatement disponible. Si on fait donc des fichiers de tests unitaires, il suffit de les relancer, les modifications du code source sont toujours instantanément prises en compte. Toujours dans le shell Python en cours (ici pip_env_lib).

Résumé : exemple de code

mkdir [source]
cd [source]
git clone [repo concerné]
cd ..
mkdir [pip_env]
cd [pip_env]
pipenv --python 3 install
pip shell
pip install -e .

.. et les modifications dans [source] peuvent commencer !


PyPI : nouveau package / contribution

La documentation détaillée en anglais se trouve ici.
Bien meilleure que ce qui suit, mais longue, longue…

Voici les notes prises à la volées, d’un contributeur plutôt habitué à faire des contributions dans le monde Python.
Toute nouvelle note / correction éventuelle est la bienvenue.

Il y a trois choses différentes qu’on a tendance à confondre, mais qui n’ont fondamentalement aucun rapport. On a tendance à les confondre parce qu’on essaie d’utiliser, pour des raisons pratiques, le même nom, mais dans trois cadres différents.
Ces trois choses à bien dissocier sont :
le nom du package PyPI : dans le fichier de déploiement setup.py, il est défini dans le paramètre « name » de la commande setup(). C’est ce qui est vu dans PyPI.
le nom du projet qu’on met dans github : c’est le répertoire qui contient tous les fichiers du projet (= il peut être totalement différent, en général on s’arrange pour les appeler d’un nom différent) = c’est ce qu’on clone dans l’ordre git clone [repo concerné]
– le développeur final qui fait un « import » dans son code : ce module là peut être aussi différent, car c’est du Python. En général, c’est le répertoire de premier niveau du package (il doit contenir, bien sûr, « __init__.py » pour être vu comme un package et importable par Python). En général : « from [ma lib] import [quelque chose]« .

En général, on essaie de s’arranger pour que le nom du package sur PyPI soit le même que le nom du package dans le code Python même.
Mais s’il est déjà pris (dans un des trois contextes), il faut forcément utiliser un autre nom, et c’est là qu’il faut faire attention à ne pas se mélanger les pinceaux.

Pour faire un nouveau package sur PyPI le plus homogène possible :

  1. essayer de faire un projet « vide »
  2. faites une version squelette « alpha1 »
  3. faites une mise à jour sur PyPI : si ça fonctionne, alors vous pouvez continuer. Sinon, il faut utiliser un autre nom

Versionning

Dans PyPI, les versions sont obligatoirement du type «a.b.c».
En général :
[a].[b].[c]
[a] = fonctionnalités totalement nouvelles / incompatibilité
[b] = améliorations / nouvelles "petites" fonctionnalités
[c] = correctifs bogues

setup.py

Lorsque vous faites une nouvelle contribution, vous devez obligatoirement fournir un fichier setup.py.
Dans ce fichier, setup.py, il y a une fonction, ici aussi, obligatoire : setup().
Cette fonction, parmi tous les paramètres, a un paramètre particulier : classifiers qui permet au niveau de PyPI de dire « ce package est rangé dans telle(s) catégorie(s) ». La liste des expressions disponibles se trouve ici.

Django 2.1 : les autorisations de l’administration grandement facilitées

Voici le (petit) souci que tout le monde a forcément dès qu’on commence à vouloir un peu pousser la customization de l’administration Django : on aimerait avoir la possibilité d’accéder à certains modèles, mais uniquement en lecture seule. J’ai des tonnes d’exemples simples, mais très souvent, ce sont des modèles avec des choix « fixes » qui ne risquent pas d’évoluer dans le temps (exemple : sexe « féminin »/ »masculin »). Seulement (1) on ne veut pas le voir apparaître dans l’admin (2) si on ne veut pas le voir apparaître, il ne faut pas le déclarer, et Django ne veut pas accéder à un modèle qui n’est pas déclaré (= erreur), donc on en arrive toujours à (3) on déclare le modèle, on applique une solution « bidouille » que je ne décris pas ici mais même si le modèle n’apparaît pas dans l’interface, en tapant à la main la bonne URL (facile à deviner qui plus est), on peut tout de même modifier ce modèle. Heureusement la version 2.1 de Django pallie à ce manque !

Eh oui, dans la classe interne «Meta», ils ont fait évoluer default_permissions, qui est écrit en base. Donc en passant à Django 2.1 il vous faudra faire le fameux makemigrations migrate.

Là ou cela devient intéressant, c’est que dans les options vous avez default_permissions et elles sont ('add', 'change', 'delete', 'view'). Le plus important et le nouveau est donc 'view' qui pallie parfaitement au problème expliqué en haut !

Il vous suffit de déclarer votre modèle, de faire la classe dérivée de ModelAdmin et de préciser uniquement ('view',).
Attention, je n’ai pas testé, si quelqu’un veut tester et confirmer que ce code fonctionne (ou pas), je suis preneur !

class SexeAdmin(models.ModelAdmin):
    class Meta:
        default_permissions = ('view',)

Plein de code en moins, plus aucune bidouille ici, plus de lisibilité, bref, de mieux en mieux !

weechat

weechat hints / aide

Côté utilisateur

La souris Quand on aimerait cliquer :
/mouse disable
Pour la remettre :
/mouse enable
Documentation Raccourci : cliquer sur le lien :
– Message privé
– Raccourcis clavier

Configuration

Toujours installer v2+ et pas moins https://weechat.org/download/
Fichier de conf :
~/.weechat/
Weechat = une seule instance.
Conf. chargée en entier ici :
~/.weechat/
Pour plusieurs instances, copier tout le dossier, ex :
cp -R ~/.weechat ~/.weechat2
Puis lancez en précisant weechat -d ~/.weechat2
Lancement + connexion auto Exemple de code :
weechat -r '/server add freenode irc.freenode.net; /connect freenode -nicks=Olivier; /set irc.server.freenode.username "Olivier2"; /set irc.server.freenode.realname "Olivier Pons"; /set irc.server.freenode.autojoin "#prognosis-dev"'

Plugins

Situés dans :
~/.weechat/
Plugins Python ici :
~/.weechat/python
Chargement manuel Exemple Python / plugin arespond.py :
/script load arespond.py
Chargement automatique Exemple Python / plugin arespond.py :
Faire un lien symbolique :
cd ~/.weechat/python/autoload
ln -s ../arespond.py arespond.py
Options / switches Définir une option manuellement :
/set plugins.var.python.[SCRIPT_NAME].[option]="[valeur]
Appuyer sur TAB pour autocompléter
Au pire lire les options dans le code même.
Exemple Python / plugin arespond.py :
Les options sont en « camelCase », mais elles sont toutes transformées en minuscules dans weechat.
Il y a trois options marquée dans le code:
"muted"
"respondAfterMinutes"
"responderText"
Donc pour y accéder, il suffit de taper :
/set plugins.var.python.arespond.[tab]
Et les trois options seront affichées sur la ligne du dessus, il suffit d’autocompléter.

Django Async Roadmap

Petite parenthèse sur l’évolution de Django. Aujourd’hui, ce qui commence à importer plus que la vitesse, c’est la parallélisation des choses.
Donc avoir un serveur Web asynchrone, c’est censé être mieux qu’un serveur Web synchrone (= bloquant).

  • Django 2.1 : rien d’asynchrone. Aucun travail dessus.
  • Django 2.2 : travail initial pour ajouter la possibilité de faire de l’asynchrone sur l’ORM et les vues, sachant que c’est une option et que tout sera synchrone par défaut. Le support async sera principalement basé sur des pools de threads.
  • Django 3.0 : Ré-écriture de la gestion interne des requêtes pour être 100% asynchrone, ajout de la gestion asynchrone des middleware, forms, cache, sessions, auth. Début du process de dépréciation pour toutes les APIs prévues pour être uniquement asynchrones.
  • Django 3.1 : amélioration du support async, peut-être changement du templating
  • Django 3.2 : Fin du process de dépréciation et Django sera presque entièrement asynchrone.

Django et git (git « pur », pas « github »)

Si jamais vous décidez d’être un vrai guerrier, un de ceux qui veulent tout maîtriser, un de ceux qui, quand on leur donne des ordres, peuvent les refuser (oui c’est rare), et si vous voulez prendre des responsabilités, bref, si vous en avez : faites vous votre propre serveur git !

Je ne décrirai pas comment installer un serveur git sous Linux tellement c’est simple. Et je ne décrirai pas non plus comment faire ça sous Windows : pour des raisons de sécurité, de fiabilité et d’éthique, évitez Windows un point c’est tout.

Donc, sous Linux :

  • Créez un dossier dans lequel vous mettez toutes vos sources (scp est votre ami si vous êtes sur un PC et que vous voulez faire cela sur un autre PC)
  • Supposons que l’on est dans /web/htdocs/interro/htdocs
  • Créez un fichier .gitignore
  • Dans ce fichier, mettez ceci :
    *.pyc
    __pycache__
    myvenv
    db.sqlite3
    /static
    .DS_Store

    Cela signifie « ignore tous ces fichiers (.DS_Store est pour les personnes sous Mac (non je ne vous en veux pas, je suppose que vous ne connaissez pas bien l’histoire de l’informatique pour avoir acheté Apple)) » pour toutes les personnes qui vont cloner ce dossier
  • Ensuite, ajoutez tous les fichiers ainsi : git add --all .
  • Et commitez (oui je sais ce n’est pas français) ou « appliquez » les modifications c’est à dire l’ajout de tous les fichiers ainsi : git commit -m "My application, first commit"
  • Vous devriez avoir un paquet de fichiers ajoutés, vérifiez que tout est bien enregistré en demandant l’état via git status. Vous devez avoir un messsage ressemblant fortement à
    nothing to commit (working directory clean)
  • Vous êtes prêts ! Vous avez crée un repository (j’ai la flemme de traduire en français), et vous y avez appliqué tous vos fichiers. Il ne vous reste plus qu’à le cloner à distance
  • (Update) : par défaut, le serveur travaille sur la branche d’origine (« master »). Seul problème : si vous essayez de faire un « push » à partir de votre PC de développement, git ne sera pas d’accord pour faire un « push » sur la branche « master ». Je pense que c’est pour des raisons de sécurité ou autre, mais c’est plutôt récent, avant cela fonctionnait. La solution : dites sur le serveur de travailler sur une autre branche c’est via l’ordre checkout. Notez bien : c’est sur le serveur. Je ne trouve pas ça très cohérent, j’aurais dit que c’était au client de dire sur quelle branche il veut « push », mais je dois manquer quelque chose… j’ai vraiment beaucoup de lacunes git. Bref. Exemple : git checkout -b development.
  • Ensuite il vous suffira de faire un git clone (dans PyCharm, c’est le menu VCS » git » clone)

Bien sûr, comme tous les gestionnaires de version délocalisés, il vous faudra toujours trois étapes pour valider vos modifications :

  1. Appliquer les modifications en local (git commit -m "mon message expliquant les modifs")
  2. Essayer de récupérer toutes les dernières modifications du repository (git pull) vers votre code « local », au cas où d’autres personnes auraient « pushé » des modifications
  3. Envoyer vos modifications sur le serveur pour qu’elles soient accessibles à tout le monde (git push)

Django : autorisations sur mesure – custom authorizations

Voici comment créer manuellement des groupes + des autorisations personnalisées :

from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
new_group, created = Group.objects.get_or_create(
    name ='co_branding_trainer')
ct = ContentType.objects.get_for_model(User)
permission_co_branding_trainer = Permission.objects.create(
    codename='co_branding_trainer',
    name='Co-branding trainer', content_type=ct)
new_group.permissions.add(permission_co_branding_trainer)

Une fois cela fait, on a deux possibilités :

  • aller dans le template et se servir de
    {% if perms.app_label.can_do_something %}
    {% endif %}
  • ou modifier le code vue dans le fichier Python. Dans ce cas, on n’a rien dans le template et si la personne connectée n’a pas les droits, la vue fait un forbidden

Comme je préfère la seconde (qui évite d’avoir dans le template des « if » un peu partout), voici comment faire : on crée un mixin qui se chargera de vérifier si, dans la vue, on a les droits : il suffit de chercher PermissionRequiredMixin dans le code de Django, et vous verrez que tout est simple, à partir de là vous pourrez faire descendre vos vues génériques de PermissionRequiredMixin.