使用 django 嵌套表单
Posted
技术标签:
【中文标题】使用 django 嵌套表单【英文标题】:Nested forms with django 【发布时间】:2013-12-11 17:11:24 【问题描述】:我有需要使用以下模型实现嵌套 django 表单的功能
class Publisher(models.Model):
name = models.CharField(max_length=256)
address1 = models.CharField(max_length=256)
address2 = models.CharField(max_length=256)
city = models.CharField(max_length=256)
class Author(models.Model):
publisher = models.ForeignKey(Publisher)
name = models.CharField(max_length=256)
address = models.CharField(max_length=256)
class Book(models.Model):
author = models.ForeignKey(Author)
name = models.CharField(max_length=256)
price = models.FloatField()
forms.py
class PublisherForm(ModelForm):
class Meta:
model = Publisher
def __init__(self, *args, **kwargs):
super(PublisherForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs = 'id':'inputIcon', 'class':'input-block', 'placeholder':'Publisher Name', 'autofocus':'autofocus'
self.fields['address'].widget.attrs = 'id':'inputIcon', 'class':'input-block', 'placeholder':'Publisher Address '
class AuthorForm(ModelForm):
class Meta:
model = Author
exclude = ('publisher',)
def __init__(self, *args, **kwargs):
super(AuthorForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs = 'id':'inputIcon', 'class':'input-block', 'placeholder':'Author Name'
self.fields['address'].widget.attrs = 'id':'inputIcon', 'class':'input-block', 'placeholder':'Author Address'
class BookForm(ModelForm):
class Meta:
model = Book
exclude = ('author',)
def __init__(self, *args, **kwargs):
super(BookForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs = 'id':'inputIcon', 'class':'input-block', 'placeholder':'Book Name'
self.fields['price'].widget.attrs = 'id':'inputIcon', 'class':'input-block', 'placeholder':'Book Price'
所以对于上述模型和表单,我需要在同一屏幕上动态创建表单,就像下面的 UI 屏幕一样
所以从上面的屏幕中,我们可以观察到三个模型表单都应该显示在同一个页面上。
1. The publisher may have many authors
2. Each author may have many books
您也可以从设计中观察到,我们有两个按钮用于
1.Add Another Author - Adding Multiple Authors
2.Add Book - Adding multiple books for Author
2。添加图书
当我们点击添加图书时,应该会创建一个新的图书表单,如屏幕截图所示
1.添加其他作者
当我们点击Add another author
按钮时,应该会显示一个新的作者记录,他可以通过点击Add Book
为这个作者添加多本书,就像上面一样
如果我们只有两个模型 A 和 B,如果 B 有 ForeignKey
到 A,那么我们可以通过使用 django formsets or inline_formsets or model_formsets
来实现这个功能,但是在上面我们应该能够
-
为
Author
添加嵌套(多个)Book
表单
为发布者添加嵌套(多个)Author
表单
那么如何实现上面的功能呢?我搜了很多,还是没搞明白上面的东西
【问题讨论】:
【参考方案1】:这可以通过玩inline formsets来完成,在创建出版商的视图中,返回作者和书籍表单集(为每个表单使用不同的前缀参数),然后使用javascript添加新表单为书籍和空表单作者。
Bellow 是我为您编写的基本示例。
诀窍是使用 javascript 在模板中生成带有与父作者相关的动态表单前缀的书籍表单集(books_formset_0
、books_formset_1
、...),然后在 sumbit 表单上,迭代每个作者以找到相关的 book_formset。
运行和测试此代码的完整django项目可以下载here。
重要提示:以下代码尚未优化,也未使用一些标准工具,如 js 模板、ajax 等,但它可以工作并展示了如何解决问题。 em>
template.py:
<script type="text/javascript" src=" STATIC_URL js/jquery.js"></script>
<script type="text/javascript">
$(function ()
$('form').delegate('.btn_add_book', 'click', function ()
var $this = $(this)
var author_ptr = $this.attr('id').split('-')[1]
var $total_author_books = $(':input[name=books_formset_' + author_ptr + '-TOTAL_FORMS]');
var author_book_form_count = parseInt($total_author_books.val())
$total_author_books.val(author_book_form_count + 1)
var $new_book_form = $('<fieldset class="author_book_form">' +
'<legend>Book</legend>' +
'<p>' +
'<label for="id_books_formset_' + author_ptr + '-' + author_book_form_count + '-name">Name:</label>' +
'<input id="id_books_formset_' + author_ptr + '-' + author_book_form_count + '-name" maxlength="256" name="books_formset_' + author_ptr + '-' + author_book_form_count + '-name" type="text" />' +
'<input id="id_books_formset_' + author_ptr + '-' + author_book_form_count + '-author" name="books_formset_' + author_ptr + '-' + author_book_form_count + '-author" type="hidden" />' +
'<input id="id_books_formset_' + author_ptr + '-' + author_book_form_count + '-id" name="books_formset_' + author_ptr + '-' + author_book_form_count + '-id" type="hidden" />' +
'</p>' +
'</fieldset>'
)
$this.parents('.author_form').find('.author_books').prepend($new_book_form)
)
$('form').delegate('#btn_add_author', 'click', function ()
var $total_authors = $(':input[name=authors_formset-TOTAL_FORMS]');
author_form_count = parseInt($total_authors.val())
$total_authors.val(author_form_count + 1)
book_form = '<fieldset class="author_book_form">' +
'<legend>Book</legend>' +
'<p>' +
'<label for="id_books_formset_' + author_form_count + '-0-name">Name:</label>' +
'<input id="id_books_formset_' + author_form_count + '-0-name" maxlength="256" name="books_formset_' + author_form_count + '-0-name" type="text" />' +
'<input id="id_books_formset_' + author_form_count + '-0-author" name="books_formset_' + author_form_count + '-0-author" type="hidden" />' +
'<input id="id_books_formset_' + author_form_count + '-0-id" name="books_formset_' + author_form_count + '-0-id" type="hidden" />' +
'</p>' +
'</fieldset>';
$new_author_form = $(
'<fieldset class="author_form">' +
'<legend>Author</legend>' +
'<p>' +
'<label for="id_authors_formset-' + author_form_count + '-name">Name:</label>' +
'<input id="id_authors_formset-' + author_form_count + '-name" maxlength="256" name="authors_formset-' + author_form_count + '-name" type="text" />' +
'<input id="id_authors_formset-' + author_form_count + '-publisher" name="authors_formset-' + author_form_count + '-publisher" type="hidden" />' +
'<input id="id_authors_formset-' + author_form_count + '-id" name="authors_formset-' + author_form_count + '-id" type="hidden" />' +
'</p>' +
'<p><input type="button" value="Add Book" class="btn_add_book" id="author-' + author_form_count + '"/></p>' +
'<div class="author_books">' +
'<input id="id_books_formset_' + author_form_count + '-TOTAL_FORMS" name="books_formset_' + author_form_count + '-TOTAL_FORMS" type="hidden" value="1" />' +
'<input id="id_books_formset_' + author_form_count + '-INITIAL_FORMS" name="books_formset_' + author_form_count + '-INITIAL_FORMS" type="hidden" value="0" />' +
'<input id="id_books_formset_' + author_form_count + '-MAX_NUM_FORMS" name="books_formset_' + author_form_count + '-MAX_NUM_FORMS" type="hidden" value="1000" />' +
book_form +
'</div >' +
'</fieldset >'
)
$('#authors').prepend($new_author_form)
)
)
</script>
<h1>Add Publisher</h1>
<form action="" method="post">
% csrf_token %
form.as_p
<p><input type="button" id="btn_add_author" value="Add another author"/></p>
<div id="authors">
authors_formset.management_form
% for form in authors_formset %
<fieldset class="author_form">
<legend>Author</legend>
form.as_p
<p><input type="button" value="Add Book" class="btn_add_book" id="author- forloop.counter0 "/></p>
<div class="author_books">
books_formset.management_form
% for form in books_formset %
<fieldset class="author_book_form">
<legend>Book</legend>
form.as_p
</fieldset>
% endfor %
</div>
</fieldset>
% endfor %
</div>
<p><input type="submit" value="Save"></p>
</form>
forms.py:
AuthorInlineFormSet = inlineformset_factory(Publisher, Author, extra=1, can_delete=False)
BookInlineFormSet = inlineformset_factory(Author, Book, extra=1, can_delete=False)
views.py:
class PublisherCreateView(CreateView):
model = Publisher
def form_valid(self, form):
result = super(PublisherCreateView, self).form_valid(form)
authors_formset = AuthorInlineFormSet(form.data, instance=self.object, prefix='authors_formset')
if authors_formset.is_valid():
authors = authors_formset.save()
authors_count = 0
for author in authors:
books_formset = BookInlineFormSet(form.data, instance=author, prefix='books_formset_%s' % authors_count)
if books_formset.is_valid():
books_formset.save()
authors_count += 1
return result
def get_context_data(self, **kwargs):
context = super(PublisherCreateView, self).get_context_data(**kwargs)
context['authors_formset'] = AuthorInlineFormSet(prefix='authors_formset')
context['books_formset'] = BookInlineFormSet(prefix='books_formset_0')
return context
【讨论】:
你可以给我一个例子,这样它对其他用户也有用吗? 嘿 juliocesar,非常感谢,你太可爱了,我想你拯救了我的一天,我将查看上面的代码并评论任何疑问:) 我们也可以用表格代替模型吗?因为有一些设计模板,我们需要为字段提供一些自定义类和 id? 那么我们可以使用我在上面自定义的Forms
从普通的formsets
执行上述功能吗? ,因为我需要在渲染时将自定义类添加到字段中
对于上述代码中的书籍和作者模型字段,字段验证没有发生对吗?以上是关于使用 django 嵌套表单的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Postman 表单数据在 Django REST Framework 中发布嵌套数组?