mirror of
https://github.com/django/django.git
synced 2024-12-23 01:25:58 +00:00
Fixed #24133 -- Replaced formatting syntax in success_url placeholders
Thanks Laurent Payot for the report, and Markus Holtermann, Tim Graham for the reviews.
This commit is contained in:
parent
5f7230e12f
commit
f48e2258a9
@ -1,4 +1,5 @@
|
|||||||
import inspect
|
import inspect
|
||||||
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
@ -162,7 +163,15 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
|
|||||||
Returns the supplied URL.
|
Returns the supplied URL.
|
||||||
"""
|
"""
|
||||||
if self.success_url:
|
if self.success_url:
|
||||||
url = self.success_url % self.object.__dict__
|
if re.search(r'%\([^\)]+\)', self.success_url):
|
||||||
|
warnings.warn(
|
||||||
|
"%()s placeholder style in success_url is deprecated. "
|
||||||
|
"Please replace them by the {} Python format syntax.",
|
||||||
|
RemovedInDjango20Warning, stacklevel=2
|
||||||
|
)
|
||||||
|
url = self.success_url % self.object.__dict__
|
||||||
|
else:
|
||||||
|
url = self.success_url.format(**self.object.__dict__)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
url = self.object.get_absolute_url()
|
url = self.object.get_absolute_url()
|
||||||
@ -288,7 +297,15 @@ class DeletionMixin(object):
|
|||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
if self.success_url:
|
if self.success_url:
|
||||||
return self.success_url % self.object.__dict__
|
if re.search(r'%\([^\)]+\)', self.success_url):
|
||||||
|
warnings.warn(
|
||||||
|
"%()s placeholder style in success_url is deprecated. "
|
||||||
|
"Please replace them by the {} Python format syntax.",
|
||||||
|
RemovedInDjango20Warning, stacklevel=2
|
||||||
|
)
|
||||||
|
return self.success_url % self.object.__dict__
|
||||||
|
else:
|
||||||
|
return self.success_url.format(**self.object.__dict__)
|
||||||
else:
|
else:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
"No URL to redirect to. Provide a success_url.")
|
"No URL to redirect to. Provide a success_url.")
|
||||||
|
@ -158,6 +158,9 @@ details on these changes.
|
|||||||
and ``Storage.save()`` to be defined without a ``max_length`` argument will
|
and ``Storage.save()`` to be defined without a ``max_length`` argument will
|
||||||
be removed.
|
be removed.
|
||||||
|
|
||||||
|
* Support for the legacy ``%(<foo>)s`` syntax in ``ModelFormMixin.success_url``
|
||||||
|
will be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-1.9:
|
.. _deprecation-removed-in-1.9:
|
||||||
|
|
||||||
1.9
|
1.9
|
||||||
|
@ -160,9 +160,15 @@ ModelFormMixin
|
|||||||
|
|
||||||
``success_url`` may contain dictionary string formatting, which
|
``success_url`` may contain dictionary string formatting, which
|
||||||
will be interpolated against the object's field attributes. For
|
will be interpolated against the object's field attributes. For
|
||||||
example, you could use ``success_url="/polls/%(slug)s/"`` to
|
example, you could use ``success_url="/polls/{slug}/"`` to
|
||||||
redirect to a URL composed out of the ``slug`` field on a model.
|
redirect to a URL composed out of the ``slug`` field on a model.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.8
|
||||||
|
|
||||||
|
Support for the new brace-based Python formatting syntax has been
|
||||||
|
added. The old ``%(slug)s`` placeholder syntax support has been
|
||||||
|
deprecated and will be removed in Django 2.0.
|
||||||
|
|
||||||
.. method:: get_form_class()
|
.. method:: get_form_class()
|
||||||
|
|
||||||
Retrieve the form class to instantiate. If
|
Retrieve the form class to instantiate. If
|
||||||
@ -248,9 +254,15 @@ DeletionMixin
|
|||||||
|
|
||||||
``success_url`` may contain dictionary string formatting, which will be
|
``success_url`` may contain dictionary string formatting, which will be
|
||||||
interpolated against the object's field attributes. For example, you
|
interpolated against the object's field attributes. For example, you
|
||||||
could use ``success_url="/parent/%(parent_id)s/"`` to redirect to a URL
|
could use ``success_url="/parent/{parent_id}/"`` to redirect to a URL
|
||||||
composed out of the ``parent_id`` field on a model.
|
composed out of the ``parent_id`` field on a model.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.8
|
||||||
|
|
||||||
|
Support for the new brace-based Python formatting syntax has been
|
||||||
|
added. The old ``%(slug)s`` placeholder syntax support has been
|
||||||
|
deprecated and will be removed in Django 2.0.
|
||||||
|
|
||||||
.. method:: get_success_url()
|
.. method:: get_success_url()
|
||||||
|
|
||||||
Returns the url to redirect to when the nominated object has been
|
Returns the url to redirect to when the nominated object has been
|
||||||
|
@ -388,6 +388,11 @@ Generic Views
|
|||||||
require a ``form_class`` to be provided anymore. If not provided ``form_class``
|
require a ``form_class`` to be provided anymore. If not provided ``form_class``
|
||||||
defaults to :meth:`~django.views.generic.edit.FormMixin.get_form_class()`.
|
defaults to :meth:`~django.views.generic.edit.FormMixin.get_form_class()`.
|
||||||
|
|
||||||
|
* Placeholders in :attr:`ModelFormMixin.success_url
|
||||||
|
<django.views.generic.edit.ModelFormMixin.success_url>` now support the Python
|
||||||
|
:py:meth:`str.format()` syntax. The legacy ``%(<foo>)s`` syntax is still
|
||||||
|
supported but will be removed in Django 2.0.
|
||||||
|
|
||||||
Internationalization
|
Internationalization
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
@ -1550,6 +1555,13 @@ will be removed in Django 2.0.
|
|||||||
Using a single equals sign with the ``{% if %}`` template tag for equality
|
Using a single equals sign with the ``{% if %}`` template tag for equality
|
||||||
testing was undocumented and untested. It's now deprecated in favor of ``==``.
|
testing was undocumented and untested. It's now deprecated in favor of ``==``.
|
||||||
|
|
||||||
|
``%(<foo>)s`` syntax in ``ModelFormMixin.success_url``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The legacy ``%(<foo>)s`` syntax in :attr:`ModelFormMixin.success_url
|
||||||
|
<django.views.generic.edit.ModelFormMixin.success_url>` is deprecated and
|
||||||
|
will be removed in Django 2.0.
|
||||||
|
|
||||||
.. removed-features-1.8:
|
.. removed-features-1.8:
|
||||||
|
|
||||||
Features removed in 1.8
|
Features removed in 1.8
|
||||||
|
@ -5,7 +5,7 @@ import warnings
|
|||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, ignore_warnings, override_settings
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.utils.deprecation import RemovedInDjango20Warning
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
from django.views.generic.base import View
|
from django.views.generic.base import View
|
||||||
@ -143,13 +143,24 @@ class CreateViewTests(TestCase):
|
|||||||
self.assertRedirects(res, 'http://testserver/edit/authors/create/')
|
self.assertRedirects(res, 'http://testserver/edit/authors/create/')
|
||||||
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe>'])
|
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe>'])
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango20Warning)
|
||||||
def test_create_with_interpolated_redirect(self):
|
def test_create_with_interpolated_redirect(self):
|
||||||
res = self.client.post('/edit/authors/create/interpolate_redirect/',
|
res = self.client.post(
|
||||||
{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
|
'/edit/authors/create/interpolate_redirect/',
|
||||||
|
{'name': 'Randall Munroe', 'slug': 'randall-munroe'}
|
||||||
|
)
|
||||||
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe>'])
|
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe>'])
|
||||||
self.assertEqual(res.status_code, 302)
|
self.assertEqual(res.status_code, 302)
|
||||||
pk = Author.objects.all()[0].pk
|
pk = Author.objects.first().pk
|
||||||
self.assertRedirects(res, 'http://testserver/edit/author/%d/update/' % pk)
|
self.assertRedirects(res, 'http://testserver/edit/author/%d/update/' % pk)
|
||||||
|
# Also test with escaped chars in URL
|
||||||
|
res = self.client.post(
|
||||||
|
'/edit/authors/create/interpolate_redirect_nonascii/',
|
||||||
|
{'name': 'John Doe', 'slug': 'john-doe'}
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 302)
|
||||||
|
pk = Author.objects.get(name='John Doe').pk
|
||||||
|
self.assertRedirects(res, 'http://testserver/%C3%A9dit/author/{}/update/'.format(pk))
|
||||||
|
|
||||||
def test_create_with_special_properties(self):
|
def test_create_with_special_properties(self):
|
||||||
res = self.client.get('/edit/authors/create/special/')
|
res = self.client.get('/edit/authors/create/special/')
|
||||||
@ -272,17 +283,28 @@ class UpdateViewTests(TestCase):
|
|||||||
self.assertRedirects(res, 'http://testserver/edit/authors/create/')
|
self.assertRedirects(res, 'http://testserver/edit/authors/create/')
|
||||||
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe (author of xkcd)>'])
|
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe (author of xkcd)>'])
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango20Warning)
|
||||||
def test_update_with_interpolated_redirect(self):
|
def test_update_with_interpolated_redirect(self):
|
||||||
a = Author.objects.create(
|
a = Author.objects.create(
|
||||||
name='Randall Munroe',
|
name='Randall Munroe',
|
||||||
slug='randall-munroe',
|
slug='randall-munroe',
|
||||||
)
|
)
|
||||||
res = self.client.post('/edit/author/%d/update/interpolate_redirect/' % a.pk,
|
res = self.client.post(
|
||||||
{'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'})
|
'/edit/author/%d/update/interpolate_redirect/' % a.pk,
|
||||||
|
{'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'}
|
||||||
|
)
|
||||||
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe (author of xkcd)>'])
|
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe (author of xkcd)>'])
|
||||||
self.assertEqual(res.status_code, 302)
|
self.assertEqual(res.status_code, 302)
|
||||||
pk = Author.objects.all()[0].pk
|
pk = Author.objects.first().pk
|
||||||
self.assertRedirects(res, 'http://testserver/edit/author/%d/update/' % pk)
|
self.assertRedirects(res, 'http://testserver/edit/author/%d/update/' % pk)
|
||||||
|
# Also test with escaped chars in URL
|
||||||
|
res = self.client.post(
|
||||||
|
'/edit/author/%d/update/interpolate_redirect_nonascii/' % a.pk,
|
||||||
|
{'name': 'John Doe', 'slug': 'john-doe'}
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 302)
|
||||||
|
pk = Author.objects.get(name='John Doe').pk
|
||||||
|
self.assertRedirects(res, 'http://testserver/%C3%A9dit/author/{}/update/'.format(pk))
|
||||||
|
|
||||||
def test_update_with_special_properties(self):
|
def test_update_with_special_properties(self):
|
||||||
a = Author.objects.create(
|
a = Author.objects.create(
|
||||||
@ -368,12 +390,18 @@ class DeleteViewTests(TestCase):
|
|||||||
self.assertRedirects(res, 'http://testserver/edit/authors/create/')
|
self.assertRedirects(res, 'http://testserver/edit/authors/create/')
|
||||||
self.assertQuerysetEqual(Author.objects.all(), [])
|
self.assertQuerysetEqual(Author.objects.all(), [])
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango20Warning)
|
||||||
def test_delete_with_interpolated_redirect(self):
|
def test_delete_with_interpolated_redirect(self):
|
||||||
a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
|
a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
|
||||||
res = self.client.post('/edit/author/%d/delete/interpolate_redirect/' % a.pk)
|
res = self.client.post('/edit/author/%d/delete/interpolate_redirect/' % a.pk)
|
||||||
self.assertEqual(res.status_code, 302)
|
self.assertEqual(res.status_code, 302)
|
||||||
self.assertRedirects(res, 'http://testserver/edit/authors/create/?deleted=%d' % a.pk)
|
self.assertRedirects(res, 'http://testserver/edit/authors/create/?deleted=%d' % a.pk)
|
||||||
self.assertQuerysetEqual(Author.objects.all(), [])
|
self.assertQuerysetEqual(Author.objects.all(), [])
|
||||||
|
# Also test with escaped chars in URL
|
||||||
|
a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
|
||||||
|
res = self.client.post('/edit/author/{}/delete/interpolate_redirect_nonascii/'.format(a.pk))
|
||||||
|
self.assertEqual(res.status_code, 302)
|
||||||
|
self.assertRedirects(res, 'http://testserver/%C3%A9dit/authors/create/?deleted={}'.format(a.pk))
|
||||||
|
|
||||||
def test_delete_with_special_properties(self):
|
def test_delete_with_special_properties(self):
|
||||||
a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
|
a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
@ -74,9 +77,11 @@ urlpatterns = [
|
|||||||
views.NaiveAuthorCreate.as_view(success_url='/edit/authors/create/')),
|
views.NaiveAuthorCreate.as_view(success_url='/edit/authors/create/')),
|
||||||
url(r'^edit/authors/create/interpolate_redirect/$',
|
url(r'^edit/authors/create/interpolate_redirect/$',
|
||||||
views.NaiveAuthorCreate.as_view(success_url='/edit/author/%(id)d/update/')),
|
views.NaiveAuthorCreate.as_view(success_url='/edit/author/%(id)d/update/')),
|
||||||
|
url(r'^edit/authors/create/interpolate_redirect_nonascii/$',
|
||||||
|
views.NaiveAuthorCreate.as_view(success_url='/%C3%A9dit/author/{id}/update/')),
|
||||||
url(r'^edit/authors/create/restricted/$',
|
url(r'^edit/authors/create/restricted/$',
|
||||||
views.AuthorCreateRestricted.as_view()),
|
views.AuthorCreateRestricted.as_view()),
|
||||||
url(r'^edit/authors/create/$',
|
url(r'^[eé]dit/authors/create/$',
|
||||||
views.AuthorCreate.as_view()),
|
views.AuthorCreate.as_view()),
|
||||||
url(r'^edit/authors/create/special/$',
|
url(r'^edit/authors/create/special/$',
|
||||||
views.SpecializedAuthorCreate.as_view()),
|
views.SpecializedAuthorCreate.as_view()),
|
||||||
@ -87,7 +92,9 @@ urlpatterns = [
|
|||||||
views.NaiveAuthorUpdate.as_view(success_url='/edit/authors/create/')),
|
views.NaiveAuthorUpdate.as_view(success_url='/edit/authors/create/')),
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/update/interpolate_redirect/$',
|
url(r'^edit/author/(?P<pk>[0-9]+)/update/interpolate_redirect/$',
|
||||||
views.NaiveAuthorUpdate.as_view(success_url='/edit/author/%(id)d/update/')),
|
views.NaiveAuthorUpdate.as_view(success_url='/edit/author/%(id)d/update/')),
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/update/$',
|
url(r'^edit/author/(?P<pk>[0-9]+)/update/interpolate_redirect_nonascii/$',
|
||||||
|
views.NaiveAuthorUpdate.as_view(success_url='/%C3%A9dit/author/{id}/update/')),
|
||||||
|
url(r'^[eé]dit/author/(?P<pk>[0-9]+)/update/$',
|
||||||
views.AuthorUpdate.as_view()),
|
views.AuthorUpdate.as_view()),
|
||||||
url(r'^edit/author/update/$',
|
url(r'^edit/author/update/$',
|
||||||
views.OneAuthorUpdate.as_view()),
|
views.OneAuthorUpdate.as_view()),
|
||||||
@ -99,6 +106,8 @@ urlpatterns = [
|
|||||||
views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/')),
|
views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/')),
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/delete/interpolate_redirect/$',
|
url(r'^edit/author/(?P<pk>[0-9]+)/delete/interpolate_redirect/$',
|
||||||
views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/?deleted=%(id)s')),
|
views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/?deleted=%(id)s')),
|
||||||
|
url(r'^edit/author/(?P<pk>[0-9]+)/delete/interpolate_redirect_nonascii/$',
|
||||||
|
views.NaiveAuthorDelete.as_view(success_url='/%C3%A9dit/authors/create/?deleted={id}')),
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/delete/$',
|
url(r'^edit/author/(?P<pk>[0-9]+)/delete/$',
|
||||||
views.AuthorDelete.as_view()),
|
views.AuthorDelete.as_view()),
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/delete/special/$',
|
url(r'^edit/author/(?P<pk>[0-9]+)/delete/special/$',
|
||||||
|
Loading…
Reference in New Issue
Block a user