【问题标题】:inlineformset_factory create new objects and edit objects after createdinlineformset_factory 创建新对象并在创建后编辑对象
【发布时间】:2015-06-27 19:24:14
【问题描述】:

在 django 文档中,有一个使用 inlineformset_factory 编辑已经创建的对象

的示例

https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#using-an-inline-formset-in-a-view

我把例子改成这样:

def manage_books(request):
    author = Author()
    BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
    if request.method == "POST":
        formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
        if formset.is_valid():
            formset.save()
            return HttpResponseRedirect(author.get_absolute_url())
    else:
        formset = BookInlineFormSet(instance=author)
    return render_to_response("manage_books.html", {
        "formset": formset,
    })

使用上面的方法,它只渲染内联模型而没有父模型。

要使用 inlineformset_factory 创建一个新对象,比如 Author,关联多本书,方法是什么?

使用上述来自 django 文档的 Author Book 模型的示例将很有帮助。 django 文档仅提供了如何使用 inlineformset_factory 编辑已经创建的对象的示例,而不是创建新对象

【问题讨论】:

  • @onyeka 我已经去过那里了。按照步骤进行。仍然在没有父模型的情况下渲染。我已经尝试了 django 文档中的确切示例,看看我是否可以编辑已经存在的对象。我仍然只渲染了 Book 模型。在视图和外壳中都尝试过,仍然只吐出 Book 模型。这个答案是从 2011 年开始的。我想从那时起很多事情都发生了变化,即使 django 文档对该 inlineformset_factory 有点模棱两可

标签: django inline-formset


【解决方案1】:

一开始我没有正确阅读您的问题。您还需要为父模型呈现表单。我没有对此进行测试,我将放弃我之前做过的事情和之前链接的答案,但它应该可以工作。

更新

如果您同时使用视图进行编辑,则应首先检查作者 ID。如果没有 ID,它会将两个表单都呈现为新实例,而如果有 ID,它将用现有数据填充它们。然后你可以检查是否有 POST 请求。

def manage_books(request, id):

    if id:
        author = Author.objects.get(pk=author_id)  # if this is an edit form, replace the author instance with the existing one
    else: 
        author = Author()
    author_form = AuthorModelForm(instance=author) # setup a form for the parent

    BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
    formset = BookInlineFormSet(instance=author)

    if request.method == "POST":
        author_form = AuthorModelForm(request.POST)

        if id: 
            author_form = AuthorModelForm(request.POST, instance=author)

        formset = BookInlineFormSet(request.POST, request.FILES)

        if author_form.is_valid():
            created_author = author_form.save(commit=False)
            formset = BookInlineFormSet(request.POST, request.FILES, instance=created_author)

            if formset.is_valid():
                created_author.save()
                formset.save()
                return HttpResponseRedirect(created_author.get_absolute_url())

    return render_to_response("manage_books.html", {
        "author_form": author_form,
        "formset": formset,
    })

【讨论】:

  • 刚试过。首先,应该是 else 有一个错字: author_form = AuthorModelForm() 我试过了,仍然没有渲染父模型。在 IRC 中,有人建议使用 modelform_factory。有什么想法吗?
  • 我编辑了我的答案。主窗体应该有一个实例,对不起。我只是自己尝试了这段代码(但是没有 POST 部分),它呈现了两种形式。
  • 啊,对不起。再次(我的交易是什么)。我搞砸了。尝试编辑。
  • 终于,工作了!事实上,formset 的方式很容易漏掉一些东西。不过我没注意到。谢谢
【解决方案2】:

我做了你正在尝试的事情: https://github.com/yakoub/django_training/tree/master/article

您需要使用前缀属性创建一个单独的表单。 然后,当您保存时,您需要遍历所有书籍并将它们与您刚刚创建的作者相关联。

【讨论】:

  • 无法让您的工作。我得到一个没有任何错误的空白屏幕。我克隆了你的仓库,但找不到你的 manage.py。它在哪里?
  • 我不明白你为什么需要 manage.py ?如果出现空白屏幕,请打开调试并查看收到的错误。
【解决方案3】:

