web-dev-qa-db-ja.com

Django:カスタムフォームウィジェットを作成する方法

カスタムウィジェットの作成方法に関するドキュメントを見つけるのに苦労しています。

私の質問は:

  • カスタムウィジェットを作成する場合、管理インターフェイスまたは通常のフォームに同等に使用できますか?
  • ユーザーがアイテムのリストを編集できるようにする場合、どのウィジェットをサブクラス化する必要がありますか?ウィジェットのどのメソッドをオーバーライド/実装する必要がありますか?
  • ユーザーの入力からデータモデルに戻るのはどのウィジェットメソッドですか?

ありがとう。

52
Nick Heiner

その通りですDjangoはこの特定のトピックに関するドキュメントを提供していません。 _Django.forms.widgets_ (I 'そのモジュールのクラスを以下で参照します)。

カスタムウィジェットを作成する場合、管理インターフェイスまたは通常のフォームに同等に使用できますか?

管理者は一部のウィジェットをオーバーライドします( _Django.contrib.admin.options.FORMFIELD_FOR_DBFIELD_DEFAULTS_ を参照)。おそらくModelAdminをサブクラス化して_formfield_overrides_属性を変更することはできますが、ModelAdminで何も行ったことがないので、ここでは手伝いません...

ユーザーにアイテムのリストの編集を許可する場合、どのウィジェットをサブクラス化する必要がありますか?ウィジェットのどのメソッドをオーバーライド/実装する必要がありますか?

あなたのウィジェットはおそらくデフォルトのウィジェットと共通点はありません(もしあればSelectで?!)。 Widgetのサブクラスで、組み込みの共通パターンを見つけた場合は、後で変更できます。

次のメソッドを実装します。

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

    簡単な例については、_Input.render_をご覧ください。また、HTMLに含まれているユーザー定義の属性もサポートしています。 「id」属性を追加することもできます。その方法については、_MultipleHiddenInput.render_を参照してください。 HTMLを直接出力するときに_mark_safe_を使用することを忘れないでください。かなり複雑なウィジェットがある場合は、テンプレートレンダリングを使用できます( example )。

  • _has_changed(self, initial, data)

    オプション。 adminで変更内容に関するメッセージを記録するために使用されます。

ユーザーの入力からデータモデルに戻るのはどのウィジェットメソッドですか?

これはウィジェットとは何の関係もありません-Djangoは以前のリクエストでどのウィジェットが使用されたかを知ることができません。フォームから送信されたフォーム(POST)データのみを使用できます。フィールドメソッド_Field.to_python_は、入力をPythonデータ型に変換するために使用されます(入力が無効な場合はValidationErrorを発生させることがあります)。

47
AndiDog

Django <1.11

他の回答に加えて、これはカスタムウィジェットの小さなコードサンプルです。

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>

ジャンゴ1.11

ウィジェットは、 フォームレンダリングAPI を使用してレンダリングされるようになりました。

22
Wtower

注:ここには3つの質問があります。最初の2つの質問については、AndiDogによる完全な回答を参照してください。私はここで3番目の質問に答えているだけです:

Q.ユーザーの入力からデータモデルに戻るのはどのウィジェットメソッドですか?

A. value_from_datadictメソッド-ウィジェットのrenderメソッドの逆のようなものです。このメソッドはおそらく、ウィジェットのDjango docsが「ウィジェットがHTMLのレンダリングと、 Widget。

5
Ghopper21

通常、既存のウィジェットの1つから継承することから始め、新しい目的のプロパティを追加してから、レンダリングメソッドを変更します。以下は、私が実装したフィルター可能な選択ウィジェットの例です。フィルタリングは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))

次に、フォームを作成するビューで、フィールドにdata_filterを設定します。

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

Djangoのサイトにあるドキュメントはこれをまったく助けません。ウィジェットのカスタマイズに関する提案 here は、form.as_p()の使用を中断し、Djangoで提示されているフォームの値、つまりウィジェットの集合を危険にさらします。

私が一番気に入った解決策は floppyforms です。テンプレートを使用してウィジェットの定義を容易にし、Django独自のフォームモジュールの(ほぼ)透過的な置き換えです。優れたドキュメントがあり、簡単に入手できます。

0
Peter Shannon