Django for Designers/Basic views

From OpenHatch wiki
Jump to navigation Jump to search

Part 2: URLs, basic views, templates, static files[edit]

Time: 20 minutes

Note for tutorial attendees[edit]

At the end of each section of the tutorial, you will switch to a new branch based on the work from the previous part. This will make it so that everyone starts out each section with the same working code, so even if you get stuck or confused in one session, you can still keep up. If you don't have a lot of git experience, but you want to return to your work from a previous part, a TA can show you how to return to the code from a previous part and ask questions about it.

Remember that *you* shouldn't be putting your modifications inside the master branch, though! Instead, make a new branch for section 2 based off the master code you just downloaded, and let's get going!

# in django-for-designers/myproject
$ git branch my-branch-2 origin/pre-part-2
$ git checkout my-branch-2

M-V-C separation concept[edit]

When you write a Django app, your code is organized in a fashion reminiscent of a well-known design pattern known as MVC, short for model, view, controller. Django's precise organization methodology is based on models, views, and templates.

Django architecture flowchart

For Django, a model represents how the data is stored. This is the part of your app that ties your data to storage in a database. You write Python code, and Django converts it and out of into SQL, the language spoken by databases.

A view in Django handles a request from the website visitor for a web page. What you do with this request is up to you, and is the domain of the view code that you write. For example, when a request comes in, you could start a background process to send a fax to Tokyo indicating how happy you are that someone came to your website. Or you could provide a HTTP response to the website visitor with a list of recently bookmarked web pages if you are providing a bookmarking app.

The templates in Django control how data is presented. Typically, a view provides a collection of data to the template. The template then might loop over that information, wrap it in HTML bulleted lists, wrap that in a standard layout for all pages across your site, and serve that out to the site visitor. The templates usually refer to some static content, such as CSS, that makes the site actually seem designed!

Keeping this separation in mind is essential to feeling at home when using Django. (And since most web programming frameworks work via a similar separation, this mindset is good to keep in mind generally.)

Writing our URLs[edit]

The first step to writing your application is to design your URL structure. You do this by creating a Python module called a URLconf. URLconfs are how Django associates a given URL with given Python code.

When a user requests a Django-powered page, the system looks at the ROOT_URLCONF setting, which contains a string in Python dotted syntax. Django loads that module and looks for a module-level variable called urlpatterns, which is a sequence of Python tuples in the following format: (regular expression, Python callback function [, optional dictionary])

Django starts at the first regular expression and makes its way down the list, comparing the requested URL against each regular expression until it finds one that matches.

You might ask, “What’s a regular expression?” Regular expressions, or "regexes", are patterns for matching text. In this case, we’re matching the URLs people go to, and using regular expressions to match whole ‘groups’ of them at once.

(If you’d like to learn more about regular expressions, read the Dive into Python guide to regular expressions sometime. Or you can look at this xkcd. Regexpal is also a helpful tool for writing a regex.)

In addition to matching text, regular expressions can capture text. Capturing means to remember that part of the string, for later use. Regexes use parentheses () to wrap the parts they’re capturing. For Django, when a regular expression matches the URL that a web surfer requests, Django extracts the captured values (if any) and passes them to a function of your choosing. This is the role of the callback function above. When a regular expression matches the url, Django calls the associated callback function with any captured parts as parameters. These callback functions typically live in one of your apps' views.py, so this will be much clearer after the next section.

When we ran

django-admin.py startproject myproject

to create our project way back when, Django created a default URLconf file called 'urls.py' inside myproject/.

To write our app's URLs, edit the file myproject/urls.py so it looks like this:

urlpatterns = patterns('',
    url(r'^$', 'bookmarks.views.index', name='home'),
    url(r'^bookmarks/$', 'bookmarks.views.index', name='bookmarks_view'),
    url(r'^tags/([\w-]+)/$', 'bookmarks.views.tag'),
)