根据 Onyeka 提供的广泛助手,我正在发布我的最终解决方案。

下面我使用 Django 文档中的 Author and Book 示例发布使用 Django 的 inlineformset_factory 的添加和编辑解决方案。

首先,添加作者对象,附加3个Book对象。

显然,这会进入您的 views.py

def add_author(request):
    '''This function creates a brand new Author object with related Book objects using inlineformset_factory'''
    author = Author()
    author_form = AuthorModelForm(instance=author) # setup a form for the parent
BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))

if request.method == "POST":
    author_form = AuthorModelForm(request.POST)
    formset = BookInlineFormSet(request.POST, request.FILES)

    if author_form.is_valid():
        created_author = author_form.save(commit=False)
        formset = BookInlineFormSet(request.POST, request.FILES, instance=created_author)

        if formset.is_valid():
            created_author.save()
            formset.save()
            return HttpResponseRedirect(created_author.get_absolute_url())
else:
    author_form = AuthorModelForm(instance=author)
    formset = BookInlineFormSet()

return render(request, "add_author.html", {
    "author_form": author_form,
    "formset": formset,
})


def edit_author(request, author_id):
    '''This function edits an Author object and its related Book objects using inlineformset_factory'''
    if id:
        author = Author.objects.get(pk=author_id)  # if this is an edit form, replace the author instance with the existing one
    else:
        author = Author()
    author_form = AuthorModelForm(instance=author) # setup a form for the parent
BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
formset = BookInlineFormSet(instance=author)

if request.method == "POST":
    author_form = AuthorModelForm(request.POST)

    if id:
        author_form = AuthorModelForm(request.POST, instance=author)

    formset = BookInlineFormSet(request.POST, request.FILES)

    if author_form.is_valid():
        created_author = author_form.save(commit=False)
        formset = BookInlineFormSet(request.POST, request.FILES, instance=created_author)

        if formset.is_valid():
            created_author.save()
            formset.save()
            return HttpResponseRedirect(created_author.get_absolute_url())

return render(request, "edit_author.html", {
    "author_id": author_id, # This author_id is referenced 
                            # in template for constructing the posting url via {% url %} tag
    "author_form": author_form,
    "formset": formset,
})

这部分进入你的 urls.py,假设视图已经被导入,并且 urlpatterns 已经构建。

...
    url(r'^add/book/$', views.add_author, name='add_author'),
    url(r'^edit/(?P<author_id>[\d]+)$', views.edit_author, name='edit_author'),
...

现在到模板部分。编辑作者对象模板 (edit_author.html) 如下所示(未应用样式)

<form action="{% url 'edit_book' author_id %}" method="POST" >
<!-- See above: We're using the author_id that was passed to template via views render of the edit_author(...) function -->
{% csrf_token %} <!-- You're dealing with forms. csrf_token must come -->
{{ author_form.as_p }}
{{ formset.as_p }}
<input type="submit" value="submit">
</form>

通过模板(add_author.html)添加一个全新的作者对象:

<form action="." method="POST" >{% csrf_token %}
{{ author_form.as_p }}
{{ formset.as_p }}
<input type="submit" value="submit">
</form>

注意:

使用动作='.'可能看起来是构建 url 的一种廉价方式,表单将表单数据发布到同一页面。在这个例子中,使用 action='.'对于 edit_author.html 模板,总是将表单发布到 /edit/ 而不是 /edit/1 或 /edit/2

使用 {% url 'edit_author' author_id %} 构造 url 可确保表单始终发布到正确的 url。未能使用 {% url %} 花费了我很多时间和麻烦。

非常感谢 Onyeka。

【讨论】:

  • 您可能希望重新访问您的论点:使用操作='。'可能看起来是构建 url 的一种廉价方式,表单将表单数据发布到同一页面。在这个例子中,使用 action='.'对于 edit_author.html 模板,总是将表单发布到 /edit/ 而不是 /edit/1 或 /edit/2。很可疑。
【解决方案4】:

我已经使用 Django 基于类的视图做到了这一点。

这是我的方法:

models.py

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)


class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=100)

forms.py

