A Smarter Django URL Tag

Posted on July 1, 2013

Django's built-in url tag is very useful. Repeating yourself goes against Django's philosophy, and the url tag helps you avoid that pitfall. But there's plenty of room for improvement. This post will show you how to make Django's url tag smarter and more useful. Note: this post applies to Django 1.5 and above. Earlier versions of Django use a deprecated syntax.

So what can we do to improve the url tag? It'd be nice if it could tell us when a path is active. That way, we can mark certain menu items and style them differently. But let's not stop there. Sometimes an exact path match is not good enough. Say you're under the "Shop" section of your site. You still want the navigation item for "Shop" marked as active, even if you're in a deeper part of the site - other than the "Shop" landing page. So let's define these behaviors:

  • Active means the paths are an exact match. If you're on the /shop/ page, and your URL tag result matches that, then that link is active. However, if you're visiting the /shop/on-sale/ section of the site, then the URL is not active.
  • Active root means that the paths match or the beginning of the path matches. So if you're visiting /shop/on-sale/, url tags for /shop/ would match. This is useful for main navigation items and the like.

One last improvement: we want to be able to use these helpers for hard-coded URLs too. For example, when linking to flatpages, you do want to be able to pass '/my-flatpage/' to the tag and see if it's active (or in the active root).

Learning Django? Subscribe to my Django articles, tips and tutorials.

With all that out of the way, let's start by defining our URL tag. In any of your existing apps, create the templatetags directory (don't forget the __init__.py file inside), and then create a file called utilities.py inside. Feel free to name this differently if you'd like.

In this file, add the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django import template
from django.template.defaulttags import URLNode, url as url_tag

register = template.Library()


@register.tag
def url(parser, token):
    validator = url_tag(parser, token)
    return SmartURLNode(validator.view_name, validator.args, validator.kwargs, validator.asvar)

You'll notice we're registering our tag with the same name as the existing core Django tag. Don't worry, since the syntax and arguments match, this isn't a problem. In our code, we need to import the old url tag so we can reuse its validation logic. No need to repeat ourselves!

For our improvements to work, we need to attach arbitrary attributes to our result string (for the URL), so we define a dummy class as follows:

1
2
3
4
5
6
class SmartURL(unicode):
    """
    This is a wrapper class that allows us to attach attributes to regular
    unicode strings.
    """
    pass

Now let's define our smarter URL node, as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Notice we inherit from the existing URLNode.
class SmartURLNode(URLNode):
    def render(self, context):
        # Step 1
        resolved_view_name = self.view_name.resolve(context)

        if resolved_view_name:
            view_name_string = "'%s'" % resolved_view_name
        else:
            view_name_string = unicode(self.view_name)

        # Step 2
        if len(view_name_string) >= 3 and view_name_string[0] == view_name_string[-1] and view_name_string[0] in ('"', "'") and view_name_string[1] == '/':
            rendered = view_name_string[1:-1]

            if self.asvar:
                context[self.asvar] = rendered
        # Step 3
        else:
            rendered = super(SmartURLNode, self).render(context)

        # Step 4
        if not self.asvar:
            return rendered
        # Step 5
        else:
            resolved_url = SmartURL(context[self.asvar])
            request = context.get('request', None)

            # Step 6
            if request:
                # Step 7
                if resolved_url == request.path:
                    resolved_url.active = 'active'
                else:
                    resolved_url.active = ''

                # Step 8
                if request.path.startswith(resolved_url):
                    resolved_url.active_root = 'active'
                else:
                    resolved_url.active_root = ''

            # Step 9
            context[self.asvar] = resolved_url
            return ''

Let's go step by step here. Use the comments above to follow each step:

  1. First we try to resolve the view name. If we passed in a variable or function, including, say, some_model_instance.get_absolute_url, then this will resolve nicely into a path. If it resolves, we turn it into a faked template string. If it doesn't resolve, then we assume they passed a hard-coded path.
  2. But we only accept hard-coded paths starting with a slash. If that's the case, we assume that's the final path, no need to further resolve the path.
  3. If it's not a hard-coded path, we pass the rendering on to the regular URLNode that we inherited from. Remember: avoid repeating yourself. Just reuse existing logic.
  4. Now, if we aren't assigning to a variable, simple return the resolved path. And yes, it's a little silly to use this tag for hard-coded paths without assignment. But it'll work!
  5. On the other hand, if we're assigning, things get interesting. First, we get our fully resolved string.
  6. Then, if there's a request object in the context, we compare paths and assign attributes as necessary.
  7. If the request path matches the url exactly, set the active attribute on our SmartURL instance.
  8. If the request path starts with the url, also known as the root, then set the active_root attribute.
  9. And that's it! Assign the SmartURL instance back into the context and we're done. As this is an assignment use, return an empty string.

