如何在Django中使用通用类视图将父ID作为外键传递给子对象的ModelForm?
我正在尝试使用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 个回答
我遇到过类似的情况,在按照接受的答案步骤操作时,碰到了两个错误 (我使用的是Python 2.7):
- 对象没有'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)
- 对象没有'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)
你可以把作者的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
在你定义的 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)
被执行。