Django Demo Project2
We also created a placeholder file for the URLConf module, named /catalog/urls.py. Add the following lines to that file:
urlpatterns = [
path('', views.index, name='index'),
]
The path() function defines the following:
-
A url pattern, which is an empty string: ‘’. We’ll discuss url patterns in detail when working on the other views.
-
A view function that will be called if the URL pattern is detected: views.index, which is the function named index() in the views.py file.
The path() function also specifies a name parameter, which is a unique identifier for this particular URL mapping. You can use the name to “reverse” the mapper, i.e. to dynamically create a URL that points to the resource that the mapper is designed to handle. For example, we can use the name parameter to link to our home page from any other page by adding the following link in a template:
<a href="{\% url 'index' \%}">Home</a>.
Note: We can hard code the link as in
<a href="/catalog/">Home</a>)
, but if we change the pattern for our home page, for example, to/catalog/index)
the templates will no longer link correctly. Using a reversed URL mapping is more robust.
View (function-based)
A view is a function that processes an HTTP request, fetches the required data from the database, renders the data in an HTML page using an HTML template, and then returns the generated HTML in an HTTP response to display the page to the user. The index view follows this model — it fetches information about the number of Book, BookInstance, available BookInstance and Author records that we have in the database, and passes that information to a template for display.
Open catalog/views.py and note that the file already imports the render() shortcut function to generate an HTML file using a template and data:
from django.shortcuts import render
# Create your views here.
Paste the following lines at the bottom of the file:
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()
# The 'all()' is implied by default.
num_authors = Author.objects.count()
context = {
'num_books': num_books,
'num_instances': num_instances,
'num_instances_available': num_instances_available,
'num_authors': num_authors,
}
# Render the HTML template index.html with the data in the context variable
return render(request, 'index.html', context=context)
The first line imports the model classes that we’ll use to access data in all our views.
The first part of the view function fetches the number of records using the objects.all() attribute on the model classes. It also gets a list of BookInstance objects that have a value of ‘a’ (Available) in the status field.
At the end of the view function we call the render() function to create an HTML page and return the page as a response. This shortcut function wraps a number of other functions to simplify a very common use case. The render() function accepts the following parameters:
-
the original request object, which is an HttpRequest.
-
an HTML template with placeholders for the data.
-
a context variable, which is a Python dictionary, containing the data to insert into the placeholders.
Template
A template is a text file that defines the structure or layout of a file (such as an HTML page), it uses placeholders to represent actual content.
A Django application created using startapp (like the skeleton of this example) will look for templates in a subdirectory named ‘templates’ of your applications. For example, in the index view that we just added, the render() function will expect to find the file index.html in /django-locallibrary-tutorial/catalog/templates/ and will raise an error if the file is not present.
You can check this by saving the previous changes and accessing 127.0.0.1:8000 in your browser - it will display a fairly intuitive error message: “TemplateDoesNotExist at /catalog/”, and other details.
Note: Based on your project’s settings file, Django will look for templates in a number of places, searching in your installed applications by default. You can find out more about how Django finds templates and what template formats it supports in the Templates section of the Django documentation.
Extending templates
The index template will need standard HTML markup for the head and body, along with navigation sections to link to the other pages of the site (which we haven’t created yet), and to sections that display introductory text and book data.
Much of the HTML and navigation structure will be the same in every page of our site. Instead of duplicating boilerplate code on every page, you can use the Django templating language to declare a base template, and then extend it to replace just the bits that are different for each specific page.
The following code snippet is a sample base template from a base_generic.html file. We’ll be creating the template for LocalLibrary shortly. The sample below includes common HTML with sections for a title, a sidebar, and main contents marked with the named block and endblock template tags. You can leave the blocks empty, or include default content to use when rendering pages derived from the template.
Note: Template tags are functions that you can use in a template to loop through lists, perform conditional operations based on the value of a variable, and so on. In addition to template tags, the template syntax allows you to reference variables that are passed into the template from the view, and use template filters to format variables (for example, to convert 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 defining a template for a particular view, we first specify the base template using the extends template tag — see the code sample below. Then we declare what sections from the template we want to replace (if any), using block/endblock sections as in the base template.
For example, the code snippet below shows how to use the extends template tag and override the content block. The generated HTML will include the code and structure defined in the base template, including the default content you defined in the title block, but the new content block in place of the default one.
{\% extends "base_generic.html" \%}
{\% block content \%}
<h1>Local Library Home</h1>
<p>
Welcome to LocalLibrary, a website developed by
<em>Mozilla Developer Network</em>!
</p>
{\% endblock \%}
The LocalLibrary base template
We will use the following code snippet as the base template for the LocalLibrary website. As you can see, it contains some HTML code and defines blocks for title, sidebar, and content. We have a default title and a default sidebar with links to lists of all books and authors, both enclosed in blocks to be easily changed in the future.
Note: We also introduce two additional template tags: url and load static. These tags will be explained in following sections.
Create a new file base_generic.html in /django-locallibrary-tutorial/catalog/templates/ and paste the following code to the file:
<!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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
<!-- 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 includes 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 displays well on different screen sizes.
The base template also references a local CSS file (styles.css) that provides additional styling. Create a styles.css file in /django-locallibrary-tutorial/catalog/static/css/ and paste the following code in the file:
.sidebar-nav {
margin-top: 20px;
padding: 0;
list-style: none;
}
The index template
Create a new HTML file index.html in /django-locallibrary-tutorial/catalog/templates/ and paste the following code in the file. This code extends our base template on the first line, and then replaces the default content block for the template.
{\% extends "base_generic.html" \%}
{\% block content \%}
<h1>Local Library Home</h1>
<p>
Welcome to LocalLibrary, a website developed by
<em>Mozilla Developer Network</em>!
</p>
<h2>Dynamic content</h2>
<p>The library has the following record counts:</p>
<ul>
<li><strong>Books:</strong> </li>
<li><strong>Copies:</strong> </li>
<li><strong>Copies available:</strong> </li>
<li><strong>Authors:</strong> </li>
</ul>
{\% endblock \%}
In the Dynamic content section we declare placeholders (template variables) for the information from the view that we want to include. The variables are enclosed with double brace (handlebars).
Note: You can easily recognize template variables and template tags (functions) - variables are enclosed in double braces (), and tags are enclosed in single braces with percentage signs ({\% extends “base_generic.html” \%}).
The important thing to note here is that variables are named with the keys that we pass into the context dictionary in the render() function of our view (see sample below). Variables will be replaced with their associated values when the template is rendered.
Reference 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 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 the template library, as shown in the code sample below. You can then use the static template tag and specify the relative URL to the required file.
<!-- Add additional CSS in static file -->
{\% load static \%}
<link rel="stylesheet" href="{\% static 'css/styles.css' \%}" />
You can add an image into the page in a similar way, for example:
{\% load static \%}
<img
src="{\% static 'catalog/images/local_library_model_uml.png' \%}"
alt="UML diagram"
style="width:555px;height:540px;" />
Link to URLs
The base template above introduced the url template tag.
<li><a href="{\% url 'index' \%}">Home</a></li>
This tag accepts the name of a path() function called in your urls.py and the values for any arguments that the associated view will receive from that function, and returns a URL that you can use to link to the resource.
Configure where to find the templates
The location where Django searches for templates is specified in the TEMPLATES object in the settings.py file. The default settings.py (as created for this tutorial) looks something like this:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
The setting of ‘APP_DIRS’: True, is the most important, as it tells Django to search for templates in a subdirectory of each application in the project, named “templates” (this makes it easier to group templates with their associated application for easy re-use).
We can also specify specific locations for Django to search for directories using ‘DIRS’: [] (but that isn’t needed yet).
Generic list and detail views
Book list page
The book list page will display a list of all the available book records in the page, accessed using the URL: catalog/books/. The page will display a title and author for each record, with the title being a hyperlink to the associated book detail page. The page will have the same structure and navigation as all other pages in the site, and we can, therefore, extend the base template (base_generic.html) we created in the previous tutorial.
URL mapping
Open /catalog/urls.py and copy in the line setting the path for ‘books/’, as shown below. Just as for the index page, this path() function defines a pattern to match against the URL (‘books/’), a view function that will be called if the URL matches (views.BookListView.as_view()), and a name for this particular mapping.
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
]
As discussed in the previous tutorial the URL must already have matched /catalog, so the view will actually be called for the URL: /catalog/books/.
The view function has a different format than before — that’s because this view will actually be implemented as a class. We will be inheriting from an existing generic view function that already does most of what we want this view function to do, rather than writing our own from scratch.
For Django class-based views we access an appropriate view function by calling the class method as_view(). This does all the work of creating an instance of the class, and making sure that the right handler methods are called for incoming HTTP requests.
View (class-based)
We could quite easily write the book list view as a regular function (just like our previous index view), which would query the database for all books, and then call render() to pass the list to a specified template. Instead, however, we’re going to use a class-based generic list view (ListView) — a class that inherits from an existing view. Because the generic view already implements most of the functionality we need and follows Django best-practice, we will be able to create a more robust list view with less code, less repetition, and ultimately less maintenance.
Open catalog/views.py, and copy the following code into the bottom of the file:
from django.views import generic
class BookListView(generic.ListView):
model = Book
That’s it! The generic view will query the database to get all records for the specified model (Book) then render a template located at /django-locallibrary-tutorial/catalog/templates/catalog/book_list.html (which we will create below). Within the template you can access the list of books with the template variable named object_list OR book_list (i.e. generically “<the model name>_list
”).
Note: This awkward path for the template location isn’t a misprint — the generic views look for templates in /application_name/the_model_name_list.html (catalog/book_list.html in this case) inside the application’s /application_name/templates/ directory (/catalog/templates/).
You can add attributes to change the default behavior above. For example, you can specify another template file if you need to have multiple views that use this same model, or you might want to use a different template variable name if book_list is not intuitive for your particular template use-case. Possibly the most useful variation is to change/filter the subset of results that are returned — so instead of listing all books you might list top 5 books that were read by other users.
class BookListView(generic.ListView):
model = Book
context_object_name = 'book_list' # your own name for the list as a template variable
queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location
Overriding methods in class-based views
While we don’t need to do so here, you can also override some of the class methods.
For example, we can override the get_queryset() method to change the list of records returned. This is more flexible than just setting the queryset attribute as we did in the preceding code fragment (though there is no real benefit in this case):
class BookListView(generic.ListView):
model = Book
def get_queryset(self):
return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
We might also override get_context_data() in order to pass additional context variables to the template (e.g. the list of books is passed by default). The fragment below shows how to add a variable named “some_data” to the context (it would then be available as a template variable).
class BookListView(generic.ListView):
model = Book
def get_context_data(self, **kwargs):
# Call the base implementation first to get the context
context = super(BookListView, self).get_context_data(**kwargs)
# Create any data and add it to the context
context['some_data'] = 'This is just some data'
return context
When doing this it is important to follow the pattern used above:
-
First get the existing context from our superclass.
-
Then add your new context information.
-
Then return the new (updated) context.
Creating the List View template
Create the HTML file /django-locallibrary-tutorial/catalog/templates/catalog/book_list.html and copy in the text below. As discussed above, this is the default template file expected by the generic class-based list view (for a model named Book in an application named catalog).
Templates for generic views are just like any other templates (although of course the context/information passed to the template may differ). As with our index template, we extend our base template in the first line and then replace the block named content.
{\% extends "base_generic.html" \%}
{\% block content \%}
<h1>Book List</h1>
{\% if book_list \%}
<ul>
{\% for book in book_list \%}
<li>
<a href=""></a>
()
</li>
{\% endfor \%}
</ul>
{\% else \%}
<p>There are no books in the library.</p>
{\% endif \%}
{\% endblock \%}
The view passes the context (list of books) by default as object_list and book_list aliases; either will work.
You might also use the {\% empty \%} template tag to define what happens if the book list is empty (although our template chooses to use a conditional instead):
<ul>
{\% for book in book_list \%}
<li><!-- code here get information from each book item --></li>
{\% empty \%}
<p>There are no books in the library.</p>
{\% endfor \%}
</ul>
Update the base template
Open the base template (/django-locallibrary-tutorial/catalog/templates/base_generic.html) and insert {\% url ‘books’ \%} into the URL link for All books, as shown below. This will enable the link in all pages (we can successfully put this in place now that we’ve created the “books” URL mapper).
<li><a href="{\% url 'index' \%}">Home</a></li>
<li><a href="{\% url 'books' \%}">All books</a></li>
<li><a href="">All authors</a></li>
What does it look like?
You won’t be able to build the book list yet, because we’re still missing a dependency — the URL map for the book detail pages, which is needed to create hyperlinks to individual books. We’ll show both list and detail views after the next section.
Book detail page
The book detail page will display information about a specific book, accessed using the URL catalog/book/
URL mapping
Open /catalog/urls.py and add the path named ‘book-detail’ shown below. This path() function defines a pattern, associated generic class-based detail view, and a name.
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
]
For the book-detail path the URL pattern uses a special syntax to capture the specific id of the book that we want to see. The syntax is very simple: angle brackets define the part of the URL to be captured, enclosing the name of the variable that the view can use to access the captured data. For example,
In this case we use ‘
Warning: The generic class-based detail view expects to be passed a parameter named pk. If you’re writing your own function view you can use whatever parameter name you like, or indeed pass the information in an unnamed argument.
Advanced path matching/regular expression primer
The pattern matching provided by path() is simple and useful for the (very common) cases where you just want to capture any string or integer. If you need more refined filtering (for example, to filter only strings that have a certain number of characters) then you can use the re_path() method.
This method is used just like path() except that it allows you to specify a pattern using a Regular expression.
Passing additional options in your URL maps
One feature that we haven’t used here, but which you may find valuable, is that you can pass a dictionary containing additional options to the view (using the third un-named argument to the path() function). This approach can be useful if you want to use the same view for multiple resources, and pass data to configure its behavior in each case.
For example, given the path shown below, for a request to /myurl/halibut/ Django will call views.my_view(request, fish=’halibut’, my_template_name=’some_path’).
path('myurl/<fish>', views.my_view, {'my_template_name': 'some_path'}, name='aurl'),
Note: Both named captured patterns and dictionary options are passed to the view as named arguments. If you use the same name for both a capture pattern and a dictionary key, then the dictionary option will be used.
View (class-based)
Open catalog/views.py, and copy the following code into the bottom of the file:
class BookDetailView(generic.DetailView):
model = Book
That’s it! All you need to do now is create a template called /django-locallibrary-tutorial/catalog/templates/catalog/book_detail.html, and the view will pass it the database information for the specific Book record extracted by the URL mapper. Within the template you can access the book’s details with the template variable named object OR book (i.e. generically “the_model_name”).
If you need to, you can change the template used and the name of the context object used to reference the book in the template. You can also override methods to, for example, add additional information to the context.
What happens if the record doesn’t exist?
If a requested record does not exist then the generic class-based detail view will raise an Http404 exception for you automatically — in production, this will automatically display an appropriate “resource not found” page, which you can customize if desired.
Just to give you some idea of how this works, the code fragment below demonstrates how you would implement the class-based view as a function if you were not using the generic class-based detail view.
def book_detail_view(request, primary_key):
try:
book = Book.objects.get(pk=primary_key)
except Book.DoesNotExist:
raise Http404('Book does not exist')
return render(request, 'catalog/book_detail.html', context={'book': book})
The view first tries to get the specific book record from the model. If this fails the view should raise an Http404 exception to indicate that the book is “not found”. The final step is then, as usual, to call render() with the template name and the book data in the context parameter (as a dictionary).
Another way you could do this if you were not using a generic view would be to call the get_object_or_404() function. This is a shortcut to raise an Http404 exception if the record is not found.
from django.shortcuts import get_object_or_404
def book_detail_view(request, primary_key):
book = get_object_or_404(Book, pk=primary_key)
return render(request, 'catalog/book_detail.html', context={'book': book})
Creating the Detail View template
Create the HTML file /django-locallibrary-tutorial/catalog/templates/catalog/book_detail.html and give it the below content. As discussed above, this is the default template file name expected by the generic class-based detail view (for a model named Book in an application named catalog).
{\% extends "base_generic.html" \%}
{\% block content \%}
<h1>Title: </h1>
<p><strong>Author:</strong> <a href=""></a></p>
<!-- author detail link not yet defined -->
<p><strong>Summary:</strong> </p>
<p><strong>ISBN:</strong> </p>
<p><strong>Language:</strong> </p>
<p><strong>Genre:</strong> </p>
<div style="margin-left:20px;margin-top:20px">
<h4>Copies</h4>
{\% for copy in book.bookinstance_set.all \%}
<hr />
<p
class="{\% if copy.status == 'a' \%}text-success{\% elif copy.status == 'm' \%}text-danger{\% else \%}text-warning{\% endif \%}">
</p>
{\% if copy.status != 'a' \%}
<p><strong>Due to be returned:</strong> </p>
{\% endif \%}
<p><strong>Imprint:</strong> </p>
<p class="text-muted"><strong>Id:</strong> </p>
{\% endfor \%}
</div>
{\% endblock \%}
Note: The author link in the template above has an empty URL because we’ve not yet created an author detail page to link to. Once the detail page exists we can get its URL with either of these two approaches:
- Use the url template tag to reverse the ‘author-detail’ URL (defined in the URL mapper), passing it the author instance for the book:
<a href="{\% url 'author-detail' book.author.pk \%}"></a>
- Call the author model’s get_absolute_url() method (this performs the same reversing operation):
<a href=""></a>
While both methods effectively do the same thing, get_absolute_url() is preferred because it helps you write more consistent and maintainable code (any changes only need to be done in one place: the author model).
The first interesting thing we haven’t seen before is the function book.bookinstance_set.all(). This method is “automagically” constructed by Django in order to return the set of BookInstance records associated with a particular Book.
{\% for copy in book.bookinstance_set.all \%}
<!-- code to iterate across each copy/instance of a book -->
{\% endfor \%}
This method is needed because you declare a ForeignKey (one-to many) field only in the “many” side of the relationship (the BookInstance). Since you don’t do anything to declare the relationship in the other (“one”) model, it (the Book) doesn’t have any field to get the set of associated records. To overcome this problem, Django constructs an appropriately named “reverse lookup” function that you can use. The name of the function is constructed by lower-casing the model name where the ForeignKey was declared, followed by _set (i.e. so the function created in Book is bookinstance_set()).
Note: Here we use all() to get all records (the default). While you can use the filter() method to get a subset of records in code, you can’t do this directly in templates because you can’t specify arguments to functions.
Beware also that if you don’t define an order (on your class-based view or model), you will also see errors from the development server like this one:
[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637
/foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]>
allow_empty_first_page=allow_empty_first_page, **kwargs)
That happens because the paginator object expects to see some ORDER BY being executed on your underlying database. Without it, it can’t be sure the records being returned are actually in the right order!
This tutorial hasn’t covered Pagination (yet!), but since you can’t use sort_by() and pass a parameter (the same with filter() described above) you will have to choose between three choices:
-
Add a ordering inside a class Meta declaration on your model.
-
Add a queryset attribute in your custom class-based view, specifying an order_by().
-
Adding a get_queryset method to your custom class-based view and also specify the order_by().
If you decide to go with a class Meta for the Author model (probably not as flexible as customizing the class-based view, but easy enough), you will end up with something like this:
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True, blank=True)
date_of_death = models.DateField('Died', null=True, blank=True)
def get_absolute_url(self):
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
return f'{self.last_name}, {self.first_name}'
class Meta:
ordering = ['last_name']
Of course, the field doesn’t need to be last_name: it could be any other.
Last but not least, you should sort by an attribute/column that actually has an index (unique or not) on your database to avoid performance issues. Of course, this will not be necessary here (we are probably getting ahead of ourselves with so few books and users), but it is something worth keeping in mind for future projects.
The second interesting (and non-obvious) thing in the template is where we display the status text for each book instance (“available”, “maintenance”, etc.). Astute readers will note that the method BookInstance.get_status_display() that we use to get the status text does not appear elsewhere in the code.
<p class="{\% if copy.status == 'a' \%}text-success{\% elif copy.status == 'm' \%}text-danger{\% else \%}text-warning{\% endif \%}">
</p>
This function is automatically created because BookInstance.status is a choices field. Django automatically creates a method get_FOO_display() for every choices field “Foo” in a model, which can be used to get the current value of the field.
Pagination
If you’ve just got a few records, our book list page will look fine. However, as you get into the tens or hundreds of records the page will take progressively longer to load (and have far too much content to browse sensibly). The solution to this problem is to add pagination to your list views, reducing the number of items displayed on each page.
Django has excellent inbuilt support for pagination. Even better, this is built into the generic class-based list views so you don’t have to do very much to enable it!
Views
Open catalog/views.py, and add the paginate_by line shown below.
class BookListView(generic.ListView):
model = Book
paginate_by = 10
With this addition, as soon as you have more than 10 records the view will start paginating the data it sends to the template. The different pages are accessed using GET parameters — to access page 2 you would use the URL /catalog/books/?page=2.
Templates
Now that the data is paginated, we need to add support to the template to scroll through the results set. Because we might want paginate all list views, we’ll add this to the base template.
Open /django-locallibrary-tutorial/catalog/templates/base_generic.html and find the “content block” (as shown below).
{\% block content \%}{\% endblock \%}
Copy in the following pagination block immediately following the {\% endblock \%}. The code first checks if pagination is enabled on the current page. If so, it adds next and previous links as appropriate (and the current page number).
{\% block pagination \%}
{\% if is_paginated \%}
<div class="pagination">
<span class="page-links">
{\% if page_obj.has_previous \%}
<a href="?page=">previous</a>
{\% endif \%}
<span class="page-current">
Page of .
</span>
{\% if page_obj.has_next \%}
<a href="?page=">next</a>
{\% endif \%}
</span>
</div>
{\% endif \%}
{\% endblock \%}
The page_obj is a Paginator object that will exist if pagination is being used on the current page. It allows you to get all the information about the current page, previous pages, how many pages there are, etc.
We use to get the current page URL for creating the pagination links. This is useful because it is independent of the object that we’re paginating.
Sessions framework
In a “real” library you may wish to provide individual users with a customized experience, based on their previous use of the site, preferences, etc. For example, you could hide warning messages that the user has previously acknowledged next time they visit the site, or store and respect their preferences (such as, the number of search results that they want to be displayed on each page).
The session framework lets you implement this sort of behavior, allowing you to store and retrieve arbitrary data on a per-site-visitor basis.
What are sessions?
All communication between web browsers and servers is via HTTP, which is stateless. The fact that the protocol is stateless means that messages between the client and server are completely independent of each other — there is no notion of “sequence” or behavior based on previous messages. As a result, if you want to have a site that keeps track of the ongoing relationships with a client, you need to implement that yourself.
Sessions are the mechanism used by Django (and most of the Internet) for keeping track of the “state” between the site and a particular browser. Sessions allow you to store arbitrary data per browser, and have this data available to the site whenever the browser connects. Individual data items associated with the session are then referenced by a “key”, which is used both to store and retrieve the data.
Django uses a cookie containing a special session id to identify each browser and its associated session with the site. The actual session data is stored in the site database by default (this is more secure than storing the data in a cookie, where they are more vulnerable to malicious users). You can configure Django to store the session data in other places (cache, files, “secure” cookies), but the default location is a good and relatively secure option.
Enable sessions
Sessions were enabled automatically when we created the skeleton website (in tutorial 2).
The configuration is set up in the INSTALLED_APPS and MIDDLEWARE sections of the project file (django-locallibrary-tutorial/locallibrary/settings.py), as shown below:
INSTALLED_APPS = [
# …
'django.contrib.sessions',
# …
MIDDLEWARE = [
# …
'django.contrib.sessions.middleware.SessionMiddleware',
# …
Use sessions
You can access the session attribute within a view from the request parameter (an HttpRequest passed in as the first argument to the view). This session attribute represents the specific connection to the current user (or to be more precise, the connection to the current browser, as identified by the session id in the browser’s cookie for this site).
The session attribute is a dictionary-like object that you can read and write as many times as you like in your view, modifying it as wished. You can do all the normal dictionary operations, including clearing all data, testing if a key is present, looping through data, etc. Most of the time though, you’ll just use the standard “dictionary” API to get and set values.
The code fragments below show how you can get, set, and delete some data with the key “my_car”, associated with the current session (browser).
Note: One of the great things about Django is that you don’t need to think about the mechanisms that tie the session to your current request in your view. If we were to use the fragments below in our view, we’d know that the information about my_car is associated only with the browser that sent the current request.
# Get a session value by its key (e.g. 'my_car'), raising a KeyError if the key is not present
my_car = request.session['my_car']
# Get a session value, setting a default if it is not present ('mini')
my_car = request.session.get('my_car', 'mini')
# Set a session value
request.session['my_car'] = 'mini'
# Delete a session value
del request.session['my_car']
Save session data
By default, Django only saves to the session database and sends the session cookie to the client when the session has been modified (assigned) or deleted. If you’re updating some data using its session key as shown in the previous section, then you don’t need to worry about this! For example:
# This is detected as an update to the session, so session data is saved.
request.session['my_car'] = 'mini'
If you’re updating some information within session data, then Django will not recognize that you’ve made a change to the session and save the data (for example, if you were to change “wheels” data inside your “my_car” data, as shown below). In this case you will need to explicitly mark the session as having been modified.
# Session object not directly modified, only data within the session. Session changes not saved!
request.session['my_car']['wheels'] = 'alloy'
# Set session as modified to force data updates/cookie to be saved.
request.session.modified = True
Note: You can change the behavior so the site will update the database/send cookie on every request by adding SESSION_SAVE_EVERY_REQUEST = True into your project settings (django-locallibrary-tutorial/locallibrary/settings.py).
Simple example — getting visit counts
As a simple real-world example we’ll update our library to tell the current user how many times they have visited the LocalLibrary home page.
Open /django-locallibrary-tutorial/catalog/views.py, and add the lines that contain num_visits into index() (as shown below).
def index(request):
# …
num_authors = Author.objects.count() # The 'all()' is implied by default.
# Number of visits to this view, as counted in the session variable.
num_visits = request.session.get('num_visits', 0)
request.session['num_visits'] = num_visits + 1
context = {
'num_books': num_books,
'num_instances': num_instances,
'num_instances_available': num_instances_available,
'num_authors': num_authors,
'num_visits': num_visits,
}
# Render the HTML template index.html with the data in the context variable.
return render(request, 'index.html', context=context)
Here we first get the value of the ‘num_visits’ session key, setting the value to 0 if it has not previously been set. Each time a request is received, we then increment the value and store it back in the session (for the next time the user visits the page). The num_visits variable is then passed to the template in our context variable.
Add the line shown at the bottom of the following block to your main HTML template (/django-locallibrary-tutorial/catalog/templates/index.html) at the bottom of the “Dynamic content” section to display the num_visits context variable.
<h2>Dynamic content</h2>
<p>The library has the following record counts:</p>
<ul>
<li><strong>Books:</strong> </li>
<li><strong>Copies:</strong> </li>
<li><strong>Copies available:</strong> </li>
<li><strong>Authors:</strong> </li>
</ul>
<p>
You have visited this page time.
</p>
Note that we use the Django built-in template tag pluralize to add an “s” when the page has been visited multiple times.
User authentication and permissions
Django provides an authentication and authorization (“permission”) system, built on top of the session framework discussed in the previous tutorial, that allows you to verify user credentials and define what actions each user is allowed to perform. The framework includes built-in models for Users and Groups (a generic way of applying permissions to more than one user at a time), permissions/flags that designate whether a user may perform a task, forms and views for logging in users, and view tools for restricting content.
Note: According to Django the authentication system aims to be very generic, and so does not provide some features provided in other web authentication systems. Solutions for some common problems are available as third-party packages. For example, throttling of login attempts and authentication against third parties (e.g. OAuth).
Enable authentication
The authentication was enabled automatically when we created the skeleton website (in tutorial 2) so you don’t need to do anything more at this point.
Note: The necessary configuration was all done for us when we created the app using the django-admin startproject command. The database tables for users and model permissions were created when we first called python manage.py migrate.
The configuration is set up in the INSTALLED_APPS and MIDDLEWARE sections of the project file (django-locallibrary-tutorial/locallibrary/settings.py), as shown below:
INSTALLED_APPS = [
# …
'django.contrib.auth', # Core authentication framework and its default models.
'django.contrib.contenttypes', # Django content type system (allows permissions to be associated with models).
# …
MIDDLEWARE = [
# …
'django.contrib.sessions.middleware.SessionMiddleware', # Manages sessions across requests
# …
'django.contrib.auth.middleware.AuthenticationMiddleware', # Associates users with requests using sessions.
# …
Createt users and groups
You already created your first user when we looked at the Django admin site in tutorial 4 (this was a superuser, created with the command python manage.py createsuperuser). Our superuser is already authenticated and has all permissions, so we’ll need to create a test user to represent a normal site user. We’ll be using the admin site to create our locallibrary groups and website logins, as it is one of the quickest ways to do so.
Note: You can also create users programmatically as shown below. You would have to do this, for example, if developing an interface to allow “ordinary” users to create their own logins (you shouldn’t give most users access to the admin site).
from django.contrib.auth.models import User
# Create user and save to the database
user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')
# Update fields and then save again
user.first_name = 'Tyrone'
user.last_name = 'Citizen'
user.save()
Note however that it is highly recommended to set up a custom user model when starting a project, as you’ll be able to easily customize it in the future if the need arises. If using a custom user model the code to create the same user would look like this:
# Get current user model from settings
from django.contrib.auth import get_user_model
User = get_user_model()
# Create user from model and save to the database
user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')
# Update fields and then save again
user.first_name = 'Tyrone'
user.last_name = 'Citizen'
user.save()
Setting up your authentication views
Django provides almost everything you need to create authentication pages to handle login, log out, and password management “out of the box”. This includes a URL mapper, views and forms, but it does not include the templates — we have to create our own!
In this section, we show how to integrate the default system into the LocalLibrary website and create the templates. We’ll put them in the main project URLs.
Note: You don’t have to use any of this code, but it is likely that you’ll want to because it makes things a lot easier. You’ll almost certainly need to change the form handling code if you change your user model, but even so, you would still be able to use the stock view functions.
Note: In this case, we could reasonably put the authentication pages, including the URLs and templates, inside our catalog application. However, if we had multiple applications it would be better to separate out this shared login behavior and have it available across the whole site, so that is what we’ve shown here!
Project URLs
Add the following to the bottom of the project urls.py file (django-locallibrary-tutorial/locallibrary/urls.py) file:
# Add Django site authentication urls (for login, logout, password management)
urlpatterns += [
path('accounts/', include('django.contrib.auth.urls')),
]
Navigate to the http://127.0.0.1:8000/accounts/ URL (note the trailing forward slash!). Django will show an error that it could not find a mapping for this URL, and list all the URLs that it tried. From this you can see the URLs that will work once we have created templates.
Note: Adding the accounts/ path as shown above adds the following URLs, along with names (given in square brackets) that can be used to reverse the URL mappings. You don’t have to implement anything else — the above URL mapping automatically maps the below mentioned URLs.
accounts/ login/ [name='login']
accounts/ logout/ [name='logout']
accounts/ password_change/ [name='password_change']
accounts/ password_change/done/ [name='password_change_done']
accounts/ password_reset/ [name='password_reset']
accounts/ password_reset/done/ [name='password_reset_done']
accounts/ reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/ reset/done/ [name='password_reset_complete']
Now try to navigate to the login URL (http://127.0.0.1:8000/accounts/login/). This will fail again, but with an error that tells you that we’re missing the required template (registration/login.html) on the template search path. You’ll see the following lines listed in the yellow section at the top:
Exception Type: TemplateDoesNotExist
Exception Value: registration/login.html
Template directory
The URLs (and implicitly, views) that we just added expect to find their associated templates in a directory /registration/ somewhere in the templates search path.
For this site, we’ll put our HTML pages in the templates/registration/ directory. This directory should be in your project root directory, that is, the same directory as the catalog and locallibrary folders. Please create these folders now.
Note: Your folder structure should now look like the below:
django-locallibrary-tutorial/ # Django top level project folder
catalog/
locallibrary/
templates/
registration/
To make the templates directory visible to the template loader we need to add it in the template search path. Open the project settings (/django-locallibrary-tutorial/locallibrary/settings.py).
Then import the os module (add the following line near the top of the file if it isn’t already present).
import os # needed by code below
Update the TEMPLATES section’s ‘DIRS’ line as shown:
# …
TEMPLATES = [
{
# …
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
# …
Login template
Warning: The authentication templates provided in this article are a very basic/slightly modified version of the Django demonstration login templates. You may need to customize them for your own use!
Create a new HTML file called /django-locallibrary-tutorial/templates/registration/login.html and give it the following contents:
{\% extends "base_generic.html" \%}
{\% block content \%}
{\% if form.errors \%}
<p>Your username and password didn't match. Please try again.</p>
{\% endif \%}
{\% if next \%}
{\% if user.is_authenticated \%}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{\% else \%}
<p>Please login to see this page.</p>
{\% endif \%}
{\% endif \%}
<form method="post" action="{\% url 'login' \%}">
{\% csrf_token \%}
<table>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</table>
<input type="submit" value="login">
<input type="hidden" name="next" value="">
</form>
{# Assumes you set up the password_reset view in your URLconf #}
<p><a href="{\% url 'password_reset' \%}">Lost password?</a></p>
{\% endblock \%}
This template shares some similarities with the ones we’ve seen before — it extends our base template and overrides the content block. The rest of the code is fairly standard form handling code, which we will discuss in a later tutorial. All you need to know for now is that this will display a form in which you can enter your username and password, and that if you enter invalid values you will be prompted to enter correct values when the page refreshes.
If you log in using valid credentials, you’ll be redirected to another page (by default this will be http://127.0.0.1:8000/accounts/profile/). The problem is that, by default, Django expects that upon logging in you will want to be taken to a profile page, which may or may not be the case. As you haven’t defined this page yet, you’ll get another error!
Open the project settings (/django-locallibrary-tutorial/locallibrary/settings.py) and add the text below to the bottom. Now when you log in you should be redirected to the site homepage by default.
Logout template
If you navigate to the logout URL (http://127.0.0.1:8000/accounts/logout/) then you’ll get an error because Django 5 does not allow logout using GET, only POST. We’ll add a form you can use to logout in a minute, but first we’ll create the page that users are taken to after logging out.
Create and open /django-locallibrary-tutorial/templates/registration/logged_out.html. Copy in the text below:
{\% extends "base_generic.html" \%}
{\% block content \%}
<p>Logged out!</p>
<a href="{\% url 'login'\%}">Click here to login again.</a>
{\% endblock \%}
This template is very simple. It just displays a message informing you that you have been logged out, and provides a link that you can press to go back to the login screen.