gwift-book/chapters/administration.tex

317 lines
12 KiB
TeX
Executable File

\chapter{Administration}
Cette partie est tellement puissante et performante, qu'elle pourrait laisser penser qu'il est possible de réaliser une application complète rien qu'en configurant l'administration.
C'est faux.
L'administration est une sorte de tour de contrôle évoluée, un \emph{back office} sans transpirer; elle se base sur le modèle de données programmé et construit dynamiquement les formulaires qui lui est associé.
Elle joue avec les clés primaires, étrangères, les champs et types de champs par \href{https://fr.wikipedia.org/wiki/Introspection}{introspection}, et présente tout ce qu'il faut pour avoir du \href{https://fr.wikipedia.org/wiki/CRUD}{CRUD} \index{CRUD} \footnote{\emph{Create-Read-Update-Delete}, c'est-à-dire le fonctionnement par défaut de beaucoup d'applications}, c'est-à-dire tout ce qu'il faut pour ajouter, lister, modifier ou supprimer des informations.
Son problème est qu'elle présente une courbe d'apprentissage asymptotique.
Il est \textbf{très} facile d'arriver rapidement à un bon résultat, au travers d'un périmètre de configuration relativement restreint.
Quoi que vous fassiez, il y a un moment où la courbe de paramétrage sera tellement ardue que vous aurez plus facile à développer ce que vous souhaitez ajouter en utilisant les autres concepts de Django.
Cette interface doit rester dans les mains d'administrateurs ou de gestionnaires, et dans leurs mains à eux uniquement: il n'est pas question de donner des droits aux utilisateurs finaux (même si c'est extrêment tentant durant les premiers tours de roues).
Indépendamment de la manière dont vous allez l'utiliser et la configurer, vous finirez par devoir développer une "vraie" application, destinée aux utilisateurs classiques, et répondant à leurs besoins uniquement.
Une bonne idée consiste à développer l'administration dans un premier temps, en \textbf{gardant en tête qu'il sera nécessaire de développer des concepts spécifiques}.
Dans cet objectif, l'administration est un outil exceptionel, qui permet de valider un modèle, de créer des objets rapidement et de valider les liens qui existent entre eux.
C'est aussi un excellent outil de prototypage et de preuve de concept.
Elle se base sur plusieurs couches que l'on a déjà (ou on va bientôt) aborder (suivant le sens de lecture que vous préférez):
\begin{enumerate}
\item
Le modèle de données
\item
Les validateurs
\item
Les formulaires
\item
Les widgets
\end{enumerate}
\section{Le modèle de données}
Comme expliqué ci-dessus, le modèle de données est constité d'un ensemble de champs typés et de relations.
L'administration permet de décrire les données qui peuvent être modifiées, en y associant un ensemble (basique) de permissions.
Si vous vous rappelez de l'application que nous avions créée dans la première partie, les URLs reprenaient déjà la partie suivante:
\begin{minted}[tabsize=4]{python}
from django.contrib import admin
from django.urls import path
from gwift.views import wish_details
urlpatterns = [
path('admin/', admin.site.urls),
[...]
]
\end{minted}
Cette URL signifie que la partie \texttt{admin} est déjà active et accessible à l'URL \texttt{\textless{}mon\_site\textgreater{}/admin}.
C'est le seul prérequis pour cette partie.
Chaque application nouvellement créée contient par défaut un fichier \texttt{admin.py}, dans lequel il est possible de déclarer les ensembles de données seront accessibles ou éditables.
Ainsi, si nous partons du modèle basique que nous avions détaillé plus tôt, avec des souhaits et des listes de souhaits:
\begin{minted}[tabsize=4]{python}
# gwift/wish/models.py
from django.db import models
class WishList(models.Model):
name = models.CharField(max_length=255)
class Item(models.Model):
name = models.CharField(max_length=255)
wishlist = models.ForeignKey(WishList, on_delete=models.CASCADE)
\end{minted}
Nous pouvons facilement arriver au résultat suivant, en ajoutant
quelques lignes de configuration dans ce fichier \texttt{admin.py}:
\begin{minted}[tabsize=4]{python}
from django.contrib import admin
from .models import Item, WishList
admin.site.register(Item)
admin.site.register(WishList)
\end{minted}
\begin{itemize}
\item
Nous importons les modèles que nous souhaitons gérer dans l'admin
\item
Et nous les déclarons comme gérables. Cette dernière ligne implique aussi qu'un modèle pourrait ne pas être disponible du tout, ce qui n'activera simplement aucune opération de lecture ou modification.
\end{itemize}
Il nous reste une seule étape à réaliser: créer un nouvel utilisateur.
Pour cet exemple, notre gestion va se limiter à une gestion manuelle; nous aurons donc besoin d'un \emph{super-utilisateur}, que nous pouvons créer grâce à la commande \texttt{python\ manage.py\ createsuperuser}.
\begin{verbatim}
$ python manage.py createsuperuser
Username (leave blank to use 'fred'): fred
Email address: fred@root.org
Password: ******
Password (again): ******
Superuser created successfully.
\end{verbatim}
\begin{figure}[H]
\centering
\includegraphics{images/django/django-site-admin.png}
\caption{Connexion au site d'administration}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics{images/django/django-site-admin-after-connection.png}
\caption{Administration}
\end{figure}
Ceci nous permet déjà d'ajouter des éléments (Items), des listes de souhaits, de visualiser les actions récentes, voire de gérer les autorisations attribuées aux utilisateurs, comme les membres du staff ou les administrateurs.
\section{Quelques conseils de base}
\begin{enumerate}
\item
Surchargez la méthode \texttt{str(self)} pour chaque classe que vous aurez définie dans le modèle.
Cela permettra de construire une représentation textuelle pour chaque instance de votre classe.
Cette information sera utilisée un peu partout dans le code, et donnera une meilleure idée de ce que l'on manipule.
En plus, cette méthode est également appelée lorsque l'administration historisera une action (et comme cette étape sera inaltérable, autant qu'elle soit fixée dans le début).
\item
La méthode \texttt{get\_absolute\_url(self)} retourne l'URL à laquelle on peut accéder pour obtenir les détails d'une instance. Par exemple:
\end{enumerate}
\begin{minted}[tabsize=4]{python}
def get_absolute_url(self):
return reverse('myapp.views.details', args=[self.id])
\end{minted}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Les attributs \texttt{Meta}:
\end{enumerate}
\begin{minted}[tabsize=4]{python}
class Meta:
ordering = ['-field1', 'field2']
verbose_name = 'my class in singular'
verbose_name_plural = 'my class when is in a list!'
\end{minted}
\begin{enumerate}
\item
Le titre:
\begin{itemize}
\item
Soit en modifiant le template de l'administration
\item
Soit en ajoutant l'assignation suivante dans le fichier \texttt{urls.py} : \texttt{admin.site.site\_header\ =\ "SuperBook\ Secret\ Area}.
\end{itemize}
\item
Prefetch
\end{enumerate}
\url{https://hackernoon.com/all-you-need-to-know-about-prefetching-in-django-f9068ebe1e60?gi=7da7b9d3ad64}
\url{https://medium.com/@hakibenita/things-you-must-know-about-django-admin-as-your-app-gets-bigger-6be0b0ee9614}
En gros, le problème de l'admin est que si on fait des requêtes imbriquées, on va flinguer l'application et le chargement de la page.
La solution consiste à utiliser la propriété \texttt{list\_select\_related} de la classe d'Admin, afin d'appliquer une jointure par défaut et et gagner en performances.
\subsection{admin.ModelAdmin}
La classe \texttt{admin.ModelAdmin} que l'on retrouvera principalement dans le fichier \texttt{admin.py} de chaque application contiendra la définition de ce que l'on souhaite faire avec nos données dans l'administration. Cette classe (et sa partie Meta)
\subsection{L'affichage}
Comme l'interface d'administration fonctionne (en trèèèès) gros comme un CRUD auto-généré, on trouve par défaut la possibilité de :
\begin{enumerate}
\item
Créer de nouveaux éléments
\item
Lister les éléments existants
\item
Modifier des éléments existants
\item
Supprimer un élément en particulier.
\end{enumerate}
Les affichages sont donc de deux types: en liste et au détail.
Pour les affichages en liste, le plus simple consiste à jouer sur la propriété \texttt{list\_display}.
Par défaut, la première colonne va accueillir le lien vers le formulaire d'édition.
On peut donc modifier ceci, voire créer de nouveaux liens vers d'autres éléments en construisant des URLs dynamiquement.
Voir aussi comment personnaliser le fil d'Ariane ?
\section{Filtres}
Chaque liste permet de spécifier des filtres spécifiques; ceux-ci peuvent être:
\begin{enumerate}
\item \textbf{Appliqués à la liste} (\texttt{list\_filter})
\item \textbf{Horizontaux} (\texttt{filter\_horizontal})
\item \textbf{Verticaux} (\texttt{filter\_vertical})
\item \textbf{Temporels} (\texttt{date\_hierarchy}
\end{enumerate}
\subsection{Appliqués à la liste}
\subsection{Horizontaux}
\subsection{Verticaux}
\subsection{Temporels}
\section{Permissions}
On l'a dit plus haut, il vaut mieux éviter de proposer un accès à l'administration à vos utilisateurs.
Il est cependant possible de configurer des permissions spécifiques pour certains groupes, en leur autorisant certaines actions de visualisation/ajout/édition ou suppression.
Cela se joue au niveau du \texttt{ModelAdmin}, en implémentant les méthodes suivantes:
\begin{minted}[tabsize=4]{python}
def has_add_permission(self, request):
return True
def has_delete_permission(self, request):
return True
def has_change_permission(self, request):
return True
\end{minted}
On peut accéder aux informations de l'utilisateur actuellement connecté au travers de l'objet \texttt{request.user}.
\section{Relations}
\subsection{Relations 1-N}
Les relations 1-n sont implémentées au travers de formsets (que l'on a normalement déjà décrits plus haut). L'administration permet de les définir d'une manière extrêmement simple, grâce à quelques propriétés.
L'implémentation consiste tout d'abord à définir le comportement du type d'objet référencé (la relation -N), puis à inclure cette définition au niveau du type d'objet référençant (la relation 1-).
\begin{minted}[tabsize=4]{python}
class WishInline(TabularInline):
model = Wish
class Wishlist(admin.ModelAdmin):
...
inlines = [WishInline]
...
\end{minted}
Et voilà : l'administration d'une liste de souhaits (\emph{Wishlist}) pourra directement gérer des relations multiples vers des souhaits.
\subsection{Autocomplétion}
Parler de l'intégration de select2.
\section{Forms}
\section{Présentation}
Parler ici des \texttt{fieldsets} et montrer comment on peut regrouper des champs dans des groupes, ajouter un peu de JavaScript, ...
\section{Actions sur des sélections}
Les actions permettent de partir d'une liste d'éléments, et autorisent
un utilisateur à appliquer une action sur une sélection d'éléments. Par
défaut, il existe déjà une action de \textbf{suppression}.
Les paramètres d'entrée sont :
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
L'instance de classe
\item
La requête entrante
\item
Le queryset correspondant à la sélection.
\end{enumerate}
\begin{minted}[tabsize=4]{python}
def double_quantity(self, request, queryset):
for obj in queryset.all():
obj.field += 1
obj.save()
double_quantity.short_description = "Doubler la quantité des souhaits."
\end{minted}
Et pour informer l'utilisateur de ce qui a été réalisé, on peut aussi
lui passer un petit message:
\begin{minted}[tabsize=4]{python}
if rows_updated = 0:
self.message_user(request, "Aucun élément n'a été impacté.")
else:
self.message_user(request, "{} élément(s) mis à jour".format(rows_updated))
\end{minted}
\section{Documentation}
Nous l'avons dit plus haut, l'administration de Django a également la possibilité de rendre accessible la documentation associée à un modèle de données.
Pour cela, il suffit de suivre les bonnes pratiques, puis \href{https://docs.djangoproject.com/en/stable/ref/contrib/admin/admindocs/}{d'activer la documentation à partir des URLs}: