Active links in Django
Django. Django. Nog eens? Django! Het is een geweldig framework, maar bij ieder framework zal je moeten vaststellen dat je wat mist.
Het probleem
De link voor de huidige pagina moet verdwijnen of er tenminste anders uitzien. Django komt niet met een directe oplossing en de meest relevante andere oplossing werkt met hardcoded paths. De beste oplossing zou zijn om met named URLs te werken.
Stel je voor dat dit de URL patterns zijn:
urlpatterns = patterns('',
url(r'^archive/(?P\d{4})/(?P\d{2})/(?P\d{2})/$’,
‘proj.app.views.archive_for_a_day’,
name=’proj_app_archive_for_a_day’),
url(r’^search/$’,
‘proj.app.views.search’,
name=’proj_app_search’),
)
In mijn menu wil ik linken naar het archief voor een bepaalde dag en de zoekpagina. Maar als ik op één van die pagina’s ben wil ik dat het list item met de overeenkomstige link de class
active
krijgt. Zoiets:
<ul>
<li{{ request|on_active_link:"proj_app_archive_for_a_day,active" }}>
<a href="{%url proj_app_archive_for_a_day year,month,day %}">Archive</a>
</li>
<li{{ request|on_active_link:"proj_app_search,active" }}>
<a href="{%url proj_app_search %}">Search</a>
</li>
</ul>
De oplossing
Die volgt uit 2 eenvoudige template filters:
matches_url_pattern
zalTrue
teruggeven bij een match, waardoor die kan gebruikt worden met een{% if %}
om de link te verwijderenon_active_link
, uit bovenstaand voorbeeld, zal het tweede argument gebruiken als eenclass
indien de naam van de URL pattern (het eerste argument) overeenkomt met het request path.
from django import template
register = template.Library()
@register.filter(name='matches_url_pattern')
def matches_url_pattern(value,args):
return is_path_a_match_for_named_url(value, args)
@register.filter(name='on_active_link')
def on_active_link(value,args):
parts = args.split(',')
if is_path_a_match_for_named_url(value, parts[0]):
return ' class="%s"' % parts[1]
return ''
def is_path_a_match_for_named_url(request, named_url):
from django.core.urlresolvers import get_resolver, Resolver404
resolver = get_resolver(None)
path = request.path
tried = []
match = resolver.regex.search(path)
if match:
new_path = path[match.end():]
for pattern in resolver.urlconf_module.urlpatterns:
try:
sub_match = pattern.resolve(new_path)
except Resolver404, e:
tried.extend([(pattern.regex.pattern + ' ' + t) for t in e.args[0]['tried']])
else:
if sub_match:
return pattern.name == named_url
return False
De laatste functie vergt het meeste uitleg. is_path_a_match_for_named_url
gebruikt code die overgenomen is van de RegexURLResolver
. Deze kan bepalen welk URL pattern met het huidige request path overeenkomt, maar biedt in de huidige versie geen mogelijkheid om de naam van dat pattern te achterhalen. Vandaar deze kopie die wel kan checken of de naam van het gevonden pattern overeenstemt met de gegeven parameter.
Het resultaat
Stel nu dat ik ben beland op /search/
. De HTML zal er als volgt uitzien:
<ul>
<li>
<a href="/archive/2008/07/21/">Archive</a>
</li>
<li class="active">
<a href="/search/">Search</a>
</li>
</ul>
Een mooiere oplossing voor dit probleem zal de filters vervangen door tags en geen expliciete verwijzing naar de request
variabele nodig hebben.
Dit artikel werd opgenomen in ontwikkeling.