динамически добавлять поле в форму - PullRequest
54 голосов
/ 26 мая 2011

В моей форме 3 поля.У меня есть кнопка отправки и кнопка «Добавить дополнительное поле».Я понимаю, что могу добавлять поля, используя метод __init__ в классе формы.

Я новичок в Python и Django и застрял с вопросом для начинающих: Мой вопрос:

Когда я нажимаюкнопка «Добавить дополнительное поле», каков процесс добавления дополнительного поля?

Нужно ли снова выводить форму?

Как и когда мне позвонить __init__ или мне даже нужно ее вызвать?

Как передать аргументыдо __init__?

Ответы [ 5 ]

59 голосов
/ 26 мая 2011

Ваша форма должна быть построена на основе некоторых переменных, переданных ей из вашего POST (или вслепую проверять наличие атрибутов). Сама форма создается каждый раз, когда представление перезагружается, с ошибками или нет, поэтому HTML должен содержать информацию о том, сколько полей существует для построения правильного количества полей для проверки.

Я бы посмотрел на эту проблему так, как работает FormSet: есть скрытое поле, содержащее число активных форм, и каждому имени формы предшествует индекс формы.

На самом деле, вы можете сделать одно поле FormSet

https://docs.djangoproject.com/en/dev/topics/forms/formsets/#formsets

Если вы не хотите использовать FormSet, вы всегда можете создать это поведение самостоятельно.

Вот тот, который сделан с нуля - он должен дать вам некоторые идеи. Он также отвечает на ваши вопросы о передаче аргументов __init__ - вы просто передаете аргументы конструктору объектов: MyForm('arg1', 'arg2', kwarg1='keyword arg')

Формы

class MyForm(forms.Form):
    original_field = forms.CharField()
    extra_field_count = forms.CharField(widget=forms.HiddenInput())

    def __init__(self, *args, **kwargs):
        extra_fields = kwargs.pop('extra', 0)

        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['extra_field_count'].initial = extra_fields

        for index in range(int(extra_fields)):
            # generate extra fields in the number specified via extra_fields
            self.fields['extra_field_{index}'.format(index=index)] = \
                forms.CharField()

View

def myview(request):
    if request.method == 'POST':
        form = MyForm(request.POST, extra=request.POST.get('extra_field_count'))
        if form.is_valid():
            print "valid!"
    else:
        form = MyForm()
    return render(request, "template", { 'form': form })

HTML

<form>
    <div id="forms">
        {{ form.as_p }}
    </div>
    <button id="add-another">add another</button>
    <input type="submit" />
</form>

JS

<script>
form_count = Number($("[name=extra_field_count]").val());
// get extra form count so we know what index to use for the next item.

$("#add-another").click(function() {
    form_count ++;

    element = $('<input type="text"/>');
    element.attr('name', 'extra_field_' + form_count);
    $("#forms").append(element);
    // build element and append it to our forms container

    $("[name=extra_field_count]").val(form_count);
    // increment form count so our view knows to populate 
    // that many fields for validation
})
</script>
10 голосов
/ 17 декабря 2014

У меня был случай, когда мне приходилось динамически создавать формы с динамическими полями. Что я сделал с этим трюком:

from django import forms

...

dyn_form = type('DynForm',  # form name is irrelevant
                (forms.BaseForm,),
                {'base_fields': fields})

Обратитесь по этой ссылке для получения дополнительной информации: Динамические формы

Но в дополнение к этому мне пришлось также вводить поля, то есть динамически добавлять поля в класс формы после его создания.

dyn_form.base_fields['field1'] = forms.IntegerField(widget=forms.HiddenInput(), initial=field1_val)
dyn_form.base_fields['field2'] = forms.CharField(widget=forms.HiddenInput(), initial=field2_val)

И это сработало.

4 голосов
/ 30 июня 2016

Этот ответ основан на @ Yuji'Tomita'Tomita с несколькими улучшениями и изменениями.

