Browse Source

Fixed #23276 -- Deprecated passing views as strings to url().

Tim Graham 10 years ago
parent
commit
a9fd740d22

+ 6 - 0
django/conf/urls/__init__.py

@@ -67,6 +67,12 @@ def url(regex, view, kwargs=None, name=None, prefix=''):
         return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
     else:
         if isinstance(view, six.string_types):
+            warnings.warn(
+                'Support for string view arguments to url() is deprecated and '
+                'will be removed in Django 2.0 (got %s). Pass the callable '
+                'instead.' % view,
+                RemovedInDjango20Warning, stacklevel=2
+            )
             if not view:
                 raise ImproperlyConfigured('Empty URL pattern view name not permitted (for pattern %r)' % regex)
             if prefix:

+ 2 - 1
django/conf/urls/i18n.py

@@ -5,6 +5,7 @@ from django.conf.urls import patterns, url
 from django.core.urlresolvers import LocaleRegexURLResolver
 from django.utils import six
 from django.utils.deprecation import RemovedInDjango20Warning
+from django.views.i18n import set_language
 
 
 def i18n_patterns(prefix, *args):
@@ -30,5 +31,5 @@ def i18n_patterns(prefix, *args):
 
 
 urlpatterns = [
-    url(r'^setlang/$', 'django.views.i18n.set_language', name='set_language'),
+    url(r'^setlang/$', set_language, name='set_language'),
 ]

+ 2 - 1
django/conf/urls/static.py

@@ -3,9 +3,10 @@ import re
 from django.conf import settings
 from django.conf.urls import url
 from django.core.exceptions import ImproperlyConfigured
+from django.views.static import serve
 
 
-def static(prefix, view='django.views.static.serve', **kwargs):
+def static(prefix, view=serve, **kwargs):
     """
     Helper function to return a URL pattern for serving files in debug mode.
 

+ 16 - 16
django/contrib/auth/tests/urls.py

@@ -3,7 +3,7 @@ from django.contrib import admin
 from django.contrib.auth import context_processors
 from django.contrib.auth.forms import AuthenticationForm
 from django.contrib.auth.urls import urlpatterns
-from django.contrib.auth.views import password_reset, login
+from django.contrib.auth import views
 from django.contrib.auth.decorators import login_required
 from django.contrib.messages.api import info
 from django.http import HttpResponse, HttpRequest
@@ -67,29 +67,29 @@ def userpage(request):
 
 
 def custom_request_auth_login(request):
-    return login(request, authentication_form=CustomRequestAuthenticationForm)
+    return views.login(request, authentication_form=CustomRequestAuthenticationForm)
 
 # special urls for auth test cases
 urlpatterns += [
-    url(r'^logout/custom_query/$', 'django.contrib.auth.views.logout', dict(redirect_field_name='follow')),
-    url(r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')),
-    url(r'^logout/next_page/named/$', 'django.contrib.auth.views.logout', dict(next_page='password_reset')),
+    url(r'^logout/custom_query/$', views.logout, dict(redirect_field_name='follow')),
+    url(r'^logout/next_page/$', views.logout, dict(next_page='/somewhere/')),
+    url(r'^logout/next_page/named/$', views.logout, dict(next_page='password_reset')),
     url(r'^remote_user/$', remote_user_auth_view),
-    url(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
-    url(r'^password_reset/custom_redirect/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='/custom/')),
-    url(r'^password_reset/custom_redirect/named/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='password_reset')),
-    url(r'^password_reset/html_email_template/$', 'django.contrib.auth.views.password_reset', dict(html_email_template_name='registration/html_password_reset_email.html')),
+    url(r'^password_reset_from_email/$', views.password_reset, dict(from_email='staffmember@example.com')),
+    url(r'^password_reset/custom_redirect/$', views.password_reset, dict(post_reset_redirect='/custom/')),
+    url(r'^password_reset/custom_redirect/named/$', views.password_reset, dict(post_reset_redirect='password_reset')),
+    url(r'^password_reset/html_email_template/$', views.password_reset, dict(html_email_template_name='registration/html_password_reset_email.html')),
     url(r'^reset/custom/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
-        'django.contrib.auth.views.password_reset_confirm',
+        views.password_reset_confirm,
         dict(post_reset_redirect='/custom/')),
     url(r'^reset/custom/named/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
-        'django.contrib.auth.views.password_reset_confirm',
+        views.password_reset_confirm,
         dict(post_reset_redirect='password_reset')),
-    url(r'^password_change/custom/$', 'django.contrib.auth.views.password_change', dict(post_change_redirect='/custom/')),
-    url(r'^password_change/custom/named/$', 'django.contrib.auth.views.password_change', dict(post_change_redirect='password_reset')),
-    url(r'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)),
-    url(r'^login_required/$', login_required(password_reset)),
-    url(r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),
+    url(r'^password_change/custom/$', views.password_change, dict(post_change_redirect='/custom/')),
+    url(r'^password_change/custom/named/$', views.password_change, dict(post_change_redirect='password_reset')),
+    url(r'^admin_password_reset/$', views.password_reset, dict(is_admin_site=True)),
+    url(r'^login_required/$', login_required(views.password_reset)),
+    url(r'^login_required_login_url/$', login_required(views.password_reset, login_url='/somewhere/')),
 
     url(r'^auth_processor_no_attr_access/$', auth_processor_no_attr_access),
     url(r'^auth_processor_attr_access/$', auth_processor_attr_access),

+ 2 - 1
django/contrib/staticfiles/urls.py

@@ -1,5 +1,6 @@
 from django.conf import settings
 from django.conf.urls.static import static
+from django.contrib.staticfiles.views import serve
 
 urlpatterns = []
 
@@ -10,7 +11,7 @@ def staticfiles_urlpatterns(prefix=None):
     """
     if prefix is None:
         prefix = settings.STATIC_URL
