Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Ticket/16418 #52

Closed
wants to merge 3 commits into from

2 participants

Donald Stufft Anssi Kääriäinen
Donald Stufft
Owner
dstufft commented May 09, 2012

Solves #16418 by explicitly checking for subclasses not assuming anything with a _meta or model attribute is a Model or QuerySet subclass.

Anssi Kääriäinen akaariai commented on the diff May 27, 2012
django/views/generic/detail.py
((6 lines not shown))
131 132
             names.append("%s/%s%s.html" % (
132 133
                 self.object._meta.app_label,
133 134
                 self.object._meta.object_name.lower(),
134 135
                 self.template_name_suffix
135 136
             ))
136  
-        elif hasattr(self, 'model') and hasattr(self.model, '_meta'):
  137
+        elif hasattr(self, 'model') and self.model is not None and issubclass(self.model, models.Model):
2
Anssi Kääriäinen Owner
akaariai added a note May 27, 2012

why the is not None and issubclass check here instead of the normal isinstance?

Donald Stufft Owner
dstufft added a note May 30, 2012

These views allow passing either a model or a queryset. self.model is not an instance of a model but the actual model class and thus isinstance won't work. The None check is that if self.queryset is used instead of self.model, self.model will be None and the first arg to issubclass must be a class, not None.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Anssi Kääriäinen
Owner

Pulled in manually in commit 484fcd3. I removed the checks for isinstance(object_list, QuerySet) and retained the hasattr(object_list, 'model') instead because it seems lists containing attribute model are rare enough to not cause problems.

Thanks to dstufft for the patch! Unfortunately I forgot to thank him in the commit message, sorry for that.

Anssi Kääriäinen akaariai closed this June 08, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
7  django/views/generic/detail.py
... ...
@@ -1,4 +1,5 @@
1 1
 from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
  2
+from django.db import models
2 3
 from django.http import Http404
3 4
 from django.utils.encoding import smart_str
4 5
 from django.utils.translation import ugettext as _
@@ -80,7 +81,7 @@ def get_context_object_name(self, obj):
80 81
         """
81 82
         if self.context_object_name:
82 83
             return self.context_object_name
83  
-        elif hasattr(obj, '_meta'):
  84
+        elif isinstance(obj, models.Model):
84 85
             return smart_str(obj._meta.object_name.lower())
85 86
         else:
86 87
             return None
@@ -127,13 +128,13 @@ def get_template_names(self):
127 128
 
128 129
         # The least-specific option is the default <app>/<model>_detail.html;
129 130
         # only use this if the object in question is a model.
130  
-        if hasattr(self.object, '_meta'):
  131
+        if isinstance(self.object, models.Model):
131 132
             names.append("%s/%s%s.html" % (
132 133
                 self.object._meta.app_label,
133 134
                 self.object._meta.object_name.lower(),
134 135
                 self.template_name_suffix
135 136
             ))
136  
-        elif hasattr(self, 'model') and hasattr(self.model, '_meta'):
  137
+        elif hasattr(self, 'model') and self.model is not None and issubclass(self.model, models.Model):
137 138
             names.append("%s/%s%s.html" % (
138 139
                 self.model._meta.app_label,
139 140
                 self.model._meta.object_name.lower(),
5  django/views/generic/list.py
... ...
@@ -1,5 +1,6 @@
1 1
 from django.core.paginator import Paginator, InvalidPage
2 2
 from django.core.exceptions import ImproperlyConfigured
  3
+from django.db.models.query import QuerySet
3 4
 from django.http import Http404
4 5
 from django.utils.encoding import smart_str
5 6
 from django.utils.translation import ugettext as _
@@ -76,7 +77,7 @@ def get_context_object_name(self, object_list):
76 77
         """
77 78
         if self.context_object_name:
78 79
             return self.context_object_name
79  
-        elif hasattr(object_list, 'model'):
  80