Suppose a visitor to your site goes to (if you view the page, don't worry about the ViewDoesNotExist message, we'll get to that in a minute) http://localhost:8000/tags/awesome/.

  • which regex pattern is tripped?
  • what function is then called?
  • what arguments is that function called with?

Save and commit your work.

The idea that a URL doesn’t have to map onto a file or a folder, or some other sort of static resource, is quite powerful. The URL is just a way of giving instructions to some server, somewhere. Note that in this example, both / and /bookmarks/ go to the same place -- they both activate bookmarks.views.index! (Rant: In Django, as in most modern frameworks, you have total control over the way your URLs look. People on the web won’t see cruft like .py or .php or even .html at the end of your URLs. There is no excuse for that kind of stuff in the modern era! (Though, putting .php on the end of all your Django URLs, while pointless, is kind of hilarious.))

Handling those URLs with some basic views[edit]

Start the development server:

# in django-for-designers/myproject
$ python manage.py runserver

Fetch “http://localhost:8000/bookmarks/” in your browser. You should get a pleasantly-colored error page with the following message:

ViewDoesNotExist at /bookmarks/

What's going on? Recall this line:

url(r'^$', 'bookmarks.views.index', name='home')

If we look in the bookmarks folder, we can see there's a views.py file... but if we look in that file, it's empty!

So, our problem is that the URL parsing is going fine, but there is no one listening at the other end of the phone! This ViewDoesNotExist error happened because you haven’t written a function index() in the module bookmarks/views.py. Well, I guess we should do that!

Let's write some views. Open bookmarks/views.py and put the following Python code in it:

from django.http import HttpResponse
 
def index(request):
    return HttpResponse("Hello, world. You're at the bookmarks index.")

This is a very simple view.

Save the views.py file, then go to http://localhost:8000/bookmarks/ in your browser, and you should see that text.

Add a tag view by adding to the views.py file. This view is slightly different, because it takes an argument (which, remember, is passed in from whatever was captured by the regular expression in the URLconf):

def tag(request, tag_name):
    return HttpResponse("This is a tag: %s" % (tag_name,))

Save views.py.

Navigate to http://localhost:8000/tags/awesome/ . It’ll run the tag() function and display whatever tag name you provide in the URL.

Add a little html to the ‘question’ view. For example, wrap the question_id in strong tags and verify that the view is indeed bold!

return HttpResponse("This is a tag: <strong>%s</strong>" % (tag_name,))

