Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[1.1.X] Fixed #5034 -- honor request.urlconf in reverse and resolve.

This enables {% url %} to honor request.urlconf set from process_request
middleware methods.

Thanks SmileyChris for the initial patch work.

Backport of [11740] from trunk

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@11741 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 4cba436269947df3124fa55c6c53ae170ce596c5 1 parent 7f2f6bc
Brian Rosner authored November 16, 2009
105  django/core/handlers/base.py
@@ -68,6 +68,9 @@ def get_response(self, request):
68 68
         from django.core import exceptions, urlresolvers
69 69
         from django.conf import settings
70 70
 
  71
+        # Reset the urlconf for this thread.
  72
+        urlresolvers.set_urlconf(None)
  73
+
71 74
         # Apply request middleware
72 75
         for middleware_method in self._request_middleware:
73 76
             response = middleware_method(request)
@@ -77,61 +80,69 @@ def get_response(self, request):
77 80
         # Get urlconf from request object, if available.  Otherwise use default.
78 81
         urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
79 82
 
  83
+        # Set the urlconf for this thread to the one specified above.
  84
+        urlresolvers.set_urlconf(urlconf)
  85
+
80 86
         resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
81 87
         try:
82  
-            callback, callback_args, callback_kwargs = resolver.resolve(
83  
-                    request.path_info)
84  
-
85  
-            # Apply view middleware
86  
-            for middleware_method in self._view_middleware:
87  
-                response = middleware_method(request, callback, callback_args, callback_kwargs)
88  
-                if response:
89  
-                    return response
90  
-
91 88
             try:
92  
-                response = callback(request, *callback_args, **callback_kwargs)
93  
-            except Exception, e:
94  
-                # If the view raised an exception, run it through exception
95  
-                # middleware, and if the exception middleware returns a
96  
-                # response, use that. Otherwise, reraise the exception.
97  
-                for middleware_method in self._exception_middleware:
98  
-                    response = middleware_method(request, e)
  89
+                callback, callback_args, callback_kwargs = resolver.resolve(
  90
+                        request.path_info)
  91
+
  92
+                # Apply view middleware
  93
+                for middleware_method in self._view_middleware:
  94
+                    response = middleware_method(request, callback, callback_args, callback_kwargs)
99 95
                     if response:
100 96
                         return response
101  
-                raise
102 97
 
103  
-            # Complain if the view returned None (a common error).
104  
-            if response is None:
105 98
                 try:
106  
-                    view_name = callback.func_name # If it's a function
107  
-                except AttributeError:
108  
-                    view_name = callback.__class__.__name__ + '.__call__' # If it's a class
109  
-                raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
110  
-
111  
-            return response
112  
-        except http.Http404, e:
113  
-            if settings.DEBUG:
114  
-                from django.views import debug
115  
-                return debug.technical_404_response(request, e)
116  
-            else:
117  
-                try:
118  
-                    callback, param_dict = resolver.resolve404()
119  
-                    return callback(request, **param_dict)
120  
-                except:
  99
+                    response = callback(request, *callback_args, **callback_kwargs)
  100
+                except Exception, e:
  101
+                    # If the view raised an exception, run it through exception
  102
+                    # middleware, and if the exception middleware returns a
  103
+                    # response, use that. Otherwise, reraise the exception.
  104
+                    for middleware_method in self._exception_middleware:
  105
+                        response = middleware_method(request, e)
  106
+                        if response:
  107
+                            return response
  108
+                    raise
  109
+
  110
+                # Complain if the view returned None (a common error).
  111
+                if response is None:
121 112
                     try:
122  
-                        return self.handle_uncaught_exception(request, resolver, sys.exc_info())
123  
-                    finally:
124  
-                        receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
125  
-        except exceptions.PermissionDenied:
126  
-            return http.HttpResponseForbidden('<h1>Permission denied</h1>')
127  
-        except SystemExit:
128  
-            # Allow sys.exit() to actually exit. See tickets #1023 and #4701
129  
-            raise
130  
-        except: # Handle everything else, including SuspiciousOperation, etc.
131  
-            # Get the exception info now, in case another exception is thrown later.
132  
-            exc_info = sys.exc_info()
133  
-            receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
134  
-            return self.handle_uncaught_exception(request, resolver, exc_info)
  113
