web-dev-qa-db-ja.com

動的なフィールド数を持つDjangoフォームクラスを作成する

私はオンラインストアのようなものに取り組んでいます。顧客がアイテムを購入するフォームを作成していますが、購入したいアイテムの数を選択できます。しかし、彼女が購入するすべてのアイテムでは、その色が何であるかを選択する必要があります。したがって、フィールドの数は一定ではありません。顧客が3つのアイテムを購入した場合、3 <select>色を選択するためのボックス。7個のアイテムを購入した場合、7個の<select>ボックス。

JavaScriptを使用して、HTMLフォームフィールドを表示および非表示にします。しかし、Djangoフォームクラスでこれをどのように処理しますか?フォームフィールドはクラス属性であることがわかります。したがって、フォームインスタンスがカラーフィールドといくつかの7。

どんな手掛かり?

49
Ram Rachum

Jacob Kaplan-Mossには、動的フォームフィールドに関する広範な記事があります。 http://jacobian.org/writing/dynamic-form-generation/

基本的に、インスタンス化中にフォームのself.fields辞書にアイテムを追加します。

69
GDorn

別のオプションがあります: formset ?フィールドはすべて同じであるため、まさにそれがフォームセットの使用目的です。

Django adminはFormSets +少しのJavaScriptを使用して任意の長さのインラインを追加します。

class ColorForm(forms.Form):
    color = forms.ChoiceField(choices=(('blue', 'Blue'), ('red', 'Red')))

ColorFormSet = formset_factory(ColorForm, extra=0) 
# we'll dynamically create the elements, no need for any forms

def myview(request):
    if request.method == "POST":
        formset = ColorFormSet(request.POST)
        for form in formset.forms:
            print "You've picked {0}".format(form.cleaned_data['color'])
    else:
        formset = ColorFormSet()
    return render(request, 'template', {'formset': formset}))

JavaScript

    <script>
        $(function() {
            // this is on click event just to demo.
            // You would probably run this at page load or quantity change.
            $("#generate_forms").click(function() {
                // update total form count
                quantity = $("[name=quantity]").val();
                $("[name=form-TOTAL_FORMS]").val(quantity);  

                // copy the template and replace prefixes with the correct index
                for (i=0;i<quantity;i++) {
                    // Note: Must use global replace here
                    html = $("#form_template").clone().html().replace(/__prefix_/g', i);
                    $("#forms").append(html);
                };
            })
        })
    </script>

テンプレート

    <form method="post">
        {{ formset.management_form }}
        <div style="display:none;" id="form_template">
            {{ formset.empty_form.as_p }}
        </div><!-- stores empty form for javascript -->
        <div id="forms"></div><!-- where the generated forms go -->
    </form>
    <input type="text" name="quantity" value="6" />
    <input type="submit" id="generate_forms" value="Generate Forms" />
33

あなたはそれを好きなようにできます

def __init__(self, n,  *args, **kwargs):
  super(your_form, self).__init__(*args, **kwargs)
  for i in range(0, n):
    self.fields["field_name %d" % i] = forms.CharField()

そして、フォームインスタンスを作成するとき、あなたはただ

forms = your_form(n)

それは単なる基本的な考え方であり、コードを好きなように変更できます。 :D

19
owenwater

私がそれをする方法は次のとおりです:

  1. 次のように、froms.Formから継承する「空の」クラスを作成します。

    class ItemsForm(forms.Form):
        pass
    
  2. 実際のフォームであるフォームオブジェクトのディクショナリを作成します。その構成はコンテキストに依存します(たとえば、外部モジュールからインポートできます)。例えば:

    new_fields = {
        'milk'  : forms.IntegerField(),
        'butter': forms.IntegerField(),
        'honey' : forms.IntegerField(),
        'eggs'  : forms.IntegerField()}
    
  3. ビューでは、python native "type"関数を使用して、可変数のフィールドを持つFormクラスを動的に生成できます。

    DynamicItemsForm = type('DynamicItemsForm', (ItemsForm,), new_fields)
    
  4. コンテンツをフォームに渡し、テンプレートでレンダリングします。

    Form = DynamicItemsForm(content)
    context['my_form'] = Form
    return render(request, "demo/dynamic.html", context)
    

「コンテンツ」は、フィールド値の辞書です(たとえば、request.POSTでも可能です)。あなたは私の例全体が説明されているのを見ることができます here

3
OZ13

別のアプローチ:通常のフィールド初期化フローを壊すのではなく、ミックスインでフィールドをオーバーライドし、生成されるたびに追加されるgenerate_dynamic_fieldsの動的フィールドのOrderedDictを返すことができます。

from collections import OrderedDict

class DynamicFormMixin:
    _fields: OrderedDict = None

    @property
    def fields(self):
      return self._fields

    @fields.setter
    def fields(self, value):
        self._fields = value
        self._fields.update(self.generate_dynamic_fields())

    def generate_dynamic_fields(self):
        return OrderedDict()

簡単な例:

class ExampleForm(DynamicFormMixin, forms.Form):
    instance = None

    def __init__(self, instance = None, data=None, files=None, auto_id='id_%s', prefix=None, initial=None,
                 error_class=ErrorList, label_suffix=None, empty_permitted=False, field_order=None,
                 use_required_attribute=None, renderer=None):
        self.instance = instance
        super().__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, field_order,
                         use_required_attribute, renderer)

    def generate_dynamic_fields(self):
        dynamic_fields = OrderedDict()
        instance = self.instance
        dynamic_fields["dynamic_choices"] = forms.ChoiceField(label=_("Number of choices"),
                                                              choices=[(str(x), str(x)) for x in range(1, instance.number_of_choices + 1)],
                                                              initial=instance.initial_choice)
        return dynamic_fields
0
bob