+        elif isinstance(object_list, QuerySet):
80 81
             return smart_str('%s_list' % object_list.model._meta.object_name.lower())
81 82
         else:
82 83
             return None
@@ -139,7 +140,7 @@ def get_template_names(self):
139 140
         # app and model name. This name gets put at the end of the template
140 141
         # name list so that user-supplied names override the automatically-
141 142
         # generated ones.
142  
-        if hasattr(self.object_list, 'model'):
  143
+        if isinstance(self.object_list, QuerySet):
143 144
             opts = self.object_list.model._meta
144 145
             names.append("%s/%s%s.html" % (opts.app_label, opts.object_name.lower(), self.template_name_suffix))
145 146
 
5  tests/regressiontests/generic_views/detail.py
@@ -92,3 +92,8 @@ def test_invalid_url(self):
92 92
 
93 93
     def test_invalid_queryset(self):
94 94
         self.assertRaises(ImproperlyConfigured, self.client.get, '/detail/author/invalid/qs/')
  95
+
  96
+    def test_non_model_object_with_meta(self):
  97
+        res = self.client.get('/detail/nonmodel/1/')
  98
+        self.assertEqual(res.status_code, 200)
  99
+        self.assertEqual(res.context['object'].id, "non_model_1")
3  tests/regressiontests/generic_views/list.py
@@ -164,3 +164,6 @@ def _make_authors(self, n):
164 164
         for i in range(n):
165 165
             Author.objects.create(name='Author %02i' % i, slug='a%s' % i)
166 166
 
  167
+    def test_non_queryset_list(self):
  168
+        res = self.client.get('/list/nonqueryset/')
  169
+        self.assertEqual(res.status_code, 200)
5  tests/regressiontests/generic_views/urls.py
@@ -52,6 +52,8 @@
52 52
         views.AuthorDetail.as_view()),
53 53
     (r'^detail/author/invalid/qs/$',
54 54
         views.AuthorDetail.as_view(queryset=None)),
  55
+    (r'^detail/nonmodel/1/$',
  56
+        views.NonModelDetail.as_view()),
55 57
 
56 58
     # Create/UpdateView
57 59
     (r'^edit/artists/create/$',
@@ -142,6 +144,9 @@
142 144
         views.AuthorList.as_view(paginate_by=5, paginator_class=views.CustomPaginator)),
143 145
     (r'^list/authors/paginated/custom_constructor/$',
144 146
         views.AuthorListCustomPaginator.as_view()),
  147
+    (r'^list/nonqueryset/$',
  148
+        views.NonQuerySetList.as_view()),
  149
+
145 150
 
146 151
     # YearArchiveView
147 152
     # Mixing keyword and possitional captures below is intentional; the views
30  tests/regressiontests/generic_views/views.py
@@ -226,3 +226,33 @@ class BookSigningTodayArchive(BookSigningConfig, generic.TodayArchiveView):
226 226
 
227 227
 class BookSigningDetail(BookSigningConfig, generic.DateDetailView):
228 228
     context_object_name = 'book'
  229
+
  230
+
  231
+class NonModel(object):
  232
+    id = "non_model_1"
  233
+
  234
+    _meta = None
  235
+
  236
+
  237
+class NonQuerySet(object):
  238
+    model = NonModel
  239
+
  240
+    def __getitem__(self, i):
  241
+        if i == 0:
  242
+            return self.model()
  243
+        raise IndexError
  244
+
  245
+
  246
+class NonModelDetail(generic.DetailView):
  247
+
  248
+    template_name = 'generic_views/detail.html'
  249
+    model = NonModel
  250
+
  251
+    def get_object(self, queryset=None):
  252
+        return NonModel()
  253
+
  254
+
  255
+class NonQuerySetList(generic.ListView):
  256
+
  257
+    template_name = 'generic_views/list.html'
  258
+    queryset = NonQuerySet()
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.