使用基于类的通用视图DetailView与ModelForm会显示一个错误 - 如何继续? [英] Using class based generic view DetailView with a ModelForm reveals a bug - how to proceed?

查看:245
本文介绍了使用基于类的通用视图DetailView与ModelForm会显示一个错误 - 如何继续?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经深刻的印象了一个功能性网站可以与教程中的通用视图一起发展。此外,表单处理的工作流程很好。我使用ModelForm帮助器类从我制作的模型创建一个表单,很高兴看到这么多的功能在一起。当我使用通用的list_detail.object_detail时,我感到失望的是,我可以单独显示所有的字段。我知道ModelForm类包含了渲染的信息,所以我想使用一个通用视图的ModelForm。



我在stackoverflow上询问一些方向,并感谢几个海报的答案和评论。我已经弄清楚如何让这个工作,但在DetailView中有一个错误。解决方案包括解决方法。



要使用具有通用视图的ModelView,并获取所有字段,以自动呈现以下工作:



创建项目,并在其中创建应用程序住院患者。



如果您有

 #inpatients / models.py 

class住院病人(models.Model):
last_name = models.CharField(max_length = 30)
first_name = models.CharField(max_length = 30,blank = True)
address = models.CharField(max_length = 50,blank = True)
city = models.CharField(max_length = 60,blank = True)
state = models.CharField(max_length = 30,blank = True)
DOB = models.DateField(blank = True,null = True)
notes = models.TextField(blank = True)

def __unicode __(self):
return u'%s,%s%s'%(self.last_name,self.first_name,self.DOB)

类别InpatientForm(ModelForm):
class Meta:
model =住院人

 #inpatients / views.py 

from django .http import HttpResponse,HttpResponseRedirect
from django.shortcuts import render_to_response
from django.views.generic import DetailView
from portal.inpatients.models import *

def formtest (request):
如果request.method =='POST':
form = InpatientForm(request.POST)
如果form.is_valid():
form.save()
return HttpResponseRedirect('/ inpatients')
else:
form = InpatientForm()
return render_to_response(formtest.html,{'form':form})

class FormDetailView(DetailView):
model = Inpatient
context_object_name ='inpatient'#定义模板中的名称
template_name_field ='inpatient_list_page.html'

def get_object(self):
inpatient = super(FormDetailView,self).get_object()
form = InpatientForm(in stance = inpatient)
return form

def get_template_names(self):
return ['inpatient_list_page.html',]
pre>

 #urls.py 
$来自django.conf.urls.defaults导入模式的b $ b包含来自django.views.generic导入的url
从portal.inpatients.models导入的ListView
import门户网站中的InpatientForm
。 inpatients.views import formDetailView

