Code

Ticket #5034: request_aware_reversing.2.diff

File request_aware_reversing.2.diff, 11.1 KB (added by SmileyChris, 6 years ago)

forgot to add some test files

Line 
1Index: django/core/urlresolvers.py
2===================================================================
3--- django/core/urlresolvers.py (revision 7028)
4+++ django/core/urlresolvers.py (working copy)
5@@ -7,11 +7,12 @@
6     (view_function, function_args, function_kwargs)
7 """
8 
9-from django.http import Http404
10+from django.http import Http404, HttpRequest
11 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
12 from django.utils.encoding import iri_to_uri, force_unicode, smart_str
13 from django.utils.functional import memoize
14 import re
15+import types
16 
17 try:
18     reversed
19@@ -201,9 +202,14 @@
20 class RegexURLResolver(object):
21     def __init__(self, regex, urlconf_name, default_kwargs=None):
22         # regex is a string representing a regular expression.
23-        # urlconf_name is a string representing the module containing urlconfs.
24+        # urlconf_name could either be a string representing the module
25+        # containing urlconfs or the module itself.
26         self.regex = re.compile(regex, re.UNICODE)
27-        self.urlconf_name = urlconf_name
28+        if isinstance(urlconf_name, types.ModuleType):
29+            self._urlconf_module = urlconf_name
30+            self.urlconf_name = urlconf_name.__name__
31+        else:
32+            self.urlconf_name = urlconf_name
33         self.callback = None
34         self.default_kwargs = default_kwargs or {}
35         self._reverse_dict = {}
36@@ -294,5 +300,8 @@
37 def reverse(viewname, urlconf=None, args=None, kwargs=None):
38     args = args or []
39     kwargs = kwargs or {}
40+    # urlconf could also be an HttpRequest (abstracting the need for high-level
41+    # code to check for a request.urlconf)
42+    if urlconf is not None and isinstance(urlconf, HttpRequest):
43+        urlconf = getattr(urlconf, 'urlconf', None)
44     return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))
45-
46Index: django/contrib/sitemaps/views.py
47===================================================================
48--- django/contrib/sitemaps/views.py    (revision 7028)
49+++ django/contrib/sitemaps/views.py    (working copy)
50@@ -1,7 +1,7 @@
51 from django.http import HttpResponse, Http404
52 from django.template import loader
53 from django.contrib.sites.models import Site
54-from django.core import urlresolvers
55+from django.core.urlresolvers import reverse
56 from django.utils.encoding import smart_str
57 
58 def index(request, sitemaps):
59@@ -9,7 +9,8 @@
60     sites = []
61     protocol = request.is_secure() and 'https' or 'http'
62     for section in sitemaps.keys():
63-        sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.sitemap', kwargs={'section': section})
64+        sitemap_url = reverse('django.contrib.sitemaps.views.sitemap',
65+                              urlconf=request, kwargs={'section': section})
66         sites.append('%s://%s%s' % (protocol, current_site.domain, sitemap_url))
67     xml = loader.render_to_string('sitemap_index.xml', {'sitemaps': sites})
68     return HttpResponse(xml, mimetype='application/xml')
69Index: django/template/defaulttags.py
70===================================================================
71--- django/template/defaulttags.py      (revision 7028)
72+++ django/template/defaulttags.py      (working copy)
73@@ -8,7 +8,7 @@
74 except NameError:
75     from django.utils.itercompat import reversed     # Python 2.3 fallback
76 
77-from django.template import Node, NodeList, Template, Context, Variable
78+from django.template import Node, NodeList, Template, Context, RequestContext, Variable
79 from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
80 from django.template import get_library, Library, InvalidTemplateLibrary
81 from django.conf import settings
82@@ -359,12 +359,16 @@
83         args = [arg.resolve(context) for arg in self.args]
84         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
85                        for k, v in self.kwargs.items()])
86+        if isinstance(context, RequestContext):
87+            urlconf = context.request
88+        else:
89+            urlconf = None
90         try:
91-            return reverse(self.view_name, args=args, kwargs=kwargs)
92+            return reverse(self.view_name, urlconf, args=args, kwargs=kwargs)
93         except NoReverseMatch:
94             try:
95                 project_name = settings.SETTINGS_MODULE.split('.')[0]
96-                return reverse(project_name + '.' + self.view_name,
97+                return reverse(project_name + '.' + self.view_name, urlconf,
98                                args=args, kwargs=kwargs)
99             except NoReverseMatch:
100                 return ''
101Index: django/template/context.py
102===================================================================
103--- django/template/context.py  (revision 7028)
104+++ django/template/context.py  (working copy)
105@@ -95,6 +95,7 @@
106     """
107     def __init__(self, request, dict=None, processors=None):
108         Context.__init__(self, dict)
109+        self.request = request
110         if processors is None:
111             processors = ()
112         else:
113Index: tests/regressiontests/urlpatterns_reverse/urlconf1.py
114===================================================================
115--- tests/regressiontests/urlpatterns_reverse/urlconf1.py       (revision 0)
116+++ tests/regressiontests/urlpatterns_reverse/urlconf1.py       (revision 0)
117@@ -0,0 +1,12 @@
118+from django.conf.urls.defaults import *
119+
120+import urlconf2
121+
122+def empty_view(request):
123+    pass
124+
125+urlpatterns = patterns('teacher_survey.views',
126+    url(r'^test/me/$', empty_view, name='test1'),
127+    url(r'^into_urlconf2/', include(urlconf2))
128+)
129+
130Index: tests/regressiontests/urlpatterns_reverse/urlconf2.py
131===================================================================
132--- tests/regressiontests/urlpatterns_reverse/urlconf2.py       (revision 0)
133+++ tests/regressiontests/urlpatterns_reverse/urlconf2.py       (revision 0)
134@@ -0,0 +1,9 @@
135+from django.conf.urls.defaults import *
136+
137+def empty_view2(request):
138+    pass
139+
140+urlpatterns = patterns('teacher_survey.views',
141+    url(r'^second_test/$', empty_view2, name='test2'),
142+)
143+
144Index: tests/regressiontests/urlpatterns_reverse/tests.py
145===================================================================
146--- tests/regressiontests/urlpatterns_reverse/tests.py  (revision 7028)
147+++ tests/regressiontests/urlpatterns_reverse/tests.py  (working copy)
148@@ -1,8 +1,16 @@
149 "Unit tests for reverse URL lookup"
150 
151-from django.core.urlresolvers import reverse_helper, NoReverseMatch
152-import re, unittest
153+import re
154+import unittest
155 
156+from django.core.urlresolvers import reverse_helper, NoReverseMatch, reverse
157+from django.conf import settings
158+from django.http import HttpRequest
159+from django.template import Template, Context, RequestContext
160+
161+import urlconf1
162+import urlconf2
163+
164 test_data = (
165     ('^places/(\d+)/$', 'places/3/', [3], {}),
166     ('^places/(\d+)/$', 'places/3/', ['3'], {}),
167@@ -35,5 +43,49 @@
168             else:
169                 self.assertEquals(got, expected)
170 
171-if __name__ == "__main__":
172-    run_tests(1)
173+class RequestAwareReversing(unittest.TestCase):
174+    def setUp(self):
175+        self.root_urlconf = settings.ROOT_URLCONF
176+
177+    def tearDown(self):
178+        settings.ROOT_URLCONF = self.root_urlconf
179+
180+    def test_reverse(self):
181+        settings.ROOT_URLCONF = urlconf1
182+        # Control
183+        self.assertEqual(reverse('test1'), '/test/me/')
184+        self.assertEqual(reverse('test2'), '/into_urlconf2/second_test/')
185+        # urlconf1 (== control)
186+        self.assertEqual(reverse('test1', urlconf=urlconf1), '/test/me/')
187+        self.assertEqual(reverse('test2', urlconf=urlconf1),
188+                         '/into_urlconf2/second_test/')
189+        # urlconf2
190+        self.assertRaises(NoReverseMatch,
191+                          lambda: reverse('test1', urlconf=urlconf2))
192+        self.assertEqual(reverse('test2', urlconf=urlconf2), '/second_test/')
193+        # urlconf2 via a request object
194+        request = HttpRequest()
195+        request.urlconf = urlconf2
196+        self.assertRaises(NoReverseMatch,
197+                          lambda: reverse('test1', urlconf=request))
198+        self.assertEqual(reverse('test2', urlconf=request), '/second_test/')
199+
200+    def test_url_tag(self):
201+        settings.ROOT_URLCONF = urlconf1
202+        request = HttpRequest()
203+        # Rendered with Context rather than RequestContext
204+        c = Context()
205+        self.assertEqual(Template('{% url test1 %}').render(c), '/test/me/')
206+        self.assertEqual(Template('{% url test2 %}').render(c),
207+                         '/into_urlconf2/second_test/')
208+        # Rendered with RequestContext without a request.urlconf
209+        c = RequestContext(request)
210+        self.assertEqual(Template('{% url test1 %}').render(c), '/test/me/')
211+        self.assertEqual(Template('{% url test2 %}').render(c),
212+                         '/into_urlconf2/second_test/')
213+        # Rendered with RequestContext with a request.urlconf
214+        request.urlconf = urlconf2
215+        c = RequestContext(request)
216+        self.assertEqual(Template('{% url test1 %}').render(c), '')
217+        self.assertEqual(Template('{% url test2 %}').render(c),
218+                         '/second_test/')
219Index: docs/url_dispatch.txt
220===================================================================
221--- docs/url_dispatch.txt       (revision 7028)
222+++ docs/url_dispatch.txt       (working copy)
223@@ -32,7 +32,10 @@
224 When a user requests a page from your Django-powered site, this is the
225 algorithm the system follows to determine which Python code to execute:
226 
227-    1. Django looks at the ``ROOT_URLCONF`` setting in your `settings file`_.
228+    1. Django decides on which URLconf module to use. First, Django looks for a
229+       ``urlconf`` attribute on the ``request`` object.
230+       Under most situations, this attribute will not be found, in which case
231+       Django looks at the ``ROOT_URLCONF`` setting in your `settings file`_.
232        This should be a string representing the full Python import path to your
233        URLconf. For example: ``"mydjangoapps.urls"``.
234     2. Django loads that Python module and looks for the variable
235@@ -596,3 +599,26 @@
236 
237 .. _model API documentation: ../model-api/#the-permalink-decorator
238 
239+Overriding the URLconf
240+======================
241+
242+As explained in `how Django processes a request`_, the URLconf module used by
243+Django can be altered via custom middleware by setting ``request.urlconf`` to a
244+string representing the full Python import path to the URLconf.
245+
246+If you are using any such middleware, you will need to consider that Django's
247+reverse URL resolution methods need to be made aware of the ``request`` object
248+so they can use the right URLconf:
249+
250+    * When you use the ``{% url %}`` tag in a template, render it with a
251+      ``RequestContext`` so the tag can check the ``request`` object.
252+
253+    * For ease of use, the ``urlconf`` argument of ``reverse()`` also accepts a
254+      ``request`` object. So when you use ``reverse()``, pass ``request`` as
255+      the ``urlconf`` argument::
256+
257+          reverse('appname.viewname', urlconf=request)
258+
259+Using ``reverse()`` without access to ``request`` (such as outside a view) or
260+using the ``permalink`` decorator will mean that the returning URL will only be
261+resolved from the default URLconf as defined in ``settings.ROOT_URLCONF``.