Django URLs, Views, and Templates

Learning Objectivesβ
Students Will Be Able To: |
---|
Describe the Django Request/Response Cycle |
Start a new Django project and create an app |
Use a URL configuration module to define routes |
Define basic View functions |
Define a Django template |
Use template inheritance (partial templates) |
Include static files in a template |
Render data in a template |
Road Mapβ
- Learning Django Game Plan
- Review the Request/Response Cycle in Django
- Start the Cat Collector Project
- Add the GitHub Remote to Sync With
- Defining Routes (URLs)
- Defining View Functions
- Using Django Templates
- Template Inheritance (Partials)
- Including Static Files in a Template
- Rendering Data in a Template
- Summary
- Labs for Cat Collector Lessons
Videosβ
1. Learning Django Game Planβ
We've got a great set of Django lessons coming your way!
These lessons will add features β piece-by-piece β to a modern full-stack reference app named Cat Collector.
Let me show you the final version we're going to build this week.
Then, after the lessons, you will use lab time to repeat what you saw in the lesson by building your own app named anything you want, let's say: Finch Collector.
Here's an overview of the high-level topics we'll be covering, in order:
- Django URLs, Views, and Templates
- Data Models and Migrations
- Django Class-based Views
- One-to-Many Models & ModelForms
- Many-to-Many Models
- Uploading Images to the Cloud
- Django Authentication
Let's begin, shall we?
2. Review the Request/Response Cycle in Djangoβ
First, let's review the diagram from the previous lesson that shows how a request flows through a Django project:

