99
1010from django .http import Http404
1111from django .core .exceptions import ImproperlyConfigured , ViewDoesNotExist
12+ from django .utils .functional import memoize
1213import re
1314
15+ _resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances.
16+ _callable_cache = {} # Maps view and url pattern names to their view functions.
17+
1418class Resolver404 (Http404 ):
1519 pass
1620
1721class NoReverseMatch (Exception ):
1822 # Don't make this raise an error when used in a template.
1923 silent_variable_failure = True
2024
25+ def get_callable (lookup_view ):
26+ if not callable (lookup_view ):
27+ mod_name , func_name = get_mod_func (lookup_view )
28+ if func_name != '' :
29+ lookup_view = getattr (__import__ (mod_name , {}, {}, ['' ]), func_name )
30+ return lookup_view
31+ get_callable = memoize (get_callable , _callable_cache )
32+
33+ def get_resolver (urlconf ):
34+ if urlconf is None :
35+ from django .conf import settings
36+ urlconf = settings .ROOT_URLCONF
37+ return RegexURLResolver (r'^/' , urlconf )
38+ get_resolver = memoize (get_resolver , _resolver_cache )
39+
2140def get_mod_func (callback ):
2241 # Converts 'django.views.news.stories.story_detail' to
2342 # ['django.views.news.stories', 'story_detail']
@@ -129,9 +148,8 @@ def resolve(self, path):
129148 def _get_callback (self ):
130149 if self ._callback is not None :
131150 return self ._callback
132- mod_name , func_name = get_mod_func (self ._callback_str )
133151 try :
134- self ._callback = getattr ( __import__ ( mod_name , {}, {}, [ '' ]), func_name )
152+ self ._callback = get_callable ( self . _callback_str )
135153 except ImportError , e :
136154 raise ViewDoesNotExist , "Could not import %s. Error was: %s" % (mod_name , str (e ))
137155 except AttributeError , e :
@@ -160,6 +178,15 @@ def __init__(self, regex, urlconf_name, default_kwargs=None):
160178 self .urlconf_name = urlconf_name
161179 self .callback = None
162180 self .default_kwargs = default_kwargs or {}
181+ self .reverse_dict = {}
182+
183+ for pattern in reversed (self .urlconf_module .urlpatterns ):
184+ if isinstance (pattern , RegexURLResolver ):
185+ for key , value in pattern .reverse_dict .iteritems ():
186+ self .reverse_dict [key ] = (pattern ,) + value
187+ else :
188+ self .reverse_dict [pattern .callback ] = (pattern ,)
189+ self .reverse_dict [pattern .name ] = (pattern ,)
163190
164191 def resolve (self , path ):
165192 tried = []
@@ -209,24 +236,12 @@ def resolve500(self):
209236 return self ._resolve_special ('500' )
210237
211238 def reverse (self , lookup_view , * args , ** kwargs ):
212- if not callable (lookup_view ):
213- mod_name , func_name = get_mod_func (lookup_view )
214- try :
215- lookup_view = getattr (__import__ (mod_name , {}, {}, ['' ]), func_name )
216- except (ImportError , AttributeError ):
217- if func_name != '' :
218- raise NoReverseMatch
219- for pattern in self .urlconf_module .urlpatterns :
220- if isinstance (pattern , RegexURLResolver ):
221- try :
222- return pattern .reverse_helper (lookup_view , * args , ** kwargs )
223- except NoReverseMatch :
224- continue
225- elif pattern .callback == lookup_view or pattern .name == lookup_view :
226- try :
227- return pattern .reverse_helper (* args , ** kwargs )
228- except NoReverseMatch :
229- continue
239+ try :
240+ lookup_view = get_callable (lookup_view )
241+ except (ImportError , AttributeError ):
242+ raise NoReverseMatch
243+ if lookup_view in self .reverse_dict :
244+ return '' .join ([reverse_helper (part .regex , * args , ** kwargs ) for part in self .reverse_dict [lookup_view ]])
230245 raise NoReverseMatch
231246
232247 def reverse_helper (self , lookup_view , * args , ** kwargs ):
@@ -235,17 +250,10 @@ def reverse_helper(self, lookup_view, *args, **kwargs):
235250 return result + sub_match
236251
237252def resolve (path , urlconf = None ):
238- if urlconf is None :
239- from django .conf import settings
240- urlconf = settings .ROOT_URLCONF
241- resolver = RegexURLResolver (r'^/' , urlconf )
242- return resolver .resolve (path )
253+ return get_resolver (urlconf ).resolve (path )
243254
244255def reverse (viewname , urlconf = None , args = None , kwargs = None ):
245256 args = args or []
246257 kwargs = kwargs or {}
247- if urlconf is None :
248- from django .conf import settings
249- urlconf = settings .ROOT_URLCONF
250- resolver = RegexURLResolver (r'^/' , urlconf )
251- return '/' + resolver .reverse (viewname , * args , ** kwargs )
258+ return '/' + get_resolver (urlconf ).reverse (viewname , * args , ** kwargs )
259+
0 commit comments