Loading django/views/generic/detail.py +3 −2 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ class SingleObjectMixin(ContextMixin): context_object_name = None slug_url_kwarg = 'slug' pk_url_kwarg = 'pk' query_pk_and_slug = False def get_object(self, queryset=None): """ Loading @@ -37,12 +38,12 @@ class SingleObjectMixin(ContextMixin): queryset = queryset.filter(pk=pk) # Next, try looking up by slug. elif slug is not None: if slug is not None and (pk is None or self.query_pk_and_slug): slug_field = self.get_slug_field() queryset = queryset.filter(**{slug_field: slug}) # If none of those are defined, it's an error. else: if pk is None and slug is None: raise AttributeError("Generic detail view %s must be called with " "either an object pk or a slug." % self.__class__.__name__) Loading docs/ref/class-based-views/mixins-single-object.txt +32 −8 Original line number Diff line number Diff line Loading @@ -50,16 +50,40 @@ SingleObjectMixin Designates the name of the variable to use in the context. .. attribute:: query_pk_and_slug .. versionadded:: 1.8 If ``True``, causes :meth:`get_object()` to perform its lookup using both the primary key and the slug. Defaults to ``False``. This attribute can help mitigate `insecure direct object reference`_ attacks. When applications allow access to individual objects by a sequential primary key, an attacker could brute-force guess all URLs; thereby obtaining a list of all objects in the application. If users with access to individual objects should be prevented from obtaining this list, setting ``query_pk_and_slug`` to ``True`` will help prevent the guessing of URLs as each URL will require two correct, non-sequential arguments. Simply using a unique slug may serve the same purpose, but this scheme allows you to have non-unique slugs. .. _insecure direct object reference: https://www.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References .. method:: get_object(queryset=None) Returns the single object that this view will display. If ``queryset`` is provided, that queryset will be used as the source of objects; otherwise, :meth:`get_queryset` will be used. ``get_object()`` looks for a :attr:`pk_url_kwarg` argument in the arguments to the view; if this argument is found, this method performs a primary-key based lookup using that value. If this argument is not found, it looks for a :attr:`slug_url_kwarg` argument, and performs a slug lookup using the :attr:`slug_field`. Returns the single object that this view will display. If ``queryset`` is provided, that queryset will be used as the source of objects; otherwise, :meth:`get_queryset` will be used. ``get_object()`` looks for a :attr:`pk_url_kwarg` argument in the arguments to the view; if this argument is found, this method performs a primary-key based lookup using that value. If this argument is not found, it looks for a :attr:`slug_url_kwarg` argument, and performs a slug lookup using the :attr:`slug_field`. .. versionchanged:: 1.8 When :attr:`query_pk_and_slug` is ``True``, ``get_object()`` will perform its lookup using both the primary key and the slug. .. method:: get_queryset() Loading docs/releases/1.8.txt +6 −0 Original line number Diff line number Diff line Loading @@ -225,6 +225,12 @@ Generic Views :attr:`~django.views.generic.list.MultipleObjectMixin.ordering` or overriding :meth:`~django.views.generic.list.MultipleObjectMixin.get_ordering()`. * The new :attr:`SingleObjectMixin.query_pk_and_slug <django.views.generic.detail.SingleObjectMixin.query_pk_and_slug>` attribute allows changing the behavior of :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()` so that it'll perform its lookup using both the primary key and the slug. Internationalization ^^^^^^^^^^^^^^^^^^^^ Loading tests/generic_views/test_detail.py +28 −0 Original line number Diff line number Diff line Loading @@ -53,6 +53,34 @@ class DetailViewTest(TestCase): self.assertEqual(res.context['author'], Author.objects.get(slug='scott-rosenberg')) self.assertTemplateUsed(res, 'generic_views/author_detail.html') def test_detail_by_pk_ignore_slug(self): author = Author.objects.get(pk=1) res = self.client.get('/detail/author/bypkignoreslug/1-roberto-bolano/') self.assertEqual(res.status_code, 200) self.assertEqual(res.context['object'], author) self.assertEqual(res.context['author'], author) self.assertTemplateUsed(res, 'generic_views/author_detail.html') def test_detail_by_pk_ignore_slug_mismatch(self): author = Author.objects.get(pk=1) res = self.client.get('/detail/author/bypkignoreslug/1-scott-rosenberg/') self.assertEqual(res.status_code, 200) self.assertEqual(res.context['object'], author) self.assertEqual(res.context['author'], author) self.assertTemplateUsed(res, 'generic_views/author_detail.html') def test_detail_by_pk_and_slug(self): author = Author.objects.get(pk=1) res = self.client.get('/detail/author/bypkandslug/1-roberto-bolano/') self.assertEqual(res.status_code, 200) self.assertEqual(res.context['object'], author) self.assertEqual(res.context['author'], author) self.assertTemplateUsed(res, 'generic_views/author_detail.html') def test_detail_by_pk_and_slug_mismatch_404(self): res = self.client.get('/detail/author/bypkandslug/1-scott-rosenberg/') self.assertEqual(res.status_code, 404) def test_verbose_name(self): res = self.client.get('/detail/artist/1/') self.assertEqual(res.status_code, 200) Loading tests/generic_views/urls.py +4 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,10 @@ urlpatterns = [ views.AuthorDetail.as_view()), url(r'^detail/author/bycustomslug/(?P<foo>[\w-]+)/$', views.AuthorDetail.as_view(slug_url_kwarg='foo')), url(r'^detail/author/bypkignoreslug/(?P<pk>[0-9]+)-(?P<slug>[\w-]+)/$', views.AuthorDetail.as_view()), url(r'^detail/author/bypkandslug/(?P<pk>[0-9]+)-(?P<slug>[\w-]+)/$', views.AuthorDetail.as_view(query_pk_and_slug=True)), url(r'^detail/author/(?P<pk>[0-9]+)/template_name_suffix/$', views.AuthorDetail.as_view(template_name_suffix='_view')), url(r'^detail/author/(?P<pk>[0-9]+)/template_name/$', Loading Loading
django/views/generic/detail.py +3 −2 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ class SingleObjectMixin(ContextMixin): context_object_name = None slug_url_kwarg = 'slug' pk_url_kwarg = 'pk' query_pk_and_slug = False def get_object(self, queryset=None): """ Loading @@ -37,12 +38,12 @@ class SingleObjectMixin(ContextMixin): queryset = queryset.filter(pk=pk) # Next, try looking up by slug. elif slug is not None: if slug is not None and (pk is None or self.query_pk_and_slug): slug_field = self.get_slug_field() queryset = queryset.filter(**{slug_field: slug}) # If none of those are defined, it's an error. else: if pk is None and slug is None: raise AttributeError("Generic detail view %s must be called with " "either an object pk or a slug." % self.__class__.__name__) Loading
docs/ref/class-based-views/mixins-single-object.txt +32 −8 Original line number Diff line number Diff line Loading @@ -50,16 +50,40 @@ SingleObjectMixin Designates the name of the variable to use in the context. .. attribute:: query_pk_and_slug .. versionadded:: 1.8 If ``True``, causes :meth:`get_object()` to perform its lookup using both the primary key and the slug. Defaults to ``False``. This attribute can help mitigate `insecure direct object reference`_ attacks. When applications allow access to individual objects by a sequential primary key, an attacker could brute-force guess all URLs; thereby obtaining a list of all objects in the application. If users with access to individual objects should be prevented from obtaining this list, setting ``query_pk_and_slug`` to ``True`` will help prevent the guessing of URLs as each URL will require two correct, non-sequential arguments. Simply using a unique slug may serve the same purpose, but this scheme allows you to have non-unique slugs. .. _insecure direct object reference: https://www.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References .. method:: get_object(queryset=None) Returns the single object that this view will display. If ``queryset`` is provided, that queryset will be used as the source of objects; otherwise, :meth:`get_queryset` will be used. ``get_object()`` looks for a :attr:`pk_url_kwarg` argument in the arguments to the view; if this argument is found, this method performs a primary-key based lookup using that value. If this argument is not found, it looks for a :attr:`slug_url_kwarg` argument, and performs a slug lookup using the :attr:`slug_field`. Returns the single object that this view will display. If ``queryset`` is provided, that queryset will be used as the source of objects; otherwise, :meth:`get_queryset` will be used. ``get_object()`` looks for a :attr:`pk_url_kwarg` argument in the arguments to the view; if this argument is found, this method performs a primary-key based lookup using that value. If this argument is not found, it looks for a :attr:`slug_url_kwarg` argument, and performs a slug lookup using the :attr:`slug_field`. .. versionchanged:: 1.8 When :attr:`query_pk_and_slug` is ``True``, ``get_object()`` will perform its lookup using both the primary key and the slug. .. method:: get_queryset() Loading
docs/releases/1.8.txt +6 −0 Original line number Diff line number Diff line Loading @@ -225,6 +225,12 @@ Generic Views :attr:`~django.views.generic.list.MultipleObjectMixin.ordering` or overriding :meth:`~django.views.generic.list.MultipleObjectMixin.get_ordering()`. * The new :attr:`SingleObjectMixin.query_pk_and_slug <django.views.generic.detail.SingleObjectMixin.query_pk_and_slug>` attribute allows changing the behavior of :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()` so that it'll perform its lookup using both the primary key and the slug. Internationalization ^^^^^^^^^^^^^^^^^^^^ Loading
tests/generic_views/test_detail.py +28 −0 Original line number Diff line number Diff line Loading @@ -53,6 +53,34 @@ class DetailViewTest(TestCase): self.assertEqual(res.context['author'], Author.objects.get(slug='scott-rosenberg')) self.assertTemplateUsed(res, 'generic_views/author_detail.html') def test_detail_by_pk_ignore_slug(self): author = Author.objects.get(pk=1) res = self.client.get('/detail/author/bypkignoreslug/1-roberto-bolano/') self.assertEqual(res.status_code, 200) self.assertEqual(res.context['object'], author) self.assertEqual(res.context['author'], author) self.assertTemplateUsed(res, 'generic_views/author_detail.html') def test_detail_by_pk_ignore_slug_mismatch(self): author = Author.objects.get(pk=1) res = self.client.get('/detail/author/bypkignoreslug/1-scott-rosenberg/') self.assertEqual(res.status_code, 200) self.assertEqual(res.context['object'], author) self.assertEqual(res.context['author'], author) self.assertTemplateUsed(res, 'generic_views/author_detail.html') def test_detail_by_pk_and_slug(self): author = Author.objects.get(pk=1) res = self.client.get('/detail/author/bypkandslug/1-roberto-bolano/') self.assertEqual(res.status_code, 200) self.assertEqual(res.context['object'], author) self.assertEqual(res.context['author'], author) self.assertTemplateUsed(res, 'generic_views/author_detail.html') def test_detail_by_pk_and_slug_mismatch_404(self): res = self.client.get('/detail/author/bypkandslug/1-scott-rosenberg/') self.assertEqual(res.status_code, 404) def test_verbose_name(self): res = self.client.get('/detail/artist/1/') self.assertEqual(res.status_code, 200) Loading
tests/generic_views/urls.py +4 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,10 @@ urlpatterns = [ views.AuthorDetail.as_view()), url(r'^detail/author/bycustomslug/(?P<foo>[\w-]+)/$', views.AuthorDetail.as_view(slug_url_kwarg='foo')), url(r'^detail/author/bypkignoreslug/(?P<pk>[0-9]+)-(?P<slug>[\w-]+)/$', views.AuthorDetail.as_view()), url(r'^detail/author/bypkandslug/(?P<pk>[0-9]+)-(?P<slug>[\w-]+)/$', views.AuthorDetail.as_view(query_pk_and_slug=True)), url(r'^detail/author/(?P<pk>[0-9]+)/template_name_suffix/$', views.AuthorDetail.as_view(template_name_suffix='_view')), url(r'^detail/author/(?P<pk>[0-9]+)/template_name/$', Loading