The Request/Response Cycle in Django is the same as in Express in that:
- Clicking links and submitting forms on the front-end (the browser) sends HTTP requests to the web app running on a web server.
- The web app has a routing mechanism that matches HTTP requests to code.
- That code typically performs CRUD, then either:
- Renders dynamic templates for READ data operations.
- Redirects the browser in the case of CREATE, UPDATE or DELETE data operations.
3. Start the Cat Collector Projectβ
Create the databaseβ
Databases are not automatically created by Django, so let's create one.
Let's use the createdb
command - which comes installed with PostgreSQL - to create the database for the Cat Collector project:
createdb catcollector
Named using lowercase and ran together will work well.
π A
dropdb
command is also available for deleting databases from PostgreSQL.
Start the Projectβ
Let's put in a 'rep' creating a Django project/app!
Move into your ~/code folder:
cd ~/code
Run the following command to create the Django project:
django-admin startproject catcollector
The above command generated and configured a Django project in a folder named catcollector - move into it:
cd catcollector
Open the project folder in VS Code:
code .
Open an New Terminal in VSCode and we're off to a great start...
Create the Appβ
A Django project contains Django apps.
Django apps represent major functionality in a project, although you would typically create only a single app within the project.
Take a look at the INSTALLED_APPS
list in catcollector/settings.py. Those pre-installed apps provide services such as the admin app and the ability to serve static files.
For Cat Collector, (as well as for your Project 3) you will need an app to implement the main functionality, in this case collecting cats πΈ
It makes sense to name the main app generically, so let's do it:
python3 manage.py startapp main_app
You'll now find a main_app folder within the top-level project folder. That folder has been configured to be a Python package - which is a way to organize modules.
Let's include it as part of the Cat Collector project by adding it to the INSTALLED_APPS
in settings.py:
INSTALLED_APPS = [
'main_app',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
Let's check to make sure the project starts up:
python3 manage.py runserver
Ignore the red message about unapplied migrations, we'll take care of those in a bit.
Browse to localhost:8000
and make sure you see the rocket on the page:

Connecting to the Databaseβ
Earlier, we created a dedicated catcollector
PostgreSQL database. A Django project's configuration lives in settings.py. Let's update it to use our catcollector
database:
Note: Some students may have to include the following fields(HOST, USER, PASSWORD), it depends how your Postgres is setup locally. For example, the most common setup for Windows and Linux users may require you to include these three fields to properly connect to your local Postgres instance.
π You can also add the 'PORT' property (see below) to change the port to something other than :8000
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'catcollector',
# 'HOST': 'localhost', <-- (optional) some computers might need this line
# 'USER': 'admin', <-- (optional) postgres user name, if you have to sign into an account to open psql, you will want to add that user name here.
# 'PASSWORD': 'password123', <-- (optional) postgres user password, if you have to sign into an account to open psql, you will want to add that user password here.
# 'PORT': 3000 <-- if you desire to use a port other than 8000, you can change that here to any valid port id, some number between 1 and 65535 that isn't in use by some other process on your machine. The reason for this port number range is because of how TCP/IP works, a TCP/IP protocol network(the most widely used protocol used on the web) allocated 16 bits for port numbers. This means that number must be greater than 0 and less than 2^15 -1.
}
}
π By default, Django uses SQLite, a lightweight database that is not recommended for deployment.
You may be seeing some red text about unapplied migrations in your terminal if your project successfully connected to the catcollector
database. Let's explain a little more about that and get that cleared up.
Migrating the Pending Migrationsβ
We use migrations to update the database's schema over time to meet the project's needs.
There are several migrations pending (i.e., waiting to be applied to the database) - so let's apply them:
python3 manage.py migrate
π Nice - no more pending migrations! We will cover migrations in more detail in the next lesson.
Now, let's code our first feature: a HOME page.
4. Add the GitHub Remote to Sync Withβ
Connect to the Remoteβ
Let's connect to a remote repo that you can sync with just in case you can't resolve typos within a reasonable amount of time:
git init
git add -A
git commit -m "Initial commit"
git remote add origin https://git.generalassemb.ly/sei-blended-learning/catcollector.git
git fetch --all
π Got some typos or errors and need to sync your code?
git reset --hard origin/sync-1-setup
Updating the Database After Syncing Codeβ
It's possible that you may receive new or different migration files when syncing code.
Just in case, always attempt to apply any new migrations after syncing code:
python3 manage.py migrate
Unable to Migrate Database Due to Errorsβ
Unfortunately, it's possible that the newly-synced code may have migrations that are incompatible with the previously applied migrations.
If this is the case, the quickest fix is to delete the current database and create a new one.
π The following steps will delete any existing data that you may have saved in your current database.
Run the following commands in Terminal (ONLY if necessary) to delete & replace the current database if having issues after syncing your code:
dropdb <database name>
createdb <database name>
python3 manage.py migrate
python3 manage.py createsuperuser
(you'll learn about this next lesson)
5. Defining Routes (URLs)β
Just like with Express, there needs to be a route defined that matches each HTTP request coming from the browser.
β What kind of error will we receive when there is no route defined that matches a given HTTP request?
404 Not Found
But, let's not forget that Django's routing system only cares about the URL ("path") of the request and ignores the HTTP method.
For the HOME page functionality, we'll use the root route (http://localhost:8000
) - let's see how...
One-time URL Setupβ
In Django, routes are defined within modules named urls.py.
There's an existing project catcollector/urls.py that we could add additional routes to, however it is best practice for each Django app to define it's own routes and to include those URLs in the project.
π There are some helpful comments at the top of catcollector/urls.py.
Start by setting up main_app's urls.py file. But wait...there's not one:
- Create the urls.py module:
touch main_app/urls.py
- Let's include it in the project's urls file - catcollector/urls.py
from django.contrib import admin
# Add the include function to the import
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# '' represents the "starts with" path
path('', include('main_app.urls')),
]
π Be sure to import the
include
function near the top.
Each item in the urlpatterns
list defines a URL-based route or, as in the case above, mounts the routes contained in another urls.py module.
Similar to how Express appends paths defined in a router module to the path in app.use
, the paths defined in 'main_app.urls'
will be appended to the path specified in the include
function.
You can now close catcollector/urls.py, since all routes we define from this point forward will be defined within main_app/urls.py.
- Now for the boilerplate needed in main_app/urls.py:
from django.urls import path
from . import views
urlpatterns = [
]
Notice that we've imported the path
function that will be used to define each route.
We've also created the required urlpatterns
list which will hold each route we define for main_app
.
Define main_app
's Home Page URLβ
With the setup done, we're ready to define the route to display the Home page.
In main_app/urls.py:
urlpatterns = [
path('', views.home, name='home'),
]
The above code defines a root route using an empty string and maps it to the views.home
view function that does not exist yet - making the server unhappy.
The name='home'
kwarg gives the route a name. Naming a route is optional, but is considered a best practice. We will see how naming a route comes in handy later on.
The Home page route has been defined! On to the view...
6. Defining View Functionsβ
β What is the equivalent to a Django View Function in Express?
Controller Function
In the route for the Home page we referenced a view function named home
.
We will define all of the app's views in main_app/views.py.
Let's define the home
view function and render a non-existing template:
from django.shortcuts import render
# Define the home view
def home(request):
# Include an .html file extension - unlike when rendering EJS templates
return render(request, 'home.html')
All view functions need to define a positional parameter to accept the request
object Django will be passing in. This request
object is very much like the req
object we worked with in Express controller functions.
As expected, refreshing localhost:8000
will cause an error complaining that the template does not exist:

