瀏覽代碼

Fixed #24127 -- Changed the default current_app to the current namespace.

Changed the url template tag to use request.resolver_match.namespace as a
default for the current_app argument if request.current_app is not set.
Marten Kenbeek 9 年之前
父節點
當前提交
bc7923beff

+ 4 - 0
django/template/context.py

@@ -152,6 +152,10 @@ class Context(BaseContext):
     def current_app(self):
         return None if self._current_app is _current_app_undefined else self._current_app
 
+    @property
+    def is_current_app_set(self):
+        return self._current_app is not _current_app_undefined
+
     @contextmanager
     def bind_template(self, template):
         if self.template is not None:

+ 9 - 2
django/template/defaulttags.py

@@ -479,9 +479,16 @@ class URLNode(Node):
         try:
             current_app = context.request.current_app
         except AttributeError:
-            # Change the fallback value to None when the deprecation path for
+            # Leave only the else block when the deprecation path for
             # Context.current_app completes in Django 1.10.
-            current_app = context.current_app
+            # Can also remove the Context.is_current_app_set property.
+            if context.is_current_app_set:
+                current_app = context.current_app
+            else:
+                try:
+                    current_app = context.request.resolver_match.namespace
+                except AttributeError:
+                    current_app = None
 
         # Try to look up the URL twice: once given the view name, and again
         # relative to what we guess is the "main" app. If they both fail,

+ 6 - 0
docs/releases/1.9.txt

@@ -895,6 +895,12 @@ Miscellaneous
   whitespace by default. This can be disabled by setting the new
   :attr:`~django.forms.CharField.strip` argument to ``False``.
 
+* If neither :attr:`request.current_app <django.http.HttpRequest.current_app>`
+  nor :class:`Context.current_app <django.template.Context>` are set, the
+  :ttag:`url` template tag will now use the namespace of the current request.
+  Set ``request.current_app`` to ``None`` if you don't want to use a namespace
+  hint.
+
 .. _deprecated-features-1.9:
 
 Features deprecated in 1.9

+ 15 - 14
docs/topics/http/urls.txt

@@ -669,11 +669,16 @@ the fully qualified name into parts and then tries the following lookup:
    example, ``'polls'``). This will yield a list of instances of that
    application.
 
-2. If there is a *current* application defined, Django finds and returns
-   the URL resolver for that instance. The *current* application can be
-   specified as an attribute on the request. Applications that expect to
-   have multiple deployments should set the ``current_app`` attribute on
-   the ``request`` being processed.
+2. If there is a current application defined, Django finds and returns the URL
+   resolver for that instance. The current application can be specified with
+   the ``current_app`` argument to the
+   :func:`~django.core.urlresolvers.reverse()` function.
+
+   The :ttag:`url` template tag uses the namespace of the currently resolved
+   view as the current application in a
+   :class:`~django.template.RequestContext`. You can override this default by
+   setting the current application on the :attr:`request.current_app
+   <django.http.HttpRequest.current_app>` attribute.
 
    .. versionchanged:: 1.8
 
@@ -682,8 +687,11 @@ the fully qualified name into parts and then tries the following lookup:
       :class:`~django.template.RequestContext` that is used to render a
       template.
 
-   The current application can also be specified manually as an argument
-   to the :func:`~django.core.urlresolvers.reverse` function.
+   .. versionchanged:: 1.9
+
+      Previously, the :ttag:`url` template tag did not use the namespace of the
+      currently resolved view and you had to set the ``current_app`` attribute
+      on the request.
 
 3. If there is no current application. Django looks for a default
    application instance. The default application instance is the instance
@@ -751,13 +759,6 @@ Using this setup, the following lookups are possible:
 
     {% url 'polls:index' %}
 
-  Note that reversing in the template requires the ``current_app`` be added as
-  an attribute to the ``request`` like this::
-
-    def render_to_response(self, context, **response_kwargs):
-        self.request.current_app = self.request.resolver_match.namespace
-        return super(DetailView, self).render_to_response(context, **response_kwargs)
-
 * If there is no current instance - say, if we were rendering a page
   somewhere else on the site - ``'polls:index'`` will resolve to the last
   registered instance of ``polls``. Since there is no default instance

+ 31 - 3
tests/template_tests/syntax_tests/test_url.py

@@ -1,7 +1,9 @@
 # coding: utf-8
-from django.core.urlresolvers import NoReverseMatch
-from django.template import TemplateSyntaxError
-from django.test import SimpleTestCase, ignore_warnings, override_settings
+from django.core.urlresolvers import NoReverseMatch, resolve
+from django.template import RequestContext, TemplateSyntaxError
+from django.test import (
+    RequestFactory, SimpleTestCase, ignore_warnings, override_settings,
+)
 from django.utils.deprecation import RemovedInDjango110Warning
 
 from ..utils import setup
@@ -252,3 +254,29 @@ class UrlTagTests(SimpleTestCase):
     def test_url_asvar03(self):
         output = self.engine.render_to_string('url-asvar03')
         self.assertEqual(output, '')
+
+    @setup({'url-namespace01': '{% url "app:named.client" 42 %}'})
+    def test_url_namespace01(self):
+        request = RequestFactory().get('/')
+        request.resolver_match = resolve('/ns1/')
+        template = self.engine.get_template('url-namespace01')
+        context = RequestContext(request)
+        output = template.render(context)
+        self.assertEqual(output, '/ns1/named-client/42/')
+
+    @setup({'url-namespace02': '{% url "app:named.client" 42 %}'})
+    def test_url_namespace02(self):
+        request = RequestFactory().get('/')
+        request.resolver_match = resolve('/ns2/')
+        template = self.engine.get_template('url-namespace02')
+        context = RequestContext(request)
+        output = template.render(context)
+        self.assertEqual(output, '/ns2/named-client/42/')
+
+    @setup({'url-namespace03': '{% url "app:named.client" 42 %}'})
+    def test_url_namespace03(self):
+        request = RequestFactory().get('/')
+        template = self.engine.get_template('url-namespace03')
+        context = RequestContext(request)
+        output = template.render(context)
+        self.assertEqual(output, '/ns2/named-client/42/')

+ 9 - 2
tests/template_tests/urls.py

@@ -1,19 +1,26 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 
-from django.conf.urls import url
+from django.conf.urls import include, url
 
 from . import views
 
-urlpatterns = [
+ns_patterns = [
     # Test urls for testing reverse lookups
     url(r'^$', views.index),
     url(r'^client/([0-9,]+)/$', views.client),
     url(r'^client/(?P<id>[0-9]+)/(?P<action>[^/]+)/$', views.client_action),
     url(r'^client/(?P<client_id>[0-9]+)/(?P<action>[^/]+)/$', views.client_action),
     url(r'^named-client/([0-9]+)/$', views.client2, name="named.client"),
+]
+
 
+urlpatterns = ns_patterns + [
     # Unicode strings are permitted everywhere.
     url(r'^Юникод/(\w+)/$', views.client2, name="метка_оператора"),
     url(r'^Юникод/(?P<tag>\S+)/$', views.client2, name="метка_оператора_2"),
+
+    # Test urls for namespaces and current_app
+    url(r'ns1/', include((ns_patterns, 'app'), 'ns1')),
+    url(r'ns2/', include((ns_patterns, 'app'))),
 ]