-    return static(prefix, view='django.contrib.staticfiles.views.serve')
+    return static(prefix, view=serve)
 
 # Only append if urlpatterns are empty
 if settings.DEBUG and not urlpatterns:

+ 3 - 1
django/contrib/staticfiles/views.py

@@ -21,7 +21,9 @@ def serve(request, path, insecure=False, **kwargs):
 
     To use, put a URL pattern such as::
 
-        (r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve')
+        from django.contrib.staticfiles import views
+
+        url(r'^(?P<path>.*)$', views.serve)
 
     in your URLconf.
 

+ 3 - 1
django/views/static.py

@@ -24,7 +24,9 @@ def serve(request, path, document_root=None, show_indexes=False):
 
     To use, put a URL pattern such as::
 
-        (r'^(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/my/files/'})
+        from django.views.static import serve
+
+        url(r'^(?P<path>.*)$', serve, {'document_root': '/path/to/my/files/'})
 
     in your URLconf. You must provide the ``document_root`` param. You may
     also set ``show_indexes`` to ``True`` if you'd like to serve a basic index

+ 2 - 0
docs/internals/deprecation.txt

@@ -44,6 +44,8 @@ about each item can often be found in the release notes of two versions prior.
 
 * The ``unordered_list`` filter will no longer support old style lists.
 
+* Support for string ``view`` arguments to ``url()`` will be removed.
+
 .. _deprecation-removed-in-1.9:
 
 1.9

+ 5 - 3
docs/intro/overview.txt

@@ -186,10 +186,12 @@ example above::
 
     from django.conf.urls import url
 
+    from . import views
+
     urlpatterns = [
-        url(r'^articles/([0-9]{4})/$', 'news.views.year_archive'),
-        url(r'^articles/([0-9]{4})/([0-9]{2})/$', 'news.views.month_archive'),
-        url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', 'news.views.article_detail'),
+        url(r'^articles/([0-9]{4})/$', views.year_archive),
+        url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
+        url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
     ]
 
 The code above maps URLs, as simple `regular expressions`_, to the location of

+ 6 - 4
docs/ref/contrib/admin/index.txt

@@ -2571,10 +2571,12 @@ your URLconf. Specifically, add these four patterns:
 
 .. code-block:: python
 