A very helpful error - let's fix it...
7. Using Django Templatesβ
By default, a Django project is configured to look for templates inside of a templates
folder within each app's folder (main_app
in this case).
Let's create that templates
folder for main_app
to hold all of its template files:
mkdir main_app/templates
Create a home.html
Templateβ
Now we can easily solve our error by creating the missing template and adding a bit of HTML:
- Create a templates/home.html file
- Add the HTML boilerplate (
!
+[tab]
) - Add a
<h1>Home</h1>
within the<body>
π Unlike EJS templates, Django templates use the common
.html
file extension.
Yep... it's not fancy, but we have now implemented a feature in Django!
π If you are experiencing an error in the terminal, try restarting your server and refreshing your browser
π Do you need to sync your code?
git reset --hard origin/sync-2-home-page
Before we continue learning more about templating etc. in Django, let's navigate to the next page - where you will have the opportunity to 'put in a rep' by implementing an "About" page...
π You Do - Define another URL, View Function and Template (10 mins)β
-
Define another route with a path of
about/
. The path's trailing slash instead of a leading slash is the Django way and is critical to follow. -
Map the route to a view named
views.about
. -
Name the route
'about'
. -
Code the
about
view function in views.py so that it renders a template namedabout.html
-
Create the templates/about.html file.
-
Add the boilerplate and add HTML between the
<body>
tags:
<h1>About the Cat Collector</h1>
<hr />
<p>Hire the Cat Collector!</p>
<footer>All Rights Reserved, © 2022 Cat Collector</footer>
- Test it out by browsing to
localhost:8000/about
π Do you need to sync your code?
git reset --hard origin/sync-3-about-page
So far, so good, but we haven't yet used any of DTL's (Django Templating Language) capability to dynamically render data, etc.
But before we continue to break the DRY principle by repeating the boilerplate in future templates, let's see how to use Django's Template Inheritance.
8. Template Inheritance (Partials)β
Django has a template inheritance feature built-in.
Template inheritance is like using partials in EJS with Express, except they're more flexible.
The reason Django calls it template inheritance is because:
- You can declare that a template extends another template.
- When a template extends a "base" template, it's content defined in a "block" replaces the same "block" in the "base" template as shown below:

