How to Django — URLs and Templates
Developing user experience through URL routes and templating
As you’re developing your website, it is important to understand that navigation is critical to the success of its user friendly experience.
For this reason, we need to build logic into how we switch between pages and how far we want our user to dive into the content, if it’s layered and multi-faceted.
While it might not seem much, the following post will cover:
- URL patterns
- Creating templates
- Dynamic URLs
URL Patterns
We have already built a url pattern into our website, which doesn’t take in any custom value, yet. http://localhost:8000/
is the default url we are given from Django, but what if we wanted to make it unique to us? Give it a bit of personality and make it our own?
Continuing from the previous post, we can control this through the following files:
- urls.py
- views.py
That post only scratched the surface of how the URLs work, so let’s dig a little deeper.
So what we covered previously was creating a simple homepage with the following url: http://localhost:8000/
sitename > sitename > urls.py
from django.contrib import admin
from django.urls import path, includeurlpatterns = [
path('admin/', admin.site.urls),
path('', include('appname.urls')),
]
When you go to the URL, Django figures out which url file to follow. Given the URL http://localhost:8000/
, Django will do the following:
1 — Firstly it will map to the appname URL, because the URL pattern is an empty string.
2 — Inside appname, again, Django will find the first appropriate path.
sitename > sitename > appname > urls.py
from django.urls import path
from . import viewsapp_name = "appname"urlpatterns = [
path('', views.home, name="home"),
]
3 — Notice that the path is empty, but still matches the URL.
4 — As a result, the user will be referred to the home
view which has been given the name “home”.
5 — Then the user will be shown the “home” view:
sitename > sitename > appname > views.py
from django.http import HttpResponse, JsonResponse# Create your views here.
def home(request):
return HttpResponse('<h1>Hello World</h1>')
Now that we understand URL patterns a little better. Let’s create a new page which requires another route. It will be a Contact page so that users can find out more information about the creator of the site.
First let’s create a new view in views.py
sitename > sitename > appname > views.py
from django.http import HttpResponse, JsonResponse# Create your views here.
def home(request):
return HttpResponse('<h1>Hello World</h1>')def contact(request):
return HttpResponse('<h1>Hi my name is Jeremy.</h1>')
With a new view created, we can add it to our urls.py
sitename > sitename > appname > urls.py
from django.urls import path
from . import viewsapp_name = "appname"urlpatterns = [
path('', views.home, name="home"),
path('contact/', views.contact, name="contact"),
]
Now when we go to the url: http://localhost:8000/contact
, you will see your new page appear. As well, the original home page is still in tact.
You can now create as many URL patterns as you like. But of course writing each pattern out manually is not the best way of doing this. We’ll cover how to be smarter with this. For now let’s jump into…
Creating templates
An effective way of creating consistency in the visual experience is through the visual elements and layout of the website. Django by default allows us to use HTML, CSS and JavaScript to achieve this, however it also allows us to be smart with these elements. Rather than simply presenting information as static pages, we can build reusable components in the form of snippets and on top of that, can access variables from the Python backend. But let’s start off with a simple static template and work our way up!
Static template
Currently, when a user jumps to a specific URL, it returns either a HttpResponse or a JsonReponse. Both of which are useful to have, but is not exactly the most visually pleasing or complex enough to be handled efficiently.
So instead of using either of these libraries, we’ll instead render HTML files which can be customised with features such as Bootstrap.
To do this, let’s first build this template. Django is very good at steering the builder of the site to follow specific methods of practice. One of which is how we organise our template files.
sitename/
manage.py
sitename/
appname/
admin.py
apps.py
models.py
tests.py
views.py
__init__.py
migrations/
templates/
appname/
home.html
As you can see, we must create a “templates” folder, followed by another folder with the name of the app. Then you may store your html files in here.
In templates > appname
let’s create a home.html
file which will store everything to do with every single page. Things like:
- Bootstrap
- Base HTML head and body
Additional elements we can add later:
- CSS — can live in the HTML file or in the static folder
- Nav bar
Inside our home.html
file, let’s add the following:
sitename > sitename > appname > templates > appname > home.html
<!DOCTYPE html><html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <title>Django Website</title> </head>
<body><h1>Hello World!</h1></body>
</html>
With a base template, we can now render it instead of the HttpResponse / JsonResponse.
sitename > sitename > appname > views.py
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse# Create your views here.
def home(request):
return render(request, "appname/home.html")def contact(request):
return HttpResponse('<h1>Hi my name is Jeremy.</h1>')
Go to http://localhost:8000/
and check out your rendered template!
Dynamic Template
Now, let’s say you want to have the Bootstrap render across all URLs in your website. It doesn’t make sense to duplicate the one HTML, but rather have whatever that will be used more than once be a base template and everything else that is new be added on top.
Django uses Jinja templating to achieve this.
So what we can do is instead of creating a new page, we’ll keep home.html
but rename it to base.html
and we’ll keep everything in that file except for whatever’s inside the body tags. Anything that is inside the body tags will be the content for that page. So information which is meant to be displayed in the contact page will sit in there only if we go to that page.
To do this, Jinja templating has these curly brackets and percent symbols which in combination with specific syntax will allow us to make dynamic html pages that return different views based on the url.
Now that we have a base.html
let’s adjust it and create the actual home.html
which will sit inside the base.html
file.
sitename > sitename > appname > templates > appname > base.html
<!DOCTYPE html><html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <title>Django Website</title> </head>
<body>{% block content %}
{% endblock %}</body>
</html>
What you’ll notice is that I’ve replaced the “Hello World” with these new syntax. Anything which extends the base template will sit inside those tags.
To extend it, use the following syntax at the top of the snippet, as well as the block content tags to show when it starts and stops:
sitename > sitename > appname > templates > appname > home.html
{% extends "appname/base.html" %}{% block content %}
<h1>Hello World!</h1>
{% endblock content %}
Additionally, with the contact page, we will also extend base.html
and update the view to render the template.
sitename > sitename > appname > templates > appname > contact.html
{% extends "appname/base.html" %}{% block content %}
<h1>Hi my name is Jeremy.</h1>
{% endblock content %}
Variables in templates
Another step towards building dynamic Django websites is to pass variables from the Python backend and presenting it onto the frontend.
We can do this through double curly brackets and adding that parameter into the render view.
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse# Create your views here.
def home(request):
return render(request, "appname/home.html")def contact(request):
return render(
request,
"appname/contact.html,
{"context":"Jeremy"}
)
We’ve just passed the string “Jeremy” which is stored in the variable “context”. Also note that a dictionary was passed. We can have this dictionary be a value that’s updated prior to being rendered. Both of which works.
def contact(request):
context = "Jeremy"
return render(
request,
"appname/contact.html,
{"context":context}
)
Or as well, it can be a dictionary within a dictionary.
def contact(request):
context = {"name":"Jeremy"}
return render(
request,
"appname/contact.html,
{"context":context}
)
To bring it into the template, let’s override the text we want to replace with the variable.
sitename > sitename > appname > templates > appname > contact.html
{% extends "appname/base.html" %}{% block content %}
<h1>Hi my name is {{ context }}.</h1>
{% endblock content %}
If the dictionary is inside a dictionary, we can treat it like a dictionary variable.
sitename > sitename > appname > templates > appname > contact.html
{% extends "appname/base.html" %}{% block content %}
<h1>Hi my name is {{ context.name }}.</h1>
{% endblock content %}
If for some reason the HTML that is rendered has trouble presenting the value, you can add a safe option onto the variable so that it will work properly. I think I read somewhere that this is not best practice…?
{% extends "appname/base.html" %}{% block content %}
<h1>Hi my name is {{ context.name|safe }}.</h1>
{% endblock content %}
Dynamic URLs
The last part of the post focuses on what happens if you want to have URLs which match the query rather than have you write every single possible route manually.
Examples of this could be the name of a post could be a hyphenated slug.
i.e.
Title: Python Tutorial 1
Slug: python-tutorial-1
Or a user who logs into their account will automatically redirect to the user’s page.
Or the url is a parameter value which is calculated in the backend, then returns the result to the page.
All of these examples follow a similar approach of creating dynamic URLs.
Let’s start off with a simple one.
Continuing from Jeremy’s contact page, we want to control the output of the name to our name, or anyone else’s name. As long as there are no spaces and they’re all letters. It will require a smidge of regex to make this happen.
In views.py
let’s add the name parameter.
sitename > sitename > appname > views.py
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse# Create your views here.
def home(request):
return render(request, "appname/home.html")def contact(request, name="Jeremy"):
name = name
return render(
request,
"appname/contact.html,
{"context":name}
)
Notice that we’ve still got Jeremy in the name parameter. This allows us to set a default if none is provided.
Now in urls.py
things get a little tricky
sitename > sitename > appname > urls.py
from django.urls import path
from django.conf.urls import url
from . import viewsapp_name = "appname"urlpatterns = [
path('', views.home, name="home"),
url(r'^(?P<name>\b[A-Za-z]\w+\b)/$', views.contact, name="contact"),
]
We are using the url component so that we can use regex to specify the start and end of the route.
r’^ = start of the route
/$’ = ends the route
?P<name> = inputs the route as the parameter
\b[A-Za-z]\w+\b = Regex specifying that it must be a one word value, letters only
Now when we enter a name into the URL after contact/
we will get:
{% extends "appname/base.html" %}{% block content %}
<h1>Hi my name is INPUT_NAME.</h1>
{% endblock content %}
e.g.
http://localhost:8000/contact/Tom{% block content %}
<h1>Hi my name is Tom.</h1>
{% endblock content %}
Summary
That was quite a lot of information in this one post, so don’t worry if you couldn’t get it all straight away. Reading it a few times and testing it out will help give you a better idea of what to expect.