diff --git a/django/conf/urls/shortcut.py b/django/conf/urls/shortcut.py index 6eb2e55e68..c00d176ad6 100644 --- a/django/conf/urls/shortcut.py +++ b/django/conf/urls/shortcut.py @@ -1,5 +1,10 @@ +import warnings + from django.conf.urls import patterns +warnings.warn("django.conf.urls.shortcut will be removed in Django 1.8.", + PendingDeprecationWarning) + urlpatterns = patterns('django.views', (r'^(?P\d+)/(?P.*)/$', 'defaults.shortcut'), ) diff --git a/django/views/defaults.py b/django/views/defaults.py index ec7a233ff7..89228c50c9 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -1,3 +1,5 @@ +import warnings + from django import http from django.template import (Context, RequestContext, loader, Template, TemplateDoesNotExist) @@ -63,12 +65,9 @@ def permission_denied(request, template_name='403.html'): def shortcut(request, content_type_id, object_id): - # TODO: Remove this in Django 2.0. - # This is a legacy view that depends on the contenttypes framework. - # The core logic was moved to django.contrib.contenttypes.views after - # Django 1.0, but this remains here for backwards compatibility. - # Note that the import is *within* this function, rather than being at - # module level, because we don't want to assume people have contenttypes - # installed. + warnings.warn( + "django.views.defaults.shortcut will be removed in Django 1.8. " + "Import it from django.contrib.contenttypes.views instead.", + PendingDeprecationWarning, stacklevel=2) from django.contrib.contenttypes.views import shortcut as real_shortcut return real_shortcut(request, content_type_id, object_id) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 1305d68859..c0863278b5 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -362,6 +362,9 @@ these changes. * Remove the backward compatible shims introduced to rename the attributes ``ChangeList.root_query_set`` and ``ChangeList.query_set``. +* ``django.conf.urls.shortcut`` and ``django.views.defaults.shortcut`` will be + removed. + * The following private APIs will be removed: - ``django.db.close_connection()`` diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index a44545ddf3..b4c3b70c63 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -352,3 +352,20 @@ private API, it will go through a regular deprecation path. Methods that return a ``QuerySet`` such as ``Manager.get_query_set`` or ``ModelAdmin.queryset`` have been renamed to ``get_queryset``. + +``shortcut`` view and URLconf +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``shortcut`` view was moved from ``django.views.defaults`` to +``django.contrib.contentypes.views`` shortly after the 1.0 release, but the +old location was never deprecated. This oversight was corrected in Django 1.6 +and you should now use the new location. + +The URLconf ``django.conf.urls.shortcut`` was also deprecated. If you're +including it in an URLconf, simply replace:: + + (r'^prefix/', include('django.conf.urls.shortcut')), + +with:: + + (r'^prefix/(?P\d+)/(?P.*)/$', 'django.contrib.contentypes.views'), diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index c5eef8bb41..c1c52f5781 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -330,7 +330,6 @@ itself. It includes a number of other URLconfs:: (r'^comments/', include('django.contrib.comments.urls')), (r'^community/', include('django_website.aggregator.urls')), (r'^contact/', include('django_website.contact.urls')), - (r'^r/', include('django.conf.urls.shortcut')), # ... snip ... ) diff --git a/tests/contenttypes_tests/__init__.py b/tests/contenttypes_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/contenttypes_tests/fixtures/testdata.json b/tests/contenttypes_tests/fixtures/testdata.json new file mode 100644 index 0000000000..52510bfe90 --- /dev/null +++ b/tests/contenttypes_tests/fixtures/testdata.json @@ -0,0 +1,47 @@ +[ + { + "pk": 1, + "model": "contenttypes_tests.author", + "fields": { + "name": "Boris" + } + }, + { + "pk": 1, + "model": "contenttypes_tests.article", + "fields": { + "author": 1, + "title": "Old Article", + "slug": "old_article", + "date_created": "2001-01-01 21:22:23" + } + }, + { + "pk": 2, + "model": "contenttypes_tests.article", + "fields": { + "author": 1, + "title": "Current Article", + "slug": "current_article", + "date_created": "2007-09-17 21:22:23" + } + }, + { + "pk": 3, + "model": "contenttypes_tests.article", + "fields": { + "author": 1, + "title": "Future Article", + "slug": "future_article", + "date_created": "3000-01-01 21:22:23" + } + }, + { + "pk": 1, + "model": "sites.site", + "fields": { + "domain": "testserver", + "name": "testserver" + } + } +] diff --git a/tests/contenttypes_tests/models.py b/tests/contenttypes_tests/models.py new file mode 100644 index 0000000000..3c6685687a --- /dev/null +++ b/tests/contenttypes_tests/models.py @@ -0,0 +1,24 @@ +from __future__ import absolute_import, unicode_literals + +from django.db import models +from django.utils.encoding import python_2_unicode_compatible + +@python_2_unicode_compatible +class Author(models.Model): + name = models.CharField(max_length=100) + + def __str__(self): + return self.name + + def get_absolute_url(self): + return '/views/authors/%s/' % self.id + +@python_2_unicode_compatible +class Article(models.Model): + title = models.CharField(max_length=100) + slug = models.SlugField() + author = models.ForeignKey(Author) + date_created = models.DateTimeField() + + def __str__(self): + return self.title diff --git a/tests/contenttypes_tests/tests.py b/tests/contenttypes_tests/tests.py new file mode 100644 index 0000000000..b39e118ec6 --- /dev/null +++ b/tests/contenttypes_tests/tests.py @@ -0,0 +1,47 @@ +from __future__ import absolute_import, unicode_literals + +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from .models import Author, Article + +class ContentTypesViewsTests(TestCase): + fixtures = ['testdata.json'] + urls = 'contenttypes_tests.urls' + + def test_shortcut_with_absolute_url(self): + "Can view a shortcut for an Author object that has a get_absolute_url method" + for obj in Author.objects.all(): + short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk) + response = self.client.get(short_url) + self.assertRedirects(response, 'http://testserver%s' % obj.get_absolute_url(), + status_code=302, target_status_code=404) + + def test_shortcut_no_absolute_url(self): + "Shortcuts for an object that has no get_absolute_url method raises 404" + for obj in Article.objects.all(): + short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk) + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) + + def test_wrong_type_pk(self): + short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects') + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) + + def test_shortcut_bad_pk(self): + short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '42424242') + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) + + def test_nonint_content_type(self): + an_author = Author.objects.all()[0] + short_url = '/shortcut/%s/%s/' % ('spam', an_author.pk) + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) + + def test_bad_content_type(self): + an_author = Author.objects.all()[0] + short_url = '/shortcut/%s/%s/' % (42424242, an_author.pk) + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) diff --git a/tests/contenttypes_tests/urls.py b/tests/contenttypes_tests/urls.py new file mode 100644 index 0000000000..2cfc90b024 --- /dev/null +++ b/tests/contenttypes_tests/urls.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import, unicode_literals + +from django.conf.urls import patterns + +urlpatterns = patterns('', + (r'^shortcut/(\d+)/(.*)/$', 'django.contrib.contenttypes.views.shortcut'), +) diff --git a/tests/view_tests/tests/defaults.py b/tests/view_tests/tests/defaults.py index 3ca7f79136..5efd338d34 100644 --- a/tests/view_tests/tests/defaults.py +++ b/tests/view_tests/tests/defaults.py @@ -13,43 +13,6 @@ class DefaultsTests(TestCase): non_existing_urls = ['/views/non_existing_url/', # this is in urls.py '/views/other_non_existing_url/'] # this NOT in urls.py - def test_shortcut_with_absolute_url(self): - "Can view a shortcut for an Author object that has a get_absolute_url method" - for obj in Author.objects.all(): - short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk) - response = self.client.get(short_url) - self.assertRedirects(response, 'http://testserver%s' % obj.get_absolute_url(), - status_code=302, target_status_code=404) - - def test_shortcut_no_absolute_url(self): - "Shortcuts for an object that has no get_absolute_url method raises 404" - for obj in Article.objects.all(): - short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk) - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - - def test_wrong_type_pk(self): - short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects') - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - - def test_shortcut_bad_pk(self): - short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '42424242') - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - - def test_nonint_content_type(self): - an_author = Author.objects.all()[0] - short_url = '/views/shortcut/%s/%s/' % ('spam', an_author.pk) - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - - def test_bad_content_type(self): - an_author = Author.objects.all()[0] - short_url = '/views/shortcut/%s/%s/' % (42424242, an_author.pk) - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - def test_page_not_found(self): "A 404 status is returned by the page_not_found view" for url in self.non_existing_urls: diff --git a/tests/view_tests/urls.py b/tests/view_tests/urls.py index 8a3b492cec..52e2eb474e 100644 --- a/tests/view_tests/urls.py +++ b/tests/view_tests/urls.py @@ -42,7 +42,6 @@ urlpatterns = patterns('', (r'^$', views.index_page), # Default views - (r'^shortcut/(\d+)/(.*)/$', 'django.views.defaults.shortcut'), (r'^non_existing_url/', 'django.views.defaults.page_not_found'), (r'^server_error/', 'django.views.defaults.server_error'),