I was hitting a wall setting up a few urls where I was using optional kwargs in the url. I imagine this is a common scenario where content can live in more than one place, especially if the url itself is holding down some context to maintain an anonymous view. Here’s a quick example:
(r’^blotter/(?P<filter_name>(categories|arrests|personnel))/$’, render_blotter, {}, ‘blotter’),
(r’^blotter/(?P<filter_name>(categories|arrests|personnel))/(?P<filter_value>([\w-]+))/$’, render_blotter, {}, ‘blotter’),
The two routes are dispatched to the same named function ‘blotter’, but have a unique signature depending on what arguments are passed. A more common example might be a YYYY/, YYYY/MM, YYYY/MM/DD/ situation where they should share a common render mapping. The url dispatcher can handle this fine, and for the most part it can be handled in the application logic without much of a headache by removing kwargs equal to None before revers() is called to retrieve the url. The crux of the issue is really the url tag in the templates which is not happy unless it gets an exact match of arguments.
A better explanation is available here. In any case, I’ve put a templatetag together to handle optional kwargs. Hopefully someone will find it useful.
"""
exactly the same as from django.template.defaulttags.url EXCEPT kwargs equal to None are removed
this allows a bit more flexibility than the use of {% url %} where nesting is rested on optional
base kw arguments.
see http://code.djangoproject.com/ticket/9176
"""
from django import template
from django.template.defaulttags import URLNode, url
register = template.Library()
class URLNodeOptional(URLNode):
"""
identical to django.template.defaulttags.URLNode
but removes kwargs equal to None before resolving the url
"""
def render(self, context):
for k, v in self.kwargs.items():
if v.resolve(context) is None:
self.kwargs.pop(k)
return super(URLNodeOptional, self).render(context)
def url_optional(parser, token):
"""
creates the default URLNode, then routes it to the Optional resolver with the same properties
by first creating the URLNode, the parsing stays in django core where it belongs.
"""
urlnode = url(parser, token)
return URLNodeOptional(urlnode.view_name, urlnode.args, urlnode.kwargs, urlnode.asvar)
url_optional = register.tag(url_optional)
