diff --git a/django/conf/project_template/project_name/urls.py b/django/conf/project_template/project_name/urls.py index 665085b17b..c0cfc4e2a3 100644 --- a/django/conf/project_template/project_name/urls.py +++ b/django/conf/project_template/project_name/urls.py @@ -13,9 +13,9 @@ Including another URLconf 1. Add an import: from blog import urls as blog_urls 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) """ -from django.conf.urls import include, url +from django.conf.urls import url from django.contrib import admin urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] diff --git a/django/conf/urls/__init__.py b/django/conf/urls/__init__.py index 6ed54930c4..64bec13ec1 100644 --- a/django/conf/urls/__init__.py +++ b/django/conf/urls/__init__.py @@ -5,7 +5,7 @@ from django.core.urlresolvers import (RegexURLPattern, RegexURLResolver, LocaleRegexURLResolver) from django.core.exceptions import ImproperlyConfigured from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning +from django.utils.deprecation import RemovedInDjango20Warning, RemovedInDjango21Warning __all__ = ['handler400', 'handler403', 'handler404', 'handler500', 'include', 'patterns', 'url'] @@ -19,12 +19,29 @@ handler500 = 'django.views.defaults.server_error' def include(arg, namespace=None, app_name=None): if app_name and not namespace: raise ValueError('Must specify a namespace if specifying app_name.') + if app_name: + warnings.warn( + 'The app_name argument to django.conf.urls.include() is deprecated. ' + 'Set the app_name in the included URLconf instead.', + RemovedInDjango21Warning, stacklevel=2 + ) if isinstance(arg, tuple): # callable returning a namespace hint - if namespace: - raise ImproperlyConfigured('Cannot override the namespace for a dynamic module that provides a namespace') - urlconf_module, app_name, namespace = arg + try: + urlconf_module, app_name = arg + except ValueError: + if namespace: + raise ImproperlyConfigured( + 'Cannot override the namespace for a dynamic module that provides a namespace' + ) + warnings.warn( + 'Passing a 3-tuple to django.conf.urls.include() is deprecated. ' + 'Pass a 2-tuple containing the list of patterns and app_name, ' + 'and provide the namespace argument to include() instead.', + RemovedInDjango21Warning, stacklevel=2 + ) + urlconf_module, app_name, namespace = arg else: # No namespace hint - use manually provided namespace urlconf_module = arg @@ -32,6 +49,17 @@ def include(arg, namespace=None, app_name=None): if isinstance(urlconf_module, six.string_types): urlconf_module = import_module(urlconf_module) patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module) + app_name = getattr(urlconf_module, 'app_name', app_name) + if namespace and not app_name: + warnings.warn( + 'Specifying a namespace in django.conf.urls.include() without ' + 'providing an app_name is deprecated. Set the app_name attribute ' + 'in the included module, or pass a 2-tuple containing the list of ' + 'patterns and app_name instead.', + RemovedInDjango21Warning, stacklevel=2 + ) + + namespace = namespace or app_name # Make sure we can iterate through the patterns (without this, some # testcases will break). diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index bc55472121..1737681f88 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -70,6 +70,15 @@ details on these changes. ``django.utils.feedgenerator.RssFeed`` will be removed in favor of ``content_type``. +* The ``app_name`` argument to :func:`~django.conf.urls.include()` will be + removed. + +* Support for passing a 3-tuple as the first argument to ``include()`` will + be removed. + +* Support for setting a URL instance namespace without an application + namespace will be removed. + .. _deprecation-removed-in-2.0: 2.0 diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 4eb8ecca4d..6db12d118b 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -293,15 +293,15 @@ with: urlpatterns = [ url(r'^polls/', include('polls.urls')), - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] .. admonition:: Doesn't match what you see? - If you're seeing ``admin.autodiscover()`` before the definition of - ``urlpatterns``, you're probably using a version of Django that doesn't - match this tutorial version. You'll want to either switch to the older - tutorial or the newer Django version. + If you're seeing ``include(admin.site.urls)`` instead of just + ``admin.site.urls``, you're probably using a version of Django that + doesn't match this tutorial version. You'll want to either switch to the + older tutorial or the newer Django version. You have now wired an ``index`` view into the URLconf. Lets verify it's working, run the following command: diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 5b6c3e5ce7..9da44c3caf 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -442,18 +442,20 @@ view, and so might an app on the same project that is for a blog. How does one make it so that Django knows which app view to create for a url when using the ``{% url %}`` template tag? -The answer is to add namespaces to your root URLconf. In the ``mysite/urls.py`` -file, go ahead and change it to include namespacing: +The answer is to add namespaces to your URLconf. In the ``polls/urls.py`` +file, go ahead and add an ``app_name`` to set the application namespace: .. snippet:: - :filename: mysite/urls.py + :filename: polls/urls.py - from django.conf.urls import include, url - from django.contrib import admin + from django.conf.urls import url + app_name = 'polls' urlpatterns = [ - url(r'^polls/', include('polls.urls', namespace="polls")), - url(r'^admin/', include(admin.site.urls)), + url(r'^$', views.index, name='index'), + url(r'^(?P[0-9]+)/$', views.detail, name='detail'), + url(r'^(?P[0-9]+)/results/$', views.results, name='results'), + url(r'^(?P[0-9]+)/vote/$', views.vote, name='vote'), ] Now change your ``polls/index.html`` template from: diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 091967c796..f4480d6a3b 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -2613,19 +2613,25 @@ Hooking ``AdminSite`` instances into your URLconf The last step in setting up the Django admin is to hook your ``AdminSite`` instance into your URLconf. Do this by pointing a given URL at the -``AdminSite.urls`` method. +``AdminSite.urls`` method. It is not necessary to use +:func:`~django.conf.urls.include()`. In this example, we register the default ``AdminSite`` instance ``django.contrib.admin.site`` at the URL ``/admin/`` :: # urls.py - from django.conf.urls import include, url + from django.conf.urls import url from django.contrib import admin urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] +.. versionchanged:: 1.9 + + In previous versions, you would pass ``admin.site.urls`` to + :func:`~django.conf.urls.include()`. + .. _customizing-adminsite: Customizing the :class:`AdminSite` class @@ -2655,12 +2661,12 @@ update :file:`myproject/urls.py` to reference your :class:`AdminSite` subclass. .. snippet:: :filename: myproject/urls.py - from django.conf.urls import include, url + from django.conf.urls import url from myapp.admin import admin_site urlpatterns = [ - url(r'^myadmin/', include(admin_site.urls)), + url(r'^myadmin/', admin_site.urls), ] Note that you may not want autodiscovery of ``admin`` modules when using your @@ -2684,12 +2690,12 @@ separate versions of the admin site -- using the ``AdminSite`` instances respectively:: # urls.py - from django.conf.urls import include, url + from django.conf.urls import url from myproject.admin import basic_site, advanced_site urlpatterns = [ - url(r'^basic-admin/', include(basic_site.urls)), - url(r'^advanced-admin/', include(advanced_site.urls)), + url(r'^basic-admin/', basic_site.urls), + url(r'^advanced-admin/', advanced_site.urls), ] ``AdminSite`` instances take a single argument to their constructor, their diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 05d58f0ef4..71e5304082 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -749,7 +749,7 @@ Next, edit your ``urls.py`` in the ``geodjango`` application folder as follows:: from django.contrib.gis import admin urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] Create an admin user: diff --git a/docs/ref/urls.txt b/docs/ref/urls.txt index 222f41a76e..0cdd29eef3 100644 --- a/docs/ref/urls.txt +++ b/docs/ref/urls.txt @@ -130,6 +130,7 @@ include() .. function:: include(module[, namespace=None, app_name=None]) include(pattern_list) + include((pattern_list, app_namespace)[, namespace=None]) include((pattern_list, app_namespace, instance_namespace)) A function that takes a full Python import path to another URLconf module @@ -137,9 +138,14 @@ include() namespace` and :term:`instance namespace` where the entries will be included into can also be specified. + Usually, the application namespace should be specified by the included + module. If an application namespace is set, the ``namespace`` argument + can be used to set a different instance namespace. + ``include()`` also accepts as an argument either an iterable that returns - URL patterns or a 3-tuple containing such iterable plus the names of the - application and instance namespaces. + URL patterns, a 2-tuple containing such iterable plus the names of the + application namespaces, or a 3-tuple containing the iterable and the names + of both the application and instance namespace. :arg module: URLconf module (or module name) :arg namespace: Instance namespace for the URL entries being included @@ -154,6 +160,20 @@ include() See :ref:`including-other-urlconfs` and :ref:`namespaces-and-include`. +.. deprecated:: 1.9 + + Support for the ``app_name`` argument is deprecated and will be removed in + Django 2.1. Specify the ``app_name`` as explained in + :ref:`namespaces-and-include` instead. + + Support for passing a 3-tuple is also deprecated and will be removed in + Django 2.1. Pass a 2-tuple containing the pattern list and application + namespace, and use the ``namespace`` argument instead. + + Lastly, support for an instance namespace without an application namespace + has been deprecated and will be removed in Django 2.1. Specify the + application namespace or remove the instance namespace. + handler400 ---------- diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 55906864ef..1054699bf9 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -385,6 +385,11 @@ URLs * Regular expression lookaround assertions are now allowed in URL patterns. +* The application namespace can now be set using an ``app_name`` attribute + on the included module or object. It can also be set by passing a 2-tuple + of (, ) as the first argument to + :func:`~django.conf.urls.include`. + Validators ^^^^^^^^^^ @@ -706,6 +711,40 @@ extending. This change necessitated a new template loader API. The old Details about the new API can be found :ref:`in the template loader documentation `. +Passing a 3-tuple or an ``app_name`` to :func:`~django.conf.urls.include()` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The instance namespace part of passing a tuple as the first argument has been +replaced by passing the ``namespace`` argument to ``include()``. The +``app_name`` argument to ``include()`` has been replaced by passing a 2-tuple, +or passing an object or module with an ``app_name`` attribute. + +If the ``app_name`` is set in this new way, the ``namespace`` argument is no +longer required. It will default to the value of ``app_name``. + +This change also means that the old way of including an ``AdminSite`` instance +is deprecated. Instead, pass ``admin.site.urls`` directly to +:func:`~django.conf.urls.url()`: + +.. snippet:: + :filename: urls.py + + from django.conf.urls import url + from django.contrib import admin + + urlpatterns = [ + url(r'^admin/', admin.site.urls), + ] + +URL application namespace required if setting an instance namespace +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the past, an instance namespace without an application namespace +would serve the same purpose as the application namespace, but it was +impossible to reverse the patterns if there was an application namespace +with the same name. Includes that specify an instance namespace require that +the included URLconf sets an application namespace. + Miscellaneous ~~~~~~~~~~~~~ diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 607efe7308..aec8b74c3d 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -716,8 +716,8 @@ displaying polls. from django.conf.urls import include, url urlpatterns = [ - url(r'^author-polls/', include('polls.urls', namespace='author-polls', app_name='polls')), - url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls', app_name='polls')), + url(r'^author-polls/', include('polls.urls', namespace='author-polls')), + url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls')), ] .. snippet:: @@ -727,6 +727,7 @@ displaying polls. from . import views + app_name = 'polls' urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), @@ -778,25 +779,44 @@ declared last in ``urlpatterns``. URL namespaces and included URLconfs ------------------------------------ -URL namespaces of included URLconfs can be specified in two ways. +Application namespaces of included URLconfs can be specified in two ways. -Firstly, you can provide the :term:`application ` and -:term:`instance ` namespaces as arguments to -:func:`~django.conf.urls.include()` when you construct your URL patterns. For -example,:: +Firstly, you can set an ``app_name`` attribute in the included URLconf module, +at the same level as the ``urlpatterns`` attribute. You have to pass the actual +module, or a string reference to the module, to +:func:`~django.conf.urls.include`, not the list of ``urlpatterns`` itself. - url(r'^polls/', include('polls.urls', namespace='author-polls', app_name='polls')), +.. snippet:: + :filename: polls/urls.py -This will include the URLs defined in ``polls.urls`` into the -:term:`application namespace` ``'polls'``, with the :term:`instance namespace` -``'author-polls'``. + from django.conf.urls import url + + from . import views + + app_name = 'polls' + urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), + ... + ] + +.. snippet:: + :filename: urls.py + + from django.conf.urls import include, url + + urlpatterns = [ + url(r'^polls/', include('polls.urls')), + ] + +The URLs defined in ``polls.urls`` will have an application namespace ``polls``. Secondly, you can include an object that contains embedded namespace data. If you ``include()`` a list of :func:`~django.conf.urls.url` instances, the URLs contained in that object will be added to the global namespace. -However, you can also ``include()`` a 3-tuple containing:: +However, you can also ``include()`` a 2-tuple containing:: - (, , ) + (, ) For example:: @@ -804,25 +824,25 @@ For example:: from . import views - polls_patterns = [ + polls_patterns = ([ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), - ] + ], 'polls') - url(r'^polls/', include((polls_patterns, 'polls', 'author-polls'))), + url(r'^polls/', include(polls_patterns)), -This will include the nominated URL patterns into the given application and -instance namespace. +This will include the nominated URL patterns into the given application +namespace. -For example, the Django admin is deployed as instances of -:class:`~django.contrib.admin.AdminSite`. ``AdminSite`` objects have a ``urls`` -attribute: A 3-tuple that contains all the patterns in the corresponding admin -site, plus the application namespace ``'admin'``, and the name of the admin -instance. It is this ``urls`` attribute that you ``include()`` into your -projects ``urlpatterns`` when you deploy an admin instance. +The instance namespace can be specified using the ``namespace`` argument to +:func:`~django.conf.urls.include`. If the instance namespace is not specified, +it will default to the included URLconf's application namespace. This means +it will also be the default instance for that namespace. -Be sure to pass a tuple to ``include()``. If you simply pass three arguments: -``include(polls_patterns, 'polls', 'author-polls')``, Django won't throw an -error but due to the signature of ``include()``, ``'polls'`` will be the -instance namespace and ``'author-polls'`` will be the application namespace -instead of vice versa. +.. versionchanged:: 1.9 + + In previous versions, you had to specify both the application namespace + and the instance namespace in a single place, either by passing them as + parameters to :func:`~django.conf.urls.include` or by including a 3-tuple + containing + ``(, , )``. diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 557204adc2..e52adf034a 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1285,11 +1285,11 @@ prepend the current active language code to all url patterns defined within url(r'^sitemap\.xml$', sitemap, name='sitemap_xml'), ] - news_patterns = [ + news_patterns = ([ url(r'^$', news_views.index, name='index'), url(r'^category/(?P[\w-]+)/$', news_views.category, name='category'), url(r'^(?P[\w-]+)/$', news_views.details, name='detail'), - ] + ], 'news') urlpatterns += i18n_patterns( url(r'^about/$', about_views.main, name='about'), @@ -1343,11 +1343,11 @@ URL patterns can also be marked translatable using the url(r'^sitemap\.xml$', sitemap, name='sitemap_xml'), ] - news_patterns = [ + news_patterns = ([ url(r'^$', news_views.index, name='index'), url(_(r'^category/(?P[\w-]+)/$'), news_views.category, name='category'), url(r'^(?P[\w-]+)/$', news_views.details, name='detail'), - ] + ], 'news') urlpatterns += i18n_patterns( url(_(r'^about/$'), about_views.main, name='about'), diff --git a/tests/admin_changelist/urls.py b/tests/admin_changelist/urls.py index 4da326ccc4..1f553a85a9 100644 --- a/tests/admin_changelist/urls.py +++ b/tests/admin_changelist/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import include, url +from django.conf.urls import url from . import admin urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] diff --git a/tests/admin_custom_urls/urls.py b/tests/admin_custom_urls/urls.py index 103f9b0121..b07e1395b9 100644 --- a/tests/admin_custom_urls/urls.py +++ b/tests/admin_custom_urls/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import include, url +from django.conf.urls import url from .models import site urlpatterns = [ - url(r'^admin/', include(site.urls)), + url(r'^admin/', site.urls), ] diff --git a/tests/admin_docs/urls.py b/tests/admin_docs/urls.py index 3fbfaff0c4..0bcdee4b4b 100644 --- a/tests/admin_docs/urls.py +++ b/tests/admin_docs/urls.py @@ -3,12 +3,12 @@ from django.contrib import admin from . import views -ns_patterns = [ +ns_patterns = ([ url(r'^xview/func/$', views.xview_dec(views.xview), name='func'), -] +], 'test') urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url(r'^admindocs/', include('django.contrib.admindocs.urls')), url(r'^', include(ns_patterns, namespace='test')), url(r'^xview/func/$', views.xview_dec(views.xview)), diff --git a/tests/admin_inlines/urls.py b/tests/admin_inlines/urls.py index 4da326ccc4..1f553a85a9 100644 --- a/tests/admin_inlines/urls.py +++ b/tests/admin_inlines/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import include, url +from django.conf.urls import url from . import admin urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] diff --git a/tests/admin_views/test_adminsite.py b/tests/admin_views/test_adminsite.py index 6e0fce57d8..575a15c23a 100644 --- a/tests/admin_views/test_adminsite.py +++ b/tests/admin_views/test_adminsite.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import datetime -from django.conf.urls import include, url +from django.conf.urls import url from django.contrib import admin from django.contrib.auth.models import User from django.core.urlresolvers import reverse @@ -16,7 +16,7 @@ site.register(User) site.register(Article) urlpatterns = [ - url(r'^test_admin/admin/', include(site.urls)), + url(r'^test_admin/admin/', site.urls), ] diff --git a/tests/admin_views/urls.py b/tests/admin_views/urls.py index 679ae916af..926eca0a2d 100644 --- a/tests/admin_views/urls.py +++ b/tests/admin_views/urls.py @@ -6,11 +6,11 @@ urlpatterns = [ url(r'^test_admin/admin/doc/', include('django.contrib.admindocs.urls')), url(r'^test_admin/admin/secure-view/$', views.secure_view, name='secure_view'), url(r'^test_admin/admin/secure-view2/$', views.secure_view2, name='secure_view2'), - url(r'^test_admin/admin/', include(admin.site.urls)), - url(r'^test_admin/admin2/', include(customadmin.site.urls)), - url(r'^test_admin/admin3/', include(admin.site.get_urls(), 'admin3', 'admin'), dict(form_url='pony')), - url(r'^test_admin/admin4/', include(customadmin.simple_site.urls)), - url(r'^test_admin/admin5/', include(admin.site2.urls)), - url(r'^test_admin/admin7/', include(admin.site7.urls)), - url(r'^test_admin/has_permission_admin/', include(custom_has_permission_admin.site.urls)), + url(r'^test_admin/admin/', admin.site.urls), + url(r'^test_admin/admin2/', customadmin.site.urls), + url(r'^test_admin/admin3/', (admin.site.get_urls(), 'admin', 'admin3'), dict(form_url='pony')), + url(r'^test_admin/admin4/', customadmin.simple_site.urls), + url(r'^test_admin/admin5/', admin.site2.urls), + url(r'^test_admin/admin7/', admin.site7.urls), + url(r'^test_admin/has_permission_admin/', custom_has_permission_admin.site.urls), ] diff --git a/tests/admin_widgets/urls.py b/tests/admin_widgets/urls.py index 7f4721c57e..3381b2f13a 100644 --- a/tests/admin_widgets/urls.py +++ b/tests/admin_widgets/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import include, url +from django.conf.urls import url from . import widgetadmin urlpatterns = [ - url(r'^', include(widgetadmin.site.urls)), + url(r'^', widgetadmin.site.urls), ] diff --git a/tests/auth_tests/urls.py b/tests/auth_tests/urls.py index 3c4dc6b12b..2851b451e1 100644 --- a/tests/auth_tests/urls.py +++ b/tests/auth_tests/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import include, url +from django.conf.urls import url from django.contrib import admin from django.contrib.auth import views from django.contrib.auth.decorators import login_required @@ -97,5 +97,5 @@ urlpatterns = auth_urlpatterns + [ url(r'^userpage/(.+)/$', userpage, name="userpage"), # This line is only required to render the password reset with is_admin=True - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] diff --git a/tests/auth_tests/urls_admin.py b/tests/auth_tests/urls_admin.py index 8d2fe3fd10..8e5b0f1f0c 100644 --- a/tests/auth_tests/urls_admin.py +++ b/tests/auth_tests/urls_admin.py @@ -2,7 +2,7 @@ Test URLs for auth admins. """ -from django.conf.urls import include, url +from django.conf.urls import url from django.contrib import admin from django.contrib.auth.admin import GroupAdmin, UserAdmin from django.contrib.auth.models import Group, User @@ -14,5 +14,5 @@ site.register(User, UserAdmin) site.register(Group, GroupAdmin) urlpatterns += [ - url(r'^admin/', include(site.urls)), + url(r'^admin/', site.urls), ] diff --git a/tests/auth_tests/urls_custom_user_admin.py b/tests/auth_tests/urls_custom_user_admin.py index f8bc194ba5..dc47be68c7 100644 --- a/tests/auth_tests/urls_custom_user_admin.py +++ b/tests/auth_tests/urls_custom_user_admin.py @@ -1,4 +1,4 @@ -from django.conf.urls import include, url +from django.conf.urls import url from django.contrib import admin from django.contrib.auth import get_user_model from django.contrib.auth.admin import UserAdmin @@ -16,5 +16,5 @@ class CustomUserAdmin(UserAdmin): site.register(get_user_model(), CustomUserAdmin) urlpatterns = [ - url(r'^admin/', include(site.urls)), + url(r'^admin/', site.urls), ] diff --git a/tests/generic_inline_admin/urls.py b/tests/generic_inline_admin/urls.py index bbabfc21a4..59f09437db 100644 --- a/tests/generic_inline_admin/urls.py +++ b/tests/generic_inline_admin/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import include, url +from django.conf.urls import url from . import admin urlpatterns = [ - url(r'^generic_inline_admin/admin/', include(admin.site.urls)), + url(r'^generic_inline_admin/admin/', admin.site.urls), ] diff --git a/tests/i18n/patterns/urls/namespace.py b/tests/i18n/patterns/urls/namespace.py index 6060c57bd6..3a34c7d815 100644 --- a/tests/i18n/patterns/urls/namespace.py +++ b/tests/i18n/patterns/urls/namespace.py @@ -4,6 +4,7 @@ from django.views.generic import TemplateView view = TemplateView.as_view(template_name='dummy.html') +app_name = 'account' urlpatterns = [ url(_(r'^register/$'), view, name='register'), url(_(r'^register-without-slash$'), view, name='register-without-slash'), diff --git a/tests/i18n/patterns/urls/wrong_namespace.py b/tests/i18n/patterns/urls/wrong_namespace.py index e19c99c935..3983cb6195 100644 --- a/tests/i18n/patterns/urls/wrong_namespace.py +++ b/tests/i18n/patterns/urls/wrong_namespace.py @@ -5,6 +5,7 @@ from django.views.generic import TemplateView view = TemplateView.as_view(template_name='dummy.html') +app_name = 'account' urlpatterns = i18n_patterns( url(_(r'^register/$'), view, name='register'), ) diff --git a/tests/proxy_models/urls.py b/tests/proxy_models/urls.py index 854ac78040..18ade2e739 100644 --- a/tests/proxy_models/urls.py +++ b/tests/proxy_models/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import include, url +from django.conf.urls import url from .admin import site urlpatterns = [ - url(r'^admin/', include(site.urls)), + url(r'^admin/', site.urls), ] diff --git a/tests/timezones/urls.py b/tests/timezones/urls.py index e5c88a0025..84b13b593d 100644 --- a/tests/timezones/urls.py +++ b/tests/timezones/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import include, url +from django.conf.urls import url from . import admin as tz_admin # NOQA: register tz_admin urlpatterns = [ - url(r'^admin/', include(tz_admin.site.urls)), + url(r'^admin/', tz_admin.site.urls), ] diff --git a/tests/urlpatterns_reverse/included_app_urls.py b/tests/urlpatterns_reverse/included_app_urls.py new file mode 100644 index 0000000000..570d6fc518 --- /dev/null +++ b/tests/urlpatterns_reverse/included_app_urls.py @@ -0,0 +1,16 @@ +from django.conf.urls import url + +from . import views + +app_name = 'inc-app' +urlpatterns = [ + url(r'^normal/$', views.empty_view, name='inc-normal-view'), + url(r'^normal/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='inc-normal-view'), + + url(r'^\+\\\$\*/$', views.empty_view, name='inc-special-view'), + + url(r'^mixed_args/([0-9]+)/(?P[0-9]+)/$', views.empty_view, name='inc-mixed-args'), + url(r'^no_kwargs/([0-9]+)/([0-9]+)/$', views.empty_view, name='inc-no-kwargs'), + + url(r'^view_class/(?P[0-9]+)/(?P[0-9]+)/$', views.view_class_instance, name='inc-view-class'), +] diff --git a/tests/urlpatterns_reverse/namespace_urls.py b/tests/urlpatterns_reverse/namespace_urls.py index 9efd0e9d64..460778860f 100644 --- a/tests/urlpatterns_reverse/namespace_urls.py +++ b/tests/urlpatterns_reverse/namespace_urls.py @@ -1,20 +1,7 @@ from django.conf.urls import include, url from . import views - - -class URLObject(object): - def __init__(self, app_name, namespace): - self.app_name = app_name - self.namespace = namespace - - def urls(self): - return ([ - url(r'^inner/$', views.empty_view, name='urlobject-view'), - url(r'^inner/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='urlobject-view'), - url(r'^inner/\+\\\$\*/$', views.empty_view, name='urlobject-special-view'), - ], self.app_name, self.namespace) - urls = property(urls) +from .tests import URLObject testobj1 = URLObject('testapp', 'test-ns1') testobj2 = URLObject('testapp', 'test-ns2') @@ -23,6 +10,8 @@ default_testobj = URLObject('testapp', 'testapp') otherobj1 = URLObject('nodefault', 'other-ns1') otherobj2 = URLObject('nodefault', 'other-ns2') +newappobj1 = URLObject('newapp') + urlpatterns = [ url(r'^normal/$', views.empty_view, name='normal-view'), url(r'^normal/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='normal-view'), @@ -45,6 +34,12 @@ urlpatterns = [ url(r'^other1/', include(otherobj1.urls)), url(r'^other[246]/', include(otherobj2.urls)), + url(r'^newapp1/', include(newappobj1.app_urls, 'new-ns1')), + url(r'^new-default/', include(newappobj1.app_urls)), + + url(r'^app-included[135]/', include('urlpatterns_reverse.included_app_urls', namespace='app-ns1')), + url(r'^app-included2/', include('urlpatterns_reverse.included_app_urls', namespace='app-ns2')), + url(r'^ns-included[135]/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')), url(r'^ns-included2/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')), diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py index c9378c784e..a8aa0b2e1b 100644 --- a/tests/urlpatterns_reverse/tests.py +++ b/tests/urlpatterns_reverse/tests.py @@ -10,7 +10,7 @@ import unittest from admin_scripts.tests import AdminScriptTestCase from django.conf import settings -from django.conf.urls import include +from django.conf.urls import include, url from django.contrib.auth.models import User from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.urlresolvers import ( @@ -26,7 +26,9 @@ from django.test import ( ) from django.test.utils import override_script_prefix from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning +from django.utils.deprecation import ( + RemovedInDjango20Warning, RemovedInDjango21Warning, +) from . import middleware, urlconf_outer, views from .views import empty_view @@ -184,6 +186,26 @@ test_data = ( ) +class URLObject(object): + urlpatterns = [ + url(r'^inner/$', views.empty_view, name='urlobject-view'), + url(r'^inner/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='urlobject-view'), + url(r'^inner/\+\\\$\*/$', views.empty_view, name='urlobject-special-view'), + ] + + def __init__(self, app_name, namespace=None): + self.app_name = app_name + self.namespace = namespace + + @property + def urls(self): + return self.urlpatterns, self.app_name, self.namespace + + @property + def app_urls(self): + return self.urlpatterns, self.app_name + + @override_settings(ROOT_URLCONF='urlpatterns_reverse.no_urls') class NoURLPatternsTests(SimpleTestCase): @@ -281,6 +303,7 @@ class URLPatternReverse(SimpleTestCase): class ResolverTests(unittest.TestCase): + @ignore_warnings(category=RemovedInDjango21Warning) def test_resolver_repr(self): """ Test repr of RegexURLResolver, especially when urlconf_name is a list @@ -460,6 +483,7 @@ class ReverseShortcutTests(SimpleTestCase): @override_settings(ROOT_URLCONF='urlpatterns_reverse.namespace_urls') +@ignore_warnings(category=RemovedInDjango21Warning) class NamespaceTests(SimpleTestCase): def test_ambiguous_object(self): @@ -500,6 +524,20 @@ class NamespaceTests(SimpleTestCase): self.assertEqual('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})) self.assertEqual('/test1/inner/+%5C$*/', reverse('test-ns1:urlobject-special-view')) + def test_app_object(self): + "Dynamic URL objects can return a (pattern, app_name) 2-tuple, and include() can set the namespace" + self.assertEqual('/newapp1/inner/', reverse('new-ns1:urlobject-view')) + self.assertEqual('/newapp1/inner/37/42/', reverse('new-ns1:urlobject-view', args=[37, 42])) + self.assertEqual('/newapp1/inner/42/37/', reverse('new-ns1:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})) + self.assertEqual('/newapp1/inner/+%5C$*/', reverse('new-ns1:urlobject-special-view')) + + def test_app_object_default_namespace(self): + "Namespace defaults to app_name when including a (pattern, app_name) 2-tuple" + self.assertEqual('/new-default/inner/', reverse('newapp:urlobject-view')) + self.assertEqual('/new-default/inner/37/42/', reverse('newapp:urlobject-view', args=[37, 42])) + self.assertEqual('/new-default/inner/42/37/', reverse('newapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})) + self.assertEqual('/new-default/inner/+%5C$*/', reverse('newapp:urlobject-special-view')) + def test_embedded_namespace_object(self): "Namespaces can be installed anywhere in the URL pattern tree" self.assertEqual('/included/test3/inner/', reverse('test-ns3:urlobject-view')) @@ -514,6 +552,13 @@ class NamespaceTests(SimpleTestCase): self.assertEqual('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1': 42, 'arg2': 37})) self.assertEqual('/ns-included1/+%5C$*/', reverse('inc-ns1:inc-special-view')) + def test_app_name_pattern(self): + "Namespaces can be applied to include()'d urlpatterns that set an app_name attribute" + self.assertEqual('/app-included1/normal/', reverse('app-ns1:inc-normal-view')) + self.assertEqual('/app-included1/normal/37/42/', reverse('app-ns1:inc-normal-view', args=[37, 42])) + self.assertEqual('/app-included1/normal/42/37/', reverse('app-ns1:inc-normal-view', kwargs={'arg1': 42, 'arg2': 37})) + self.assertEqual('/app-included1/+%5C$*/', reverse('app-ns1:inc-special-view')) + def test_namespace_pattern_with_variable_prefix(self): "When using an include with namespaces when there is a regex variable in front of it" self.assertEqual('/ns-outer/42/normal/', reverse('inc-outer:inc-normal-view', kwargs={'outer': 42})) @@ -769,6 +814,7 @@ class NoRootUrlConfTests(SimpleTestCase): @override_settings(ROOT_URLCONF='urlpatterns_reverse.namespace_urls') class ResolverMatchTests(SimpleTestCase): + @ignore_warnings(category=RemovedInDjango21Warning) def test_urlpattern_resolve(self): for path, url_name, app_name, namespace, view_name, func, args, kwargs in resolve_test_data: # Test legacy support for extracting "function, args, kwargs" @@ -793,6 +839,7 @@ class ResolverMatchTests(SimpleTestCase): self.assertEqual(match[1], args) self.assertEqual(match[2], kwargs) + @ignore_warnings(category=RemovedInDjango21Warning) def test_resolver_match_on_request(self): response = self.client.get('/resolver_match/') resolver_match = response.resolver_match @@ -845,10 +892,65 @@ class ViewLoadingTests(SimpleTestCase): class IncludeTests(SimpleTestCase): + url_patterns = [ + url(r'^inner/$', views.empty_view, name='urlobject-view'), + url(r'^inner/(?P[0-9]+)/(?P[0-9]+)/$', views.empty_view, name='urlobject-view'), + url(r'^inner/\+\\\$\*/$', views.empty_view, name='urlobject-special-view'), + ] + app_urls = URLObject('inc-app') + def test_include_app_name_but_no_namespace(self): msg = "Must specify a namespace if specifying app_name." with self.assertRaisesMessage(ValueError, msg): - include('urls', app_name='bar') + include(self.url_patterns, app_name='bar') + + def test_include_urls(self): + self.assertEqual(include(self.url_patterns), (self.url_patterns, None, None)) + + @ignore_warnings(category=RemovedInDjango21Warning) + def test_include_namespace(self): + # no app_name -> deprecated + self.assertEqual(include(self.url_patterns, 'namespace'), (self.url_patterns, None, 'namespace')) + + @ignore_warnings(category=RemovedInDjango21Warning) + def test_include_namespace_app_name(self): + # app_name argument to include -> deprecated + self.assertEqual( + include(self.url_patterns, 'namespace', 'app_name'), + (self.url_patterns, 'app_name', 'namespace') + ) + + @ignore_warnings(category=RemovedInDjango21Warning) + def test_include_3_tuple(self): + # 3-tuple -> deprecated + self.assertEqual( + include((self.url_patterns, 'app_name', 'namespace')), + (self.url_patterns, 'app_name', 'namespace') + ) + + def test_include_2_tuple(self): + self.assertEqual( + include((self.url_patterns, 'app_name')), + (self.url_patterns, 'app_name', 'app_name') + ) + + def test_include_2_tuple_namespace(self): + self.assertEqual( + include((self.url_patterns, 'app_name'), namespace='namespace'), + (self.url_patterns, 'app_name', 'namespace') + ) + + def test_include_app_name(self): + self.assertEqual( + include(self.app_urls), + (self.app_urls, 'inc-app', 'inc-app') + ) + + def test_include_app_name_namespace(self): + self.assertEqual( + include(self.app_urls, 'namespace'), + (self.app_urls, 'inc-app', 'namespace') + ) @override_settings(ROOT_URLCONF='urlpatterns_reverse.urls') diff --git a/tests/view_tests/default_urls.py b/tests/view_tests/default_urls.py index 2811bf7740..f23a286305 100644 --- a/tests/view_tests/default_urls.py +++ b/tests/view_tests/default_urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import include, url +from django.conf.urls import url from django.contrib import admin urlpatterns = [ # This is the same as in the default project template - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ]