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="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://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="http://static.example.com/pretty.css" media="screen" rel="stylesheet">
<link href="http://static.example.com/lo_res.css" media="tv,projector" rel="stylesheet">
<link href="http://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="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<link href="http://static.example.com/fancy.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
<script src="http://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="http://static.example.com/fancy.css" media="all" rel="stylesheet">
<script src="http://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 'http://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", "http://othersite.com/actions.js"]
...

>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" media="all" rel="stylesheet">
<script src="http://uploads.example.com/animations.js"></script>
<script src="http://othersite.com/actions.js"></script>

Mai si STATIC_URL vaut 'http://static.example.com/':

>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://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="http://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="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>

>>> print(w.media["css"])
<link href="http://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="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
<script src="http://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="http://static.example.com/jQuery.js"></script>
<script src="http://static.example.com/calendar.js"></script>
<script src="http://static.example.com/time.js"></script>
<script src="http://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="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
<script src="http://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="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<link href="http://static.example.com/layout.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
<script src="http://static.example.com/whizbang.js"></script>
Back to Top