Learn web development

Django Tutorial Part 5: Creating our home page

我们的志愿者还没有将这篇文章翻译为 中文 (简体)加入我们帮助完成翻译!
您也可以阅读此文章的English (US)版。

We're now ready to add the code to display our first full page — a home page for the LocalLibrary website that shows how many records we have of each model type and provides sidebar navigation links to our other pages. Along the way we'll gain practical experience in writing basic URL maps and views, getting records from the database, and using templates.

Prerequisites: Read the Django Introduction. Complete previous tutorial topics (including Django Tutorial Part 4: Django admin site).
Objective: To understand how to create simple url maps and views (where no data is encoded in the URL), and how to get data from models and create templates.

Overview

Now we have defined our models and created some initial library records to work with, it's time to write the code to present that information to users. The first thing we need to do is determine what information we want to be able to display in our pages, and then define appropriate URLs for returning those resources. Then we're going to need to create the url mapper, views, and templates to display those pages. 

The diagram below is provided as a reminder of the main flow of data and things that need to be implemented when handling an HTTP request/response. As we've already created the model, the main things we'll need to create are:

  • URL maps to forward the supported URLs (and any information encoded in the URLs) to the appropriate view functions.
  • View functions to get the requested data from the models, create an HTML page displaying the data, and return it to the user to view in the browser.
  • Templates used by the views to render the data.

As you'll see in the next section, we're going to have 5 pages to display, which is a lot to document in one article. Therefore, most of this article will concentrate on showing you how to implement just the home page (we'll move onto the other pages in a subsequent article). This should give you a good end-to-end understanding of how URL mappers, views, and models work in practice.

Defining the resource URLs

As this version of LocalLibrary is essentially read-only for end users, we just need to provide a landing page for the site (a home page), and pages that display list and detail views for books and authors. 

The URLs that we're going to need for our pages are:

  • catalog/ — The home/index page.
  • catalog/books/ — The list of all books.
  • catalog/authors/ — The list of all authors.
  • catalog/book/<id> — The detail view for the specific book with a field primary key of <id> (the default). So for example, /catalog/book/3, for the third book added.
  • catalog/author/<id> — The detail view for the specific author with a primary key field named <id>. So for example, /catalog/author/11, for the 11th author added.

The first three URLs are used to list the index, books, and authors. These don't encode any additional information, and while the results returned will depend on the content in the database, the queries run to get the information will always be the same.

By contrast the final two URLs are used to display detailed information about a specific book or author — these encode the identity of the item to display in the URL (shown as <id> above). The URL mapper can extract the encoded information and pass it to the view, which will then dynamically determine what information to get from the database. By encoding the information in our URL we only need one url mapping, view, and template to handle every book (or author). 

Note: Django allows you to construct your URLs any way you like — you can encode information in the body of the URL as shown above or use URL GET parameters (e.g. /book/?id=6). Whichever approach you use, the URLs should be kept clean, logical and readable (check out the W3C advice here).

The Django documentation tends to recommend encoding information in the body of the URL, a practice that they feel encourages better URL design.

As discussed in the overview, the rest of this article describes how we construct the index page.

Creating the index page

The first page we'll create will be the index page (catalog/). This will display a little static HTML, along with some calculated "counts" of different records in the database. To make this work we'll have to create an URL mapping, view and template. 

Note: It's worth paying a little extra attention in this section. Some of the material is common to all of the pages.

URL mapping

We created a basic /catalog/urls.py file for our catalog application when we created the skeleton website. The catalog application URLs were included into the project with a mapping of catalog/, so the URLs that get to this mapper must have started with catalog/ (the mapper is working on all strings in the URL after the forward slash).

Open urls.py and paste in the line shown in bold below. 

urlpatterns = [
    url(r'^$', views.index, name='index'),
]

This url() function defines a URL pattern (r'^$'), and a view function that will be called if the pattern is detected (views.index — a function named index() in views.py). The URL pattern is a Python regular expression (RE). We'll talk a bit more about REs further along in this tutorial, but for this case all you need to know is that an RE of ^$ will pattern match against an empty string (^ is a string start marker and $ is an end of string marker). 

Note: Note that in  /locallibrary/locallibrary/urls.py 

urlpatterns += [
    url(r'^catalog/', include('catalog.urls')),
]

The regular expressions in this case don’t have a $ (end-of-string match character) but do include a trailing slash. Whenever Django encounters include() (django.conf.urls.include()), it chops off whatever part of the URL matched up to that point and sends the remaining string to the included URLconf for further processing.

 The matched URL is actually catalog/ + <empty string> ( /catalog/ is assumed because include() was the method used). Our first view function will be called if we receive an HTTP request with a URL of /catalog/.

This url() function also specifies a name parameter, which uniquely identifies this particular URL mapping. You can use this name to "reverse" the mapper — to dynamically create a URL pointing to the resource the mapper is designed to handle. For example, with this in place we can now link to our home page by creating the following link in our template:

<a href="{% url 'index' %}">Home</a>.

Note: We could of course hard code the above link (e.g. <a href="/catalog/">Home</a>), but then if we changed the pattern for our home page (e.g. to /catalog/index) the templates would no longer link correctly. Using a reversed url mapping is much more flexible and robust!

View (function-based)

A view is a function that processes an HTTP request, fetches data from the database as needed, generates an HTML page by rendering this data using an HTML template, and then returns the HTML in an HTTP response to be shown to the user. The index view follows this model — it fetches information about how many Book, BookInstance, available BookInstance and Author records we have in the database, and passes them to a template for display.

Open catalog/views.py, and note that the file already imports the render() shortcut function which generates HTML files using a template and data. 

from django.shortcuts import render
# Create your views here.

Copy the following code at the bottom of the file. The first line imports the model classes that we will use to access data in all our views.

from .models import Book, Author, BookInstance, Genre
def index(request):
    """
    View function for home page of site.
    """
    # Generate counts of some of the main objects
    num_books=Book.objects.all().count()
    num_instances=BookInstance.objects.all().count()
    # Available books (status = 'a')
    num_instances_available=BookInstance.objects.filter(status__exact='a').count()
    num_authors=Author.objects.count()  # The 'all()' is implied by default.
    # Render the HTML template index.html with the data in the context variable
    return render(
        request,
        'index.html',
        context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors},
    )

