python - fields - añadir dinámicamente el campo a un formulario
django formset js (5)
Tengo 3 campos en mi formulario. Tengo un botón de enviar 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 me quedo 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?
¿La forma debe ser renderizada nuevamente?
¿Cómo y cuándo llamo __init__
o incluso tengo que llamarlo?
¿Cómo paso los argumentos a __init__
?
Esta respuesta se basa en @ de Yuji''Tomita''Tomita con varias mejoras y cambios.
Aunque la respuesta @ Yuji''Tomita''Tomita es excelente e ilustra bien y sencillamente la dirección a seguir para construir la funcionalidad "agregar campo adicional en una forma django", descubrí 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 realmente cambia 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 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>
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 = $(''<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);
})
La solución de Yuji ''Tomita'' Tomita es la mejor que encontrarás, pero suponiendo que tienes una forma de pasos múltiples y usas la aplicación django-formtools, tendrás algunos problemas de los que tendrás que ocuparte. 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 varios pasos, no necesitará el campo adicional, en este código usamos el campo othercolumsvalue en el primer paso.
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],
})
Al anular las funciones get_form () y get_context_data () puede anular el formulario antes de que se represente. Ya no necesitará JavaScript para su plantilla de archivo:
{% 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 %}
Los campos del paso2 que se hicieron dinámicamente también se reconceptuaron de las herramientas de formulario debido al mismo nombre. Pero para llegar allí tendrás que trabajar alrededor de los bucles de cada plantilla como puedes ver:
de la función get_context_data () -
othercolums_count = []
for i in range(0, data_step1[''othercolumsvalue'']):
othercolums_count.append(str(i))
Su formulario debería construirse en función de algunas variables que se le pasaron desde su POST (o verificar ciegamente los atributos). El formulario en sí se construye cada vez que se vuelve a cargar la vista, los 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.
Me gustaría ver este problema de la misma manera que funciona FormSet
: hay un campo oculto que contiene la cantidad de formularios activos, y cada nombre de formulario se antepone con el índice del formulario.
De hecho, podría crear un FormSet
un campo
https://docs.djangoproject.com/en/dev/topics/forms/formsets/#formsets
Si no desea usar un FormSet
, siempre puede crear este comportamiento usted mismo.
Aquí hay uno hecho desde cero: debe darte algunas ideas. También responde tus preguntas sobre pasar argumentos a __init__
- simplemente pasas argumentos a un constructor de objetos: MyForm(''arg1'', ''arg2'', kwarg1=''keyword arg'')
Formularios
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>
<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>
Tuve un caso cuando tuve que crear dinámicamente formularios con campos dinámicos. Eso 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 obtener más información: Formularios dinámicos
Pero además de eso tuve que inyectar campos también, es decir, agregar dinámicamente campos 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 %}
<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 %}