-    url(r'^admin/password_reset/$', 'django.contrib.auth.views.password_reset', name='admin_password_reset'),
-    url(r'^admin/password_reset/done/$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'),
-    url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm', name='password_reset_confirm'),
-    url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete'),
+    from django.contrib.auth import views as auth_views
+
+    url(r'^admin/password_reset/$', auth_views.password_reset, name='admin_password_reset'),
+    url(r'^admin/password_reset/done/$', auth_views.password_reset_done, name='password_reset_done'),
+    url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$', auth_views.password_reset_confirm, name='password_reset_confirm'),
+    url(r'^reset/done/$', auth_views.password_reset_complete, name='password_reset_complete'),
 
 (This assumes you've added the admin at ``admin/`` and requires that you put
 the URLs starting with ``^admin/`` before the line that includes the admin app

+ 15 - 9
docs/ref/contrib/sitemaps.txt

@@ -54,8 +54,10 @@ Initialization
 To activate sitemap generation on your Django site, add this line to your
 :doc:`URLconf </topics/http/urls>`::
 
-   url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap',
-       {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap')
+    from django.contrib.sitemaps.views import sitemap
+
+    url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
+        name='django.contrib.sitemaps.views.sitemap')
 
 This tells Django to build a sitemap when a client accesses :file:`/sitemap.xml`.
 
@@ -277,6 +279,7 @@ Here's an example of a :doc:`URLconf </topics/http/urls>` using both::
 
     from django.conf.urls import url
     from django.contrib.sitemaps import FlatPageSitemap, GenericSitemap
+    from django.contrib.sitemaps.views import sitemap
     from blog.models import Entry
 
     info_dict = {
@@ -294,8 +297,8 @@ Here's an example of a :doc:`URLconf </topics/http/urls>` using both::
         # ...
 
         # the sitemap
-        url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap',
-            {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
+        url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
+            name='django.contrib.sitemaps.views.sitemap'),
     ]
 
 .. _URLconf: ../url_dispatch/
@@ -311,8 +314,11 @@ the sitemap. For example::
 
     # sitemaps.py
     from django.contrib import sitemaps
+    from django.contrib.sitemaps.views import sitemap
     from django.core.urlresolvers import reverse
 
+    from . import views
+
     class StaticViewSitemap(sitemaps.Sitemap):
         priority = 0.5
         changefreq = 'daily'
@@ -332,12 +338,12 @@ the sitemap. For example::
     }
 
     urlpatterns = [
-        url(r'^$', 'views.main', name='main'),
-        url(r'^about/$', 'views.about', name='about'),
-        url(r'^license/$', 'views.license', name='license'),
+        url(r'^$', views.main, name='main'),
+        url(r'^about/$', views.about, name='about'),
+        url(r'^license/$', views.license, name='license'),
         # ...
-        url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap',
-            {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap')
+        url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
+            name='django.contrib.sitemaps.views.sitemap')
     ]
 
 

+ 14 - 4
docs/ref/urls.txt

@@ -78,7 +78,7 @@ The ``optional_dictionary`` and ``optional_name`` parameters are described in
 static()
 --------
 
-.. function:: static.static(prefix, view='django.views.static.serve', **kwargs)
+.. function:: static.static(prefix, view=django.views.static.serve, **kwargs)
 
 Helper function to return a URL pattern for serving files in debug mode::
 
@@ -89,6 +89,11 @@ Helper function to return a URL pattern for serving files in debug mode::
         # ... the rest of your URLconf goes here ...
     ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 
+.. versionchanged:: 1.8
+
+    The ``view`` argument changed from a string
+    (``'django.views.static.serve'``) to the function.
+
 url()
 -----
 
@@ -111,9 +116,14 @@ function or method. See :ref:`views-extra-options` for an example.
 See :ref:`Naming URL patterns <naming-url-patterns>` for why the ``name``
 parameter is useful.
 
-The ``prefix`` parameter has the same meaning as the first argument to
-``patterns()`` and is only relevant when you're passing a string as the
-``view`` parameter.
+.. deprecated:: 1.8
+
+    Support for string ``view`` arguments is deprecated and will be removed in
+    Django 2.0. Pass the callable  instead.
+
+    The ``prefix`` parameter has the same meaning as the first argument to
+    ``patterns()`` and is only relevant when you're passing a string as the
+    ``view`` parameter.
 
 include()
 ---------

+ 11 - 2
docs/releases/1.8.txt

@@ -548,6 +548,13 @@ Updating your code is as simple as ensuring that ``urlpatterns`` is a list of
         url('^other/$', views.otherview),
     ]
 
