diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py index 8cc413aa65..a5ddb5fe3e 100644 --- a/django/views/generic/detail.py +++ b/django/views/generic/detail.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist +from django.db import models from django.http import Http404 from django.utils.translation import ugettext as _ from django.views.generic.base import TemplateResponseMixin, ContextMixin, View @@ -81,7 +82,7 @@ class SingleObjectMixin(ContextMixin): """ if self.context_object_name: return self.context_object_name - elif hasattr(obj, '_meta'): + elif isinstance(obj, models.Model): return obj._meta.object_name.lower() else: return None @@ -128,13 +129,13 @@ class SingleObjectTemplateResponseMixin(TemplateResponseMixin): # The least-specific option is the default /_detail.html; # only use this if the object in question is a model. - if hasattr(self.object, '_meta'): + if isinstance(self.object, models.Model): 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'): + elif hasattr(self, 'model') and self.model is not None and issubclass(self.model, models.Model): names.append("%s/%s%s.html" % ( self.model._meta.app_label, self.model._meta.object_name.lower(), diff --git a/tests/regressiontests/generic_views/detail.py b/tests/regressiontests/generic_views/detail.py index 0b5d8737dd..60c5f7afe9 100644 --- a/tests/regressiontests/generic_views/detail.py +++ b/tests/regressiontests/generic_views/detail.py @@ -92,3 +92,8 @@ class DetailViewTest(TestCase): def test_invalid_queryset(self): self.assertRaises(ImproperlyConfigured, self.client.get, '/detail/author/invalid/qs/') + + def test_non_model_object_with_meta(self): + res = self.client.get('/detail/nonmodel/1/') + self.assertEqual(res.status_code, 200) + self.assertEqual(res.context['object'].id, "non_model_1") diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py index 14d4f685a6..5e15c6c9c1 100644 --- a/tests/regressiontests/generic_views/urls.py +++ b/tests/regressiontests/generic_views/urls.py @@ -53,6 +53,8 @@ urlpatterns = patterns('', views.AuthorDetail.as_view()), (r'^detail/author/invalid/qs/$', views.AuthorDetail.as_view(queryset=None)), + (r'^detail/nonmodel/1/$', + views.NonModelDetail.as_view()), # Create/UpdateView (r'^edit/artists/create/$', diff --git a/tests/regressiontests/generic_views/views.py b/tests/regressiontests/generic_views/views.py index d11d810f3d..f7fcf6f509 100644 --- a/tests/regressiontests/generic_views/views.py +++ b/tests/regressiontests/generic_views/views.py @@ -226,3 +226,18 @@ class BookSigningTodayArchive(BookSigningConfig, generic.TodayArchiveView): class BookSigningDetail(BookSigningConfig, generic.DateDetailView): context_object_name = 'book' + + +class NonModel(object): + id = "non_model_1" + + _meta = None + + +class NonModelDetail(generic.DetailView): + + template_name = 'generic_views/detail.html' + model = NonModel + + def get_object(self, queryset=None): + return NonModel()