Django如何结合DetailView和FormView?

24

我有一个视图,需要显示有关特定模型实例的信息,因此我使用 DetailView。 我还需要该视图处理常规表单(不是模型表单),在GET上显示表单并在POST上验证它。 为此,我尝试使用 FormView,但两种视图类的组合无法工作:

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 视图来解决此问题,在其中处理所有这些关于 GETPOST 的细节,但我认为这不是一个干净的解决方案。

我的问题是:是否有一种方式可以在 Django 的默认 CBV 实现中实现我想要的功能而不创建任何 Mixin?


3
为什么不想使用mixin?https://docs.djangoproject.com/en/dev/topics/class-based-views/mixins/#using-formmixin-with-detailview - Henrik Andersson
这似乎是一个足够简单的任务,内置的CBV应该可以处理。 - miki725
6个回答

37

一种解决方法是使用mixin, 如上面limelights的评论所述。

另一种方法是拥有两个独立的视图,一个是DetailView,另一个是FormView。然后在前者的模板中,显示与后者相同的表单,但不要将action属性留空,而是将其设置为FormView的URL。参考以下示例代码(请注意,我没有进行任何测试,可能存在错误):

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 装饰器是可选的。如果您不希望通过 GET 访问 MyFormView 并且只希望在提交表单时处理它,则可以不使用该装饰器。

1
这是一个有效的解决方案,但我希望在内置的CBV实现中漏掉了一些微不足道的东西,可以让我做到这一点。 - miki725
2
恐怕您不是-这就是 mixin 的用途。 :-) - Berislav Lopac

20

Django也对这个问题有详细的文档。

https://docs.djangoproject.com/en/1.8/topics/class-based-views/mixins/#using-formmixin-with-detailview

他们建议创建两个不同的视图,在提交时,让详细视图引用表单视图。

我正在尝试看看这种方法是否可行:

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)

7
通过使用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>

1
在lightbird的Django By Example中,他们使用了一个名为MCBV的库来混合通用视图:
“我的指南教程将使用基于修改后的Django通用视图的类视图库;该库称为MCBV(M代表模块化),与通用CBV相比的主要区别是可以轻松地混合和匹配多个通用视图(例如ListView和CreateView,DetailView和UpdateView等)”
您可以在这里查看解释:helper-functions 并将其用于混合FormView和DetailView,或其他任何内容
代码:MCBV

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 中包含该条目。


0

我使用了ModelForms实现我的解决方案,类似于以下内容: 在我的DetailView的get_context_data方法中,我执行了:

form = CommentForm(
        instance=Comment(
            school=self.object, user=self.request.user.profile
        )
    )
    context['form'] = form

我的 FormView 如下:

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接