Let's apply this concept in Cat Collector by first creating a base.html template (named by convention):
touch main_app/templates/base.html
The "base" template contains all of the boilerplate and markup that belongs on every template that extends it, such as the <head>
, navigation, even a footer (if you wish).
π Multiple "base" templates are rarely needed but can be defined if need be.
This will be our sweet boilerplate for now:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Cat Collector</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
/>
</head>
<body>
<header class="navbar-fixed">
<nav>
<div class="nav-wrapper">
<ul>
<li>
<a href="/" class="left brand-logo"> Cat Collector</a>
</li>
</ul>
<ul class="right">
<li><a href="/about">About</a></li>
</ul>
</div>
</nav>
</header>
<main class="container">{% block content %} {% endblock %}</main>
<footer class="page-footer">
<div class="right">
All Rights Reserved, © 2021 Cat Collector
</div>
</footer>
</body>
</html>
Cat Collector will be using the Materialize CSS Framework which is based on Google's Material Design philosophy.
However, the most important part of the boilerplate in regards to template inheritance is:
{% block content %} {% endblock %}
This is our first look at DTL(Django Templating Language) template tags, block
& endblock
, enclosed within the template tag delimiters {% %}
.
Django template tags control the logic within a template. Depending upon the tag, they may or may not result in content being emitted in the page.
Whenever another template extends this base.html, that other template's {% block content %}
will replace the same block in base.html.
To see template inheritance in action, let's update about.html so that it extends base.html:
{% extends 'base.html' %} {% block content %}
<h1>About the Cat Collector</h1>
<hr />
<p>Hire the Cat Collector!</p>
<footer>All Rights Reserved, © 2022 Cat Collector</footer>
{% endblock %}
Refresh (or navigate to localhost:8000/about). Yeah, it's not great (yet), but the template inheritance is working and we can stay nice and DRY.
9. Including Static Files in a Templateβ
As you know, web apps usually have static files such as .css
, .js
, image files, etc.
If we want Cat Collector to look better, we're going to have to be able to define some custom CSS.
Django projects are pre-configured with a 'django.contrib.staticfiles'
app installed for the purpose of serving static files.
If you look at the bottom of settings.py, there is a STATIC_URL = 'static/'
variable that declares the name of the folder that will contain static files within a Django app.
We need that, so let's create it:
mkdir main_app/static
Next, let's create a folder within static
dedicated to CSS:
mkdir main_app/static/css
Now let's create a style.css
:
touch main_app/static/css/style.css
Just to make sure that style.css is properly loaded, let's put in a touch of hideous CSS:
body {
background-color: red;
}
We also have to update base.html by first adding the load
template tag at the top:
{% load static %}
<!DOCTYPE html>
Finally, add this <link>
below the Materialize CDN:
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}" />
The static
DTL template tag ensures that the correct URL is assigned to the href
.
π Django caches templates & statics, so we'll need to restart the server!
Then, Refresh - and... "red city" tells us that style.css is being loaded. Let's update it with the following (more pleasing) CSS:
body {
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
flex: 1 0 auto;
}
footer.page-footer {
padding-top: 0;
text-align: right;
}
That's better!

π Do you need to sync your code?
git reset --hard origin/sync-4-statics
10. Rendering Data in a Templateβ
To see how data is rendered dynamically using Django templating, we're going to implement the following user story:
As a User, when I click the View All My Cats link, I want to see a page listing of all of my cats.
Luckily the process of how to add a feature to a web application from the previous unit you had "tattooed" applies to all web frameworks!
Step 1 - Identify the "Proper" Routeβ
Although Django's URL-based routing doesn't follow the RESTful routing methodology, we can still rely on our training thus far and use similar paths when possible.
RESTful routing calls for a path of /cats
when we want to see all cats - so we'll go with it!
Step 2 - Create the UIβ
For the UI, it makes sense to add the View All My Cats link to the navigation bar in base.html:
<li><a href="/about">About</a></li>
<!-- new markup below -->
<li><a href="/cats">View All My Cats</a></li>
π It's important to continue to use leading slashes in the HTML!
A quick refresh and we have our link:

