76š
Your form would have to be constructed based on some variables passed to it from your POST (or blindly check for attributes). The form itself is constructed every time the view is reloaded, errors or not, so the HTML needs to contain information about how many fields there are to construct the correct amount of fields for validation.
Iād look at this problem the way FormSet
s work: there is a hidden field that contains the number of forms active, and each form name is prepended with the form index.
In fact, you could make a one field FormSet
https://docs.djangoproject.com/en/dev/topics/forms/formsets/#formsets
If you donāt want to use a FormSet
you can always create this behavior yourself.
Hereās one made from scratch ā it should give you some ideas. It also answers your questions about passing arguments to __init__
ā you just pass arguments to an objects constructor: MyForm('arg1', 'arg2', kwarg1='keyword arg')
Forms
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>
let 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 ++;
let 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>
16š
Iāve had a case when I had to dynamically create forms with dynamic fields. That I did with this trick:
from django import forms
...
dyn_form = type('DynForm', # form name is irrelevant
(forms.BaseForm,),
{'base_fields': fields})
Refer to this link for more info:
Dynamic Forms
But in addition to that I had to inject fields as well i.e. dynamically add fields to a form class once it was created.
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)
And that worked.
- [Django]-Best practices for adding .gitignore file for Python projects?
- [Django]-Why am I getting this error in Django?
- [Django]-Redirect to named url pattern directly from urls.py in django?
4š
A way without javascript and the field type is not describe in the js:
PYTHON
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 %}
- [Django]-Object does not support item assignment error
- [Django]-Saving ModelForm error(User_Message could not be created because the data didn't validate)
- [Django]-Folder Structure for Python Django-REST-framework and Angularjs
4š
This answer is based on the of @YujiāTomitaāTomita with several improvements and changes.
Although @YujiāTomitaāTomita answer is great and illustrates nicely and simple the direction to follow in order to build the āadd extra field in a django formā functionality, I found that there are some issues with some parts of the code.
Here I provide my working code based on the initial proposal of @YujiāTomitaāTomita:
Views (in the view.py file)
Nothing really changes in the views:
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 (in the form.py file)
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()
Template 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>
Template 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);
})
- [Django]-Why does Django's render() function need the "request" argument?
- [Django]-In Django, how does one filter a QuerySet with dynamic field lookups?
- [Django]-Django ā "Incorrect type. Expected pk value, received str" error
3š
Yuji āTomitaā Tomitaās solution is the acutally best you will find, but assuming you have a multiple step form and you use the django-formtools app you will have some issues you will have to take care of. Thank you Yuji āTomitaā Tomita, you helped me a lot š
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)
For a multiple-step form, you will not need the extra field, in this code we use othercolumsvalue field in the first-step.
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],
})
By overriding the get_form() and get_context_data() functions you can override the form befor it gets rendered. You will not need JavaScript anymore either for your template-file:
{% 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 %}
The fields from step2 the were made dynamically were also reconized from the formtools because of the same name. But to get there you will have to work around the for-each template loops as you can see:
from the get_context_data()-function
othercolums_count = []
for i in range(0, data_step1['othercolumsvalue']):
othercolums_count.append(str(i))
- [Django]-Django: Catching Integrity Error and showing a customized message using template
- [Django]-Django multiprocessing and database connections
- [Django]-Django filter on the basis of text length