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

Динамически добавлять поле в форму

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

Я новичок в Python и Django и застрял в новичке: Мой вопрос:

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

Должна ли отображаться форма?

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

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

4b9b3361

Ответ 1

Ваша форма должна быть построена на основе некоторых переменных, переданных ей из вашего 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()

Вид

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>

Ответ 2

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

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)

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

Ответ 3

Этот ответ основан на @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

Способ без 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

TEMPLATE

{% 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 %}

Ответ 5

Решение 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, сделанные динамически, также были переработаны из formtools из-за того же имени. Но чтобы добраться туда, вам придется обойти контуры каждого шаблона, как вы можете видеть:

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

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