+                        view_name = callback.func_name # If it's a function
  114
+                    except AttributeError:
  115
+                        view_name = callback.__class__.__name__ + '.__call__' # If it's a class
  116
+                    raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
  117
+
  118
+                return response
  119
+            except http.Http404, e:
  120
+                if settings.DEBUG:
  121
+                    from django.views import debug
  122
+                    return debug.technical_404_response(request, e)
  123
+                else:
  124
+                    try:
  125
+                        callback, param_dict = resolver.resolve404()
  126
+                        return callback(request, **param_dict)
  127
+                    except:
  128
+                        try:
  129
+                            return self.handle_uncaught_exception(request, resolver, sys.exc_info())
  130
+                        finally:
  131
+                            receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
  132
+            except exceptions.PermissionDenied:
  133
+                return http.HttpResponseForbidden('<h1>Permission denied</h1>')
  134
+            except SystemExit:
  135
+                # Allow sys.exit() to actually exit. See tickets #1023 and #4701
  136
+                raise
  137
+            except: # Handle everything else, including SuspiciousOperation, etc.
  138
+                # Get the exception info now, in case another exception is thrown later.
  139
+                exc_info = sys.exc_info()
  140
+                receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
  141
+                return self.handle_uncaught_exception(request, resolver, exc_info)
  142
+        finally:
  143
+            # Reset URLconf for this thread on the way out for complete
  144
+            # isolation of request.urlconf
  145
+            urlresolvers.set_urlconf(None)
135 146
 
136 147
     def handle_uncaught_exception(self, request, resolver, exc_info):
137 148
         """
30  django/core/urlresolvers.py
@@ -10,6 +10,7 @@
10 10
 import re
11 11
 
12 12
 from django.http import Http404
  13
+from django.conf import settings
13 14
 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
14 15
 from django.utils.datastructures import MultiValueDict
15 16
 from django.utils.encoding import iri_to_uri, force_unicode, smart_str
@@ -32,6 +33,9 @@
32 33
 # be empty.
33 34
 _prefixes = {}
34 35
 
  36
+# Overridden URLconfs for each thread are stored here.
  37
+_urlconfs = {}
  38
+
35 39
 class Resolver404(Http404):
36 40
     pass
37 41
 
@@ -300,9 +304,13 @@ def reverse(self, lookup_view, *args, **kwargs):
300 304
                 "arguments '%s' not found." % (lookup_view_s, args, kwargs))
301 305
 
302 306
 def resolve(path, urlconf=None):
  307
+    if urlconf is None:
  308
+        urlconf = get_urlconf()
303 309
     return get_resolver(urlconf).resolve(path)
304 310
 
305 311
 def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
  312
+    if urlconf is None:
  313
+        urlconf = get_urlconf()
306 314
     resolver = get_resolver(urlconf)
307 315
     args = args or []
308 316
     kwargs = kwargs or {}
@@ -371,3 +379,25 @@ def get_script_prefix():
371 379
     """
372 380
     return _prefixes.get(currentThread(), u'/')
373 381
 
  382
+def set_urlconf(urlconf_name):
  383
+    """
  384
+    Sets the URLconf for the current thread (overriding the default one in
  385
+    settings). Set to None to revert back to the default.
  386
+    """
  387
+    thread = currentThread()
  388
+    if urlconf_name:
  389
+        _urlconfs[thread] = urlconf_name
  390
+    else:
  391
+        # faster than wrapping in a try/except
  392
+        if thread in _urlconfs:
  393
+            del _urlconfs[thread]
  394
+
  395
+def get_urlconf(default=None):
  396
+    """
  397
+    Returns the root URLconf to use for the current thread if it has been
  398
+    changed from the default one.
  399
+    """
  400
+    thread = currentThread()
  401
+    if thread in _urlconfs:
  402
+        return _urlconfs[thread]
  403
+    return default
3  docs/topics/http/urls.txt
@@ -40,7 +40,8 @@ algorithm the system follows to determine which Python code to execute:
40 40
 
41 41
     1. Django determines the root URLconf module to use. Ordinarily,
42 42
        this is the value of the ``ROOT_URLCONF`` setting, but if the incoming
43  
-       ``HttpRequest`` object has an attribute called ``urlconf``, its value
  43
