Añadir campo dinámicamente a un formulario

Tengo 3 campos en mi formulario. Tengo un botón de envío y un botón para “Agregar campo adicional”. Entiendo que puedo agregar campos usando el método __init__ en la clase de formulario.

Soy nuevo en Python y Django y estoy atascado con una pregunta para principiantes: Mi pregunta es:

Cuando hago clic en el botón “Agregar campo adicional”, ¿cuál es el proceso para agregar el campo adicional?

¿Hay que volver a hacer el formulario?

¿Cómo y cuándo llamo a __init__ o tengo que llamar?

¿Cómo paso argumentos a __init__ ?

Su formulario debería construirse basándose en algunas variables que se le pasaron desde su POST (o verificar ciegamente los atributos). El formulario en sí se construye cada vez que se recarga la vista, errores o no, por lo que el HTML debe contener información sobre cuántos campos hay para construir la cantidad correcta de campos para la validación.

Consideraría este problema en la forma en que funciona FormSet : hay un campo oculto que contiene el número de formularios activos, y cada nombre de formulario aparece junto al índice del formulario.

De hecho, podrías hacer un campo FormSet

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

Si no desea utilizar un FormSet , siempre puede crear este comportamiento usted mismo.

Aquí hay uno hecho desde cero – debería darle algunas ideas. También responde a sus preguntas acerca de pasar argumentos a __init__ – simplemente pasa argumentos a un constructor de objetos: MyForm('arg1', 'arg2', kwarg1='keyword arg')

Formas

 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() 

Ver

 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.as_p }}

JS

  

He tenido un caso en el que tuve que crear dinámicamente formularios con campos dynamics. Lo que hice con este truco:

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

Consulte este enlace para más información: Formularios dynamics.

Pero además de eso, también tuve que inyectar campos, es decir, agregar campos dinámicamente a una clase de formulario una vez que se creó.

 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) 

Y eso funcionó.

Una forma sin javascript y el tipo de campo no se describe en el js:

PITÓN

  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 

MODELO

 {% for field_dog in f.fields_dogs %}    {% trans 'Dog' %} #{{forloop.counter}} {% trans 'Name' %}       {{field_dog.name.errors}}{{field_dog.name}}      {% endfor %} 

Esta respuesta se basa en la de @ Yuji’Tomita’Tomita con varias mejoras y cambios.

Aunque la respuesta de @ Yuji’Tomita’Tomita es excelente e ilustra muy bien la dirección a seguir para construir la funcionalidad “agregar campo extra en un formulario django”, encontré que hay algunos problemas con algunas partes del código.

Aquí proporciono mi código de trabajo basado en la propuesta inicial de @ Yuji’Tomita’Tomita:

Vistas (en el archivo view.py)

Nada cambia realmente en las vistas:

 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 }) 

Formulario (en el archivo 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() 

Plantilla HTML

 
{{ form.errors }} {{ form.non_field_errors }} {% if errormsgs %} {% for value in errormsgs %} {{ value }} {% endfor %} {% endif %} {% for error in form_empty_layer.non_field_errors %} {{ error }} {% endfor %} {% for field in form_empty_layer.visible_fields %} {{ field }} {% endfor %}
// used in order to save the number of added fields (this number will pass to forms.py through the view)

Plantilla 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 = $(''); // 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); }) 

La solución de Tomita de Yuji ‘Tomita’ es lo mejor que puede encontrar, pero asumiendo que tiene un formulario de pasos múltiples y utiliza la aplicación django-formtools, tendrá algunos problemas que deberá resolver. Gracias Yuji ‘Tomita’ Tomita, me ayudaste mucho 🙂

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) 

Para un formulario de pasos múltiples, no necesitará el campo adicional, en este código usamos el campo othercolumsvalue en el primer paso.

vistas.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], }) 

Al anular las funciones get_form () y get_context_data () , puede anular el formulario antes de que sea procesado. Ya no necesitarás JavaScript para tu archivo de plantilla:

  {% if step1.othercolumsvalue > 0 %}  Checkbox Columname  {% for i in othercolums_count %}  
{% endfor %} {% endif %}

Los campos de la etapa 2 se crearon dinámicamente también se reconstruyeron de las herramientas de forma debido al mismo nombre. Pero para llegar allí, tendrá que trabajar alrededor de los bucles de plantillas para cada uno, como puede ver:

de la función get_context_data () –

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