Django结合了DetailView和FormView

mik*_*725 20 django django-class-based-views

我有一个视图,我需要显示有关某个模型实例的信息,因此我使用了DetailView.我还需要相同的视图来处理常规表单(不是模型表单),同时显示表单GET并验证它POST.要做到这一点,我试图使用一个FormView但是两个视图clases的组合不起作用:

class FooView(FormView, DetailView):
    # configs here
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

在请求中,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))
Run Code Online (Sandbox Code Playgroud)

然而,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)
Run Code Online (Sandbox Code Playgroud)

正如你可以看到BaseDetailView.get永不调用super因此ProcessFormView.get永远不会被调用因此表单将不会被添加到上下文中.这可以通过创建所有这些细微之处的一个混合视图是固定的GET,并POST可以被照顾的,但是我不觉得这是一个干净的解决方案.

我的问题:有没有办法用Django的默认CBV实现完成我想要的东西而不创建任何mixins?

Ber*_*pac 32

一种解决方案是使用mixins,根据上面的limelights评论.

另一种方法是有两个单独的视图,一个是a DetailView,另一个是a FormView.然后,在前者的模板中,显示您在后者中使用的相同表单,除了您不会将该action属性保留为空 - 而是将其设置为该URL的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'
Run Code Online (Sandbox Code Playgroud)

my_detail_view.html:

<!-- some representation of the MyModel object -->

<form method="post" action="{% url "my_form_view_url" %}">

{{ form }}

</form>
Run Code Online (Sandbox Code Playgroud)

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'),
# ...
Run Code Online (Sandbox Code Playgroud)

请注意,require_POST装饰器是可选的,如果您不希望MyFormView可以访问GET它,并希望仅在提交表单时处理它.

  • 我担心你不是 - 这就是mixins的用途.:-) (2认同)

dyv*_*yve 12

Django还有一个关于这个问题的相当冗长的文档.

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

他们建议制作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)
Run Code Online (Sandbox Code Playgroud)


Adm*_*mir 6

通过使用 FormMixin

视图.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)
Run Code Online (Sandbox Code Playgroud)

表格.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
Run Code Online (Sandbox Code Playgroud)

网址.py

urlpatterns = [
    url(r'^view-profile/(?P<pk>\d+)', ViewProfile.as_view(), name='view-profile'),
]
Run Code Online (Sandbox Code Playgroud)

模板

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>
Run Code Online (Sandbox Code Playgroud)