Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Администратор django использует FormSet + бит 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" />

Ответ 3

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

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

Ответ 4

Я бы сделал это следующим образом:

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

Ответ 5

Другой подход: вместо того, чтобы нарушать нормальный поток инициализации поля, мы можем переопределить поля с помощью 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