reverse, lazy and resolve - Django url-resolvers

Working with urls is a core part of web development. The url-resolver utilities django provides makes working with them just that much easier. Look at using `reverse`, `reverse_lazy`, and `resolve` utility functions to write better idiomatic django.
Download HD Version next video: Wagtail CMS View Methods in the Page Model

resources

demo/urls.py

from django.conf.urls import url
from django.contrib import admin

from home.views import index, AboutView

urlpatterns = [
    url(r'^$', index, name='index'),
    url(r'^about/$', AboutView.as_view(), name='about'),
    url(r'^admin/', admin.site.urls),
]

home/views.py

from django.shortcuts import render
from django.core.urlresolvers import reverse, reverse_lazy, resolve
from django.views.generic import RedirectView


class AboutView(RedirectView):
    template_name = 'home/about.html'
    url = reverse_lazy('index')


def index(request):
    home1 = reverse('index')
    home2 = reverse_lazy('about')

    return render(request, 'home/index.html', {'urls': [home1, home2]})

In this video, we're gonna look at Django's URL resolvers. This is basically the shortcuts of reverse , reverse_lazy , and resolve. This can help you deal with URLs without having to hardcode them or figuring out how to take a URL and figure out metadata about it. First let's look a bit at our app, specifically at our URL's .py file. If you look, we have our root URL that goes to an IndexView, and the name of the URL is "index." We also have an "about" URL that uses a class-based view of AboutView, and the name of this URL is "about." We aren't gonna worry about admins, so don't worry about it. Next thing let's do is let's go ahead and run our project. If we look at it in our browser, you can see we have the URLs as the header and one and two are "none." Here we're going to list URLs based on function calls of reverse and reverse_lazy.

So if we'll go into our views of our home project, you'll see we have our AboutView and our IndexView. AboutView is a class-based view, and IndexView is a function-based view. If we edit our url1 variable and we change it to be reverse , and in reverse we pass it a string of "index," this is going to look up in our URL's .py file, or any subsequent URL's .py file in different apps, for a named URL of "index." Then what that's going to do is it's going to return back a string of the actual URL. So if we go back into our browser and refresh the page, you can see our url1 is just a slash.

Next thing is if we do our url2, we can do reverse_lazy , and we'll pass it in "about" because that's the name of the URL I wanna use. And then if we open this up in our browser, refresh the page, it goes from "none" to "about," so it uses the URL of "about." Go into your HTML templates, and you do the URL template tag, and you pass it the name of a URL, it uses something similar to reverse to get the URL. So now we've seen these in action, but it looks like they do the exact same thing, so let's go ahead and take a little bit of a closer look at what's going on. Plot an IPDB.set_trace statement to our code so that we can drop into the shell on execution, refresh our page to drop into our shell, and then go back to our shell. You can see we're at the IPDB prompt so that we can start messing around. To do url1, you can see we get back a slash as a string.

If we do url2, you can see we get back an object of some sort, and it's Django.utils.functional.lazy.<locals>__proxy__ object yadda yadda yadda, like, what's going on here? If we'll do the type of url1, you can see it returns back a class of string, and if we do type of url2, you can see we get back this object again. Basically what this object is is Django has a "lazy" object available in the system so that anything that it encapsulates can be lazily evaluated. What that means is it's not going to evaluate or get back the result that it needs until it's actually needed. So as an example of this, Python, to get back a string, is going to call the __str__() to return back what it does. So if we were to call that method, if we were to do url2.__str__ and call it like a function, it would return "about." It means it evaluated out to figure out what that string is supposed to be, but it only did it once we explicitly asked for it and not before then. So the question is, why would you want to use reverse_lazy over reverse ?

Well, if we'll go back into our views, we have our AboutView. If we add a URL because we're doing a RedirectView, and we'll set our URL, normally we would want to hardcode it to slash-about because this is actually really simple to do, so if we try to go to the "about" page it'll just redirect, in this case, back to the "about page" and create an infinite redirect situation, which is actually not what we want. However, if we change that to "index," it would do whatever it needs to do and it would go back to the "index" page. You normally have a URL on a RedirectView or a success URL for a FormView or CreateView. However, we don't like to hardcode URLs because they might change.

Instead, we're going to use the name of a URL, so you might want to use reverse and use "index" so that it goes to the proper URL. If we go ahead and jump over and start our server, you can see it doesn't get very far and just bombs out, and you get this. It's improperly configured, the included URL.conf "demo.urls" does not appear to have any patterns in it. What? What's going on? I know there's a demo.urls there. What happend was, when Python read this file, it tried to execute the reverse function and since it hadn't loaded the URLs yet because it's just parsing the file, then it had no idea what to actually do, and so we get this error.

The first time I ran into this issue, I spent a few hours not realizing what was going on, so unless you've actually seen this issue, that error message is the worst error message in the world because it doesn't actually tell you how to fix your existing problem. So instead what we'll do is we'll use reverse_lazy , and if we try to start our server again, it actually starts. And then if we open up our browser and we try to go to slash-about, you'll see it just redirects right back to our home page. If we look at our console output you can see, hey, went to "about" and did a 302 redirect. So that's when reverse_lazy is useful, is when you need the URL at some point but it can wait til the very end, just like in this case.

So finally, let's go ahead and look at the final thing, and this is something that you might not actually use very often, really, if ever. I've been doing Django development for several years and I've never actually had a need to use this, but it's good to know about it because the underlying Django system does actually use it, and that is the resolve function. We'll do an IPDB again in our "index," refresh our browser so that we can drop into the shell. We'll just do a res equals resolve , and we'll do "about." What this is gonna do is it's gonna return a object of ResolverMatch, and you see it's gonna have some information with it. The great thing about it is it's an object we can actually do something with. So if we do res dot and hit Tab, you can see we have a whole bunch of stuff that's available to us. Res dot url_name, this actually returns back the URL name of "about," which is the name equal that we set in our URL.

You can take any URL that's in your system and in code, you can say, "Hey, I have this URL, what's the name of that URL so I can use that instead?" You can also do the func to verify what actual view it's being used with, and you can see it's using the AboutView because in class-based views, the AsView function actually returns back a function. So in this case, it is seeing the AboutView, and it's actually returning the function so we can actually do something with it as well. Then, finally, if we use the resolve function and we pass it in a URL that doesn't exist, it's going to return back a Resolver404 which is a subclass of Raise404, so it just raises an exception and then gives you a whole bunch of information so that you can actually try to figure out what the problem was.

So, you can't just do it with random URLs because it'll raise an exception, unless you catch it and do something with it. So with that, that's a quick intro to the reverse function, the reverse_lazy function, and the resolve . Hopefully this can help you use the named URLs that are available and be able to use those through your app so you aren't hardcoding your URLs because it's really frustrating if you hardcode your URLs in a bunch of different places and then all of a sudden, you need to change a URL. Well, if you use named URLs and use these reverse functions, then all you have to do is change the URL in the URL's .py file, and you're ready to go because the rest of your code will just catch up.

comments powered by Disqus