it-roy-ru.com

Создание класса формы Django с динамическим числом полей

Я работаю над чем-то вроде интернет-магазина. Я делаю форму, в которой покупатель покупает товар, и она может выбрать, сколько из этих товаров она хотела бы купить. Но на каждом предмете, который она покупает, ей нужно выбрать цвет. Таким образом, существует непостоянное количество полей: если клиент покупает 3 товара, он должен получить 3 поля <select> для выбора цвета, если он покупает 7 товаров, он должен получить 7 таких ящиков <select>.

Я заставлю поля формы HTML появляться и исчезать, используя JavaScript. Но как мне справиться с этим в моем классе формы Django? Я вижу, что поля формы являются атрибутами класса, поэтому я не знаю, как справиться с тем фактом, что у некоторого экземпляра формы должно быть 3 цветовых поля, а у некоторых - 7.

Любая подсказка?

46
Ram Rachum

У Джейкоба Каплана-Мосса есть обширная рецензия на динамические поля формы: http://jacobian.org/writing/dynamic-form-generation/

По сути, вы добавляете больше элементов в словарь self.fields формы во время создания экземпляра.

66
GDorn

Вот еще один вариант: как насчет formset ? Поскольку ваши поля одинаковы, именно для этого используются формы.

Администратор Django использует 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" />
30
Yuji 'Tomita' Tomita

ты можешь сделать это как

 def __init __ (self, n, * args, ** kwargs): 
 super (your_form, self) .__ init __ (* args, ** kwargs) 
 для i в диапазоне (0, n): 
 self.fields ["field_name% d"% i] = forms.CharField () 

и когда вы создаете экземпляр формы, вы просто делаете

 forms = your_form (n) 

это просто основная идея, вы можете изменить код на любой другой. : D

16
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 функцию «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)). Вы можете увидеть весь мой пример, объясненный здесь .

2
OZ13

Другой подход: вместо того, чтобы нарушать нормальный поток инициализации поля, мы можем переопределить поля с помощью mixin, вернуть OrderedDict динамических полей в generate_dynamic_fields, который будет добавляться всякий раз, когда он установлен.

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