+Passing a string as ``view`` to :func:`~django.conf.urls.url`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Related to the previous item, referencing views as strings in the ``url()``
+function is deprecated. Pass the callable view as described in the previous
+section instead.
+
 ``django.test.SimpleTestCase.urls``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -580,8 +587,10 @@ for reversing instead.
 If you are using :mod:`django.contrib.sitemaps`, add the ``name`` argument to
 the ``url`` that references :func:`django.contrib.sitemaps.views.sitemap`::
 
-    url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap',
-        {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap')
+    from django.contrib.sitemaps.views import sitemap
+
+    url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
+        name='django.contrib.sitemaps.views.sitemap')
 
 to ensure compatibility when reversing by Python path is removed in Django 2.0.
 

+ 38 - 79
docs/topics/http/urls.txt

@@ -74,11 +74,13 @@ Here's a sample URLconf::
 
     from django.conf.urls import url
 
+    from . import views
+
     urlpatterns = [
-        url(r'^articles/2003/$', 'news.views.special_case_2003'),
-        url(r'^articles/([0-9]{4})/$', 'news.views.year_archive'),
-        url(r'^articles/([0-9]{4})/([0-9]{2})/$', 'news.views.month_archive'),
-        url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', 'news.views.article_detail'),
+        url(r'^articles/2003/$', views.special_case_2003),
+        url(r'^articles/([0-9]{4})/$', views.year_archive),
+        url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
+        url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
     ]
 
 Notes:
@@ -96,7 +98,7 @@ Example requests:
 
 * A request to ``/articles/2005/03/`` would match the third entry in the
   list. Django would call the function
-  ``news.views.month_archive(request, '2005', '03')``.
+  ``views.month_archive(request, '2005', '03')``.
 
 * ``/articles/2005/3/`` would not match any URL patterns, because the
   third entry in the list requires two digits for the month.
@@ -110,7 +112,7 @@ Example requests:
   pattern requires that the URL end with a slash.
 
 * ``/articles/2003/03/03/`` would match the final pattern. Django would call
-  the function ``news.views.article_detail(request, '2003', '03', '03')``.
+  the function ``views.article_detail(request, '2003', '03', '03')``.
 
 .. _Dive Into Python's explanation: http://www.diveintopython.net/regular_expressions/street_addresses.html#re.matching.2.3
 
@@ -131,11 +133,13 @@ Here's the above example URLconf, rewritten to use named groups::
 
     from django.conf.urls import url
 
+    from . import views
+
     urlpatterns = [
-        url(r'^articles/2003/$', 'news.views.special_case_2003'),
-        url(r'^articles/(?P<year>[0-9]{4})/$', 'news.views.year_archive'),
-        url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', 'news.views.month_archive'),
-        url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', 'news.views.article_detail'),
+        url(r'^articles/2003/$', views.special_case_2003),
+        url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
+        url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
+        url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
     ]
 
 This accomplishes exactly the same thing as the previous example, with one
@@ -143,11 +147,11 @@ subtle difference: The captured values are passed to view functions as keyword
 arguments rather than positional arguments. For example:
 
 * A request to ``/articles/2005/03/`` would call the function
-  ``news.views.month_archive(request, year='2005', month='03')``, instead
-  of ``news.views.month_archive(request, '2005', '03')``.
+  ``views.month_archive(request, year='2005', month='03')``, instead
+  of ``views.month_archive(request, '2005', '03')``.
 
 * A request to ``/articles/2003/03/03/`` would call the function
-  ``news.views.article_detail(request, year='2003', month='03', day='03')``.
+  ``views.article_detail(request, year='2003', month='03', day='03')``.
 
 In practice, this means your URLconfs are slightly more explicit and less prone
 to argument-order bugs -- and you can reorder the arguments in your views'
@@ -191,9 +195,9 @@ Each captured argument is sent to the view as a plain Python string, regardless
 of what sort of match the regular expression makes. For example, in this
 URLconf line::
 
-    url(r'^articles/(?P<year>[0-9]{4})/$', 'news.views.year_archive'),
+    url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
 
-...the ``year`` argument to ``news.views.year_archive()`` will be a string, not
+...the ``year`` argument to ``views.year_archive()`` will be a string, not
 an integer, even though the ``[0-9]{4}`` will only match integer strings.
 
 Specifying defaults for view arguments
@@ -205,9 +209,11 @@ Here's an example URLconf and view::
     # URLconf
     from django.conf.urls import url
 
