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 1. Add an import: from blog import urls as blog_urls
2. Add a URL to urlpatterns: url(r'^blog/', include(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 from django.contrib import admin
urlpatterns = [ 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) RegexURLResolver, LocaleRegexURLResolver)
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils import six 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'] __all__ = ['handler400', 'handler403', 'handler404', 'handler500', 'include', 'patterns', 'url']
@ -19,11 +19,28 @@ handler500 = 'django.views.defaults.server_error'
def include(arg, namespace=None, app_name=None): def include(arg, namespace=None, app_name=None):
if app_name and not namespace: if app_name and not namespace:
raise ValueError('Must specify a namespace if specifying app_name.') 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): if isinstance(arg, tuple):
# callable returning a namespace hint # callable returning a namespace hint
try:
urlconf_module, app_name = arg
except ValueError:
if namespace: if namespace:
raise ImproperlyConfigured('Cannot override the namespace for a dynamic module that provides a 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 urlconf_module, app_name, namespace = arg
else: else:
# No namespace hint - use manually provided namespace # No namespace hint - use manually provided namespace
@ -32,6 +49,17 @@ def include(arg, namespace=None, app_name=None):
if isinstance(urlconf_module, six.string_types): if isinstance(urlconf_module, six.string_types):
urlconf_module = import_module(urlconf_module) urlconf_module = import_module(urlconf_module)
patterns = getattr(urlconf_module, 'urlpatterns', 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 # Make sure we can iterate through the patterns (without this, some
# testcases will break). # testcases will break).

View File

@ -70,6 +70,15 @@ details on these changes.
``django.utils.feedgenerator.RssFeed`` will be removed in favor of ``django.utils.feedgenerator.RssFeed`` will be removed in favor of
``content_type``. ``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: .. _deprecation-removed-in-2.0:
2.0 2.0

View File

@ -293,15 +293,15 @@ with:
urlpatterns = [ urlpatterns = [
url(r'^polls/', include('polls.urls')), 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? .. admonition:: Doesn't match what you see?
If you're seeing ``admin.autodiscover()`` before the definition of If you're seeing ``include(admin.site.urls)`` instead of just
``urlpatterns``, you're probably using a version of Django that doesn't ``admin.site.urls``, you're probably using a version of Django that
match this tutorial version. You'll want to either switch to the older doesn't match this tutorial version. You'll want to either switch to the
tutorial or the newer Django version. older tutorial or the newer Django version.
You have now wired an ``index`` view into the URLconf. Lets verify it's You have now wired an ``index`` view into the URLconf. Lets verify it's
working, run the following command: 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 make it so that Django knows which app view to create for a url when using the
``{% url %}`` template tag? ``{% url %}`` template tag?
The answer is to add namespaces to your root URLconf. In the ``mysite/urls.py`` The answer is to add namespaces to your URLconf. In the ``polls/urls.py``
file, go ahead and change it to include namespacing: file, go ahead and add an ``app_name`` to set the application namespace:
.. snippet:: .. snippet::
:filename: mysite/urls.py :filename: polls/urls.py
from django.conf.urls import include, url from django.conf.urls import url
from django.contrib import admin
app_name = 'polls'
urlpatterns = [ urlpatterns = [
url(r'^polls/', include('polls.urls', namespace="polls")), url(r'^$', views.index, name='index'),
url(r'^admin/', include(admin.site.urls)), 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: 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`` 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 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 In this example, we register the default ``AdminSite`` instance
``django.contrib.admin.site`` at the URL ``/admin/`` :: ``django.contrib.admin.site`` at the URL ``/admin/`` ::
# urls.py # urls.py
from django.conf.urls import include, url from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
urlpatterns = [ 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-adminsite:
Customizing the :class:`AdminSite` class Customizing the :class:`AdminSite` class
@ -2655,12 +2661,12 @@ update :file:`myproject/urls.py` to reference your :class:`AdminSite` subclass.
.. snippet:: .. snippet::
:filename: myproject/urls.py :filename: myproject/urls.py
from django.conf.urls import include, url from django.conf.urls import url
from myapp.admin import admin_site from myapp.admin import admin_site
urlpatterns = [ 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 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:: respectively::
# urls.py # urls.py
from django.conf.urls import include, url from django.conf.urls import url
from myproject.admin import basic_site, advanced_site from myproject.admin import basic_site, advanced_site
urlpatterns = [ urlpatterns = [
url(r'^basic-admin/', include(basic_site.urls)), url(r'^basic-admin/', basic_site.urls),
url(r'^advanced-admin/', include(advanced_site.urls)), url(r'^advanced-admin/', advanced_site.urls),
] ]
``AdminSite`` instances take a single argument to their constructor, their ``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 from django.contrib.gis import admin
urlpatterns = [ urlpatterns = [
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', admin.site.urls),
] ]
Create an admin user: Create an admin user:

View File

@ -130,6 +130,7 @@ include()
.. function:: include(module[, namespace=None, app_name=None]) .. function:: include(module[, namespace=None, app_name=None])
include(pattern_list) include(pattern_list)
include((pattern_list, app_namespace)[, namespace=None])
include((pattern_list, app_namespace, instance_namespace)) include((pattern_list, app_namespace, instance_namespace))
A function that takes a full Python import path to another URLconf module 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 namespace` and :term:`instance namespace` where the entries will be included
into can also be specified. 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 ``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 URL patterns, a 2-tuple containing such iterable plus the names of the
application and instance namespaces. 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 module: URLconf module (or module name)
:arg namespace: Instance namespace for the URL entries being included :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`. 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 handler400
---------- ----------

View File

@ -385,6 +385,11 @@ URLs
* Regular expression lookaround assertions are now allowed in URL patterns. * 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 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 Details about the new API can be found :ref:`in the template loader
documentation <custom-template-loaders>`. 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 Miscellaneous
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

View File

@ -716,8 +716,8 @@ displaying polls.
from django.conf.urls import include, url from django.conf.urls import include, url
urlpatterns = [ urlpatterns = [
url(r'^author-polls/', include('polls.urls', namespace='author-polls', app_name='polls')), url(r'^author-polls/', include('polls.urls', namespace='author-polls')),
url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls', app_name='polls')), url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls')),
] ]
.. snippet:: .. snippet::
@ -727,6 +727,7 @@ displaying polls.
from . import views from . import views
app_name = 'polls'
urlpatterns = [ urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'), url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'), 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 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 Firstly, you can set an ``app_name`` attribute in the included URLconf module,
:term:`instance <instance namespace>` namespaces as arguments to at the same level as the ``urlpatterns`` attribute. You have to pass the actual
:func:`~django.conf.urls.include()` when you construct your URL patterns. For module, or a string reference to the module, to
example,:: :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 from django.conf.urls import url
:term:`application namespace` ``'polls'``, with the :term:`instance namespace`
``'author-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'),
...
]
.. 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 Secondly, you can include an object that contains embedded namespace data. If
you ``include()`` a list of :func:`~django.conf.urls.url` instances, you ``include()`` a list of :func:`~django.conf.urls.url` instances,
the URLs contained in that object will be added to the global namespace. 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:: For example::
@ -804,25 +824,25 @@ For example::
from . import views from . import views
polls_patterns = [ polls_patterns = ([
url(r'^$', views.IndexView.as_view(), name='index'), url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'), 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 This will include the nominated URL patterns into the given application
instance namespace. namespace.
For example, the Django admin is deployed as instances of The instance namespace can be specified using the ``namespace`` argument to
:class:`~django.contrib.admin.AdminSite`. ``AdminSite`` objects have a ``urls`` :func:`~django.conf.urls.include`. If the instance namespace is not specified,
attribute: A 3-tuple that contains all the patterns in the corresponding admin it will default to the included URLconf's application namespace. This means
site, plus the application namespace ``'admin'``, and the name of the admin it will also be the default instance for that namespace.
instance. It is this ``urls`` attribute that you ``include()`` into your
projects ``urlpatterns`` when you deploy an admin instance.
Be sure to pass a tuple to ``include()``. If you simply pass three arguments: .. versionchanged:: 1.9
``include(polls_patterns, 'polls', 'author-polls')``, Django won't throw an
error but due to the signature of ``include()``, ``'polls'`` will be the In previous versions, you had to specify both the application namespace
instance namespace and ``'author-polls'`` will be the application namespace and the instance namespace in a single place, either by passing them as
instead of vice versa. 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'), url(r'^sitemap\.xml$', sitemap, name='sitemap_xml'),
] ]
news_patterns = [ news_patterns = ([
url(r'^$', news_views.index, name='index'), url(r'^$', news_views.index, name='index'),
url(r'^category/(?P<slug>[\w-]+)/$', news_views.category, name='category'), url(r'^category/(?P<slug>[\w-]+)/$', news_views.category, name='category'),
url(r'^(?P<slug>[\w-]+)/$', news_views.details, name='detail'), url(r'^(?P<slug>[\w-]+)/$', news_views.details, name='detail'),
] ], 'news')
urlpatterns += i18n_patterns( urlpatterns += i18n_patterns(
url(r'^about/$', about_views.main, name='about'), 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'), url(r'^sitemap\.xml$', sitemap, name='sitemap_xml'),
] ]
news_patterns = [ news_patterns = ([
url(r'^$', news_views.index, name='index'), url(r'^$', news_views.index, name='index'),
url(_(r'^category/(?P<slug>[\w-]+)/$'), news_views.category, name='category'), url(_(r'^category/(?P<slug>[\w-]+)/$'), news_views.category, name='category'),
url(r'^(?P<slug>[\w-]+)/$', news_views.details, name='detail'), url(r'^(?P<slug>[\w-]+)/$', news_views.details, name='detail'),
] ], 'news')
urlpatterns += i18n_patterns( urlpatterns += i18n_patterns(
url(_(r'^about/$'), about_views.main, name='about'), 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 from . import admin
urlpatterns = [ 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 from .models import site
urlpatterns = [ 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 from . import views
ns_patterns = [ ns_patterns = ([
url(r'^xview/func/$', views.xview_dec(views.xview), name='func'), url(r'^xview/func/$', views.xview_dec(views.xview), name='func'),
] ], 'test')
urlpatterns = [ urlpatterns = [
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', admin.site.urls),
url(r'^admindocs/', include('django.contrib.admindocs.urls')), url(r'^admindocs/', include('django.contrib.admindocs.urls')),
url(r'^', include(ns_patterns, namespace='test')), url(r'^', include(ns_patterns, namespace='test')),
url(r'^xview/func/$', views.xview_dec(views.xview)), 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 from . import admin
urlpatterns = [ 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 import datetime
from django.conf.urls import include, url from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -16,7 +16,7 @@ site.register(User)
site.register(Article) site.register(Article)
urlpatterns = [ 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/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-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/secure-view2/$', views.secure_view2, name='secure_view2'),
url(r'^test_admin/admin/', include(admin.site.urls)), url(r'^test_admin/admin/', admin.site.urls),
url(r'^test_admin/admin2/', include(customadmin.site.urls)), url(r'^test_admin/admin2/', customadmin.site.urls),
url(r'^test_admin/admin3/', include(admin.site.get_urls(), 'admin3', 'admin'), dict(form_url='pony')), url(r'^test_admin/admin3/', (admin.site.get_urls(), 'admin', 'admin3'), dict(form_url='pony')),
url(r'^test_admin/admin4/', include(customadmin.simple_site.urls)), url(r'^test_admin/admin4/', customadmin.simple_site.urls),
url(r'^test_admin/admin5/', include(admin.site2.urls)), url(r'^test_admin/admin5/', admin.site2.urls),
url(r'^test_admin/admin7/', include(admin.site7.urls)), url(r'^test_admin/admin7/', admin.site7.urls),
url(r'^test_admin/has_permission_admin/', include(custom_has_permission_admin.site.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 from . import widgetadmin
urlpatterns = [ 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 import admin
from django.contrib.auth import views from django.contrib.auth import views
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -97,5 +97,5 @@ urlpatterns = auth_urlpatterns + [
url(r'^userpage/(.+)/$', userpage, name="userpage"), url(r'^userpage/(.+)/$', userpage, name="userpage"),
# This line is only required to render the password reset with is_admin=True # 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. 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 import admin
from django.contrib.auth.admin import GroupAdmin, UserAdmin from django.contrib.auth.admin import GroupAdmin, UserAdmin
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
@ -14,5 +14,5 @@ site.register(User, UserAdmin)
site.register(Group, GroupAdmin) site.register(Group, GroupAdmin)
urlpatterns += [ 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 import admin
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
@ -16,5 +16,5 @@ class CustomUserAdmin(UserAdmin):
site.register(get_user_model(), CustomUserAdmin) site.register(get_user_model(), CustomUserAdmin)
urlpatterns = [ 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 from . import admin
urlpatterns = [ 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') view = TemplateView.as_view(template_name='dummy.html')
app_name = 'account'
urlpatterns = [ urlpatterns = [
url(_(r'^register/$'), view, name='register'), url(_(r'^register/$'), view, name='register'),
url(_(r'^register-without-slash$'), view, name='register-without-slash'), 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') view = TemplateView.as_view(template_name='dummy.html')
app_name = 'account'
urlpatterns = i18n_patterns( urlpatterns = i18n_patterns(
url(_(r'^register/$'), view, name='register'), 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 from .admin import site
urlpatterns = [ 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 from . import admin as tz_admin # NOQA: register tz_admin
urlpatterns = [ 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 django.conf.urls import include, url
from . import views from . import views
from .tests import URLObject
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)
testobj1 = URLObject('testapp', 'test-ns1') testobj1 = URLObject('testapp', 'test-ns1')
testobj2 = URLObject('testapp', 'test-ns2') testobj2 = URLObject('testapp', 'test-ns2')
@ -23,6 +10,8 @@ default_testobj = URLObject('testapp', 'testapp')
otherobj1 = URLObject('nodefault', 'other-ns1') otherobj1 = URLObject('nodefault', 'other-ns1')
otherobj2 = URLObject('nodefault', 'other-ns2') otherobj2 = URLObject('nodefault', 'other-ns2')
newappobj1 = URLObject('newapp')
urlpatterns = [ urlpatterns = [
url(r'^normal/$', views.empty_view, name='normal-view'), 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'), 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'^other1/', include(otherobj1.urls)),
url(r'^other[246]/', include(otherobj2.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-included[135]/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
url(r'^ns-included2/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')), 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 admin_scripts.tests import AdminScriptTestCase
from django.conf import settings 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.contrib.auth.models import User
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.core.urlresolvers import ( from django.core.urlresolvers import (
@ -26,7 +26,9 @@ from django.test import (
) )
from django.test.utils import override_script_prefix from django.test.utils import override_script_prefix
from django.utils import six 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 . import middleware, urlconf_outer, views
from .views import empty_view 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') @override_settings(ROOT_URLCONF='urlpatterns_reverse.no_urls')
class NoURLPatternsTests(SimpleTestCase): class NoURLPatternsTests(SimpleTestCase):
@ -281,6 +303,7 @@ class URLPatternReverse(SimpleTestCase):
class ResolverTests(unittest.TestCase): class ResolverTests(unittest.TestCase):
@ignore_warnings(category=RemovedInDjango21Warning)
def test_resolver_repr(self): def test_resolver_repr(self):
""" """
Test repr of RegexURLResolver, especially when urlconf_name is a list 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') @override_settings(ROOT_URLCONF='urlpatterns_reverse.namespace_urls')
@ignore_warnings(category=RemovedInDjango21Warning)
class NamespaceTests(SimpleTestCase): class NamespaceTests(SimpleTestCase):
def test_ambiguous_object(self): 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/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}))
self.assertEqual('/test1/inner/+%5C$*/', reverse('test-ns1:urlobject-special-view')) 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): def test_embedded_namespace_object(self):
"Namespaces can be installed anywhere in the URL pattern tree" "Namespaces can be installed anywhere in the URL pattern tree"
self.assertEqual('/included/test3/inner/', reverse('test-ns3:urlobject-view')) 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/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')) 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): def test_namespace_pattern_with_variable_prefix(self):
"When using an include with namespaces when there is a regex variable in front of it" "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})) 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') @override_settings(ROOT_URLCONF='urlpatterns_reverse.namespace_urls')
class ResolverMatchTests(SimpleTestCase): class ResolverMatchTests(SimpleTestCase):
@ignore_warnings(category=RemovedInDjango21Warning)
def test_urlpattern_resolve(self): def test_urlpattern_resolve(self):
for path, url_name, app_name, namespace, view_name, func, args, kwargs in resolve_test_data: for path, url_name, app_name, namespace, view_name, func, args, kwargs in resolve_test_data:
# Test legacy support for extracting "function, args, kwargs" # Test legacy support for extracting "function, args, kwargs"
@ -793,6 +839,7 @@ class ResolverMatchTests(SimpleTestCase):
self.assertEqual(match[1], args) self.assertEqual(match[1], args)
self.assertEqual(match[2], kwargs) self.assertEqual(match[2], kwargs)
@ignore_warnings(category=RemovedInDjango21Warning)
def test_resolver_match_on_request(self): def test_resolver_match_on_request(self):
response = self.client.get('/resolver_match/') response = self.client.get('/resolver_match/')
resolver_match = response.resolver_match resolver_match = response.resolver_match
@ -845,10 +892,65 @@ class ViewLoadingTests(SimpleTestCase):
class IncludeTests(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): def test_include_app_name_but_no_namespace(self):
msg = "Must specify a namespace if specifying app_name." msg = "Must specify a namespace if specifying app_name."
with self.assertRaisesMessage(ValueError, msg): 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') @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 from django.contrib import admin
urlpatterns = [ urlpatterns = [
# This is the same as in the default project template # This is the same as in the default project template
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', admin.site.urls),
] ]