使用 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_0books_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 嵌套表单的主要内容,如果未能解决你的问题,请参考以下文章

django 脆表单:在表单中嵌套表单集

如何使用 Postman 表单数据在 Django REST Framework 中发布嵌套数组?

Django Rest Framework 序列化器作为表单和嵌套关系

如何在 Django 的表单中拥有嵌套的内联表单集?

从 Django 模板中的嵌套列表生成表

如何在 django 模板中使用嵌套的 forloop 生成表时对 t 行进行编号