Here are some usage examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{# First we load the library. #}
{% load utilities %}

{# Our standard tag usage. #}
<a href="{% url 'home_page' %}">Home Page</a>

{# Our standard tag usage with assignment. #}
{% url 'home_page' as url %}<a href="{{ url }}">Home Page</a>

{# Here we use assignment, and add the active class when the pages match. #}
{% url 'another_page' as url %}<a class="{{ url.active }}" href="{{ url }}">Another Page</a>

{# We use assignment again, but we match the root rather than the whole path. #}
{% url 'last_page' as url %}<a class="{{ url.active_root }}" href="{{ url }}">Last Page</a>

{# Flatpages work too! #}
{% url '/about/' as url %}<a class="{{ url.active }}" href="{{ url }}">About Page</a>

And in our CSS files, we can now do:

1
2
a { color: black; }
a.active { color: red; }

That completes this tutorial. Got some other improvements you'd like to make? Leave a comment below. As always, questions and feedback are also welcome.

Tags:Django

Comments

SanderNov. 4, 2013, 1:21 p.m.

Nice stuff!

But could you please change your code font-style to something more readable. This is hurting my eyes. I like Consolas for example.

Reply
silviogutierrezNov. 4, 2013, 4:50 p.m.

I've been meaning to - classic form vs function dilemma, I suppose.

Reply
asdfsdfsdMarch 4, 2019, 11:25 a.m.

asdfasdfasdf

Reply
JohnMarch 6, 2019, 4:10 p.m.

I have noticed a trend of people using these tags a lot like on the following website https://www.assignmentgeek.com.au/. It is more user friendly and allows for an easier access.

Reply
fggfggfMay 18, 2019, 1:57 p.m.

Le savoir-faire novateur et la technologie de pointe sont une arme magique pour gagner dans la compétition moderne. replique montre Avec l'amélioration du niveau de vie des personnes, leur niveau d'appréciation et de consommation s'améliore également constamment, ce qui pose un nouveau sujet pour les opérateurs de production et leur fournit un nouveau champ de concurrence. Depuis le développement actuel du marché, les produits grand public avec une technologie de processus élevée, une qualité de produit élevée et des idées originales et uniques ont conquis de plus en plus de consommateurs. Dans le travail de promotion des ventes proprement dit, le vendeur peut d'abord éveiller la curiosité du client, attirer son attention et son intérêt, puis vendre les avantages de la marchandise à partir du milieu et passer rapidement au stade de l'entretien. Des méthodes spécifiques pour susciter la curiosité peuvent être souples et variées, essayez d’être maniables et utilisez librement. replique stylo mont blanc De nombreuses expériences de concurrence sur le marché ont prouvé que seule l'innovation de concept peut saisir cette opportunité. Lorsque de nombreux entrepreneurs prospères parlent d'expérience, ils disent toujours que l'entrepreneuriat a besoin d'innovation.

Reply
david jasonMay 23, 2019, 8:51 a.m.

Superbly written article, if only all bloggers offered the same content as you through blogging tips, the internet would be a far better place for learning anything.

Reply
pauMay 25, 2019, 3:26 p.m.

I was really trying to find something that would help me lose weight when a friend of mine told ma about forskolin extract. It has done wonders for me as I was able to achieve dream body.

Reply
TomJune 3, 2019, 8 a.m.

I am glad to read the information you shared on this page about. james

Reply
PeterJune 7, 2019, 5:33 p.m.

I really respected the way you write this message.I was extremely satisfied to see this site. Thanks a lot for sharing the story’s Thanks a lot for sharing the knowledgeable resource in my field, I also attempt to find the various pertinent industry segments. Home Improvement Mortgage

Reply
davidJune 10, 2019, 2:58 p.m.

It was a very good post indeed. I thoroughly enjoyed reading it in my lunch time. Will surely come and visit this blog more often. ขายอะไหล่คอมพิวเตอร์

Reply
davidJune 13, 2019, 10:55 a.m.

Thanks so much with this fantastic new web site. I’m very fired up to show it to anyone. It makes me so satisfied your vast understanding and wisdom have a new channel for trying into the world. online assignment help

Reply

Post New Comment