如何在Django中使用通用类视图将父ID作为外键传递给子对象的ModelForm?

2 投票
3 回答
3973 浏览
提问于 2025-04-18 14:55

我正在尝试使用Django的通用类视图来构建一个CRUD(增删改查)界面,目的是管理一个包含两个模型的数据库。我已经成功创建了父模型的CRUD界面,但在实现子模型的创建时遇到了困难。为了和其他Django的例子保持一致,我们把父模型称为“作者”,子模型称为“书籍”。那么,最简单的方法是什么,让用户能够为一个作者添加书籍呢?

从HTML的角度来看,我想在作者的详细信息页面上做一个链接,这个链接里包含作者的ID,然后在书籍的表单中预先填入这个ID,最后在处理书籍表单时使用这个ID作为书籍的主键(PK)。但是我不太明白该如何用Django来实现这一点。我已经阅读了几个相关的文档和帖子,但它们似乎都在回答稍微不同的问题。

以下是相关的代码:

models.py

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

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

views.py

class BookCreate(CreateView):
    form_class = BookForm

    def get_success_url(self):
        return reverse('myapp:author_read',args=(self.object.author.pk))

forms.py

class BookForm(forms.Modelform):
    class Meta:
        model = Book

urls.py

url(r'^(?P<pk>\d+)/$', AuthorRead.as_view(), name='author_read'),
url(r'^book/create/(?P<author_id>\d+)/$', BookCreate.as_view(), name='book_create'),

templates/myapp/author_detail.html

...
<p><a href="{% url 'myapp:book_create' author_id=Author.pk %}">Add a book</a></p>
...

templates/myapp/book_form.html

<form action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Done">
</form>

问题

1) 我该如何将书籍页面URL中的作者ID传递到作者表单中,并正确处理?根据上面的示例代码,Django调试器显示它以这种方式存在:

View function            Arguments      Keyword arguments         URL name
myapp.views.BookCreate   ()             {'author_id': u'1234'}    book_create

但我不明白该如何从...上下文中提取这个变量,并将其放入表单中。

1a) 我可以把它做成一个URL参数,而不是URL的一部分,比如说 book/create?author=1234 而不是 book/create/1234/ 吗?或者干脆把整个请求做成POST,这样就不在URL里了?哪种做法更好,怎么实现?

2) 一旦变量在表单中,如何让它作为一个隐藏输入存在,这样用户就看不见它了?

3 个回答

0

我遇到过类似的情况,在按照接受的答案步骤操作时,碰到了两个错误 (我使用的是Python 2.7)

  1. 对象没有'fields'这个属性,这个问题是通过参考一个类似问题的答案解决的: https://stackoverflow.com/a/8928501/2097023,来自@scotchandsoda:

...self.fields 应该在调用 super(...) 之前放置。

def __init__(self, users_list, **kw):
    super(BaseWriteForm, self).__init__(**kw)
    self.fields['recipients'].queryset = User.objects.filter(pk__in=users_list)
  1. 对象没有'get'这个属性,这个问题是通过参考这个答案解决的: https://stackoverflow.com/a/36951830/2097023,来自@luke_dupin:

...这个错误也可能是因为在表单的初始化时错误地传递了参数,而这个表单是用来管理模型的。

举个例子:

class MyForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(self, *args, **kwargs)

注意到self被重复传递了吗?应该是:

class MyForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
0

你可以把作者的ID传递给表单,这里有一些步骤可以参考:

class BookForm(forms.Modelform):
    author = None

    class Meta:
        model = Book

    def __init__(self, *args, **kwargs):
        self.author = kwargs.pop('author')
        super(BookForm, self).__init__(*args, **kwargs)

    def save(self, commit=True):
        # your custom save (returns book)


class BookCreate(CreateView):
    form_class = BookForm

    def get_form_kwargs(self):
        kwargs = super(BookCreate, self).get_form_kwargs()
        kwargs['author'] = # pass your author object

        return kwargs
2

在你定义的 author_detail.html 这个网址里,作者的 ID 变量可以通过 self.kwargs['author_id'] 在视图中访问到。

# views.py
class BookCreate(CreateView):
...
def form_valid(self, form):
    book = form.save(commit=False)
    author_id = form.data['author_id']
    author = get_object_or_404(Author, id=author_id)
    book.author = author
    return super(BookCreate, self).form_valid(form)
...
def get_context_data(self, **kwargs):
    context = super(BookCreate, self).get_context_data(**kwargs)
    context['a_id'] = self.kwargs['author_id']
    return context

然后在你的表单中,你可以添加:

class BookForm(forms.Modelform):
    ...
    def __init__(self, *args, **kwargs):
        self.fields["author_id"] = forms.CharField(widget=forms.HiddenInput())
        super(BookForm, self).__init__(self, *args, **kwargs)

接着在模板中:

  <input type=hidden name="author_id" value="{{ a_id }}">

在视图中的 form_valid 方法里,应该获取这个 ID,找到对应的作者,并把这个作者设置为书籍的作者。使用 commit=False 是为了在你设置作者之前,先不把模型保存到数据库里,而调用 super 方法会导致 form.save(commit=True) 被执行。

撰写回答