+    from . import views
+
     urlpatterns = [
-        url(r'^blog/$', 'blog.views.page'),
-        url(r'^blog/page(?P<num>[0-9]+)/$', 'blog.views.page'),
+        url(r'^blog/$', views.page),
+        url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
     ]
 
     # View (in blog/views.py)
@@ -216,7 +222,7 @@ Here's an example URLconf and view::
         ...
 
 In the above example, both URL patterns point to the same view --
-``blog.views.page`` -- but the first pattern doesn't capture anything from the
+``views.page`` -- but the first pattern doesn't capture anything from the
 URL. If the first pattern matches, the ``page()`` function will use its
 default argument for ``num``, ``"1"``. If the second pattern matches,
 ``page()`` will use whatever ``num`` value was captured by the regex.
@@ -290,13 +296,16 @@ Another possibility is to include additional URL patterns by using a list of
 
     from django.conf.urls import include, url
 
+    from apps.main import views as main_views
+    from credit import views as credit_views
+
     extra_patterns = [
-        url(r'^reports/(?P<id>[0-9]+)/$', 'credit.views.report'),
-        url(r'^charge/$', 'credit.views.charge'),
+        url(r'^reports/(?P<id>[0-9]+)/$', credit_views.report),
+        url(r'^charge/$', credit_views.charge),
     ]
 
     urlpatterns = [
-        url(r'^$', 'apps.main.views.homepage'),
+        url(r'^$', main_views.homepage),
         url(r'^help/', include('apps.help.urls')),
         url(r'^credit/', include(extra_patterns)),
     ]
@@ -381,7 +390,7 @@ For example::
     ]
 
 In this example, for a request to ``/blog/2005/``, Django will call
-``blog.views.year_archive(request, year='2005', foo='bar')``.
+``views.year_archive(request, year='2005', foo='bar')``.
 
 This technique is used in the
 :doc:`syndication framework </ref/contrib/syndication>` to pass metadata and
@@ -444,60 +453,6 @@ URLconf, regardless of whether the line's view actually accepts those options
 as valid. For this reason, this technique is only useful if you're certain that
 every view in the included URLconf accepts the extra options you're passing.
 
-Passing callable objects instead of strings
-===========================================
-
-Some developers find it more natural to pass the actual Python function object
-rather than a string containing the path to its module. This alternative is
-supported -- you can pass any callable object as the view.
-
-For example, given this URLconf in "string" notation::
-
-    from django.conf.urls import url
-
-    urlpatterns = [
-        url(r'^archive/$', 'mysite.views.archive'),
-        url(r'^about/$', 'mysite.views.about'),
-        url(r'^contact/$', 'mysite.views.contact'),
-    ]
-
-You can accomplish the same thing by passing objects rather than strings. Just
-be sure to import the objects::
-
-    from django.conf.urls import url
-    from mysite.views import archive, about, contact
-
-    urlpatterns = [
-        url(r'^archive/$', archive),
-        url(r'^about/$', about),
-        url(r'^contact/$', contact),
-    ]
-
-The following example is functionally identical. It's just a bit more compact
-because it imports the module that contains the views, rather than importing
-each view individually::
-
-    from django.conf.urls import url
-    from mysite import views
-
-    urlpatterns = [
-        url(r'^archive/$', views.archive),
-        url(r'^about/$', views.about),
-        url(r'^contact/$', views.contact),
-    ]
-
-The style you use is up to you.
-
-Note that :doc:`class based views</topics/class-based-views/index>` must be
-imported::
-
-    from django.conf.urls import url
-    from mysite.views import ClassBasedView
-
-    urlpatterns = [
-        url(r'^myview/$', ClassBasedView.as_view()),
-    ]
-
 Reverse resolution of URLs
 ==========================
 
@@ -553,9 +508,11 @@ Consider again this URLconf entry::
 
     from django.conf.urls import url
 
+    from . import views
+
     urlpatterns = [
         #...
-        url(r'^articles/([0-9]{4})/$', 'news.views.year_archive', name='news-year-archive'),
+        url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),
         #...
     ]
 
@@ -756,9 +713,11 @@ For example::
 
     from django.conf.urls import include, url
 
