29 votes

Django : Comment construire un widget de formulaire personnalisé ?

J'ai du mal à trouver de la documentation sur la façon d'écrire un widget personnalisé.

Mes questions sont les suivantes :

  • Si je crée un widget personnalisé, peut-il être utilisé de manière équivalente pour l'interface d'administration ou pour les formulaires normaux ?
  • Si je veux permettre à l'utilisateur de modifier une liste d'éléments, quel widget dois-je sous-classer ? Quelles méthodes du widget dois-je remplacer/implémenter ?
  • Quelle méthode de widget est responsable du retour de l'entrée de l'utilisateur vers le modèle de données ?

Merci.

30voto

AndiDog Points 28417

Vous avez raison de dire que Django ne fournit pas de documentation sur ce sujet spécifique. Je vous conseille de regarder les widgets intégrés dans la section django.forms.widgets (Je ferai référence aux classes de ce module ci-dessous).

Si je crée un widget personnalisé, peut-il être utilisé de manière équivalente pour l'interface d'administration ou pour les formulaires normaux ?

Admin remplace certains widgets (voir django.contrib.admin.options.FORMFIELD_FOR_DBFIELD_DEFAULTS ). Vous pouvez probablement sous-classer ModelAdmin et changer le formfield_overrides mais je n'ai jamais rien fait avec l'attribut ModelAdmin donc je ne peux pas aider ici...

Si je veux permettre à l'utilisateur de modifier une liste d'éléments, quel widget dois-je sous-classer ? Quelles méthodes du widget dois-je remplacer/implémenter ?

Votre widget n'a probablement rien en commun avec les widgets par défaut (avec Select s'il y en a !). Sous-classe de Widget et si vous trouvez un modèle commun avec les buildins, vous pouvez toujours le changer plus tard.

Mettez en œuvre les méthodes suivantes :

  • render(self, name, value, attrs=None, renderer=None)

    Vérifiez Input.render pour un exemple simple. Il prend également en charge les attributs définis par l'utilisateur qui sont inclus dans le HTML. Vous pouvez également ajouter des attributs "id", voir MultipleHiddenInput.render sur la façon de le faire. N'oubliez pas d'utiliser mark_safe lors de la sortie directe du HTML. Si vous avez un widget plutôt complexe, vous pouvez utiliser le rendu par modèle ( exemple ).

  • _has_changed(self, initial, data)

    En option. Utilisé dans l'administration pour enregistrer des messages sur ce qui a été modifié.

Quelle méthode de widget est responsable du retour de l'entrée de l'utilisateur vers le modèle de données ?

Cela n'a rien à voir avec le widget - Django ne peut pas savoir quel widget a été utilisé dans une requête antérieure. Il ne peut utiliser que les données envoyées par le formulaire (POST). Par conséquent, la méthode du champ Field.to_python est utilisé pour convertir les données d'entrée au type de données Python (peut lever le paramètre ValidationError si l'entrée n'est pas valide).

25voto

Wtower Points 135

Django <1.11

En plus des autres réponses, voici un petit échantillon de code d'un widget personnalisé :

widgets.py :

from django.forms.widgets import Widget
from django.template import loader
from django.utils.safestring import mark_safe

class MyWidget(Widget):
    template_name = 'myapp/my_widget.html'

    def get_context(self, name, value, attrs=None):
        return {'widget': {
            'name': name,
            'value': value,
        }}

    def render(self, name, value, attrs=None):
        context = self.get_context(name, value, attrs)
        template = loader.get_template(self.template_name).render(context)
        return mark_safe(template)

my_widget.html :

<textarea id="mywidget-{{ widget.name }}" name="{{ widget.name }}">
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

Django 1.11

Les widgets sont désormais rendus à l'aide de l'élément API de rendu de formulaires .

4voto

Ghopper21 Points 2709

NOTE : Il y a trois questions ici. Pour les deux premières questions, voir la réponse plus complète d'AndiDog. Je ne réponds ici qu'à la troisième question :

Q. Quelle méthode de widget est responsable du retour de l'entrée de l'utilisateur vers le modèle de données ?

A. Le site value_from_datadict c'est en quelque sorte l'inverse de la méthode du widget. render méthode. Cette méthode est probablement celle à laquelle la documentation de Django sur les widgets fait référence lorsqu'elle dit "Le widget gère le rendu du HTML, et l'extraction des données d'un dictionnaire GET/POST qui correspond au widget". Il n'y a rien de plus sur ce point dans les docs, mais vous pouvez voir comment cela fonctionne à partir du code des widgets intégrés.

3voto

Al Conrad Points 1

En général, je commence par hériter d'un des widgets existants, j'ajoute une nouvelle propriété souhaitée, puis je modifie une méthode de rendu. Voici un exemple de widget de sélection filtrable que j'ai implémenté. Le filtrage est effectué via jquery mobile.

class FilterableSelectWidget(forms.Select):
    def __init__(self, attrs=None, choices=()):
        super(FilterableSelectWidget, self).__init__(attrs, choices)
        # choices can be any iterable, but we may need to render this widget
        # multiple times. Thus, collapse it into a list so it can be consumed
        # more than once.
        self._data_filter = {}

    @property
    def data_filter(self):
        return self._data_filter

    @data_filter.setter
    def data_filter(self, attr_dict):
        self._data_filter.update(attr_dict)

    def render_option(self, selected_choices, option_value, option_label):
        option_value = force_text(option_value)
        if option_value in selected_choices:
            selected_html = mark_safe(' selected="selected"')
            if not self.allow_multiple_selected:
                # Only allow for a single selection.
                selected_choices.remove(option_value)
        else:
            selected_html = ''
        # use self.data_filter
        filtertext = self.data_filter.get(option_value)
        data_filtertext = 'data-filtertext="{filtertext}"'.\
            format(filtertext=filtertext) if filtertext else ''
        return format_html('<option value="{0}"{1} {3}>{2}</option>',
                           option_value,
                           selected_html,
                           force_text(option_label),
                           mark_safe(data_filtertext))

Ensuite, dans les vues où je crée un formulaire, je vais définir le data_filter pour le champ.

        some_form.fields["some_field"] = \
            forms.ChoiceField(choices=choices,
                              widget=FilterableSelectWidget)
        some_form.fields["some_field"].widget.data_filter = \
            data_filter

2voto

Peter Shannon Points 41

La documentation sur le site de Django n'aide pas du tout à ce sujet. Il s'agit de suggestions sur la personnalisation des widgets, aquí , rompre l'utilisation de form.as_p() ce qui met alors en péril la valeur des formulaires tels qu'ils sont présentés dans Django, c'est-à-dire : un assemblage de widgets.

Les solutions que j'ai préférées sont floppyforms . Il facilite la définition de widgets à l'aide de modèles et constitue un remplacement (presque) transparent du module de formulaires propre à Django. Il dispose d'une excellente documentation et est facile à prendre en main.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X