gwift-book/chapters/templates.tex

259 lines
8.9 KiB
TeX
Executable File

\chapter{Templates}
Avant de commencer à interagir avec nos données au travers de listes, formulaires et d'interfaces sophistiquées, quelques mots sur les templates: il s'agit en fait de \textbf{squelettes} de présentation, recevant en entrée un dictionnaire contenant des clés-valeurs et ayant pour but de les afficher selon le format que vous définirez.
Un squelette de page HTML basique ressemble à ceci:
\begin{minted}{html}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title></title>
</head>
<body>
<p>Hello world!</p>
</body>
</html>
\end{minted}
Notre première vue permettra de récupérer la liste des objets de type \texttt{Wishlist} que nous avons définis dans le fichier \texttt{wish/models.py}.
Supposez que cette liste soit accessible \textit{via} la clé \texttt{wishlists} d'un dictionnaire passé au template.
Cette liste devient dès lors accessible grâce aux tags \texttt{\{\% for wishlist in wishlists \%\}}.
A chaque tour de boucle, nous pourrons directement accéder à la variable \texttt{\{\{ wishlist \}\}}.
De même, il sera possible d'accéder aux propriétés de cette objet de la même manière: \texttt{\{\{ wishlist.id \}\}}, \texttt{\{\{ wishlist.description \}\}}, \ldots et d'ainsi respecter la mise en page que nous souhaitons.
En reprenant l'exemple de la page HTML définie ci-dessus, nous pouvons l'agrémenter de la manière suivante:
\begin{minted}{html}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title></title>
</head>
<body>
<p>Mes listes de souhaits</p>
<ul>
{% for wishlist in wishlists %}
<li>{{ wishlist.name }}: {{ wishlist.description }}</li>
{% endfor %}
</ul>
</body>
</html>
\end{minted}
\begin{figure}[H]
\centering
\scalebox{1.0}{\includegraphics[max size={\textwidth}{\textheight}]{images/html/my-first-wishlists.png}}
\end{figure}
\section{Entête, héritage}
Plutôt que de réécrire à chaque fois le même entête, nous pouvons nous simplifier la vie en implémentant un héritage au niveau des templates.
Pour cela, il suffit de définir des blocs de contenu, et d'\emph{étendre} une page de base, puis de surcharger ces mêmes blocs.
Par exemple, si on repart de notre page de base ci-dessus, on va y définir deux blocs réutilisables:
\begin{minted}{html}
<!-- templates/base.html -->
{% load static %}<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{% block title %}Gwift{% endblock %}</title> <1>
</head>
<body>
{% block body %}<p>Hello world!</p>{% endblock %} <2>
</body>
</html>
\end{minted}
Nous avons à présent un bloc \texttt{title} et un bloc \texttt{body}, qui peuvent être surchargés dès qu'une page se définit comme extension de notre page \texttt{base.html}:
\begin{minted}{html}
<!-- templates/wishlist/wishlist_list.html -->
{% extends "base.html" %}
{% block title %}{{ block.super }} - Listes de souhaits{% endblock %} <2>
{% block body %}
<p>Mes listes de souhaits</p>
<ul>
{% for wishlist in wishlists %}
<li>{{ wishlist.name }}: {{ wishlist.description }}</li>
{% endfor %}
</ul>
{% endblock% %}
\end{minted}
\section{Extensions}
Attention: il est primordial que les extensions/tags ne fassent aucune requête en base de données.
Il est vraiment important que les données (re)travaillées soient disponibles dès que l'appel sera réalisé, sans quoi cela dégradera énormément les performances à l'affichage de la page.
Chronologie T -> T+1 -> T+2
Données affichées: X -> X, Y -> X,Y,Z
Et ça sera dégueulasse.
\subsection{Extensions natives}
Django vient avec un ensemble de *tags* ou *template tags*.
On a vu la boucle \texttt{for} ci-dessus, mais il existe \href{https://docs.djangoproject.com/fr/1.9/ref/templates/builtins/}{beaucoup d'autres tags nativement présents}.
Les principaux sont par exemple:
\begin{itemize}
\item Les conditions, qui permettent de vérifier un contexte et de n'afficher le contenu d'un bloc que si la condition est vérifiée
\begin{itemize}
\item \texttt{if} \ldots \texttt{elif} \ldots \texttt{endif}
\end{itemize}
\item Les opérateurs de comparaisons:
\begin{itemize}
\item \texttt{<}
\item \texttt{>}
\item \texttt{==}
\item \texttt{in}
\item \texttt{not in}
\end{itemize}
\end{itemize}
Regroupements avec le tag `{% regroup ... by ... as ... %}`.
* `{% url %}` pour construire facilement une URL à partir de son nom
* `urlize` qui permet de remplacer des URLs trouvées dans un champ de type CharField ou TextField par un lien cliquable.
* ...
Chacune de ces fonctions peut être utilisée autant au niveau des templates qu'au niveau du code.
Il suffit d'aller les chercher dans le package \texttt{django.template.defaultfilters}.
Par exemple:
\begin{minted}{python}
from django.db import models
from django.template.defaultfilters import urlize
class Suggestion(models.Model):
"""Représentation des suggestions.
"""
subject = models.TextField(verbose_name="Sujet")
def urlized_subject(self):
"""
Voir https://docs.djangoproject.com/fr/3.0/howto/custom-template-tags/
"""
return urlize(self.subject, autoescape=True)
\end{minted}
\subsubsection{Regroup by}
\subsubsection{Inclusion}
\subsection{Extensions non-natives}
En plus des quelques tags survolés ci-dessus, il est également possible de construire ses propres tags. La structure est un peu bizarre, car elle consiste à ajouter un paquet dans une de vos applications, à y définir un nouveau module et à y définir un ensemble de fonctions.
Chacune de ces fonctions correspondra à un tag appelable depuis vos templates.
Il existe trois types de tags *non-builtins*:
\begin{enumerate}
\item *Les filtres* - on peut les appeler grâce au *pipe* `|` directement après une valeur dans le template.
\item *Les tags simples* - ils peuvent prendre une valeur ou plusieurs en paramètre et retourne une nouvelle valeur. Pour les appeler, c'est *via* les tags \texttt{\{\% nom\_de\_la\_fonction param1 param2 \ldots \%\}}.
\item *Les tags d'inclusion*: ils retournent un contexte (ie. un dictionnaire), qui est ensuite passé à un nouveau template. Type \texttt{\{\% include '...' ... \%\}}.
\end{enumerate}
Pour l'implémentation:
1. On prend l'application `wish` et on y ajoute un répertoire `templatetags`, ainsi qu'un fichier `\_\_init\_\_.py`.
2. Dans ce nouveau paquet, on ajoute un nouveau module que l'on va appeler `tools.py`
3. Dans ce module, pour avoir un aperçu des possibilités, on va définir trois fonctions (une pour chaque type de tags possible).
\subsubsection{Filtres}
\begin{minted}{python}
# wish/tools.py
from django import template
from wish.models import Wishlist
register = template.Library()
@register.filter(is_safe=True)
def add_xx(value):
return '%sxx' % value
\end{minted}
\subsubsection{Tags simples}
Un \textbf{tag simple} reçoit une valeur ou un objet en entrée et génère une valeur de retour simple (un objet, un type natif, ...).
\begin{minted}{python}
# wish/tools.py
from django import template
from wish.models import Wishlist
register = template.Library()
@register.simple_tag
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
\end{minted}
\subsubsection{Tags d'inclusion}
Les \textit{tags d'inclusion} sont des tags associés à un (morceau de) template.
C'est-à-dire qu'une fois qu'ils auront réalisés le traitement qui leur est demandé, il généreront un canevas HTML qui sera \textbf{inclus} à l'endroit où le tag aura été appelé.
\begin{minted}{python}
# wish/tools.py
from django import template
from wish.models import Wishlist
register = template.Library()
@register.inclusion_tag('wish/templatetags/wishlists_list.html')
def wishlists_list():
return { 'list': Wishlist.objects.all() }
\end{minted}
\subsection{Pagination}
\section{Structure et configuration}
Il est conseillé que les templates respectent la structure de vos différentes applications, mais dans un répertoire à part.
Par convention, nous les placerons dans un répertoire \texttt{templates}.
La hiérarchie des fichiers devient alors celle-ci:
Par défaut, Django cherchera les templates dans les répertoirer d'installation.
Vous devrez vous éditer le fichier \texttt{gwift/settings.py} et ajouter, dans la variable \texttt{TEMPLATES}, la clé \texttt{DIRS} de la manière suivante:
\begin{minted}{python}
TEMPLATES = [
{
...
'DIRS': [ 'templates' ],
...
},
]
\end{minted}
\subsection{Fichiers statiques}
\section{Dynamisme - HTMX}