问题
I have a two models Publisher
and Book
like below
models.py
class Publisher(models.Model):
name = models.CharField(max_length=255)
class Book(models.model):
name = models.CharField(max_length=255)
price = models.DecimalField()
generic = generic.GenericForeignKey()
publisher_id = models.PositiveIntegerField()
forms.py
class PublisherForm(ModelForm):
model = Publisher
class BookForm(ModelForm):
model = Book
exclude = ('generic', 'publisher_id',)
def __init__(self, *args, **kwargs):
super(BookForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs = {'id':'inputId', 'class':'input-block-level, 'placeholder':'Name'}
self.fields['name'].error_messages = {'required': 'Please enter name'}
self.fields['age'].widget.attrs = {'id':'inputId', 'class':'input-block-level, 'placeholder':'Age'}
self.fields['age'].error_messages = {'required': 'Please enter age'}
views.py
Here in this view i will send the publisher id, because Book
model does not had a foreign Key to Publisher model
from .forms import BookForm
@login_required
def create_multiple_books(request, publisher_id):
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
BookFormset = formset_factory(BookForm, max_num=10, formset=RequiredFormSet)
if request.method == 'POST':
book_formset = BookFormset(request.POST, request.FILES)
if book_formset.is_valid():
for form in book_formset.forms:
obj = form.save(commit=False)
obj.publisher_id = publisher_id
obj.save()
return redirect(reverse('created'))
else:
book_formset = BookFormset()
c = {'book_formset': book_formset,
'publisher_id':publisher_id,
}
c.update(csrf(request))
return render_to_response('add_books.html',c,context_instance = RequestContext(request))
template.html
So in the below template rendered the forms as form.as_p
its working fine and multiple records are creating to that publisher id
successfully
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
// Code adapted from http://djangosnippets.org/snippets/1389/
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\\d+-)');
var replacement = prefix + '-' + ndx + '-';
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex,
replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.name) el.name = el.name.replace(id_regex, replacement);
}
function deleteForm(btn, prefix) {
var formCount = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (formCount > 1) {
// Delete the item/form
$(btn).parents('.item').remove();
var forms = $('.item'); // Get all the forms
// Update the total number of forms (1 less than before)
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
var i = 0;
// Go through the forms and set their indices, names and IDs
for (formCount = forms.length; i < formCount; i++) {
$(forms.get(i)).children().children().each(function() {
updateElementIndex(this, prefix, i);
});
}
} // End if
else {
alert("You have to enter at least one todo item!");
}
return false;
}
function addForm(btn, prefix) {
var formCount = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
// You can only submit a maximum of 10 todo items
if (formCount < 10) {
// Clone a form (without event handlers) from the first form
var row = $(".item:first").clone(false).get(0);
// Insert it after the last form
$(row).removeAttr('id').hide().insertAfter(".item:last").slideDown(300);
// Remove the bits we don't want in the new row/form
// e.g. error messages
$(".errorlist", row).remove();
$(row).children().removeClass('error');
// Relabel/rename all the relevant bits
$(row).children().children().each(function() {
updateElementIndex(this, prefix, formCount);
if ( $(this).attr('type') == 'text' )
$(this).val('');
});
// Add an event handler for the delete item/form link
$(row).find('.delete').click(function() {
return deleteForm(this, prefix);
});
// Update the total form count
$('#id_' + prefix + '-TOTAL_FORMS').val(formCount + 1);
} // End if
else {
alert("Sorry, you can only enter a maximum of ten items.");
}
return false;
}
// Register the click event handlers
$("#add").click(function() {
return addForm(this, 'form');
});
$(".delete").click(function() {
return deleteForm(this, 'form');
});
});
</script>
</head>
<body>
<form action="" method="POST">{% csrf_token %}
<div class="section">
{{ todo_list_form.as_p }}
</div>
<h2>Todo Items</h2>
{{ book_formset.management_form }}
{% for form in book_formset.forms %}
<div class="item">
{{ form.as_p }}
<p style=""><a class="delete" href="#">Delete</a></p>
</div>
{% endfor %}
<p><a id="add" href="#">Add another item</a></p>
<input type="submit" value=" Submit " />
</form>
</body>
</html>
But when i display the html fields from the form and render like below
{% for form in book_formset.forms %}
<div class="item">
<div class="with_name_design">{{ form.name }}</div>
{% if form.name.errors %}
{{form.name.errors}}
{% endif %}
<div class="with_age_design">{{ form.age }}</div>
{% if form.age.errors %}
{{form.age.errors}}
{% endif %}
</div>
{% endfor %}
The form is displaying succesfully and when i clicked on the link Add another item
a new form is generating with above jquery, and when i tried to submit by entering all the details and clicked on submit,the next form which was added by the jquery is displaying validation errors like name, age is required
?(This is happening only in this case that is displaying the fields seperately instead of form.as_p(), and if we render as form.as_p() its working fine and records are creating in to database)
So i really could n't able to figure out why it is succeded when i rendered the form as form.as_p()
and why not when i rendered individual fields with their errors
Am i missing anything/need to and anything in the above javascript code that generates additional form?
because when we render the fields individually, the form generated by clicking on the Add another form button is displaying validation errors ?
I really wasted a lot of time in figuring out the above javascript, as i got it some where by googling around,
So finally the above functionlality is working when we render the formset forms as form.as_p()
, but why the above functionality is not working when we render the form fields individually ?
Can anyone please let me know how to solve the above issue(Also may be above code will be useful form many users to create the forms dynamically like we have inline forms in django admin)
Edit
K thanks schillingt,
So according to ur answer below the have i modified the javascript, and html like below
{% for form in book_formset.forms %}
<div class="item">
<div class="with_name_design">{{ form.name }}</div>
{% if form.name.errors %}
<span>{{form.name.errors.0}}</span>
{% endif %}
<div class="with_age_design">{{ form.age }}</div>
{% if form.age.errors %}
<span>{{form.age.errors.0}}</span>
{% endif %}
</div>
{% endfor %}
and form has been rendered with errors after form validation
But i am facing the different issue as below
- When we click on
Add another item
button, a new form has been creating successfully. - And when we submitted the forms with
empty data
, thevalidation
error messages are displayed correctly below the respectivefields
Issue one
- And now when we try to
add another form
, all the previous forms including error messages are redisplaying again
like if we have two
forms and when we click on submit
without data, the validation error messages are generating for both the forms, and now immediately when we click on Add another item
, totally four forms
have been created, i mean the previously created two forms are repeated including the validation messages
Issue two
- So now immediately when we try to delete a form, i mean when we click on
delete
form button, all the forms(like4 forms
) in this case are deleted ?
So how can you please let me know how to solve this ?
回答1:
The problem is that the code to fetch all of the elements to change the prefix counter aren't actually selecting any elements.
Change in deleteForm:
forms.get(i)).children().children().each
to:
forms.get(i)).find('input,select,textarea').each
and change in addForm:
$(row).children().children().each
To:
$(row).find('input,select,textarea').each
This will select all of the elements that would be included in the form on the POST to the server.
来源:https://stackoverflow.com/questions/19589346/creating-a-django-form-in-formset-dynamically-like-inline-forms-in-django-admin