将基于类的通用视图 DetailView 与 ModelForm 一起使用会发现一个错误 - 如何继续?

发布于 2024-11-18 18:37:59 字数 5125 浏览 6 评论 0原文

一个功能性网站能够如此快速地与教程中的通用视图结合在一起,给我留下了深刻的印象。此外,表单处理的工作流程也很好。我使用 ModelForm 帮助程序类从我制作的模型创建表单,并且很高兴看到如此多的功能组合在一起。当我使用通用的 list_detail.object_detail 时,我很失望,我可以显示的只是单独的字段。我知道 ModelForm 类包含用于渲染的信息,因此我想将 ModelForm 与通用视图一起使用。

我在 stackoverflow 上四处询问以获得一些指导,并感谢几位发帖者的回答和评论。我已经弄清楚如何让它工作,但 DetailView 中有一个错误。该解决方案包括一个解决方法。

要将 ModelView 与通用视图结合使用并让所有字段自动呈现,请执行以下操作:

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

如果你有

# 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

并且

# 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',]

#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 }}

有效。使用基于类的通用视图的说明位于 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.com/browser/ django/trunk/django/views/generic/detail.py 在第 127 行到第 140 行,我们在 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             ))

错误是第 131 行的代码被执行并终止,并显示错误消息 <'ModelFormOptions' object has no attribute 'app_label'>。我的结论是 _meta 对象已定义。我认为问题是在 ModelForm 中定义了 Meta 类。该元可能没有设置预期的字段。解决方法只是重写 get_template_names 并返回正确的模板。

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

我应该如何报告该错误?

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/

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?

,'portal.inpatients.views.formtest'), (r'^inpatients/

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:


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?

, ListView.as_view( model=Inpatient, template_name='inpatient_list_page.html')), (r'^inpatient-detail/(?P<pk>\d+)/

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:


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?

, 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:

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

若水微香 2024-11-25 18:37:59

我相信你是对的。这是一个错误,源于 ModelForm 和 Models 都有 _meta 属性。每当从包含 _meta 属性的 get_object() 返回对象时,都会出现同样的错误。

get_object 不必返回 Model 实例。您可以通过查看 DetailView 的源代码并阅读其文档字符串来确认这一点:

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()`.
    """

请注意,文档字符串明确表示通过重写 self.get_object() 可以支持任何对象。

另一个确凿的证据来自该错误本身发生的位置,即 SingleObjectTemplateResponseMixinget_template_names 方法

    # 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
        ))

再次查看这段代码,注释本身说“如果有问题的对象是模型”。从这个评论我们可以推断该对象并不总是必须是模型。

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

要回答有关如何报告错误的问题,您应该遵循 https://docs.djangoproject.com/en/1.3/internals/contributing/#reporting-bugs

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文