from django.forms import ModelForm
from django.forms.models import inlineformset_factory

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset

from .models import Author, Book

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', )

    @property
    def helper(self):
        helper = FormHelper()
        helper.form_tag = False # This is crucial.

        helper.layout = Layout(
            Fieldset('Create new author', 'name'),
        )

        return helper


class BookFormHelper(FormHelper):
    def __init__(self, *args, **kwargs):
        super(BookFormHelper, self).__init__(*args, **kwargs)
        self.form_tag = False
        self.layout = Layout(
            Fieldset("Add author's book", 'title'),
        )


BookFormset = inlineformset_factory(
    Author,
    Book,
    fields=('title', ),
    extra=2,
    can_delete=False,
)

views.py

from django.views.generic import CreateView
from django.http import HttpResponseRedirect

from .forms import AuthorForm, BookFormset, BookFormHelper
from .models import Book, Author

class AuthorCreateView(CreateView):
    form_class = AuthorForm
    template_name = 'library/manage_books.html'
    model = Author
    success_url = '/'

    def get(self, request, *args, **kwargs):
        self.object = None
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        book_form = BookFormset()
        book_formhelper = BookFormHelper()

        return self.render_to_response(
            self.get_context_data(form=form, book_form=book_form)
        )

    def post(self, request, *args, **kwargs):
        self.object = None
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        book_form = BookFormset(self.request.POST)

        if (form.is_valid() and book_form.is_valid()):
            return self.form_valid(form, book_form)

        return self.form_invalid(form, book_form)

    def form_valid(self, form, book_form):
        """
        Called if all forms are valid. Creates a Author instance along
        with associated books and then redirects to a success page.
        """
        self.object = form.save()
        book_form.instance = self.object
        book_form.save()

        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form, book_form):
        """
        Called if whether a form is invalid. Re-renders the context
        data with the data-filled forms and errors.
        """
        return self.render_to_response(
            self.get_context_data(form=form, book_form=book_form)
        )

    def get_context_data(self, **kwargs):
        """ Add formset and formhelper to the context_data. """
        ctx = super(AuthorCreateView, self).get_context_data(**kwargs)
        book_formhelper = BookFormHelper()

        if self.request.POST:
            ctx['form'] = AuthorForm(self.request.POST)
            ctx['book_form'] = BookFormset(self.request.POST)
            ctx['book_formhelper'] = book_formhelper
        else:
            ctx['form'] = AuthorForm()
            ctx['book_form'] = BookFormset()
            ctx['book_formhelper'] = book_formhelper

        return ctx

urls.py

from django.conf.urls import patterns, url
from django.views.generic import TemplateView

from library.views import AuthorCreateView

urlpatterns = patterns('',
    url(r'^author/manage$', AuthorCreateView.as_view(), name='handle-books'),
    url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'),
)

ma​​nage_books.html

{% load crispy_forms_tags %}

<head>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet"
    href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head>

<div class='container'>
  <form method='post'>
    {% crispy form %}
    {{ book_form.management_form }}
    {{ book_form.non_form_errors }}

    {% crispy book_form book_formhelper %}
    <input class='btn btn-primary' type='submit' value='Save'>
  </form>
<div>

注意:

  • 这是一个使用inlineformset_factory 的简单可运行示例 特性和 Django 通用的基于类的视图
  • 我假设 django-crispy-forms 已安装,并且是正确的 已配置。
  • 代码库托管在:https://bitbucket.org/slackmart/library_example

我知道显示的解决方案代码更多,但开始使用 Django 基于类的视图很棒。

【讨论】:

  • 没有。没关系。我也喜欢 cbv 方法。我认为最终,这应该提供最短、最简单的方法来完成这种内联表单集。感谢您的输入。目前使用基于函数的方法,但我下一次尝试使用 inlineformsets,将尝试使用 cbv 方法。
猜你喜欢
  • 2011-01-22
  • 1970-01-01
  • 2014-06-22
  • 1970-01-01
  • 1970-01-01
  • 2015-03-31
  • 1970-01-01
  • 2020-02-27
  • 1970-01-01
相关资源
最近更新 更多