Хотя ответ @ Yuji'Tomita'Tomita отличный и хорошо иллюстрирует простое направление, которому нужно следовать для построения функции «добавить дополнительное поле в форме django», я обнаружил, что есть некоторые проблемы с некоторыми частями код.

Здесь я предоставляю свой рабочий код, основанный на первоначальном предложении @ Yuji'Tomita'Tomita:

Просмотры (в файле view.py)

Ничего действительно не меняется во взглядах:

def myview(request):

  if request.method == 'POST':

    form = MyForm(request.POST, extra=request.POST.get('total_input_fields'))

      if form.is_valid():
        print "valid!"
      else:
        form = MyForm()
return render(request, "template", { 'form': form })

Форма (в файле form.py)

class MyForm(forms.Form):

    empty_layer_name = forms.CharField(max_length=255, required=True, label="Name of new Layer")

    total_input_fields = forms.CharField(widget=forms.HiddenInput())


    def __init__(self, *args, **kwargs):

      extra_fields = kwargs.pop('extra', 0)

      # check if extra_fields exist. If they don't exist assign 0 to them
      if not extra_fields:
         extra_fields = 0

      super(MyForm, self).__init__(*args, **kwargs)
      self.fields['total_input_fields'].initial = extra_fields

      for index in range(int(extra_fields)):
        # generate extra fields in the number specified via extra_fields
        self.fields['extra_field_{index}'.format(index=index)] = forms.CharField()

Шаблон HTML

<form id="empty-layer-uploader" method="post" enctype="multipart/form-data" action="{% url "layer_create" %}">
        <div id="form_empty_layer">
          <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
            {{ form.errors }}
            {{ form.non_field_errors }}
            {% if errormsgs %}
              {% for value in errormsgs %}
                </p>  {{ value }} </p>
              {% endfor %}
            {% endif %}
            {% for error in form_empty_layer.non_field_errors %}
              {{ error }} </br>
            {% endfor %}
            </br>
            {% for field in form_empty_layer.visible_fields %}
              {{ field }} </br>
            {% endfor %}
        </div>
        </br>
        <button type="button" id="add-another">add another</button> </br> </br>
        <button type="submit" id="empty-layer-button" name="emptylayerbtn">Upload</button>
        </br></br>
        // used in order to save the number of added fields (this number will pass to forms.py through the view)
        <input type="text" name="total_input_fields"/>
</form>

Шаблон Jquery

// check how many times elements with this name attribute exist: extra_field_*
form_count = $('input[name*="extra_field_*"]').length;

// when the button 'add another' is clicked then create a new input element
$(document.body).on("click", "#add-another",function(e) {
  new_attribute = $('<input type="text"/>');
  // add a name attribute with a corresponding number (form_count)
  new_attribute.attr('name', 'extra_field_' + form_count);
  // append the new element in your html
  $("#form_empty_layer").append(new_attribute);
  // increment the form_count variable
  form_count ++;
  // save the form_count to another input element (you can set this to invisible. This is what you will pass to the form in order to create the django form fields
  $("[name=total_input_fields]").val(form_count);

})
4 голосов
/ 21 июля 2015

Способ без javascript и типа поля не описан в js:

ПИТОН

 def __init__(self, *args, **kwargs):
        super(Form, self).__init__(*args, **kwargs)

        ##ajouts des champs pour chaque chien
        for index in range(int(nb_dogs)):
            self.fields.update({
                'dog_%s_name' % index: forms.CharField(label=_('Name'), required=False, max_length=512),
            })

 def fields_dogs(self):
        fields = []
        for index in range(int(nb_dogs)):
            fields.append({
                'name': self['dog_%s_name' % index],
            })
        return fields

ШАБЛОН