+    from app.helps import views
+
     help_patterns = [
-        url(r'^basic/$', 'apps.help.views.views.basic'),
-        url(r'^advanced/$', 'apps.help.views.views.advanced'),
+        url(r'^basic/$', views.basic),
+        url(r'^advanced/$', views.advanced),
     ]
 
     url(r'^help/', include((help_patterns, 'bar', 'foo'))),

+ 18 - 12
docs/topics/i18n/translation.txt

@@ -1100,22 +1100,25 @@ prepend the current active language code to all url patterns defined within
     from django.conf.urls import include, url
     from django.conf.urls.i18n import i18n_patterns
 
+    from about import views as about_views
+    from news import views as news_views
+    from sitemap.views imort sitemap
+
     urlpatterns = [
-        url(r'^sitemap\.xml$', 'sitemap.view', name='sitemap_xml'),
+        url(r'^sitemap\.xml$', sitemap, name='sitemap_xml'),
     ]
 
     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'),
+        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'),
     ]
 
     urlpatterns += i18n_patterns(
-        url(r'^about/$', 'about.view', name='about'),
+        url(r'^about/$', about_views.main, name='about'),
         url(r'^news/', include(news_patterns, namespace='news')),
     )
 
-
 After defining these URL patterns, Django will automatically add the
 language prefix to the URL patterns that were added by the ``i18n_patterns``
 function. Example::
@@ -1155,22 +1158,25 @@ URL patterns can also be marked translatable using the
     from django.conf.urls.i18n import i18n_patterns
     from django.utils.translation import ugettext_lazy as _
 
+    from about import views as about_views
+    from news import views as news_views
+    from sitemaps.views import sitemap
+
     urlpatterns = [
-        url(r'^sitemap\.xml$', 'sitemap.view', name='sitemap_xml'),
+        url(r'^sitemap\.xml$', sitemap, name='sitemap_xml'),
     ]
 
     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'),
+        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'),
     ]
 
     urlpatterns += i18n_patterns(
-        url(_(r'^about/$'), 'about.view', name='about'),
+        url(_(r'^about/$'), about_views.main, name='about'),
         url(_(r'^news/'), include(news_patterns, namespace='news')),
     )
 
-
 After you've created the translations, the
 :func:`~django.core.urlresolvers.reverse` function will return the URL in the
 active language. Example::

+ 2 - 1
tests/admin_scripts/urls.py

@@ -1,10 +1,11 @@
 import os
 from django.conf.urls import url
 from django.utils._os import upath
