A Simple Django Workflow Walkthrough - noisebridge/pyclass-project GitHub Wiki

Django can look complicated at first. Just navigating to a single, simple page can involve code in three or four different files. Django utilizes a design paradigm widely known as "MVC architecture", or rather, a slight variation they tend to refer to as "MTV". In this the code is split between a Model, a View, and a Controller. Or, in Django's version, a Model, Template, and View. While this isn't going to attempt to be a thorough explanation of this system (The Django Book's first chapter already does a much better job of that than I could ever attempt) it will attempt to walk through a section of our code and explain how it works and how to trace your way through the process that renders a webpage when you navigate to a URL.

For our example we're going to be using one of the simplest views currently in the code. It's not intended for the final version and is just a hacked-together bit designed for testing. It doesn't even have a link anywhere on the site. It's purpose is to display the avatar defined for the currently logged-in user. That's it. It lives at localhost:8000/display_avatar. Be certain that your server is running and you're logged into the site if you want to follow along and we'll dive in.

URLconfs - pyclass/urls.py

So assuming everything went well if you clicked on that link you should have been presented with a very basic page displaying an avatar. If you got a page asking you to log in do that now and you should be sent to the correct location (we'll cover how that works in a bit). If you just got a page that says "Avatar" that's a problem slightly beyond the scope of this walkthrough. Just ignore it for the moment. If you got a more complicated error message that's probably because I did something terribly wrong elsewhere and you should open an issue about what happened. Either way we can continue since working code isn't the purpose of this. What this should actually help you with is how to tell where something is breaking when it does break.

So, getting back to the matter at hand, how did we reach this page? The way Django works when you enter a URL that information first goes to the URLconf. These are python modules specified in settings.py that tell Django how to match a pattern (i.e. what you entered as the URL) to the view code used to render the page. If you look at our settings.py you'll find a line of code that looks like ROOT_URLCONF = 'pyclass.urls', this means that our root URLconf can be found at pyclass/urls.py. So let's open that file now and check it out.

Opening it up we find a variable named urlpatterns this is defined as the output of patterns() and is composed of a tuple of, in our case, url() functions and their attributes. The first argument for any of these is Regular Expression or RegEx. This is the pattern that we're trying to match. There's a lot of fancy stuff you can do to match or extract data from the URL, but in our case we just want something simple "display_avatar". That isn't too hard to find and should look like:

url(r'^display_avatar$', profile_views.display_avatar),

As I said, this is a pretty simple example and consists of only two arguments: the pattern we're trying to match and the view to call when that pattern is found. Django works through this list in order and as soon as it finds a match it calls the view that makes up the second argument. There are, of course, more complicated things and additional optional arguments, but these are the two required ones. The format specifies a particular function that, by convention, is in a module called "views.py". In our case we renamed the view module when we imported it for ease of use by using:

from pyclass.profiles import views as profile_views

So tracing this along further it looks like our next stop is the display_avatar() function in module profile.views.

Views - pyclass/profiles/views.py

Now that we've reached the view function we'll get to see how the code starts to come together. Everything up to this point was about finding out how to reach the view, but what that view does is perform the logic required to display the page and then return an object containing the webpage. It's kind of a big deal.

At the same time ours isn't very large. In fact, it's so small I can easily just put the whole thing here:

from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from pyclass.profiles.models import Interest, UserProfile

@login_required
def display_avatar(request):
    profile = UserProfile.objects.get(user=request.user)
    return render(request, "profiles/display_avatar.html", {"profile": profile})

So how do only four lines, excluding imports, comprise an entire webpage? Hell, if you take out the function declaration and the return line there's only one line of code actually doing anything! Well, it gets a lot of help. Let's go over everything line-by-line.

The first line (after the imports) is a decorator. The simple explanation is that this is a way to modify the function without having to change its code. In this case it only allows the view to be accessed by a logged-in user. If the user isn't logged-in it will send them to the login page and then return them once they're logged in. Since this view just displays your own avatar (and not anyone else's) there's no point in a user who isn't logged-in being here. Want to explore this in more detail? It's in the docs!

OK, that's done. Let's get to the meat of the function.

All views take request as a required argument. This is the HTTP request and it's full of useful information as we'll find out in a second.

The next line is just a variable definition, but it's where a big chunk of the work is getting done. What we have here is an object and, not to go into too much detail about how, it's pulling in the UserProfile object whose "user" field matches that of the user who requested the view (i.e. the logged-in user who navigated to the page). But what's in that UserProfile object? Where in the hell did that come from? The imports at the top show us that we actually imported it from the module pyclass.profiles.models. This is actually something we created ourselves, not something built into Django or Python. Let's pause for a moment here and follow this back to see what's going on so we can understand just what this variable will actually contain.

Model - pyclass/profiles/models.py

class UserProfile(models.Model):
    user = models.OneToOneField(User)
    avatar = models.ImageField(upload_to="avatars",
                               default=settings.STATIC_URL + "default-avatar.png")

... more code follows ...

Now this is getting a bit more complicated so I'm going to gloss over this section a lot more than the others. The long and short of it is that models define elements in the database we're using to power the site. Each object is generally a table in that database and each attribute is a column defined as a field by Django. Django does a lot of work in the background to make working with the database easy. For our purposes there are two attributes we care about: user and avatar.

Translating the code a bit the attribute user refers to the, well, User. This is defined by Django and while it's something that deserves to be discussed it's going outside the scope of this walkthrough. If you want more information you're probably best off reading up on user authentication in Django. For the purposes of this walkthrough it's sufficient to say that it's the user who's profile this is. We called the profile based on the user who requested the view and this is how we found them.

The second attribute is avatar. Again, this gets more complicated, but it's sufficient to say this is a representation of what the user's avatar is. Later in this walkthrough we'll see ways of working with it to get what we want. If you want to read up on the specifics the relevant section of documentation is for the ImageField object.

There's more to the UserProfile object, but for what we want right now we've covered it all. Let's go back to the view and see how we get this profile's information rendered into a page.

Back to the view - pyclass/profiles/views.py

So now we know what the profile variable means, more or less. It contains the information we want on this particular user's avatar. Now all we need to do is get that to a webpage and we can display it. That's where the next line comes in.

return render(request, "profiles/display_avatar.html", {"profile": profile})

This one line does it all. There are a bunch of ways to do this, but Django helpfully provides the shortcut function render() that will handle pretty much everything we need to do to render a page. It takes two required arguments and we're using one of the most common optional arguments as well.

The first argument is just the request. It basically needs this so it can run another function internally and in return it'll provide us with a bunch of useful variables. This is the primary way it differs from the very similar and older shortcut function render_to_response(). One of those variables is the current user (while we could access that here in the view we wouldn't be able to later as I'll explain shortly) and we're using it on the site to tell if someone's logged in and display their username so we'll pretty much be using render() everywhere.

The second argument is the template to use. This defines how the web page is going to look. We'll come back to it in a second.

The third, optional, argument is known as the context. This is a dictionary mapping keys to values we want accessible in the template. There they'll be usable by their key. So here we're sending the variable profile to the template where we can access it as profile. So if I needed to access a string in the template I could add that in to what we have here like so:

much_lamer_string = "everybody probably hates this string" 
{"profile": profile, "my_awesome_string": "I'm a string!!"})

and I'd be able to use my_awesome_string in the template. Because I didn't pass it as part of the dictionary much_lamer_string is staying here. Good. It's terrible and it knows it.

This is, essentially, why we're using render(), it bundles in a bunch of useful variables like these for free just by using it.

So going back to that template, that's how we're going to define how our page actually looks and utilize the context we pass in. At this point we're almost done and since it's just returning the output of render() (i.e., our rendered web page) we can safely leave the view and see what the template is doing.

There's one trick left though. While it looks like the template would be at "profiles/display_avatar.html" it's not. Settings.py has a variable TEMPLATE_DIRS that specifies where to look for templates. In ours we have a slightly complicated bit of code that really just means that it's at "pyclass/templates/". Since Django already knows this it will prepend that to the template argument without us specifying it in the argument. So where we really want to look is "pyclass/templates/profiles/display_avatar.html". Open it up, we're almost done.

Template - pyclass/templates/profiles/display_avatar.html

{% extends "base.html" %}

{% block content %}
    <img src="{{ profile.avatar.url }}" alt="Avatar">
{% endblock content %}

So after all of that work here's where we end up. Seems a bit anti-climactic almost.

First off that {% extends "base.html" %} is something pretty cool that Django does which means that our page has to do a lot less work. All of the basic stuff that defines how the site look is all there. If you put that extends block at the beginning of a template it will understand that all our current template does is add something to the base. Specifically we'll be filling in blocks that are defined in "base.html". If you want to check it out in more detail it lives at "pyclass/templates/base.html". For what we're doing we're just adding in content that will be put into block content. We describe this area in both files by using the block tags a seen in the code. If we wanted to put something into "footer" it would be the same process, but we'd replace "content" with "footer" like so:

{% extends "base.html" %}

{% block footer %}
{% endblock footer %}

So here all we're putting into the content block is a simple <img> tag. Since we sent profile to this template we can use it here and we do. When you want to access a variable in a template you enclose it in curly braces like so:

{{ some_variable }}

Not only can you access variables, but you can also often call methods within them to get their output. This is something you should read a proper tutorial about, but it basically uses simple dot notation for everything. We want the attribute avatar in profile and that avatar object as a method that returns a URL that references its location. This is probably sounding pretty complicated and it is, but the important thing to realize here is that we have access to profile and since avatar is part of it we can access it and make it tell us where it can be found. We do this by using {{ profile.avatar.url }} which will give us a string containing the URL. So we just put that in the <img> tag where the URL should go and it shows us the avatar! Hooray!

We left the view behind, but for due diligence this template is actually being rendered by the render() function. The view then returns the rendered page as an object and Django sees that it reaches the user who requested it.

Conclusion

So while this wasn't a proper tutorial in Django hopefully it showed you the basic way that things work. How you can follow the path from visting a URL to getting output and each of the steps used along the way as Django accesses the URLconf to find the view to use, the view to perform the logic, the models where the view gets the data it needs, and finally the templates that describes what gets displayed the page. This example was intentionally very light on the logic, but a more complicated view that did something like search would have all of that searching performed in the view and then send it's results to the template that would be in charge of listing it out.

From here you can probably start reading the project code and have a better idea of what, roughly, is going on. But what you really should do is head over to the Resources page and start going through a Django tutorial if you haven't already.

⚠️ **GitHub.com Fallback** ⚠️