{% for field_dog in f.fields_dogs %}
        <thead>
            <tr>
                <th style="background-color: #fff; border-width: 0px;"></th>
                <th>{% trans 'Dog' %} #{{forloop.counter}}</th>
                <th>{% trans 'Name' %}</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td style="background-color: #fff; border-width: 0px;"></td>
                <td style="background-color: #fff; border-width: 0px;"></td>
                <td>{{field_dog.name.errors}}{{field_dog.name}}</td>
            </tr>
            <tr>
                <td style="padding: 10px; border-width: 0px;"></td>
            </tr>
        </tbody>
{% endfor %}
0 голосов
/ 01 августа 2017

Yuji 'Tomita' Решение Tomita - лучшее, что вы найдете, но если у вас есть многоэтапная форма и вы используете приложение django-formtools, у вас будут проблемы, о которых вам придется позаботиться.Спасибо Yuji 'Tomita' Tomita, вы мне очень помогли:)

forms.py

class LicmodelForm1(forms.Form):
     othercolumsvalue = forms.IntegerField(min_value=0, initial=0)
class LicmodelForm2(forms.Form):
    def __init__(self, *args, **kwargs):
    extra_fields = kwargs.pop('extra', 0)

    super(LicmodelForm2, self).__init__(*args, **kwargs)

    for index in range(int(extra_fields)):
        # generate extra fields in the number specified via extra_fields
        self.fields['othercolums_{index}'.format(index=index)] = \
            forms.CharField()
        self.fields['othercolums_{index}_nullable'.format(index=index)] = \
            forms.BooleanField(required=False)

Для многоэтапной формы вам не понадобитсядополнительное поле, в этом коде мы используем поле othercolumsvalue в первом шаге.

views.py

class MyFormTool(SessionWizardView):
def get_template_names(self):
    return [TEMPLATES[self.steps.current]]

def get_context_data(self, form, **kwargs):
    context = super(MyFormTool, self).get_context_data(form=form, **kwargs)
    data_step1 = self.get_cleaned_data_for_step('step1')
    if self.steps.current == 'step2':

        #prepare tableparts for the needLists
        needList_counter = 0
        for i in self.wellKnownColums:
            if data_step1[i] is True:
                needList_counter = needList_counter + 1
                pass

        #prepare tableparts for othercolums
        othercolums_count = []
        for i in range(0, data_step1['othercolumsvalue']):
            othercolums_count.append(str(i))

        context.update({'step1': data_step1})
        context.update({'othercolums_count': othercolums_count})

    return context

def get_form(self, step=None, data=None, files=None):
    form = super(MyFormTool, self).get_form(step, data, files)

    if step is None:
        step = self.steps.current

    if step == 'step2':
        data = self.get_cleaned_data_for_step('step1')
        if data['othercolumsvalue'] is not 0:
            form = LicmodelForm2(self.request.POST,
                                 extra=data['othercolumsvalue'])
    return form

def done(self, form_list, **kwargs):
    print('done')
    return render(self.request, 'formtools_done.html', {
        'form_data' : [form.cleaned_data for form in form_list],
        })

Переопределяя get_form () и get_context_data () функций, которые вы можете переопределить для формы перед ее визуализацией.Вам больше не понадобится JavaScript для вашего файла шаблона:

            {% if step1.othercolumsvalue > 0 %}
            <tr>
                <th>Checkbox</th>
                <th>Columname</th>
            </tr>
            {% for i in othercolums_count %}
                <tr>
                    <td><center><input type="checkbox" name="othercolums_{{ i }}_nullable" id="id_othercolums_{{ i }}_nullable" /></center></td>
                    <td><center><input type="text" name="othercolums_{{ i }}" required id="id_othercolums_{{ i }}" /></center></td>
                </tr>
            {% endfor %}
        {% endif %}

Поля из шага 2, созданные динамически, также были преобразованы из формул из-за того же имени.Но чтобы попасть туда, вам нужно будет обойти циклы для каждого шаблона, как вы видите:

из get_context_data () - функция

        othercolums_count = []
        for i in range(0, data_step1['othercolumsvalue']):
            othercolums_count.append(str(i))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...