urlpatterns = patterns('',
(r'^ formtest / $','portal.inpatients.views.formtest'),
(r '^ inpatient / $',ListView.as_view(
model = Inpatient,template_name ='inpatient_list_page.html')),
(r'^ inpatient-detail /(?P< pk> \d + )/ $',FormDetailView.as_view()),


#包含

{%block content%}
& H2>住院患者< / H2>
< ul>
{%forininatat in object_list%}
< li>< a href ='/ inpatient-detail / {{aninpatient.id}} /'>
{{aninpatient}},id = {{aninpatient.id}}< / a>< / li>
{%endfor%}
< / ul>
{{inpatient.as_p}}
{%endblock%}
#是的,一种hokey。该模板用于列表视图和详细视图。
#注意如何使用一行渲染表单 - {{inpatient.as_p}}

它的工作原理使用基于类的通用视图的说明生活在 https:// docs .djangoproject.com / en / 1.3 / topics / class-based-views / 说明很清楚。使事情发挥作用的关键是重新定义get_object。在执行额外工作部分的文档中,很好地描述了如何执行此操作,其步骤是调用原始版本的get_object,然后再调用额外的工作。我意识到的一点是返回对象可以是一个ModelForm对象。 get_object返回的对象直接进入渲染中的模板。通过采集检索到的住院对象并通过InpatientForm运行它,它可以作为一个窗体传递给视图,然后渲染自身。



关于错误:DetailView中的错误get_template_names函数是否尝试从不存在的结构中创建一个模板名称。
https://code.djangoproject我们在SingleObjectTemplateResponseMixin.get_template_names中的行127到140上的



  127#最不具体的选项是默认的< app> /< model> _detail.html; 
128#只有当对象是一个模型时,才使用它。
129 if hasattr(self.object,'_meta'):
130 names.append(%s /%s%s.html%(
131 self.object._meta。 app_label,
132 self.object._meta.object_name.lower(),
133 self.template_name_suffix
134))
135 elif hasattr(self,'model')和hasattr (self.model,'_meta'):
136 names.append(%s /%s%s.html%(
137 self.model._meta.app_label,
138 self.model._meta.object_name.lower(),
139 self.template_name_suffix
140))

错误是,第131行上的代码被执行,并且死亡,错误消息'ModelFormOptions'对象没有属性'app_label'>。我得出结论,定义了_meta对象。我想,问题是在ModelForm中定义了类Meta。那个Meta可能没有预期的字段设置。解决方法只是重写get_template_names并返回正确的模板。



我是Django和Python的新手。我感谢贡献者的答案和评论,提出以下问题。 (
将list_detail.object_list中的链接放在list_detail.object_detail 中,
在object_detail中使用表单
滚动自己的Django中的通用视图



如何报告错误?

解决方案

你是对的,我相信。这是一个错误,源于事实,ModelForm和Models都有_meta属性。从 get_object()返回一个对象返回包含_meta属性的任何时候,同样的错误会显示出来。



get_object 不必返回一个Model实例。您可以通过查看 DetailView 的来源并阅读它的docstring来确认:





< pre class =lang-py prettyprint-override> class DetailView(SingleObjectTemplateResponseMixin,BaseDetailView):

渲染对象的detail视图

默认情况下,这是一个从`self.queryset`查找的模型实例,但是
视图将支持通过覆盖self.get_object()来显示* any *对象

请注意,doc字符串显式表示覆盖self.get_object支持任何对象)



另一个确凿证据是从这个bug本身出现的位置,即$ code> get_template_names方法 SingleObjectTemplateResponseMixin

 #最不特定的选项是默认的<应用> /<模型> _detail.html; 
#只有当对象是一个模型时才使用这个。
if hasattr(self.object,'_meta'):
names.append(%s /%s%s.html%(
self.object._meta.app_label,
self.object._meta.object_name.lower(),
self.template_name_suffix
))
elif hasattr(self,'model')和hasattr(self.model,'_meta '):
names.append(%s /%s%s.html%(
self.model._meta.app_label,
self.model._meta.object_name.lower )
self.template_name_suffix
))

再次看这个代码,评论本身说如果对象是一个模型。从这个评论我们可以推断,对象并不总是一个模型。



但是,如果你想创建一个允许某人编辑/创建的视图/删除一个模型你真的应该看看编辑视图包括FormView,CreateView,EditView和DeleteView。您可以在 https:// docs.djangoproject.com/en/1.3/ref/class-based-views/#editing-views



回答有关如何报告错误,您应该遵循 https:// docs .djangoproject.com / en / 1.3 /内部/贡献/#报告错误


I've been impressed how rapidly a functional website can go together with generic views in the tutorials. Also, the workflow for form processing is nice. I used the ModelForm helper class to create a form from a model I made and was delighted to see that so much functionality came together. When I used the generic list_detail.object_detail I was disappointed that all that I could display were fields individually. I knew the ModelForm class contained information for rendering, so I wanted to use the ModelForm with a generic view.

I was asking around on stackoverflow to get some direction, and appreciate the answers and comments from several posters. I've figured out how to get this to work, but there is a bug in DetailView. The solution includes a workaround.

To use a ModelView with the generic view and get all the fields to render automatically the following works:

Create a project, and in it create application inpatients.

If you have

# inpatients/models.py

class Inpatient(models.Model):
    last_name = models.CharField(max_length=30)
    first_name = models.CharField(max_length=30,blank=True)
    address = models.CharField(max_length=50,blank=True)
    city = models.CharField(max_length=60,blank=True)
    state = models.CharField(max_length=30,blank=True)
    DOB = models.DateField(blank=True,null=True)
    notes = models.TextField(blank=True)

    def __unicode__(self):
        return u'%s, %s %s' % (self.last_name, self.first_name, self.DOB)

class InpatientForm(ModelForm):
    class Meta:
        model = Inpatient

and

# inpatients/views.py

from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.views.generic import DetailView
from portal.inpatients.models import *

def formtest(request):
    if request.method == 'POST':
        form = InpatientForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/inpatients')
    else:
        form = InpatientForm()
    return render_to_response("formtest.html", {'form': form})

class FormDetailView(DetailView):
    model=Inpatient
    context_object_name='inpatient'   # defines the name in the template
    template_name_field='inpatient_list_page.html'

    def get_object(self):
        inpatient=super(FormDetailView,self).get_object()
        form=InpatientForm(instance=inpatient)
        return form

    def get_template_names(self):
        return ['inpatient_list_page.html',]

and

#urls.py

from django.conf.urls.defaults import patterns, include, url
from django.views.generic import ListView
from portal.inpatients.models import Inpatient, InpatientForm
from portal.inpatients.views import FormDetailView