+from django.views.static import serve
 
 here = os.path.dirname(upath(__file__))
 
 urlpatterns = [
-    url(r'^custom_templates/(?P<path>.*)$', 'django.views.static.serve', {
+    url(r'^custom_templates/(?P<path>.*)$', serve, {
         'document_root': os.path.join(here, 'custom_templates')}),
 ]

+ 2 - 1
tests/generic_views/urls.py

@@ -1,4 +1,5 @@
 from django.conf.urls import url
+from django.contrib.auth import views as auth_views
 from django.views.decorators.cache import cache_page
 from django.views.generic import TemplateView
 
@@ -257,5 +258,5 @@ urlpatterns = [
         views.BookSigningDetail.as_view()),
 
     # Useful for testing redirects
-    url(r'^accounts/login/$', 'django.contrib.auth.views.login')
+    url(r'^accounts/login/$', auth_views.login)
 ]

+ 5 - 3
tests/middleware/extra_urls.py

@@ -1,7 +1,9 @@
 from django.conf.urls import url
 
+from . import views
+
 urlpatterns = [
-    url(r'^customurlconf/noslash$', 'middleware.views.empty_view'),
-    url(r'^customurlconf/slash/$', 'middleware.views.empty_view'),
-    url(r'^customurlconf/needsquoting#/$', 'middleware.views.empty_view'),
+    url(r'^customurlconf/noslash$', views.empty_view),
+    url(r'^customurlconf/slash/$', views.empty_view),
+    url(r'^customurlconf/needsquoting#/$', views.empty_view),
 ]

+ 5 - 3
tests/middleware/urls.py

@@ -1,7 +1,9 @@
 from django.conf.urls import url
 
+from . import views
+
 urlpatterns = [
-    url(r'^noslash$', 'middleware.views.empty_view'),
-    url(r'^slash/$', 'middleware.views.empty_view'),
-    url(r'^needsquoting#/$', 'middleware.views.empty_view'),
+    url(r'^noslash$', views.empty_view),
+    url(r'^slash/$', views.empty_view),
+    url(r'^needsquoting#/$', views.empty_view),
 ]

+ 3 - 1
tests/model_permalink/urls.py

@@ -1,5 +1,7 @@
 from django.conf.urls import url
 
+from . import views
+
 urlpatterns = [
-    url(r'^guitarists/(\w{1,50})/$', 'model_permalink.views.empty_view', name='guitarist_detail'),
+    url(r'^guitarists/(\w{1,50})/$', views.empty_view, name='guitarist_detail'),
 ]

+ 1 - 1
tests/template_tests/urls.py

@@ -15,5 +15,5 @@ urlpatterns = [
 
     # Unicode strings are permitted everywhere.
     url(r'^Юникод/(\w+)/$', views.client2, name="метка_оператора"),
-    url(r'^Юникод/(?P<tag>\S+)/$', 'template_tests.views.client2', name="метка_оператора_2"),
+    url(r'^Юникод/(?P<tag>\S+)/$', views.client2, name="метка_оператора_2"),
 ]

+ 3 - 2
tests/test_client/urls.py

@@ -1,4 +1,5 @@
 from django.conf.urls import url
+from django.contrib.auth import views as auth_views
 from django.views.generic import RedirectView
 
 from . import views
@@ -32,6 +33,6 @@ urlpatterns = [
     url(r'^mass_mail_sending_view/$', views.mass_mail_sending_view),
     url(r'^django_project_redirect/$', views.django_project_redirect),
 
-    url(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
-    url(r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
+    url(r'^accounts/login/$', auth_views.login, {'template_name': 'login.html'}),
+    url(r'^accounts/logout/$', auth_views.logout),
 ]

+ 23 - 22
tests/urlpatterns_reverse/erroneous_urls.py

@@ -1,26 +1,27 @@
+import warnings
+
 from django.conf.urls import url
 
 from . import views
 
-urlpatterns = [
-    # View has erroneous import
-    url(r'erroneous_inner/$', views.erroneous_view),
-    # Module has erroneous import
-    # Remove in Django 2.0 along with erroneous_views_module as this is only
-    # an issue with string in urlpatterns
-    url(r'erroneous_outer/$', 'urlpatterns_reverse.erroneous_views_module.erroneous_view'),
-    # Module is an unqualified string
-    url(r'erroneous_unqualified/$', 'unqualified_view'),
-    # View does not exist
-    # Remove in Django 2.0 along with erroneous_views_module as this is only
-    # an issue with string in urlpatterns
-    url(r'missing_inner/$', 'urlpatterns_reverse.views.missing_view'),
-    # View is not callable
-    # Remove in Django 2.0 along with erroneous_views_module as this is only
-    # an issue with string in urlpatterns
-    url(r'uncallable/$', 'urlpatterns_reverse.views.uncallable'),
-    # Module does not exist
-    url(r'missing_outer/$', 'urlpatterns_reverse.missing_module.missing_view'),
-    # Regex contains an error (refs #6170)
-    url(r'(regex_error/$', views.empty_view),
-]
+# Test deprecated behavior of passing strings as view to url().
+# Some of these can be removed in Django 2.0 as they aren't convertable to
+# callabls.
+with warnings.catch_warnings(record=True):
+    warnings.filterwarnings('ignore', module='django.conf.urls')
+    urlpatterns = [
+        # View has erroneous import
+        url(r'erroneous_inner/$', views.erroneous_view),
+        # Module has erroneous import
+        url(r'erroneous_outer/$', 'urlpatterns_reverse.erroneous_views_module.erroneous_view'),
+        # Module is an unqualified string
+        url(r'erroneous_unqualified/$', 'unqualified_view'),
+        # View does not exist
+        url(r'missing_inner/$', 'urlpatterns_reverse.views.missing_view'),
+        # View is not callable
+        url(r'uncallable/$', 'urlpatterns_reverse.views.uncallable'),
+        # Module does not exist
+        url(r'missing_outer/$', 'urlpatterns_reverse.missing_module.missing_view'),
+        # Regex contains an error (refs #6170)
+        url(r'(regex_error/$', views.empty_view),
+    ]

+ 3 - 3
tests/urlpatterns_reverse/namespace_urls.py

@@ -10,9 +10,9 @@ class URLObject(object):
 
     def urls(self):
         return ([
-            url(r'^inner/$', 'urlpatterns_reverse.views.empty_view', name='urlobject-view'),
-            url(r'^inner/(?P<arg1>[0-9]+)/(?P<arg2>[0-9]+)/$', 'urlpatterns_reverse.views.empty_view', name='urlobject-view'),
-            url(r'^inner/\+\\\$\*/$', 'urlpatterns_reverse.views.empty_view', name='urlobject-special-view'),
+            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)
 

+ 7 - 4
tests/urlpatterns_reverse/urls.py

@@ -2,12 +2,15 @@ import warnings
 
 from django.conf.urls import patterns, url, include
 
-from .views import empty_view, empty_view_partial, empty_view_wrapped, absolute_kwargs_view
+from .views import (
+    absolute_kwargs_view, defaults_view, empty_view, empty_view_partial,
+    empty_view_wrapped, nested_view,
+)
 
 
 other_patterns = [
     url(r'non_path_include/$', empty_view, name='non_path_include'),
-    url(r'nested_path/$', 'urlpatterns_reverse.views.nested_view'),
+    url(r'nested_path/$', nested_view),
 ]
 
 # test deprecated patterns() function. convert to list of urls() in Django 2.0
@@ -68,8 +71,8 @@ with warnings.catch_warnings(record=True):
         url(r'absolute_arg_view/$', absolute_kwargs_view),
 
         # Tests for #13154. Mixed syntax to test both ways of defining URLs.
-        url(r'defaults_view1/(?P<arg1>[0-9]+)/', 'urlpatterns_reverse.views.defaults_view', {'arg2': 1}, name='defaults'),
-        (r'defaults_view2/(?P<arg1>[0-9]+)/', 'urlpatterns_reverse.views.defaults_view', {'arg2': 2}, 'defaults'),
+        url(r'defaults_view1/(?P<arg1>[0-9]+)/', defaults_view, {'arg2': 1}, name='defaults'),
+        (r'defaults_view2/(?P<arg1>[0-9]+)/', defaults_view, {'arg2': 2}, 'defaults'),
 
         url('^includes/', include(other_patterns)),
     )

+ 5 - 4
tests/view_tests/generic_urls.py

@@ -2,6 +2,7 @@
 from __future__ import unicode_literals
 
 from django.conf.urls import url
+from django.contrib.auth import views as auth_views
 from django.views.generic import RedirectView
 
 from .models import Article, DateArticle
@@ -27,12 +28,12 @@ numeric_days_info_dict = dict(date_based_info_dict, day_format='%d')
 date_based_datefield_info_dict = dict(date_based_info_dict, queryset=DateArticle.objects.all())
 
 urlpatterns = [
-    url(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
-    url(r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
+    url(r'^accounts/login/$', auth_views.login, {'template_name': 'login.html'}),
+    url(r'^accounts/logout/$', auth_views.logout),
 
     # Special URLs for particular regression cases.
-    url('^中文/$', 'view_tests.views.redirect'),
-    url('^中文/target/$', 'view_tests.views.index_page'),
+    url('^中文/$', views.redirect),
+    url('^中文/target/$', views.index_page),
 ]
 
 # redirects, both temporary and permanent, with non-ASCII targets

+ 3 - 1
tests/view_tests/regression_21530_urls.py

@@ -1,5 +1,7 @@
 from django.conf.urls import url
 
+from . import views
+
 urlpatterns = [
-    url(r'^index/$', 'view_tests.views.index_page', name='index'),
+    url(r'^index/$', views.index_page, name='index'),
 ]

+ 2 - 2
tests/view_tests/urls.py

@@ -3,7 +3,7 @@ from os import path
 
 from django.conf.urls import url, include
 from django.utils._os import upath
-from django.views import defaults, i18n
+from django.views import defaults, i18n, static
 
 from . import views
 
@@ -71,7 +71,7 @@ urlpatterns = [
     url(r'^jsi18n_template/$', views.jsi18n),
 
     # Static views
-    url(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}),
+    url(r'^site_media/(?P<path>.*)$', static.serve, {'document_root': media_dir}),
 ]
 
 urlpatterns += [