Step 3 - Define the Routeβ
Now let's add the new route to main_app/urls.py:
urlpatterns = [
path('', views.home, name='home'),
path('about/', views.about, name='about'),
# route for cats index
path('cats/', views.cats_index, name='index'),
]
We only have a single views.py, so by naming the view cat_index
we're anticipating that there might be another index view for a different resource in the future (toys maybe?).
Of course, by referencing a nonexistent view, the server's not happy π.
Step 4 - Code the Viewβ
When working in Django, we'll just have to get used to calling controller functions views instead.
Let's code the cats_index
view inside of views.py:
# Add new view
def cats_index(request):
# We pass data to a template very much like we did in Express!
return render(request, 'cats/index.html', {
'cats': cats
})
Two interesting things above:
-
We're namespacing the
index.html
template by putting it in a newtemplates/cats
folder for organizational purposes - just like we did in Express. -
Similar to how we passed data to a template in Express using a JS object β in Django, we pass a dictionary and we pass it as the third argument in the function (Django's
render
function).
- You may have noticed that third argument contains a word that has "squiggles" under it. That's because.... π
..We Need Some Cats! πβ
In the next lesson, we'll create a Cat
model, but for now we're simply going to use a Python list containing a couple of "cat" dictionaries placed near the top of views.py:
# Add this cats list below the imports
cats = [
{'name': 'Lolo', 'breed': 'tabby', 'description': 'furry little demon', 'age': 3},
{'name': 'Sachi', 'breed': 'calico', 'description': 'gentle and loving', 'age': 2},
]
Step 5 - Respond to the Client's HTTP Requestβ
β
We've already written the code to respond using the render()
method in the view.
However, we need to create the cats/index.html template currently being rendered.
First we need the templates/cats
folder we'll use to organize cat related templates:
mkdir main_app/templates/cats
Now create the cats/index.html template file:
touch main_app/templates/cats/index.html
Now the fun stuff!
{% extends 'base.html' %} {% block content %}
<h1>Cat List</h1>
{% for cat in cats %}
<div class="card">
<div class="card-content">
<span class="card-title">{{ cat.name }}</span>
<p>Breed: {{ cat.breed }}</p>
<p>Description: {{ cat.description }}</p>
{% if cat.age > 0 %}
<p>Age: {{ cat.age }}</p>
{% else %}
<p>Age: Kitten</p>
{% endif %}
</div>
</div>
{% endfor %} {% endblock %}
There are two control flow template tag constructs you'll use quite a bit:
- The
{% for %}
/{% endfor %}
block β used to perform looping - The
{% if %}
/{% elif %} / {% else %} / {% endif %}
block β used for branching.
π Django's template tags are designed to mimic their Python counterparts, BUT they are not embedding Python in the same way EJS embedded JavaScript. For example, Python does not have
endfor
orendif
as part of the language.
The double curly brace syntax {{}}
is used to print the values of variables and object properties.
If the property on an object is a method, it is automatically invoked by the template engine without any arguments and we do not put parentheses after the method name. For example, assuming a Person object has a getFullName
method, it would be printed in the template like this {{ person.getFullName }}
. This is another example of how DTL is its own language - and not Python. It is necessary to clarify that DTL is not technically a programming language, it is a templating language - like HTML or EJS. A templating language is a tool used to specify placeholders and a structure for how information will be presented (or processed) by another process.
Some of you have probably already clicked the link and are understandably grinning!
π Do you need to sync your code?
git reset --hard origin/sync-5-finish-urls
11. Summaryβ
You now have a minimal (but functional) application that renders an index page which dynamically displays a hard-coded list of cats π±.
You now know pretty much all there is to know about the structure of a Django app!
In the next lesson, we'll learn about Django Models where we'll code a Cat
Model and use it to "CRUD" πΈπΈπΈ inside of the database!
12. Labs for Cat Collector Lessonsβ
In this unit, the goal of each Django lab will be to repeat everything we've done in the lesson -- except you'll "collect" something new (like Finches) and call the project something like finchcollector, or whatever you are collecting π
The final version of your "__ Collector" will be a deliverable.
Because your completed Collector project will be fairly comprehensive, makes it a great candidate for an addition to your portfolio.
Be sure to create your project within your ~/code folder and push it to a repo in your personal GitHub account.