【问题标题】:Django combine DetailView and FormViewDjango 结合 DetailView 和 FormView
【发布时间】:2013-05-31 16:18:23
【问题描述】:

我有一个视图,我需要在其中显示有关某个模型实例的信息,因此我使用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 视图来解决,其中可以处理 GET 和 POST 的所有这些细微差别,但我不认为这是一个干净的解决方案。

我的问题:有没有什么方法可以在不创建任何 mixins 的情况下使用 Django 的默认 CBV 实现来完成我想要的?

【问题讨论】:

标签: django django-class-based-views


【解决方案1】:

根据上面的聚光灯评论,一种解决方案是使用 mixins。

另一种方法是有两个单独的视图,一个是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 装饰器是可选的,如果您不希望 MyFormView 可被 GET 访问,并且希望仅在提交表单时对其进行处理。

【讨论】:

  • 这是一个有效的解决方案,但是我希望我在内置 CBV 实现中遗漏了一些微不足道的东西,这将使我能够做到这一点。
  • 恐怕你不是——这就是 mixins 的用途。 :-)
【解决方案2】:

Django 也有关于这个问题的相当长的文档。

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

他们建议制作2个不同的视图,并让详细视图参考帖子上的表单视图。

我目前正在查看此 hack 是否可行:

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)

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      在 lightbird 的 Django By Example 中,他们使用库 MCBV 来混合通用视图:

      我的指南的教程将使用基于修改后的 Django 通用视图的基于类的视图库;该库称为 MCBV(M 代表模块化),与通用 CBV 相比的主要区别在于可以轻松混合和匹配多个通用视图(例如 ListView 和 CreateView、DetailView 和 UpdateView 等)

      你可以按照这里的解释:helper-functions

      并用它来混合 FormView 和 DetailView,或者其他什么

      代码:MCBV

      【讨论】:

        【解决方案5】:

        我使用 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)
        

        【讨论】:

          【解决方案6】:

          这是一个旧帖子,但很好的参考。

          一个优雅且可重复使用的方法是为表单使用自定义包含标签。

          模板标签/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 中

          【讨论】:

            猜你喜欢
            • 2017-05-08
            • 2014-07-07
            • 1970-01-01
            • 2017-05-30
            • 2022-11-06
            • 2013-05-19
            • 2014-01-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多