web-dev-qa-db-fra.com

Django: Comment construire un widget de formulaire personnalisé?

J'ai de la difficulté à trouver de la documentation sur la rédaction d'un widget personnalisé.

Mes questions sont:

  • Si je construis 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 chargée de passer de l'entrée de l'utilisateur au modèle de données?

Merci.

51
Nick Heiner

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

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

L'administrateur remplace certains widgets (voir Django.contrib.admin.options.FORMFIELD_FOR_DBFIELD_DEFAULTS ). Vous pouvez probablement sous-classer ModelAdmin et modifier l'attribut _formfield_overrides_, mais je n'ai jamais rien fait avec ModelAdmin et je ne peux donc pas vous 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 le cas échéant ?!). Sous-classe de Widget et si vous trouvez un modèle commun avec des fonctions intégrées, vous pourrez toujours le changer plus tard.

Implémentez les méthodes suivantes:

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

    Consultez _Input.render_ pour un exemple simple. Il prend également en charge les attributs définis par l'utilisateur inclus dans le code HTML. Vous pouvez également vouloir ajouter des attributs "id", voir _MultipleHiddenInput.render_ pour savoir comment faire. N'oubliez pas d'utiliser _mark_safe_ lors de la sortie directe de HTML. Si vous avez un widget plutôt complexe, vous pouvez utiliser le rendu de modèle ( exemple ).

  • _has_changed(self, initial, data)

    Optionnel. Utilisé dans admin pour consigner les messages concernant les modifications.

Quelle méthode de widget est chargée de passer de l'entrée de l'utilisateur au 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 précédente. Il ne peut utiliser que les données du formulaire (POST) envoyées à partir du formulaire. Par conséquent, la méthode de champ _Field.to_python_ est utilisée pour convertir les entrées en type de données Python (peut générer ValidationError si l'entrée n'est pas valide).

47
AndiDog

Django <1.11

Outre les autres réponses, il s'agit d'un petit exemple 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 maintenant rendus à l'aide de l'API de rendu form .

21
Wtower

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

Q. Quelle méthode de widget est responsable de la conversion de l'entrée de l'utilisateur dans le modèle de données?

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

5
Ghopper21

En général, je commence par hériter de l'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 se fait 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 filtre 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
3
Al Conrad

La documentation sur le site de Django n'aide pas du tout. Ce sont des suggestions sur la personnalisation des widgets, here , interrompent l’utilisation de form.as_p(), ce qui compromet la valeur des formulaires présentée dans Django, c’est-à-dire un ensemble de widgets.

La solution que j’ai le plus aimée, c’est des disquettes . Il facilite la définition des widgets à l'aide de modèles et constitue un remplacement (presque) transparent du module de formulaires de Django. Il a une excellente documentation et est facile à trouver.

0
Peter Shannon