Skip to content

Commit

Permalink
Improved urlresolvers so that URLconfs can be passed objects instead …
Browse files Browse the repository at this point in the history
…of strings

git-svn-id: http://code.djangoproject.com/svn/django/trunk@3554 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
adrianholovaty committed Aug 11, 2006
1 parent 4805675 commit 0b71ffa
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 17 deletions.
50 changes: 33 additions & 17 deletions django/core/urlresolvers.py
Expand Up @@ -86,10 +86,15 @@ def __call__(self, match_obj):
class RegexURLPattern(object): class RegexURLPattern(object):
def __init__(self, regex, callback, default_args=None): def __init__(self, regex, callback, default_args=None):
# regex is a string representing a regular expression. # regex is a string representing a regular expression.
# callback is something like 'foo.views.news.stories.story_detail', # callback is either a string like 'foo.views.news.stories.story_detail'
# which represents the path to a module and a view function name. # which represents the path to a module and a view function name, or a
# callable object (view).
self.regex = re.compile(regex) self.regex = re.compile(regex)
self.callback = callback if callable(callback):
self._callback = callback
else:
self._callback = None
self._callback_str = callback
self.default_args = default_args or {} self.default_args = default_args or {}


def resolve(self, path): def resolve(self, path):
Expand All @@ -106,23 +111,28 @@ def resolve(self, path):
# In both cases, pass any extra_kwargs as **kwargs. # In both cases, pass any extra_kwargs as **kwargs.
kwargs.update(self.default_args) kwargs.update(self.default_args)


try: # Lazily load self.func. return self.callback, args, kwargs
return self.func, args, kwargs
except AttributeError:
self.func = self.get_callback()
return self.func, args, kwargs


def get_callback(self): def _get_callback(self):
mod_name, func_name = get_mod_func(self.callback) if self._callback is not None:
return self._callback
mod_name, func_name = get_mod_func(self._callback_str)
try: try:
return getattr(__import__(mod_name, '', '', ['']), func_name) self._callback = getattr(__import__(mod_name, '', '', ['']), func_name)
except ImportError, e: except ImportError, e:
raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e)) raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e))
except AttributeError, e: except AttributeError, e:
raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e)) raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))
return self._callback
callback = property(_get_callback)


def reverse(self, viewname, *args, **kwargs): def reverse(self, viewname, *args, **kwargs):
if viewname != self.callback: mod_name, func_name = get_mod_func(viewname)
try:
lookup_view = getattr(__import__(mod_name, '', '', ['']), func_name)
except (ImportError, AttributeError):
raise NoReverseMatch
if lookup_view != self.callback:
raise NoReverseMatch raise NoReverseMatch
return self.reverse_helper(*args, **kwargs) return self.reverse_helper(*args, **kwargs)


Expand Down Expand Up @@ -185,22 +195,28 @@ def resolve404(self):
def resolve500(self): def resolve500(self):
return self._resolve_special('500') return self._resolve_special('500')


def reverse(self, viewname, *args, **kwargs): def reverse(self, lookup_view, *args, **kwargs):
if not callable(lookup_view):
mod_name, func_name = get_mod_func(lookup_view)
try:
lookup_view = getattr(__import__(mod_name, '', '', ['']), func_name)
except (ImportError, AttributeError):
raise NoReverseMatch
for pattern in self.urlconf_module.urlpatterns: for pattern in self.urlconf_module.urlpatterns:
if isinstance(pattern, RegexURLResolver): if isinstance(pattern, RegexURLResolver):
try: try:
return pattern.reverse_helper(viewname, *args, **kwargs) return pattern.reverse_helper(lookup_view, *args, **kwargs)
except NoReverseMatch: except NoReverseMatch:
continue continue
elif pattern.callback == viewname: elif pattern.callback == lookup_view:
try: try:
return pattern.reverse_helper(*args, **kwargs) return pattern.reverse_helper(*args, **kwargs)
except NoReverseMatch: except NoReverseMatch:
continue continue
raise NoReverseMatch raise NoReverseMatch


def reverse_helper(self, viewname, *args, **kwargs): def reverse_helper(self, lookup_view, *args, **kwargs):
sub_match = self.reverse(viewname, *args, **kwargs) sub_match = self.reverse(lookup_view, *args, **kwargs)
result = reverse_helper(self.regex, *args, **kwargs) result = reverse_helper(self.regex, *args, **kwargs)
return result + sub_match return result + sub_match


Expand Down
45 changes: 45 additions & 0 deletions docs/url_dispatch.txt
Expand Up @@ -431,3 +431,48 @@ Note that extra options will *always* be passed to *every* line in the included
URLconf, regardless of whether the line's view actually accepts those options 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 as valid. For this reason, this technique is only useful if you're certain that
every view in the the included URLconf accepts the extra options you're passing. every view in the the included URLconf accepts the extra options you're passing.

Passing callable objects instead of strings
===========================================

**New in the Django development version.**

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::

urlpatterns = patterns('',
(r'^archive/$', 'mysite.views.archive'),
(r'^about/$', 'mysite.views.about'),
(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 mysite.views import archive, about, contact

urlpatterns = patterns('',
(r'^archive/$', archive),
(r'^about/$', about),
(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 mysite import views

urlpatterns = patterns('',
(r'^archive/$', views.archive),
(r'^about/$', views.about),
(r'^contact/$', views.contact),
)

The style you use is up to you.

Note that if you use this technique -- passing objects rather than strings --
the view prefix (as explained in "The view prefix" above) will have no effect.

0 comments on commit 0b71ffa

Please sign in to comment.