Fixed #23482 -- Added SingleObjectMixin.query_pk_and_slug
Enabling the attribute causes get_object() to perform its lookup using both the primary key and the slug.
This commit is contained in:
parent
85f6d89313
commit
8c581ff394
|
@ -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):
|
||||
"""
|
||||
|
@ -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__)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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…
Reference in New Issue