The first part of the view function fetches counts of records using the objects.all() attribute on the model classes. It also gets a list of BookInstance objects that have a status field value of 'a' (Available). You can find out a little bit more about how to access from models in our previous tutorial (Django Tutorial Part 3: Using models > Searching for records).

At the end of the function we call the render() function to create and return an HTML page as a response (this shortcut function wraps a number of other functions, simplifying this very common use-case). This takes as parameters the original request object (an HttpRequest), an HTML template with placeholders for the data, and a context variable (a Python dictionary containing the data that will be inserted into those placeholders). 

We'll talk more about templates and the context variable in the next section; let's create our template so we can actually display something to the user!

Template

A template is a text file defining the structure or layout of a file (such as an HTML page), with placeholders used to represent actual content. Django will automatically look for templates in a directory named 'templates' in your application. So for example, in the index view we just added, the render() function will expect to be able to find the file /locallibrary/catalog/templates/index.html, and will raise an error if the file cannot be found. You can see this if you save the previous changes and go back to your browser — accessing 127.0.0.1:8000 will now give you a fairly intuitive error message "TemplateDoesNotExist at /catalog/", plus other details.

Note: Django will look in a number of places for templates, based on your project's settings file (searching in your installed applications is a default setting!). You can find out more about how Django finds templates and what template formats it supports in Templates (Django docs).

Extending templates