+       ``HttpRequest`` object has an attribute called ``urlconf`` (set by
  44
+       middleware :ref:`request processing <request-middleware>`), its value
44 45
        will be used in place of the ``ROOT_URLCONF`` setting.
45 46
 
46 47
     2. Django loads that Python module and looks for the variable
7  tests/regressiontests/urlpatterns_reverse/middleware.py
... ...
@@ -0,0 +1,7 @@
  1
+from django.core.urlresolvers import set_urlconf
  2
+
  3
+import urlconf_inner
  4
+
  5
+class ChangeURLconfMiddleware(object):
  6
+    def process_request(self, request):
  7
+        request.urlconf = urlconf_inner.__name__
37  tests/regressiontests/urlpatterns_reverse/tests.py
@@ -16,11 +16,16 @@
16 16
 
17 17
 import unittest
18 18
 
  19
+from django.conf import settings
19 20
 from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404
20 21
 from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
21 22
 from django.shortcuts import redirect
22 23
 from django.test import TestCase
23 24
 
  25
+import urlconf_outer
  26
+import urlconf_inner
  27
+import middleware
  28
+
24 29
 test_data = (
25 30
     ('places', '/places/3/', [3], {}),
26 31
     ('places', '/places/3/', ['3'], {}),
@@ -239,3 +244,35 @@ def test_app_lookup_object_without_default(self):
239 244
         self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
240 245
         self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
241 246
 
  247
+
  248
+class RequestURLconfTests(TestCase):
  249
+    def setUp(self):
  250
+        self.root_urlconf = settings.ROOT_URLCONF
  251
+        self.middleware_classes = settings.MIDDLEWARE_CLASSES
  252
+        settings.ROOT_URLCONF = urlconf_outer.__name__
  253
+
  254
+    def tearDown(self):
  255
+        settings.ROOT_URLCONF = self.root_urlconf
  256
+        settings.MIDDLEWARE_CLASSES = self.middleware_classes
  257
+
  258
+    def test_urlconf(self):
  259
+        response = self.client.get('/test/me/')
  260
+        self.assertEqual(response.status_code, 200)
  261
+        self.assertEqual(response.content, 'outer:/test/me/,'
  262
+                                           'inner:/inner_urlconf/second_test/')
  263
+        response = self.client.get('/inner_urlconf/second_test/')
  264
+        self.assertEqual(response.status_code, 200)
  265
+        response = self.client.get('/second_test/')
  266
+        self.assertEqual(response.status_code, 404)
  267
+
  268
+    def test_urlconf_overridden(self):
  269
+        settings.MIDDLEWARE_CLASSES += (
  270
+            '%s.ChangeURLconfMiddleware' % middleware.__name__,
  271
+        )
  272
+        response = self.client.get('/test/me/')
  273
+        self.assertEqual(response.status_code, 404)
  274
+        response = self.client.get('/inner_urlconf/second_test/')
  275
+        self.assertEqual(response.status_code, 404)
  276
+        response = self.client.get('/second_test/')
  277
+        self.assertEqual(response.status_code, 200)
  278
+        self.assertEqual(response.content, 'outer:,inner:/second_test/')
12  tests/regressiontests/urlpatterns_reverse/urlconf_inner.py
... ...
@@ -0,0 +1,12 @@
  1
+from django.conf.urls.defaults import *
  2
+from django.template import Template, Context
  3
+from django.http import HttpResponse
  4
+
  5
+def inner_view(request):
  6
+    content = Template('{% url outer as outer_url %}outer:{{ outer_url }},'
  7
+                       '{% url inner as inner_url %}inner:{{ inner_url }}').render(Context())
  8
+    return HttpResponse(content)
  9
+
  10
+urlpatterns = patterns('',
  11
+    url(r'^second_test/$', inner_view, name='inner'),
  12
+)
9  tests/regressiontests/urlpatterns_reverse/urlconf_outer.py
... ...
@@ -0,0 +1,9 @@
  1
+from django.conf.urls.defaults import *
  2
+
  3
+import urlconf_inner
  4
+
  5
+
  6
+urlpatterns = patterns('',
  7
+    url(r'^test/me/$', urlconf_inner.inner_view, name='outer'),
  8
+    url(r'^inner_urlconf/', include(urlconf_inner.__name__))
  9
+)

0 notes on commit 4cba436

Please sign in to comment.
Something went wrong with that request. Please try again.