Django Template System Basics

A Django template is a text file. While in the vast majority of cases this text file is an HTML file, Django templates can also be non-HTML files. Non-HTML examples include email templates and CSV templates.

To turn a plain text file into a Django template, the template designer adds template tags, variables and filters.

A template tag is surrounded by {% and %}. A template tag does something. This is deliberately vague because Django’s tags are extremely flexible. Some example functions performed by template tags are:

  • Display Logic. E.g., {% if %}...{% endif %}
  • Loop Control. E.g., {% for x in y %}...{% endfor %}
  • Block Declaration. E.g., {% block content %}...{% endblock %}
  • Content Import. E.g., {% include "header.html" %}
  • Inheritance. E.g., {% extends "base.html" %}

It’s also possible to create custom template tags to extend the DTL.

A template variable is surrounded by {{ and }}. A template variable is something. Template variables are passed to the template at runtime in the context. We’ll dig deeper into template contexts shortly.

Template variables don’t just handle simple data, they work with more complex data structures too. For example:

  • Simple Variables. E.g., {{ title }}
  • Object Attributes. E.g., {{ page.title }}
  • Dictionary Lookups. E.g., {{ dict.key }}
  • List Indexes. E.g., {{ list_items.0 }}
  • Method Calls. E.g., {{ var.upper }}, {{ mydict.pop }}

With few exceptions, the methods and attributes available to the Python object are also accessible in the template via the dot operator.

Filters modify a variable for display. You apply a filter to a variable using the | (pipe) character. There are dozens of built-in filters; here are some examples:

  • Change Case. E.g., {{ name|title }} or {{ units|lower }}
  • Truncation. E.g., {{ post_content|truncatewords:50 }}
  • Date Formatting. E.g., {{ order_date|date:"D M Y" }}
  • List Slicing. E.g., {{ list_items|slice:":3" }}
  • Default Values. E.g., {{ item_total|default:"nil" }}

This is a sample of the template tags, variable methods and filters available in Django. We’ll be covering all the most common elements of the DTL in more detail as we work through the book. Chapter 11 includes an exercise covering all common tags and filters, complete with example implementations of each.

How Django Finds Templates

When startproject created your Django site, it added a TEMPLATES setting to your settings.py file that looks like this:

  1. 1 TEMPLATES = [
  2. 2 {
  3. 3 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  4. 4 'DIRS': [],
  5. 5 'APP_DIRS': True,
  6. 6 'OPTIONS': {
  7. 7 'context_processors': [
  8. 8 'django.template.context_processors.debug',
  9. 9 'django.template.context_processors.request',
  10. 10 'django.contrib.auth.context_processors.auth',
  11. 11 'django.contrib.messages.context_processors.messages',
  12. 12 ],
  13. 13 },
  14. 14 },
  15. 15 ]

The most important line to note here is line 5. When APP_DIRS is True, the Django template engine will look for templates in a folder called “templates” in each app listed in INSTALLED_APPS.

Django doesn’t create the folder for you, so let’s create one for our events app. Once you have added the folder, your directory tree should look like this:

  1. \events
  2. \migrations
  3. \templates
  4. __init.py__
  5. # ...

Following on from this, you might think adding templates to your project should be as simple as adding template files to your \templates folder. In a single app project, you can get away with this, but it’s not recommended.

Why? Because Django uses short-circuit logic when searching for templates. This can be a problem when you have two apps in a project with a template with the same name.

Say you have created an index.html template for your events app and you add a 3rd party app that also uses a template called index.html. Your folder structure will look like this:

  1. \events
  2. \templates
  3. index.html
  4. \other_app
  5. \templates
  6. index.html

When you tell Django to load the template index.html it will load the first file it finds (based on the order of your apps in the INSTALLED_APPS setting). So, if you want to load the index template for other_app, but the events app is listed first in INSTALLED_APPS, Django will load the wrong template.

We solve this problem by namespacing our templates. Namespacing templates is simple—we add a folder named after the app to our templates folder. This is what the above example looks like after namespacing the templates:

  1. \events
  2. \templates
  3. \events
  4. index.html
  5. \other_app
  6. \templates
  7. \other_app
  8. index.html

Now, when you want to load a template, you include the namespace (“events/index.html” or “other_app/index.html” in this example), and Django will always load the correct template.

As with most things in Django, namespacing templates is a convention, not a hard and fast rule. But, if you want maximum portability for your apps and to avoid some headaches later on, it is a convention that you would do well to follow.

