我有一个视图,我需要显示有关某个模型实例的信息,因此我使用DetailView
。我还需要相同的视图来处理常规表单(不是模型表单),同时在GET
上显示表单并在POST
上验证它。为此,我尝试使用FormView
,但两个视图clases的组合不起作用:
class FooView(FormView, DetailView):
# configs here
在GET
中(为了简化问题,我只会显示问题GET
,因为POST
有不同的问题),它不起作用,因为表单永远不会被添加到上下文。原因与该类的方法解析顺序有关:
>>> inspect.getmro(FooView)
(FooView,
django.views.generic.edit.FormView,
django.views.generic.detail.DetailView,
django.views.generic.detail.SingleObjectTemplateResponseMixin,
django.views.generic.base.TemplateResponseMixin,
django.views.generic.edit.BaseFormView,
django.views.generic.edit.FormMixin,
django.views.generic.detail.BaseDetailView,
django.views.generic.detail.SingleObjectMixin,
django.views.generic.base.ContextMixin,
django.views.generic.edit.ProcessFormView,
django.views.generic.base.View,
object)
在请求中,Django必须获取表单并将其添加到上下文中。这发生在ProcessFormView.get
:
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
然而,定义get
的MRO的第一个类是BaseDetailView
:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
正如您所见,BaseDetailView.get
从不调用super
,因此永远不会调用ProcessFormView.get
,因此表单不会添加到上下文中。这可以通过创建一个mixin视图来解决,其中可以处理GET
和POST
的所有细微差别,但我觉得这不是一个干净的解决方案。
我的问题:有没有办法用Django的默认CBV实现完成我想要的东西而不创建任何mixins?
答案 0 :(得分:29)
一种解决方案是使用mixins,根据上述评论。
另一种方法是拥有两个单独的视图,一个是DetailView
,另一个是FormView
。然后,在前者的模板中,显示您在后者中使用的相同表单,除了您不会将action
属性留空 - 而是将其设置为{{1的url }}。有点像这样的事情(请注意任何错误,因为我在没有任何测试的情况下写这个):
在FormView
:
views.py
在class MyDetailView(DetailView):
model = MyModel
template_name = 'my_detail_view.html'
def get_context_data(self, **kwargs):
context = super(MyDetailView, self).get_context_data(**kwargs)
context['form'] = MyFormClass
return context
class MyFormView(FormView):
form_class = MyFormClass
success_url = 'go/here/if/all/works'
:
my_detail_view.html
在<!-- some representation of the MyModel object -->
<form method="post" action="{% url "my_form_view_url" %}">
{{ form }}
</form>
:
urls.py
请注意# ...
url('^my_model/(?P<pk>\d+)/$', MyDetailView.as_view(), name='my_detail_view_url'),
url('^my_form/$', require_POST(MyFormView.as_view()), name='my_form_view_url'),
# ...
装饰器是可选的,如果您不希望require_POST
可以MyFormView
访问,并希望它仅在表单处理时处理提交。
答案 1 :(得分:12)
Django还有一个关于这个问题的相当冗长的文档。
他们建议制作2个不同的视图,并让详细视图参考帖子上的表单视图。
我目前正在看这个黑客是否可行:
class MyDetailFormView(FormView, DetailView):
model = MyModel
form_class = MyFormClass
template_name = 'my_template.html'
def get_context_data(self, **kwargs):
context = super(MyDetailFormView, self).get_context_data(**kwargs)
context['form'] = self.get_form()
return context
def post(self, request, *args, **kwargs):
return FormView.post(self, request, *args, **kwargs)
答案 2 :(得分:2)
使用FormMixin
views.py
from django.contrib.auth import get_user_model
from django.core.urlresolvers import (
reverse_lazy
)
from django.http import Http404
from django.shortcuts import (
render,
redirect
)
from django.views.generic import (
DetailView,
FormView,
)
from django.views.generic.edit import FormMixin
from .forms import SendRequestForm
User = get_user_model()
class ViewProfile(FormMixin, DetailView):
model = User
context_object_name = 'profile'
template_name = 'htmls/view_profile.html'
form_class = SendRequestForm
def get_success_url(self):
return reverse_lazy('view-profile', kwargs={'pk': self.object.pk})
def get_object(self):
try:
my_object = User.objects.get(id=self.kwargs.get('pk'))
return my_object
except self.model.DoesNotExist:
raise Http404("No MyModel matches the given query.")
def get_context_data(self, *args, **kwargs):
context = super(ViewProfile, self).get_context_data(*args, **kwargs)
profile = self.get_object()
# form
context['form'] = self.get_form()
context['profile'] = profile
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
#put logic here
return super(ViewProfile, self).form_valid(form)
def form_invalid(self, form):
#put logic here
return super(ViewProfile, self).form_invalid(form)
forms.py
from django import forms
class SendRequestForm(forms.Form):
request_type = forms.CharField()
def clean_request_type(self):
request_type = self.cleaned_data.get('request_type')
if 'something' not in request_type:
raise forms.ValidationError('Something must be in request_type field.')
return request_type
urls.py
urlpatterns = [
url(r'^view-profile/(?P<pk>\d+)', ViewProfile.as_view(), name='view-profile'),
]
模板
username -{{object.username}}
id -{{object.id}}
<form action="{% url 'view-profile' object.id %}" method="POST">
{% csrf_token %}
{{form}}
<input type="submit" value="Send request">
</form>
答案 3 :(得分:1)
在Lightbird中的Django示例中,他们使用库MCBV来混合通用视图:
我的指南教程将使用基于类修改的Django通用视图的基于类的视图库;该库名为MCBV(M代表模块化),与通用CBV相比的主要区别在于可以轻松混合和匹配多个通用视图(例如ListView和CreateView,DetailView和UpdateView等)。
您可以按照此处的说明进行操作:helper-functions
并使用它来混合FormView和DetailView,或其他任何
代码:MCBV
答案 4 :(得分:0)
我使用ModelForms执行了我的解决方案,如下所示: 在我的DetailView的方法get_context_data上我做了:
class SchoolComment(FormView):
form_class = CommentForm
def get_success_url(self):
return resolve_url('schools:school-profile', self.kwargs.get('pk'))
def form_valid(self, form):
form.save()
return super(SchoolComment, self).form_valid(form)
我的FormView就像:
{{1}}
答案 5 :(得分:0)
那是一篇旧文章,但值得参考。
一种优雅且可重复使用的方法是为表单使用自定义包含标签。
templatetags / my_forms_tag.py
from django import template
from ..forms import MyFormClass
register = template.Library()
@register.inclusion_tag('<app>\my_form.html')
def form_tag(*args, **kwargs):
my_form = MyFormClass()
return {'my_form ':my_form}
my_form.html
<form method="post" action="{% url "my_form_view_url" %}">
{{ form }}
</form>
如果您放置了包含标签,则该视图将由FormView承担。它可以接收您在包含中传递的任何上下文。不要忘记加载my_form_tag并为MyForm创建视图,并将条目包括在urls.py
中