urlpatterns = patterns('',
    (r'^formtest/$','portal.inpatients.views.formtest'),
    (r'^inpatients/$', ListView.as_view(
        model=Inpatient, template_name='inpatient_list_page.html')),
    (r'^inpatient-detail/(?P<pk>\d+)/$', FormDetailView.as_view()),
)

# with a template containing

{% block content %}
    <h2>Inpatients</h2>
    <ul>
        {% for aninpatient in object_list %}
            <li><a href='/inpatient-detail/{{ aninpatient.id }}/'>
            {{ aninpatient }}, id={{ aninpatient.id }}</a></li>
        {% endfor %}
    </ul>
    {{ inpatient.as_p }}
{% endblock %}
# Yeah, kind of hokey. The template is for both the list view and detail view. 
# Note how the form is rendered with one line - {{ inpatient.as_p }}

it works. The instructions for using class based generic views lives at https://docs.djangoproject.com/en/1.3/topics/class-based-views/ Instructions there are pretty clear. The key to making things work is to redefine get_object. In the documentation under the section "Performing extra work" it nicely describes how to do this, the steps being to call the original version of get_object, and then to the extra work. The bit that I realized is that the return object can be a ModelForm object. The object that get_object returns goes straight into the template in a render. By taking the retrieved inpatient object and running it through InpatientForm it can be passed to a view as a form which then renders itself.

As to the bug: The bug in DetailView is that the get_template_names function tries to make a template name from a structure that does not exist. In https://code.djangoproject.com/browser/django/trunk/django/views/generic/detail.py on lines 127 to 140 we have within SingleObjectTemplateResponseMixin.get_template_names:

127        # The least-specific option is the default <app>/<model>_detail.html;
128         # only use this if the object in question is a model.
129         if hasattr(self.object, '_meta'):
130             names.append("%s/%s%s.html" % (
131                 self.object._meta.app_label,
132                 self.object._meta.object_name.lower(),
133                 self.template_name_suffix
134             ))
135         elif hasattr(self, 'model') and hasattr(self.model, '_meta'):
136             names.append("%s/%s%s.html" % (
137                 self.model._meta.app_label,
138                 self.model._meta.object_name.lower(),
139                 self.template_name_suffix
140             ))

The error is that the code on line 131 is executed and dies with error message <'ModelFormOptions' object has no attribute 'app_label'>. I conclude that the _meta object is defined. I suppose that the problem is that in a ModelForm the class Meta is defined. That Meta probably doesn't have the fields set that are expected. The workaround is just to rewrite get_template_names and return the correct template.

I'm new to Django and Python. I appreciate the answers and comments by the contributors at the following previous questions I asked. ( Putting links in list_detail.object_list to list_detail.object_detail, Using form in object_detail, Rolling your own generic views in Django)

What should I do to report the bug?

解决方案

You are right I believe. This is a bug which stems from the fact that both ModelForm and Models have a _meta attribute. This same bug would exhibit itself anytime an object is returned from get_object() that contains a _meta attribute.

get_object does not have to return a Model instance. You can confirm this by looking at the source for DetailView and reading it's docstring:

class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
    """
    Render a "detail" view of an object.

    By default this is a model instance looked up from `self.queryset`, but the
    view will support display of *any* object by overriding `self.get_object()`.
    """

Notice that the doc string explicitly says that any object is supported by overriding self.get_object().

Another piece of corroborating evidence is from the location where this bug itself occurs which is the get_template_names method of SingleObjectTemplateResponseMixin.

    # The least-specific option is the default <app>/<model>_detail.html;
    # only use this if the object in question is a model.
    if hasattr(self.object, '_meta'):
        names.append("%s/%s%s.html" % (
            self.object._meta.app_label,
            self.object._meta.object_name.lower(),
            self.template_name_suffix
        ))
    elif hasattr(self, 'model') and hasattr(self.model, '_meta'):
        names.append("%s/%s%s.html" % (
            self.model._meta.app_label,
            self.model._meta.object_name.lower(),
            self.template_name_suffix
        ))

Again looking at this code, the comment itself say "If the object in question is a model". From this comment we can infer that the object doesn't always have to be a model.

However if you are trying to create a view that allows someone to edit/create/delete a model you really should have a look at the Editing Views which include FormView, CreateView, EditView and DeleteView. You can see more information for these at https://docs.djangoproject.com/en/1.3/ref/class-based-views/#editing-views.

To answer the question as to how to report the bug, you should follow the guidelines detailed at https://docs.djangoproject.com/en/1.3/internals/contributing/#reporting-bugs.

这篇关于使用基于类的通用视图DetailView与ModelForm会显示一个错误 - 如何继续?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