Before moving on, add the new folder to your events app. When you’re done, the folder structure will look like this:

  1. \events
  2. \migrations
  3. \templates
  4. \events
  5. __init.py__
  6. # ...

Creating a Site Template

All modern websites have a site template that creates a common look and branding for every page on the website.

The most common place for storing site template files in Django is in the website app that Django created automatically for you when you ran startproject. Django didn’t create the \templates folder for you, so create it now. When you’re finished, your folder structure should look like this:

  1. \myclub_site
  2. \templates
  3. __init__.py
  4. ...

As your website app is not in INSTALLED_APPS, Django won’t automatically look for templates in the \myclub_site\templates folder, you must tell Django where to look by adding a path to the DIRS setting. Let’s modify settings.py (changes in bold):

  1. 1 TEMPLATES = [
  2. 2 {
  3. 3 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  4. 4 'DIRS': [os.path.join(BASE_DIR, 'myclub_site/templates')],
  5. 5 'APP_DIRS': True,
  6. 6 # ...

Line 4 looks complicated, but is easy to understand—os.path.join is a Python command to create a file path by joining strings together (concatenating). In this example, we are joining myclub_site/templates to our project directory to create the full path to our templates directory, i.e., <your project path>/myclub_root/myclub_site/templates.

The DIRS list is not just for letting Django know where your site templates are—it’s useful for listing any template resources that exist outside of your existing apps. Note that Django will search your DIRS list in the order listed, so keep in mind my previous warnings about templates with the same name when linking to external resources.

Path Settings Change in Django 3.1!

From Django 3.1, the Django setting.py file replaces the os module from Python with pathlib.

So:

  1. import os

Is now:

  1. from pathlib import Path

For existing projects, this will not affect the operation of your code. If you start a new project with Django 3.1, however, the code in my books, courses and website will break on the DIRS and STATICFILES_DIRS settings.

You have two options to resolve this problem. The simplest is to add import os to your Django 3.1 settings file.

The second option is to change your settings to use pathlib. This is the best option, as Django will use pathlib from 3.1 onwards, so you might as well get used to the change.

You need to make two changes.

Change:

  1. 'DIRS': [os.path.join(BASE_DIR, 'myclub_site/templates')],

in your TEMPLATES setting to:

  1. 'DIRS': [BASE_DIR / 'myclub_site/templates'],

And change:

  1. STATICFILES_DIRS = [
  2. os.path.join(BASE_DIR, 'myclub_site/static'),
  3. ]

to:

  1. STATICFILES_DIRS = [BASE_DIR / 'myclub_site/static')]

Now we have the template folder created and the folder path listed so Django can find our site template, it’s time to create a simple template. We will name this file base.html (new file):

  1. # \myclub_site\templates\base.html
  2. 1 <!doctype html>
  3. 2 <html>
  4. 3 <head>
  5. 4 <meta charset="utf-8">
  6. 5 <title>Basic Site Template</title>
  7. 6 </head>
  8. 7
  9. 8 <body>
  10. 9 <h1>{{ title }}</h1>
  11. 10 <p>{{ cal }}</p>
  12. 11 </body>
  13. 12 </html>

This is plain HTML except for lines 9 and 10. In line 9, we’ve created a Django variable tag and named it title, and in line 10, we’ve created another variable tag and named it cal. If you remember from the view we created in the last chapter, these are the same variable names we gave the event title and calendar respectively.

Displaying a Template

Now we’ve created the template, we need to tell Django to use our new base template when displaying content on the site. This is done in your views.py file. Modify the index view as follows (changes in bold):

  1. # \events\views.py
  2. 1 from django.shortcuts import render
  3. 2 from django.http import HttpResponse
  4. 3 from datetime import date
  5. 4 import calendar
  6. 5 from calendar import HTMLCalendar
  7. 6
  8. 7
  9. 8 def index(request, year=date.today().year, month=date.today().month):
  10. 9 year = int(year)
  11. 10 month = int(month)
  12. 11 if year < 1900 or year > 2099: year = date.today().year
  13. 12 month_name = calendar.month_name[month]
  14. 13 title = "MyClub Event Calendar - %s %s" % (month_name, year)
  15. 14 cal = HTMLCalendar().formatmonth(year, month)
  16. 15 # return HttpResponse("<h1>%s</h1><p>%s</p>" % (title, cal))
  17. 16 return render(request, 'base.html', {'title': title, 'cal': cal})

For our new view, we have replaced the call to HttpResponse() with a call to render(). I have commented out the original HttpResponse (line 15) so you can more easily see the changes. You don’t have to remove the HttpResponse import from django.http (line 2) as we’re going to use it in a later chapter.

render() is a special Django helper function that creates a shortcut for communicating with a web browser. If you remember from Chapter 5, when Django receives a request from a browser, it finds the right view, and the view returns a response to the browser.

In the example from Chapter 5, we simply returned some HTML text. However, when we wish to use a template, Django first must load the template, create a context—which is a dictionary of variables and associated data passed back to the browser—and return an HttpResponse.

You can code each of these steps separately in Django, but in the majority of cases, it’s more common (and easier) to use Django’s render() function, which provides a shortcut that executes all three steps in a single function. Using render() is so common that Django added it to the views.py file for you when startapp created the events app (line 1).

When you supply the original request, the template and a context to render(), it returns the formatted response without you having to code the intermediate steps.

In our modified views.py (line 16), we are returning the original request object from the browser, the name of our site template and a dictionary (the context) containing our title and cal variables from the view.

Once you have modified your views.py file, save it and fire up the development server. If you navigate to http://127.0.0.1:8000/, you should see your simple new site template (Figure 6-1).

Django Template System Basics - 图1

Figure 6-1: The unformatted base template.

Hmm. Something isn’t quite right—the calendar is rendering as plain text, not as HTML. If you look at the page source, you can see why:

  1. ~~ snip
  2. <p>&lt;table border=&quot;0&quot; cellpadding=&quot;0&quot;
  3. cellspacing=&quot;0&quot; class=&quot;month&quot;&gt;&lt;tr&gt;
  4. &lt;th colspan=&quot;7&quot; class=&quot;month&quot;
  5. &gt;May 2019&lt;/th&gt;&lt;/tr&gt; ...

All the HTML codes are escaped!

This is because, by default, Django autoescapes all code before sending it to the browser. This is a built-in security feature designed to prevent a hacker inserting malicious code into your site code.

To get Django to render the HTML correctly, you must turn autoescape off for the calendar code. As this is a common task, the Django developers created the autoescape tag to make life easy for you. Make the following changes to your base.html file (changes in bold):

  1. # \myclub_site\templates\base.html
  2. 1 <!doctype html>
  3. 2 <html>
  4. 3 <head>
  5. 4 <meta charset="utf-8">
  6. 5 <title>Basic Site Template</title>
  7. 6 </head>
  8. 7
  9. 8 <body>
  10. 9 <h1>{{ title }}</h1>
  11. 10 <p>{% autoescape off %}{{ cal }}{% endautoescape %}</p>
  12. 11 </body>
  13. 12 </html>

Now, when you refresh your browser, the site homepage should look like Figure 6-2.

Django Template System Basics - 图2

Figure 6-2: The site template rendered with autoescape off for the calendar code.

Template Inheritance

While our base template is rendering fine in the browser, it’s not an effective site template because it’s tied to the events app. A site template needs to be independent of all the other apps in your Django project.

This is where Django’s template inheritance comes in handy.

With Django’s template inheritance, you create a parent template containing content shared by every page on the website and child templates that inherit these shared features from the parent. Child templates can then add content and formatting unique to the child.

This is easier to understand in practice. First, modify your base.html file as follows (changes in bold):

  1. # \myclub_site\templates\base.html
  2. 1 <!doctype html>
  3. 2 <html>
  4. 3 <head>
  5. 4 <meta charset="utf-8">
  6. 5 <title>
  7. 6 {% block title %}
  8. 7 {{ page_title|default:"Untitled Page" }}
  9. 8 {% endblock title %}
  10. 9 </title>
  11. 10 </head>
  12. 11
  13. 12 <body>
  14. 13 {% block content %}
  15. 14 <p>Placeholder text in base template. Replace with page content.</p>
  16. 15 {% endblock content %}
  17. 16 </body>
  18. 17 </html>

Let’s have a closer look at what’s changed:

  • Lines 6 and 8. I have added a pair of Django block tags. The block tag defines a block of text other templates can replace. I’ve named the block title.
  • Line 7. I’ve created a new template variable called page_title. I have also added a default filter to the variable. If a view passes a value for page_title in the context, the template will render the page_title variable in the page title; otherwise, it will render “Untitled Page”.
  • Lines 13 and 15. Another pair of Django block tags to define a replaceable block for the page content.
  • Line 14 is placeholder text to render when a child template has not replaced the content block.

If you fire up the development server again and navigate to http://127.0.0.1:8000, you will see that Django now renders your base template (Figure 6-3). Note we didn’t pass a value for page_title to the template, so the page title shows the default.

Django Template System Basics - 图3

Figure 6-3: The site base template with no content.

Next, we will create a child template for the events calendar that inherits common content from the base template and then adds new content unique to the event calendar. Create a new file called calendar_base.html in your events\templates\events folder and add the following (new file):

  1. # \events\templates\events\calendar_base.html
  2. 1 {% extends 'base.html' %}
  3. 2
  4. 3 {% block title %}{{ title }}{% endblock title %}
  5. 4
  6. 5 {% block content %}
  7. 6 <h1>{{ title }}</h1>
  8. 7 <p>{% autoescape off %}{{ cal }}{% endautoescape %}</p>
  9. 8 {% endblock content %}

Let’s have a look at this file to see what’s going on:

  • Line 1. The {% extends %} template tag is where Django’s template inheritance magic happens. When you add the {% extends %} tag to a template file, you are telling Django to load all the content from the parent template (base.html). All you have to do in the child is define what blocks the child replaces and add any additional HTML and code unique to the child.
  • Line 3. We replace the title block tag from base.html with a new block that will contain the title variable from the index view.
  • Lines 5 and 8. We’re replacing the content block from base.html with a new content block.
  • Lines 6 and 7. These are the same lines that were originally in the base template—they’ve been moved to calendar_base.html, so the site template is not tied to the events app.

To display the new child template in the browser, we must modify the index view to load the new template (change in bold):

  1. # \events\views.py
  2. # ...
  3. 1 def index(request, year=date.today().year, month=date.today().month):
  4. 2 year = int(year)
  5. 3 month = int(month)
  6. 4 if year < 2000 or year > 2099: year = date.today().year
  7. 5 month_name = calendar.month_name[month]
  8. 6 title = "MyClub Event Calendar - %s %s" % (month_name,year)
  9. 7 cal = HTMLCalendar().formatmonth(year, month)
  10. 8 return render(request,
  11. 9 'events/calendar_base.html',
  12. 10 {'title': title, 'cal': cal}
  13. 11 )

Only one change: in line 9, I’ve replaced the base.html template with the calendar_base.html template. Note the namespacing on the template to ensure Django always selects the right template. I have also reformatted the render() function to shorten the line length, but the function remains the same otherwise.

Refresh your browser, and the site should look like Figure 6-4. Note the page title and the content have changed.

Django Template System Basics - 图4

Figure 6-4: The events calendar is now a child of the site template, and shows the correct title and page content.

Displaying Database Data

So far, we’ve been showing dynamic data generated by Python (dates and a calendar). Displaying formatted database data in a browser is also essential in a modern web application. In the previous example, we created an HTML calendar, added it to the context in the view and used a template to display the formatted calendar at a URL set by a URLconf.

We follow exactly the same process for rendering database data to the browser, except we’re adding a QuerySet to the context, not HTML. Let’s start by adding a simple view to our views.py file (new code):

  1. # myclub_root\events\views.py
  2. # Add this import to the top of your file
  3. from .models import Event
  4. # ...
  5. 1 def all_events(request):
  6. 2 event_list = Event.objects.all()
  7. 3 return render(request,
  8. 4 'events/event_list.html',
  9. 5 {'event_list': event_list}
  10. 6 )

You can see from this view, it takes very little code to render model data to the browser. After importing the Event model from models.py, the view simply retrieves a QuerySet containing all the event records (line 2) and then renders it to a template with the QuerySet added to the context (line 5).

While we’re retrieving all events with the all() function in this example, all the model filtering, sorting and slicing functions we covered in Chapter 4 are available in the view, so you have a great deal of flexibility to decide what records need to be displayed.

For the event records to display in the browser, we still need to add a template and a URLconf. Let’s start with adding a template to our events app (new file):

  1. # \events\templates\events\event_list.html
  2. 1 {% extends "base.html" %}
  3. 2
  4. 3 {% block title %}Displaying Model Data{% endblock title %}
  5. 4
  6. 5 {% block content %}
  7. 6 <h1>All Events in Database</h1>
  8. 7 <ul>
  9. 8 {% for event in event_list %}
  10. 9 <li>{{ event.name }}</li>
  11. 10 {% endfor %}
  12. 11 </ul>
  13. 12 {% endblock content %}

This template is not a lot different than the calendar base template, so should be easy to follow. The magic happens in lines 8, 9 and 10. Lines 8 and 10 are a simple {% for %}/{% endfor %} set of template tags for creating a for loop in the template. We’ll cover for loops in templates in more detail in Chapter 11, but for the moment, just remember they behave exactly the same as a Python for loop.

Line 9 displays the name attribute of each event record in the QuerySet as the for loop iterates over the data. In this template, we’re showing the event names in a bulleted list.

Finally, we add a URLconf so we can navigate to the event list in a browser (change in bold):

  1. # myclub_root\events\urls.py
  2. # ...
  3. urlpatterns = [
  4. path('', views.index, name='index'),
  5. path('events/', views.all_events, name='show-events'),
  6. # ...

A simple change—our event list template will show at the URL /events/. Navigate to http://127.0.0.1:8000/events/ in your browser, and the page should look like Figure 6.5.

Django Template System Basics - 图5

Figure 6-5: Rendering database data in a browser with a Django view and template.

Loading Static Files

The basic site template and event calendar are functioning OK, but they’re not very pretty—the site lacks a stylesheet, images and other niceties that make up a professional website.

Django treats static files—images, CSS and JavaScript—different than templates. Django’s creators wanted it to be fast and scalable, so right from the beginning, Django was designed to make it easy to serve static media from a different server to the one the main Django application was running on.

Django achieves speed and scalability by keeping static media in a different directory to the rest of the application. This directory is defined in the settings.py file and is called “static” by default:

  1. STATIC_URL = '/static/'

This line should be at or near the end of your settings.py file. We need to add another setting so Django can find the static files for our site. Add the following below the STATIC_URL setting:

  1. STATICFILES_DIRS = [
  2. os.path.join(BASE_DIR, 'myclub_site/static'),
  3. ]

The STATICFILES_DIRS list serves the same function for static files as the DIRS list does for templates. In this case, we are telling Django to look for static files in the static directory in our site root. Now we need to create a static folder in our site root. Once you have created the new folder, your project directory will look like this:

  1. \myclub_root
  2. \myclub_site
  3. \static
  4. \templates
  5. # more files ...

Now we’ve created a folder for our static media, we will modify the base.html template to include static media, create a stylesheet, and add a logo and a top banner image to the site. We’ll be working with four files:

  1. base.html. We’ll update this file to include media and additional structural elements.
  2. main.css. A new stylesheet for the site.
  3. logo.png. Create or upload a logo for the site.
  4. top_banner.jpg. Create or upload an 800x200px banner image for the site.

Listing 1: base.html

Let’s start with the modified base template (changes in bold):

  1. # \myclub_site\templates\base.html
  2. 1 {% load static %}
  3. 2 <!doctype html>
  4. 3 <html>
  5. 4 <head>
  6. 5 <meta charset="utf-8">
  7. 6 <title>
  8. 7 {% block title %}
  9. 8 {{ page_title|default:"Untitled Page" }}
  10. 9 {% endblock title %}
  11. 10 </title>
  12. 11 <link href="{% static 'main.css' %}" rel="stylesheet" type="text/css">
  13. 12 </head>
  14. 13 <body>
  15. 14 <div id="wrapper">
  16. 15 <header id="header">
  17. 16 <div id="logo"><img src="{% static 'logo.png' %}" alt="" /></div>
  18. 17 <div id="top_menu">Home | Calendar | About | Contact</div>
  19. 18 <div id="topbanner"><img src="{% static 'top_banner.jpg' %}" alt="" /></div>
  20. 19 </header>
  21. 20 <aside id="rightsidebar">
  22. 21 <nav id="nav">
  23. 22 <ul>
  24. 23 <li>Menu 1</li>
  25. 24 <li>Menu 2</li>
  26. 25 <li>Menu 3</li>
  27. 26 </ul>
  28. 27 </nav>
  29. 28 </aside>
  30. 29 <section id="main">
  31. 30 {% block content %}
  32. 31 <p>Placeholder text in base template. Replace with page content.</p>
  33. 32 {% endblock content %}
  34. 33 </section>
  35. 34 <footer id="footer">Copyright &copy;
  36. 35 <script type="text/JavaScript">
  37. 36 document.write(new Date().getFullYear());
  38. 37 </script> MyClub
  39. 38 </footer>
  40. 39 </div>
  41. 40 </body>
  42. 41 </html>

Most of the new code in this file is plain HTML5 markup; however, there are few new elements worth noting:

  • Line 1. The {% load static %} tag links the static elements in the template to your STATIC_ROOT.
  • Line 11. We’re adding a stylesheet to the template. Using the {% static %} tag avoids hard coding the URL in the template, which is always preferable. The {% static %} tag is replaced with the full path to main.css at runtime.
  • Lines 15 to 19. We’ve added a <header> section to the template and added a logo, placeholder for the top menu and a banner image. Note the use of the {% static %} tag again when loading resources.
  • Lines 20 to 28. We’ve added a conventional right sidebar element with a placeholder menu.
  • Lines 34 to 38. Finally, we’ve added a footer element that uses JavaScript to render the current year.

Other than a few tags, there is nothing Django-specific necessary to upgrade the template. This is by design. Django is frontend agnostic, so it will do nothing to stop you adding whatever you like to the frontend—whether that be anything from making the templates responsive with Bootstrap to adding JavaScript frameworks like jQuery and Angular.

Listing 2: main.css

  1. # \myclub_site\static\main.css
  2. 1 @charset "utf-8";
  3. 2 #header {
  4. 3 border-style: none;
  5. 4 width: 800px;
  6. 5 height: auto;
  7. 6 }
  8. 7 #wrapper {
  9. 8 margin-top: 0px;
  10. 9 margin-left: auto;
  11. 10 margin-right: auto;
  12. 11 background-color: #FFFFFF;
  13. 12 width: 800px;
  14. 13 }
  15. 14 body {
  16. 15 background-color: #E0E0E0;
  17. 16 font-family: "Trebuchet MS", Helvetica, sans-serif;
  18. 17 font-size: 0.9em;
  19. 18 text-align: justify;
  20. 19 color: #474747;
  21. 20 }
  22. 21 h1 {
  23. 22 color: #270c39;
  24. 23 }
  25. 24 #footer {
  26. 25 text-align: center;
  27. 26 font-size: 0.8em;
  28. 27 padding-top: 10px;
  29. 28 padding-bottom: 10px;
  30. 29 background-color: #FFFFFF;
  31. 30 border-top: thin solid #BBBBBB;
  32. 31 clear: both;
  33. 32 color: #969696;
  34. 33 }
  35. 34 #nav li {
  36. 35 padding-top: 10px;
  37. 36 padding-bottom: 10px;
  38. 37 font-size: 1em;
  39. 38 list-style-type: none;
  40. 39 border-bottom: thin solid #e0e0e0;
  41. 40 color: #1e9d36;
  42. 41 left: 0px;
  43. 42 list-style-position: inside;
  44. 43 }
  45. 44 #nav li a {
  46. 45 text-decoration: none;
  47. 46 }
  48. 47 #rightsidebar {
  49. 48 width: 180px;
  50. 49 height: 350px;
  51. 50 float: right;
  52. 51 padding-right: 20px;
  53. 52 }
  54. 53 #main {
  55. 54 width: 560px;
  56. 55 float: left;
  57. 56 margin: 0 10px 50px 20px;
  58. 57 padding-right: 10px;
  59. 58 }
  60. 59 #logo {
  61. 60 padding: 10px;
  62. 61 float: left;
  63. 62 }
  64. 63 #top_menu {
  65. 64 float: right;
  66. 65 padding: 50px 20px 0px 0px;
  67. 66 font-size: 1.2em;
  68. 67 color: #270c39;
  69. 68 }
  70. 69 table.month td {
  71. 70 border: 1px solid rgb(221, 221, 221);
  72. 71 padding: 20px;
  73. 72 text-align: center;
  74. 73 }
  75. 74 table.month th {
  76. 75 padding: 2px 0px;
  77. 76 text-align: center;
  78. 77 }
  79. 78 th.month {
  80. 79 display: none;
  81. 80 }

This file is standard CSS. If you are not familiar with CSS, you can enter the code as written and learn more about style sheets as you go, or if you want to learn more now, you can check out W3schools.

logo.png and top_banner.jpg

You can download these files from the book website (they’re included in the source code), or you can create your own. Either way, put them both in the \myclub_site\static\ folder.

If you fire up the development server again and navigate to http://127.0.0.1:8000, you will see the results of your efforts (Figure 6-6). I am sure you agree that, while it still has a way to go, the new template is much prettier than the last!

Django Template System Basics - 图6

Figure 6-6: The event calendar template with styling and structural elements added.

OK, if you are using the printed textbook, this will look a lot less impressive as the images are black and white, but if you use my code and stylesheets the site will be in color.