Add and commit your code. Remember to write a good commit message that explains what changed. (For examples of how not to write git commits, see http://www.commitlogsfromlastnight.com/.)

Handling URLs with templates[edit]

These placeholder responses are okay, but you wouldn't want to make a real webpage in a giant Python string in the middle of a view function! Fortunately, Django comes with a built-in template renderer! Let's edit views.py. First, at the top, edit the imports to add one fresh import:

from django.shortcuts import render

Then, modify the index function so it looks like this:

def index(request):
    context = {}
    return render(request, 'index.html', context)

render is a shortcut function that takes a request, a relative path to a template file, and an optional dictionary of contextual data and, instead of returning "Hello world" like before, returns a rendered web page.

Let's spin up our development web server and see our new view in action!

# in django-for-designers/myproject
$ python manage.py runserver

http://localhost:8000/

TemplateDoesNotExist at /

Oops. We get an error because we haven't actually made our index.html template file yet. Let's do that.

First, we'll make a templates folder inside our bookmarks app:

# in django-for-designers/myproject
$ mkdir bookmarks/templates

You can put your templates anywhere and tell Django where to look for them in settings.py. By default, though, Django will look inside each of your apps for a templates folder.

Now let's make a file called index.html inside our templates folder:

<!doctype html>
<html>
<head>
    <title>My bookmarking app</title>
</head>

<body>
    <div id="header">
        <h1>My bookmarking app</h1>
    </div>
    <div id="content">
        <h2>All bookmarks</h2>
        <ul>
            <li>Bookmark</li>
            <li>Another bookmark</li>
            <li>A third bookmark</li>
        </ul>
    </div>
    <div id="footer">
        Brought to you by PyCon US 2013
    </div>
</body>
</html>

Now if you go to http://localhost:8000, you should see the webpage we made in our template file!

If you do not, you may need to restart your development server before it "sees" your template.

Save and commit your new template and template-based view.

Django templates 101[edit]

Let's rewrite the tags view to use a template as well.

def tag(request, tag_name):
    context = {
        'tag': tag_name,
    }
    return render(request, 'tag.html', context)

Our tag view gets fed in a variable we can use -- the name of the tag, so we add that to the context. That makes it available to the template to render.

Let's make our tag template. To do that, create a new file called tag.html in the same directory as index.html that you just created.

<!doctype html>
<html>
<head>
    <title>My bookmarking app</title>
</head>

<body>
    <div id="header">
        <h1>My bookmarking app</h1>
    </div>
    <div id="content">
        <h2>Bookmarks tagged {{ tag }}</h2>
        <ul>
            <li>Bookmark</li>
            <li>Another bookmark</li>
            <li>A third bookmark</li>
        </ul>
    </div>
    <div id="footer">
        Brought to you by PyCon US 2013
    </div>
</body>
</html>

In Django's templating language, to put a variable into the template, put it inside double {⁠{ brackets }}. If you look at http://localhost:8000/tags/awesome/, you'll now see "awesome" (or whatever tag name you choose) in the space occupied by {⁠{ tag }}.

You'll notice--these two templates share an awful lot of code. Repeating yourself in this way is no fun, and a recipe for messy, inconsistent styling. Fortunately, Django templates support inheritance!

Let's make a base.html template. This template isn't rendered by any view, but all (or most) of our templates can inherit from it:

<!doctype html>
<html>
<head>
    <title>My bookmarking app</title>
</head>

<body>
    <div id="header">
        <h1>My bookmarking app</h1>
    </div>
    <div id="content">
        <h2>{% block subheader %}{% endblock %}</h2>
        {% block content %}
        Sample content -- you should never see this, unless an inheriting template fails to have any content block!
        {% endblock %}
    </div>
    <div id="footer">
        Brought to you by PyCon US 2013
    </div>
</body>
</html>

This file contains the generic html layout we want for our site. It also includes two {% block %} tags, which are places where templates that inherit from this template can insert their own special HTML. Here we have a {% block %} for the subheader, and a block for the page content.

Let's make index.html inherit from this template:

{% extends 'base.html' %}

{% block subheader %}All bookmarks{% endblock %}

{% block content %}
<ul>
    <li>Bookmark</li>
    <li>Another bookmark</li>
    <li>A third bookmark</li>
</ul>
{% endblock %}

And tag.html:

{% extends 'base.html' %}

{% block subheader %}Bookmarks tagged {{ tag }}{% endblock %}

{% block content %}
<ul>
    <li>Bookmark</li>
    <li>Another bookmark</li>
    <li>A third bookmark</li>
</ul>
{% endblock %}

The {% extends %} tag tells the template renderer which template this template file inherits from. Each block tag then specifies the content that ought to go in each block.

There are lots of built-in template tags and filters; we'll see a few more later. The full docs on them live here.

Reload your webserver (if necessary) and take a look at http://localhost:8000 and http://localhost:8000/tags/awesome/ . They're both based on templates now -- and based on the same base template, so any changes we make to base.html will appear in both automatically!

As a rule, in Django, things in {% %} control the "logic" of the template, while things in {⁠{ }} actually stick data into the template in particular places.

Static files[edit]

Now we know how to create HTML within Django's templating language. For a good web application, though, you will probably want to embed stylesheets, scripts, images, and other files.

How can we include static files such as these?

Move outside your myproject/ directory and move the contents of the sample-static-files/ folder that came with your git repo into myproject/bookmarks/.

# in django-for-designers/myproject
$ cd ..
# in django-for-designers
$ mkdir myproject/bookmarks/static
$ git mv sample-static-files/* myproject/bookmarks/static/

Django automatically looks for a folder named 'static' inside any of your Django apps. You can also put static files in other places, such as inside myproject/ directly. To do that, you would need to add the absolute path of the folder to STATICFILES_DIRS in settings.py.

For this tutorial we'll just leave our static files inside bookmarks/, for the sake of simplicity.

For consistency, now is a good time to return to the directory we used to be in:

# in django-for-designers
$ cd myproject
# in django-for-designers/myproject
$ ls

Make sure you see manage.py, and then let's continue.

Now let's add some more structure to our HTML templates to make them easier to style. First, base.html:

<!doctype html>
<html>
<head>
    <title>My bookmarking app</title>
    <link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400,700,900' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" href="/static/css/style.css" type="text/css" media="screen" charset="utf-8">
</head>

<body>
    <div id="container">
        <div id="header">
            {% block bookmark_widget %}
            {% endblock %}
            <h1><a href="/">My bookmarking app</a></h1>
        </div>
        <div id="content">
            <h2>{% block subheader %}{% endblock %}</h2>
            {% block content %}
            Sample content -- you should never see this, unless an inheriting template fails to have any content block!
            {% endblock %}
        </div>
        <div id="footer">
            Brought to you by PyCon US 2013
        </div>
    </div>
</body>
</html>

And also index.html:

{% block content %}
<ul class="bookmarks">
    <li>
        <a class="bookmark-link" href="">Bookmark</a>
        <div class="metadata"><span class="author">Posted by Jane Smith</span> | <span class="timestamp">2012-2-29</span> | <span class="tags"><a href="">funny</a> <a href="">haha</a></span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">Another bookmark</a>
        <div class="metadata"><span class="author">Posted by Jeanne Doe</span> | <span class="timestamp">2012-2-28</span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">A third bookmark</a>
        <div class="metadata"><span class="author">Posted by Jayne Cobb</span> | <span class="timestamp">2012-2-28</span> | <span class="tags"><a href="">funny</a></span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">La la la bookmark</a>
        <div class="metadata"><span class="author">Posted by Jeanne Doe</span> | <span class="timestamp">2012-2-28</span> | <span class="tags"><a href="">blah</a> <a href="">school</a></span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">Rhubarb rhubarb bookmark</a>
        <div class="metadata"><span class="author">Posted by Jeanne Doe</span> | <span class="timestamp">2012-2-27</span></div>
    </li>
</ul>
{% endblock %}

{% block bookmark_widget %}
<div id="new-bookmark-widget">
    <form method="post">
        <h3>Bookmark</h3>
        <p><label>URL</label> <input></p>
        <p><label>Title</label> <input></p>
        <p><label>Tags (comma-separated)</label> <input></p>
        <p><button id="new-bookmark-submit">Submit</button>Submit</button>
    </form>
</div>
{% endblock %}

and tag.html:

{% block content %}
<ul class="bookmarks">
    <li>
        <a class="bookmark-link" href="">Bookmark</a>
        <div class="metadata"><span class="author">Posted by Jane Smith</span> | <span class="timestamp">2012-2-29</span> | <span class="tags"><a href="">funny</a> <a href="">haha</a></span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">Another bookmark</a>
        <div class="metadata"><span class="author">Posted by Jeanne Doe</span> | <span class="timestamp">2012-2-28</span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">A third bookmark</a>
        <div class="metadata"><span class="author">Posted by Jayne Cobb</span> | <span class="timestamp">2012-2-28</span> | <span class="tags"><a href="">funny</a></span></div>
    </li>
</ul>
{% endblock %}

Now if we reload our site at http://localhost:8000, we can see that the prewritten static files have styled it some, and it looks significantly nicer than it did before!

Save and commit your work!

Next page