The index template is going to need standard HTML markup for the head and body, along with sections for navigation (to the other pages in the site that we haven't yet created) and for displaying some introductory text and our book data. Much of this text (the HTML and navigation structure) will be the same for every page on our site. Rather than forcing developers to duplicate this "boilerplate" in every page, the Django templating language allows you to declare a base template, and then extend it, replacing just the bits that are different for each specific page. 

For example, a base template base_generic.html might look like the text below. As you can see, this contains some "common" HTML and sections for title, sidebar and content marked up using named block and endblock template tags (shown in bold). The blocks can be empty, or contain content that will be used "by default" for derived pages.

Note: Template tags are like functions that you can use in a template to loop through lists, perform conditional operations based on the value of a variable, etc. In addition to template tags the template syntax allows you to reference template variables (that are passed into the template from the view) and use template filters, which reformat variables (for example, setting a string to lower case).

<!DOCTYPE html>
<html lang="en">
<head>
  {% block title %}<title>Local Library</title>{% endblock %}
</head>
<body>
  {% block sidebar %}<!-- insert default navigation text for every page -->{% endblock %}
  {% block content %}<!-- default content text (typically empty) -->{% endblock %}
</body>
</html>

When we want to define a template for a particular view, we first specify the base template  (with the extends template tag — see the next code listing). If there are any sections that we want to replace in the template we declare these, using identical block/endblock sections as used in the base template. 

For example, the code fragment below shows how we use the extends template tag, and override the content block. The final HTML produced would have all the HTML and structure defined in the base template (including the default content you've defined inside the title block), but with your new content block inserted in place of the detault one.

{% extends "base_generic.html" %}
{% block content %}
<h1>Local Library Home</h1>
<p>Welcome to <em>LocalLibrary</em>, a very basic Django website developed as a tutorial example on the Mozilla Developer Network.</p>
{% endblock %}

The LocalLibrary base template

The base template we plan to use for the LocalLibrary website is listed below. As you can see, this contains some HTML and defined blocks for title, sidebar, and content. We have a default title (which we may want to change) and a default sidebar with links to lists of all books and authors (which we will probably not want to change, but we've allowed scope to do so if needed by putting this in a block).

Note: We also introduce two additional template tags: url and load static. These are discussed in following sections.

Create a new file — /locallibrary/catalog/templates/base_generic.html — and give it the following contents:

<!DOCTYPE html>
<html lang="en">
<head>
  {% block title %}<title>Local Library</title>{% endblock %}
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  <!-- Add additional CSS in static file -->
  {% load static %}
  <link rel="stylesheet" href="{% static 'css/styles.css' %}">
</head>
<body>
  <div class="container-fluid">
    <div class="row">
      <div class="col-sm-2">
      {% block sidebar %}
      <ul class="sidebar-nav">
          <li><a href="{% url 'index' %}">Home</a></li>
          <li><a href="">All books</a></li>
          <li><a href="">All authors</a></li>
      </ul>
     {% endblock %}
      </div>
      <div class="col-sm-10 ">
      {% block content %}{% endblock %}
      </div>
    </div>
  </div>
</body>
</html>

The template uses (and includes) JavaScript and CSS from Bootstrap to improve the layout and presentation of the HTML page. Using Bootstrap or another client-side web framework is a quick way to create an attractive page that can scale well on different browser sizes, and it also allows us to deal with the page presentation without having to get into any of the details — we just want to focus on the server-side code here!

The base template also references a local css file (styles.css) that provides a little additional styling. Create /locallibrary/catalog/static/css/styles.css and give it the following content:

.sidebar-nav {
    margin-top: 20px;
    padding: 0;
    list-style: none;
}

The index template

Create the HTML file /locallibrary/catalog/templates/index.html and give it the content shown below. As you can see we extend our base template in the first line, and then replace the default content block with a new one for this template. 

{% extends "base_generic.html" %}
{% block content %}
<h1>Local Library Home</h1>
  <p>Welcome to <em>LocalLibrary</em>, a very basic Django website developed as a tutorial example on the Mozilla Developer Network.</p>
<h2>Dynamic content</h2>
  <p>The library has the following record counts:</p>
  <ul>
    <li><strong>Books:</strong> {{ num_books }}</li>
    <li><strong>Copies:</strong> {{ num_instances }}</li>
    <li><strong>Copies available:</strong> {{ num_instances_available }}</li>
    <li><strong>Authors:</strong> {{ num_authors }}</li>
  </ul>
{% endblock %}

In the Dynamic content section we've declared placeholders (template variables) for the information we wanted to include from the view.  The variables are marked using the "double brace" or "handlebars" syntax (see in bold above).

Note: You can easily recognise whether you're dealling with template variables or template tags (functions) because the variables have double braces ({{ num_books }}) while the tags are enclosed in single braces with percentage signs ({% extends "base_generic.html" %}).

The important thing to note here is that these variables are named with the keys that we passed into the context dictionary in our view's render() function (see below); these will be replaced by their associated values when the template is rendered.  

return render(
    request,
    'index.html',
     context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors},
)

Referencing static files in templates

Your project is likely to use static resources, including JavaScript, CSS, and images. Because the location of these files might not be known (or might change), Django allows you to specify the location of these files in your templates relative to the STATIC_URL global setting (the default skeleton website sets the value of STATIC_URL to '/static/', but you might choose to host these on a content delivery network or elsewhere).

Within the template you first call the load template tag specifying "static" to add this template library (as shown below). After static is loaded, you can then use the static template tag, specifying the relative URL to the file of interest.

 <!-- Add additional CSS in static file -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/styles.css' %}">

You could if desired add an image into the page in the same sort of fashion. For example:

{% load static %}
<img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="My image" style="width:555px;height:540px;"/>

Note: The changes above specify where the files are located, but Django does not serve them by default. While we enabled serving by the development web server in the global URL mapper (/locallibrary/locallibrary/urls.py) when we created the website skeleton, you will still need to arrange for them to be served in production. We'll look at this later.

For more information on working with static files see Managing static files (Django docs).

Linking to URLs

The base template above introduced the url template tag.

<li><a href="{% url 'index' %}">Home</a></li>

This tag takes the name of a url() function called in your urls.py and values for any arguments the associated view will receive from that function, and returns a URL that you can use to link to the resource.

What does it look like?

At this point we should have created everything needed to display the index page. Run the server (python3 manage.py runserver) and open your browser to http://127.0.0.1:8000/. If everything is set up correctly, your site should look something like the following screenshot.

Index page for LocalLibrary website

Note: You won't be able to use the All books and All authors links yet, because the urls, views, and templates for those pages haven't been defined (currently we've just inserted placeholders for those links in the base_generic.html template).

Challenge yourself

Here are a couple of tasks to test your familiarity with model queries, views and templates. 

  1. Declare a new title block in the index template and change the page title to match this particular page.
  2. Modify the view to generate a count of genres and a count of books that contain a particular word (case insensitive) and then add these fields to the template.

Summary

We've now created the home page for our site — an HTML page that displays some counts of records from the database and has links to our other still-to-be-created pages. Along the way we've learned a lot of fundamental information about url mappers, views, querying the database using our models, how to pass information to a template from your view, and how to create and extend templates.

In our next article we'll build on our knowledge to create the other four pages.

See also

文档标签和贡献者