Fixed #21927 -- Made application and instance namespaces more distinct.

Made URL application namespaces be set in the included URLconf and
instance namespaces in the call to include(). Deprecated other ways
to set application and instance namespaces.
This commit is contained in:
Marten Kenbeek 2015-05-28 17:25:52 +02:00 committed by Tim Graham
parent 39937de7e6
commit 1e82094f1b
30 changed files with 352 additions and 113 deletions

View File

@ -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),
]

View File

@ -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).

View File

@ -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

View File

@ -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:

View File

@ -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<question_id>[0-9]+)/$', views.detail, name='detail'),
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
Now change your ``polls/index.html`` template from:

View File

@ -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

View File

@ -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:

View File

@ -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
----------

View File

@ -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 (<list of patterns>, <application namespace>) 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 <custom-template-loaders>`.
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
~~~~~~~~~~~~~

View File

@ -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<pk>\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 <application namespace>` and
:term:`instance <instance namespace>` 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<pk>\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::
(<list of url() instances>, <application namespace>, <instance namespace>)
(<list of url() instances>, <application namespace>)
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<pk>\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
``(<list of url() instances>, <application namespace>, <instance namespace>)``.

View File

@ -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<slug>[\w-]+)/$', news_views.category, name='category'),
url(r'^(?P<slug>[\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<slug>[\w-]+)/$'), news_views.category, name='category'),
url(r'^(?P<slug>[\w-]+)/$', news_views.details, name='detail'),
]
], 'news')
urlpatterns += i18n_patterns(
url(_(r'^about/$'), about_views.main, name='about'),

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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)),

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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'),

View File

@ -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'),
)

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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<arg1>[0-9]+)/(?P<arg2>[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<arg2>[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<arg1>[0-9]+)/(?P<arg2>[0-9]+)/$', views.view_class_instance, name='inc-view-class'),
]

View File

@ -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<arg1>[0-9]+)/(?P<arg2>[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<arg1>[0-9]+)/(?P<arg2>[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')),

View File

@ -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<arg1>[0-9]+)/(?P<arg2>[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<arg1>[0-9]+)/(?P<arg2>[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')

View File

@ -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),
]