Fichiers annexes de formulaire (classe Media
)¶
L’affichage d’un formulaire Web attractif et ergonomique exige plus que du code HTML simple, il nécessite également des feuilles de style CSS, et si vous voulez utiliser des composants plaisants, il sera peut-être nécessaire d’inclure aussi du code JavaScript sur chaque page. La combinaison exacte de CSS et de JavaScript nécessaire pour une page donnée dépend des composants de formulaire employés sur cette page.
C’est là où la définition de fichiers annexes intervient. Django permet d’associer différents fichiers comme des feuilles de style et des scripts à des formulaires et des composants qui ont besoin de ces fichiers. Par exemple, si vous voulez utiliser un calendrier pour afficher les champs date, vous pouvez définir un composant Calendrier
personnalisé. Ce composant peut ensuite être associé à des fichiers CSS et JavaScript requis pour afficher le calendrier. Quand le composant Calendrier
est utilisé dans un formulaire, Django est capable d’identifier les fichiers CSS et JavaScript nécessaires et de fournir la liste des noms de fichiers au formulaire de manière à ce qu’ils puissent être inclus dans la page Web.
Fichiers annexes dans l’administration de Django
L’application d’administration de Django définit un certain nombre de composants personnalisés pour les calendriers, les sélections filtrées, etc. Ces composants définissent des exigences de fichiers annexes et l’administration de Django utilise ces composants personnalisés au lieu des composants par défaut de Django. Les gabarits de l’administration n’incluent que les fichiers nécessaires pour l’affichage des composants de la page en cours.
Si vous appréciez les composants utilisés par le site d’administration de Django, vous pouvez librement les utiliser dans votre propre application ! Ils se trouvent dans django.contrib.admin.widgets
.
Quelle bibliothèque JavaScript ?
Il existe plusieurs bibliothèques JavaScript et beaucoup d’entre elles contiennent des composants utiles pour améliorer l’interface d’une application (comme par exemple des composants de calendrier). Django a délibérément évité de choisir parmi ces bibliothèques JavaScript. Chacune a ses forces et ses faiblesses, utilisez donc celle qui correspond à vos besoins. Django est capable d’intégrer n’importe quelle bibliothèque JavaScript.
Fichiers annexes définis statiquement¶
La façon la plus simple de définir des fichiers annexes est sous forme de définition statique. Avec cette méthode, la déclaration se fait dans une classe Media
interne. Les propriétés de la classe interne définissent les exigences.
Voici un exemple :
from django import forms
class CalendarWidget(forms.TextInput):
class Media:
css = {
"all": ["pretty.css"],
}
js = ["animations.js", "actions.js"]
Ce code définit un composant CalendarWidget
, qui est basé sur TextInput
. Chaque fois que CalendarWidget
sera utilisé dans un formulaire, ce dernier sera amené à inclure le fichier CSS pretty.css
et les fichiers JavaScript animations.js
et actions.js
.
Cette définition statique est convertie au moment de l’exécution en une propriété de composant nommée media
. La liste des fichiers annexes d’une instance de composant CalendarWidget
peut être obtenue par cette propriété :
>>> w = CalendarWidget()
>>> print(w.media)
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
Voici une liste de toutes les options possibles de Media
. Il n’y a pas d’option obligatoire.
css
¶
Un dictionnaire décrivant les fichiers CSS requis par les différentes formes de support d’affichage.
Les valeurs du dictionnaire doivent être composées de tuples/listes de noms de fichiers. Consultez la section sur les chemins pour plus de détails sur la manière de définir les chemins de ces fichiers.
Les clés du dictionnaire correspondent aux types de support d’affichage. Ce sont les mêmes types que les fichiers CSS acceptent dans les déclarations « media » : “all”, “aural”, “braille”, “embossed”, “handheld”, “print”, “projection”, “screen”, “tty” and “tv”. Si vous avez besoin de feuilles de style différentes en fonction de types de support différents, indiquez des listes de fichiers CSS correspondant à chacun de ces types. L’exemple suivant fournit deux options CSS, une pour l’affichage à l’écran et l’autre pour l’impression :
class Media:
css = {
"screen": ["pretty.css"],
"print": ["newspaper.css"],
}
Si un groupe de fichiers CSS convient à plusieurs types de support d’affichage, la clé de dictionnaire peut être une liste de types de support d’affichage séparés par des virgules. Dans l’exemple suivant, les télévisions et les projecteurs ont les mêmes exigences en terme de support :
class Media:
css = {
"screen": ["pretty.css"],
"tv,projector": ["lo_res.css"],
"print": ["newspaper.css"],
}
Si cette dernière définition de CSS devait être affichée, cela produirait le code HTML suivant :
<link href="https://static.example.com/pretty.css" media="screen" rel="stylesheet">
<link href="https://static.example.com/lo_res.css" media="tv,projector" rel="stylesheet">
<link href="https://static.example.com/newspaper.css" media="print" rel="stylesheet">
js
¶
Un tuple décrivant les fichiers JavaScript nécessaires. Consultez la section sur les chemins pour plus de détails sur la manière de définir les chemins de ces fichiers.
extend
¶
Une valeur booléenne définissant le comportement de l’héritage des déclarations Media
.
Par défaut, tout objet utilisant une définition statique de Media
hérite de tous les fichiers annexes associés au composant parent, et ceci quelle que soit la manière dont le parent définit ses propres exigences. Par exemple, si nous devions améliorer notre composant Calendar
basique de l’exemple ci-dessus :
>>> class FancyCalendarWidget(CalendarWidget):
... class Media:
... css = {
... "all": ["fancy.css"],
... }
... js = ["whizbang.js"]
...
>>> w = FancyCalendarWidget()
>>> print(w.media)
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<link href="https://static.example.com/fancy.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://static.example.com/whizbang.js"></script>
Le composant FancyCalendar
hérite de tous les fichiers annexes de son composant parent. Si vous ne souhaitez pas que Media
soit hérité de cette façon, ajoutez une déclaration extend=False
à la déclaration de Media
:
>>> class FancyCalendarWidget(CalendarWidget):
... class Media:
... extend = False
... css = {
... "all": ["fancy.css"],
... }
... js = ["whizbang.js"]
...
>>> w = FancyCalendarWidget()
>>> print(w.media)
<link href="https://static.example.com/fancy.css" media="all" rel="stylesheet">
<script src="https://static.example.com/whizbang.js"></script>
Si vous avez besoin de pouvoir contrôler encore plus l’héritage, définissez vos fichiers annexes en utilisant une propriété dynamique. Les propriétés dynamiques vous donnent un contrôle total sur les fichiers hérités et ceux qui ne le seront pas.
Media
comme propriété dynamique¶
Si vous avez besoin d’effectuer certaines manipulations encore plus sophistiquées sur les fichiers annexes requis, vous pouvez définir directement la propriété media
. Cela peut être réalisé en définissant une propriété du composant qui renvoie une instance de forms.Media
. le constructeur de forms.Media
accepte les paramètres nommés css
and js
dans le même format que celui qui est utilisé pour les définitions statiques de fichiers annexes.
Par exemple, la définition statique de notre composant de calendrier pourrait tout aussi bien être réalisée de manière dynamique :
class CalendarWidget(forms.TextInput):
@property
def media(self):
return forms.Media(
css={"all": ["pretty.css"]}, js=["animations.js", "actions.js"]
)
Consultez la section sur les objets Media pour plus de détails sur la manière de construire des valeurs de retour pour les propriétés dynamiques media
.
Chemins dans les définitions de fichiers annexes¶
Chemins sous forme de chaînes¶
Les chemins sous forme de chaînes utilisés pour définir les fichiers annexes peuvent être relatifs ou absolus. Si un chemin commence par /
, http://
ou https://
, il sera interprété comme un chemin absolu et laissé tel quel. Tous les autres chemins seront préfixés par la valeur appropriée. Si l’application django.contrib.staticfiles
est installée, elle sera utilisée pour servir ces fichiers.
Que l’application django.contrib.staticfiles
soit utilisée ou non, les réglages STATIC_URL
et STATIC_ROOT
sont obligatoires pour produire une page Web complète.
Pour trouver le bon préfixe à utiliser, Django contrôle si le réglage STATIC_URL
est différent de None
et se rabat sur MEDIA_URL
en cas de besoin. Par exemple, si le réglage MEDIA_URL
de votre site est 'https://uploads.example.com/'
et que STATIC_URL
vaut None
:
>>> from django import forms
>>> class CalendarWidget(forms.TextInput):
... class Media:
... css = {
... "all": ["/css/pretty.css"],
... }
... js = ["animations.js", "https://othersite.com/actions.js"]
...
>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" media="all" rel="stylesheet">
<script src="https://uploads.example.com/animations.js"></script>
<script src="https://othersite.com/actions.js"></script>
Mai si STATIC_URL
vaut 'https://static.example.com/'
:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://othersite.com/actions.js"></script>
Ou si staticfiles
est configuré avec le stockage ManifestStaticFilesStorage
:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.27e20196a850.js"></script>
<script src="https://othersite.com/actions.js"></script>
Chemins sous forme d’objets¶
Il est aussi possible de définir les chemins des fichiers annexes comme objets hachables implémentant une méthode __html__()
. Cette méthode est typiquement ajoutée au moyen du décorateur html_safe()
. L’objet est responsable de produire le contenu HTML complet des balises <script>
ou <link>
:
>>> from django import forms
>>> from django.utils.html import html_safe
>>>
>>> @html_safe
... class JSPath:
... def __str__(self):
... return '<script src="https://example.org/asset.js" defer>'
...
>>> class SomeWidget(forms.TextInput):
... class Media:
... js = [JSPath()]
...
Objets Media
¶
Lorsque vous interrogez l’attribut media
d’un composant ou d’un formulaire, la valeur renvoyée est un objet forms.Media
. Comme nous l’avons déjà vu, la représentation textuelle d’un objet Media
contient le code HTML nécessaire pour inclure les fichiers concernés dans le bloc <head>
de votre page HTML.
Cependant, les objets Media
ont certaines autres propriétés intéressantes.
Sous-ensembles de fichiers annexes¶
Si vous ne souhaitez obtenir que les fichiers d’un type particulier, vous pouvez utiliser l’opérateur d’indice pour filtrer le support qui vous intéresse. Par exemple :
>>> w = CalendarWidget()
>>> print(w.media)
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
>>> print(w.media["css"])
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
Lorsque vous employez l’opérateur d’indice, la valeur renvoyée est un nouvel objet Media
, mais qui ne contient que les fichiers qui vous intéressent.
Combinaison d’objets Media
¶
Les objets Media
peuvent aussi être fusionnés. Lorsque l’on additionne deux objets Media
, l’objet Media
résultant contient l’union des fichiers annexes contenus dans les deux objets :
>>> from django import forms
>>> class CalendarWidget(forms.TextInput):
... class Media:
... css = {
... "all": ["pretty.css"],
... }
... js = ["animations.js", "actions.js"]
...
>>> class OtherWidget(forms.TextInput):
... class Media:
... js = ["whizbang.js"]
...
>>> w1 = CalendarWidget()
>>> w2 = OtherWidget()
>>> print(w1.media + w2.media)
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://static.example.com/whizbang.js"></script>
Ordre des fichiers annexes¶
L’ordre dans lequel les fichiers annexes sont insérés dans le DOM est souvent important. Par exemple, vous pourriez avoir un script dépendant de jQuery. Ainsi, la combinaison d’objets Media
tente de conserver l’ordre relatif dans lequel les fichiers annexes sont définis dans chaque classe Media
.
Par exemple :
>>> from django import forms
>>> class CalendarWidget(forms.TextInput):
... class Media:
... js = ["jQuery.js", "calendar.js", "noConflict.js"]
...
>>> class TimeWidget(forms.TextInput):
... class Media:
... js = ["jQuery.js", "time.js", "noConflict.js"]
...
>>> w1 = CalendarWidget()
>>> w2 = TimeWidget()
>>> print(w1.media + w2.media)
<script src="https://static.example.com/jQuery.js"></script>
<script src="https://static.example.com/calendar.js"></script>
<script src="https://static.example.com/time.js"></script>
<script src="https://static.example.com/noConflict.js"></script>
La combinaison d’objets Media
ayant des fichiers annexes dans un ordre conflictuel produit un avertissement MediaOrderConflictWarning
.
Media
pour les formulaires¶
Les composants ne sont pas les seuls objets qui peuvent posséder des définitions media
, les formulaires peuvent aussi en définir. Les règles concernant les définitions media
des formulaires sont les mêmes que pour les composants : les déclarations peuvent être statiques ou dynamiques ; les règles des chemins et de l’héritage de ces déclarations sont également identiques.
Que vous définissiez une déclaration media
ou non, tous les objets Form
possèdent une propriété media
. La valeur par défaut de cette propriété est le résultat de la fusion de toutes les définitions media
de tous les composants du formulaire :
>>> from django import forms
>>> class ContactForm(forms.Form):
... date = DateField(widget=CalendarWidget)
... name = CharField(max_length=40, widget=OtherWidget)
...
>>> f = ContactForm()
>>> f.media
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://static.example.com/whizbang.js"></script>
Si vous souhaitez associer des fichiers annexes supplémentaires à un formulaire, par exemple des règles CSS pour la mise en forme du formulaire, ajoutez une déclaration Media
au formulaire :
>>> class ContactForm(forms.Form):
... date = DateField(widget=CalendarWidget)
... name = CharField(max_length=40, widget=OtherWidget)
... class Media:
... css = {
... "all": ["layout.css"],
... }
...
>>> f = ContactForm()
>>> f.media
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<link href="https://static.example.com/layout.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://static.example.com/whizbang.js"></script>