Mots-clé : django-form

Django >= 1.11 : faire des widgets de formulaires sur mesure

Les « widgets » sont les composants affichés dans les formulaires qui permettent la saisie des informations (combo, checkboxes, etc.).
A partir de la version 1.11, les widgets ne sont plus du tout construits comme sur les versions précédentes.
Ce qu’il faut retenir : avant, c’était le développeur qui pouvait surcharger les widgets, j’ai fait un petit article ici.
Ce qu’il faut retenir sur la nouvelle gestion des widgets : le graphiste peut avoir accès à l’habillage, et configurer sur mesure l’affichage des widgets. C’est un changement très important du point de vue de travail en équipe, surtout si vous avez un graphiste / front end dans votre équipe.
La documentation est très fournie, jetez un coup d’oeil par là.
Mais son inconvénient, c’est qu’elle est, justement, très fournie… voici donc un résumé.

« surcharger » les widgets

  • Dans settings.py, rajouter dans les applications installées (INSTALLED_APPS) l’application 'django.forms'
  • Toujours dans settings.py, dans la directive TEMPLATES, ajoutez la directive 'DIRS' s’il elle n’y est pas déjà, et précisez dans un tableau les répertoires supplémentaires, par exemple pour moi j’ai :
    'DIRS': [os.path.join(BASE_DIR, 'templates')],
  • Surchargez le composant qui vous intéresse en déclarant le template, exemple :
    class MonCheckboxSelectMultiple(CheckboxSelectMultiple):
        template_name = 'include/widget/checkbox_select.html'
  • Organisez vous dans votre dossier templates à l’identique de la déclaration. Pour remprendre mon exemple j’ai un fichier templates/include/widget/checkbox_select.html dans lequel j’ai copié collé le code source du template Django, puis je l’ai modifié pour qu’il convienne à mes besoins.

Allez chercher les templates, et profitez-en pour lire le code source des formulaires, vous verrez il est simple, et vous verrez comment les templates sont déclarés : Python36/Lib/site-packages/django/forms.

Django : mémo des formulaires « sur mesure »

Voici un mémo des « étapes » à remplir dans l’ordre pour faire un formulaire sur mesure en Django « dans l’ordre »:

  • Créer un formulaire. Classique. Faire un ModelForm et utiliser la classe Meta pour déclarer le modèle via model, et déclarer les champs via fields.
  • Construire ces champs du modèle dans le formulaire. Attention, ne pas confondre le types du champ (qui est en base = le champ du modèle), c’est ce que vous créez, et le widget dans lequel ce champ sera rendu (cf mon exemple dans l’article précédent).
  • Construire la vue, dans le cas update du principe CRUD, j’hérite de generic.UpdateView
  • Dans le formulaire d’origine, créer la fonction dit si le formulaire est valide ou pas : def is_valid(self)
  • Dans la vue, après que le formulaire ait été validé, écrire la fonction def form_valid(self, form) qui est appelée lorsque le formulaire est valide, et utiliser les données nettoyées du formulaire form.cleaned_data pour enregistrer ce que l’on veut
  • Pré-remplir les champs du formulaire, y compris les champs « sur mesure » : c’est dans la vue, via def get_initial(self)

Ouf ! Une fois que tout ça est implémenté, Django construit la vue en lecture (GET) selon un chemin qui ressemble en gros à ça :

  • vue -> form_class
  • forme -> class Meta -> model + fields
  • forme -> constructeur __init__ (ajout champs sur mesure)
  • vue -> get_initial (remplir les valeurs de tous les champs du formulaire)

Django construit la vue en écriture (POST) selon un chemin qui ressemble en gros à ça :

  • vue -> form_class
  • forme -> class Meta -> model + fields
  • forme -> constructeur __init__ (ajout champs sur mesure)
  • forme -> def is_valid(self)
  • forme -> def clean_xx(self) (code qui valide/ou pas le champ xx)
  • forme -> def clean(self) (code qui valide/ou pas tous les champs)
  • vue -> def form_valid(self, form) (sauver ici avec form.cleaned_data)

Alors oui je sais c’est compliqué. Mais quand on y réfléchit bien, sur le modèle MVC, on ne peut pas faire autrement, et c’est le mieux possible. Et encore je n’ai pas parlé de la routine « save() » qui est dans le formulaire : elle est automatiquement appelée lors is_valid() renvoie true, et dans le cas d’un ModelForm elle sauvegarde le formulaire.

Django : les formulaires « sur mesure »

Voici les étapes lorsqu’on sort des sentiers battus et qu’on veut créer un formulaire sur mesure, ainsi qu’une vue sur mesure.

J’ai toujours eu besoin d’un ModelForm : je garde les champs que je veux, voici un code basique qui crée le champ label, avec toutes les traductions nécessaires :

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = ('label', )
    e = {'required': _(u'This field is required'),
        'invalid': _(u'This field contains invalid data')}

    a = u'{}<span class="important-field">*</span>'.format(_(u'Question:'))
    label = forms.CharField(
        label=a, localize=True, required=True,
        widget=widgets.TextInput(attrs={
            'title': a,
            'class': 'form-control form-control'}),
        error_messages=e)

Ensuite pour cette classe, je crée un champ dynamiquement via le constructeur :

    def __init__(self, *args, **kwargs):
        super(QuestionForm, self).__init__(*args, **kwargs)
        # -----------------------------------------------------
        # Création dynamique de champs custom
        # self.fields est de type OrderedDict(), qui se base
        # sur l'ordre d'ajout des éléments. Alors si on veut
        # un autre ordre, pas d'autre choix que de reconstruire
        # le dictionnaire en y appliquant l'ordre qu'on veut :
        #
        # création dynamique de l'image :
        a = _(u'Picture')
        photo = ImageField(
            label=a, allow_empty_file=True, required=False,
            widget=forms.FileInput(attrs={
                'title': a,
                'placeholder': _(u'picture'),
                'class': 'form-control',
                'accept': "image/*", }),
            error_messages=e)
        new_fields = OrderedDict([
            ('label', self.fields['label']),
            ('photo', photo),
        ])
        self.fields = new_fields

Django : créer un checkbox multiple choice avec bootstrap

Je me sers de cet habillage ici.

C’est, en gros, une « surcharge » de bootstrap avec quelques composants en plus.

Seulement j’aurais aimé avoir une sélection de plusieurs choix = cases à cocher qui soit « compatible » avec cet habillage.
Je n’aurais jamais cru que c’était aussi simple.
En fait Django a généralisé plein d’affichages HTML à tel point qu’il suffit de faire du pas à pas et de regarder ce qu’il fait pour comprendre.
Même le rendu par « item » de chaque « Renderer » est surchargeable. Donc voici le code qui prend… 10 lignes, et qui réhabille totalement le rendu des cases à cocher :

from django.forms import CheckboxSelectMultiple
from django.forms.widgets import ChoiceFieldRenderer, \
    CheckboxChoiceInput

class BootstrapChoiceFieldRenderer(ChoiceFieldRenderer):
    choice_input_class = CheckboxChoiceInput
    outer_html = '<div{id_attr}>{content}</div>'
    inner_html = '<div class="checkbox">'
                 '{choice_value}{sub_widgets}'
                 '</div>'

class CheckboxSelectMultipleBootstrap(CheckboxSelectMultiple):
    renderer = BootstrapChoiceFieldRenderer