在formset中动态创建一个django表单,如django admin中的内联表单和jquery
我有两个模型Publisher
和Book
如下
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
在此视图中,我将发送发布者ID,因为Book
模型没有外部Key to Publisher模型
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
因此,在下面的模板中,将表单呈现为form.as_p
其工作正常,并且多个记录成功创建到该publisher id
$(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 // eg 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'); }); });
但是当我从表单中显示html字段并渲染如下
{% for form in book_formset.forms %} {{ form.name }} {% if form.name.errors %} {{form.name.errors}} {% endif %} {{ form.age }} {% if form.age.errors %} {{form.age.errors}} {% endif %} {% endfor %}
表单显示成功,当我点击链接Add another item
,新表单正在生成上面的jquery,当我尝试通过输入所有细节提交并点击提交时,jquery添加的下一个表单是显示validation错误,如name, age is required
?(这只发生在这种情况下,单独显示字段而不是form.as_p(),如果我们渲染为form.as_p()它的工作正常和记录正在创建到数据库)
所以当我将表单呈现为form.as_p()
,我真的无法弄清楚为什么它成功了,为什么不在我individual fields with their errors
渲染individual fields with their errors
我错过了上述javascript代码中生成其他forms的任何/需要和任何内容吗?
因为当我们单独渲染字段时,单击“ 添加另一个表单”按钮生成的表单是否显示validation错误?
我真的浪费了很多时间来搞清楚上面的javascript,因为我通过谷歌搜索得到了一些,
最后,当我们将formset表单呈现为form.as_p()
,上面的functionlality正在工作,但是当我们单独呈现表单字段时,为什么上述function不起作用?
任何人都可以让我知道如何解决上述问题(也许上面的代码将很有用,形成许多用户动态创建表单,就像我们在django管理员中有内联表单)
编辑
K谢谢schillingt,
所以根据你的回答下面我已经修改了javascript和html如下
{% for form in book_formset.forms %} {{ form.name }} {% if form.name.errors %} {{form.name.errors.0}} {% endif %} {{ form.age }} {% if form.age.errors %} {{form.age.errors.0}} {% endif %} {% endfor %}
form validation
后, form validation
已呈现错误
但我面临的问题如下
- 当我们单击“
Add another item
按钮时,表单已成功创建。 - 当我们使用
empty data
提交表单时,validation
错误消息会在相应fields
下方正确显示
问题一
- 现在,当我们尝试
add another form
,所有以前的表单(包括错误消息)都会再次重新显示
就好像我们有two
表单,当我们点击submit
没有数据时,两个表单都会生成validation错误消息,现在我们点击Add another item
,总共创建了four forms
,我的意思是先前创建的两个four forms
表格重复,包括validation信息
问题二
- 所以现在我们立即尝试删除表单时,我的意思是当我们点击
delete
表单按钮时,这种情况下的所有表单(如4 forms
)都被删除了吗?
那么你怎么能让我知道如何解决这个问题?
问题是获取所有元素以更改前缀计数器的代码实际上并没有选择任何元素。
deleteForm中的更改:
forms.get(i)).children().children().each
至:
forms.get(i)).find('input,select,textarea').each
并在addForm中更改:
$(row).children().children().each
至:
$(row).find('input,select,textarea').each
这将选择POST中包含在表单中的所有元素到服务器。