Overview

After we defined our models and created some initial movie records to work with, it’s time to write the code that presents that information to users. The first thing we need to do is determine what information we want to display in our pages, and define the URLs to use for returning those resources. Then we’ll create a URL mapper, views, and templates to display the pages.

Defining the resource URLs

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

The URLs that we’ll need for our pages are:

  • movies/ — The home (index) page.
  • movies/list/ — A list of all movies.
  • movies/director/ — A list of all directors.
  • movies/*<id>* — The detail view for a particular movie, with a field primary key of *<id>* (the default).
  • movies/director/*<id>* — The detail view for the specific director with a primary key field of <id>.

Whichever approach you use, the URLs should be kept clean, logical, and readable, as recommended by the W3C.

Creating the list page

The first page we’ll create is the list page . The index page will include some static HTML, along with generated “counts” of different records in the database. To make this work we’ll create a URL mapping, a view, and a template.

URL mapping

We firstly updated the yourproject/urls.py file to ensure that whenever an URL that starts with movies/ is received, the URLConf module movies.urls will process the remaining substring.

1
2
3
urlpatterns += [
path('movies/', include('movies.urls')),
]

We also created a placeholder file for the URLConf module, named /movies/urls.py. Add the following lines to that file:

1
2
3
4
5
6
7
8
9
10
11
12
from . import views
from django.urls import path

urlpatterns = [
# ex: /movies/
path('', views.index, name='index'),
# ex: /movies/list/
path('/list/', views.movie_list, name='movie_list'),
# ex: /movies/5
path('<int:movie_id>/', views.movie_detail, name='movie_detail'),
...
]

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:

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

And now let’s add a very simple views to ,these views are slightly different, because they take an argument:

1
2
3
4
5
6
7
8
9
from django.http import HttpResponse

def movie_detail(request, movie_id):
return HttpResponse("You're looking at movie %s." % movie_id)

## OR
def movie_detail(request, question_id):
response = "You're looking at the results of movie %s."
return HttpResponse(response % movie_id)

Your view can read records from a database, or not. It can use a template syste or a third-party Python template system such as jinjia2. It can generate a PDF file, output XML, create a ZIP file on the fly, anything you want, using whatever Python libraries you want.

Here’s one stab at a new movie_list() view, which displays the latest 5 movies and separated by commas, according to movie_release_date:

1
2
3
4
5
from .models import Movie
def movie_list(request):
latest_movie_list = Movie.objects.order_by('-movie_release_date')[:5]
output = ', '.join([m.movie_title for m in latest_movie_list])
return HttpResponse(output)

Then, we need to create a very simple template for render the latest_movie_list. Django will look for templates in there.

Put the following code in that template:

movie/templates/movie/movie-list.html

1
2
3
4
5
6
7
8
9
{% if latest_movie_list %}
<ul>
{% for m in latest_movie_list %}
<li><a href="/movies/{{ m.id }}/">{{ m.movie_title }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No movies are available.</p>
{% endif %}

Remember, when we wrote the link to a movie i, the link was partially hardcoded like this:

1
<li><a href="/movies/{{ m.id }}/">{{ m.movie_title }}</a></li>

You can remove a reliance on specific URL paths defined in your url configurations by using the {% url %} template tag:

1
<li><a href="{% url 'movie_detail' m.id %}">{{ m.movie_title  }}</a></li>

Now let’s update our view to use the template:

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.http import HttpResponse
from django.template import loader

from .models import Movie


def movie_list(request):
latest_movie_list = Movie.objects.order_by('-movie_release_date')[:5]
template = loader.get_template('movie/movie-list.html')
context = {
'latest_movie_list': latest_movie_list,
}
return HttpResponse(template.render(context, request))

It’s a very common idiom to load a template, fill a context and return an HttpResponseobject with the result of the rendered template. Django provides a shortcut.

The render() function takes the request object as its first argument, a template name as its second argument and a dictionary as its optional third argument.Here’s the full rewritten:

1
2
3
4
5
6
from django.shortcuts import render
# we no longer need to import loader and HttpResponse
def movie_list(request):
latest_movie_list = Movie.objects.order_by('-movie_release_date')[:5]
context = {'latest_movie_list': latest_movie_list}
return render(request, 'movie/movie-list.html', context)

Now we want to raising a 404 error:

the page that displays the question text for a given poll. Here’s the view:

1
2
3
4
5
6
7
8
9
from django.shortcuts import render
from django.http import Http404

def movie_detail(request,movie_id):
try:
movie = Movie.objects.get(pk=movie_id)
except Movie.DoesNotExist:
raise Http404("Movie does not exist")
return render(request, 'movie/movie_detail.html', {'movie': movie})

The new concept here: The view raises the Http404 exception if a movie with the requested ID doesn’t exist.

It’s a very common idiom to use get() and raise Http404 if the object doesn’t exist. Django provides a shortcut. Here’s the detail()view, rewritten:

1
2
3
4
5
from django.shortcuts import get_object_or_404, render

def movie_detail(request,movie_id):
movie = get_object_or_404(Movie, pk=movie_id)
return render(request, 'movie/movie_detail.html', {'movie': movie})

The get_object_or_404() function takes a Django model as its first argument and an arbitrary number of keyword arguments, which it passes to the get() function of the model’s manager. It raises Http404 if the object doesn’t exist.

There’s also a get_list_or_404() function, which works just as get_object_or_404() – except using filter() instead of get(). It raises Http404 if the list is empty.

1
2
3
4
5
6
7
8
9
10
11
from django.shortcuts import get_list_or_404

def movie_list(request):
movie_list = get_list_or_404(Movie, movie_is_fav=True)
# This example is equivalent to:
from django.http import Http404

def movie_list(request):
movie_list = list(Movie.objects.filter(movie_is_fav=True))
if not movie_list:
raise Http404("No Movie matches the given query.")

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.

For example, in a blog application, you might have the following views:

  • Blog homepage – displays the latest few entries.
  • Entry “detail” page – permalink page for a single entry.
  • Year-based archive page – displays all months with entries in the given year.
  • Month-based archive page – displays all days with entries in the given month.
  • Day-based archive page – displays all entries in the given day.
  • Comment action – handles posting comments to a given entry.

Open yourapps/views.py and note that the file already imports the render() shortcut function to generate HTML file using a template and data:

Paste the following lines at the bottom of the file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from django.shortcuts import render, redirect,get_object_or_404
from django.db.models import Q
from django.views.generic import View
# imports the model classes that we'll use to access data in all our views.
from .models import Movie, Director, Rating
from taggit.models import Tag
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import Q

def movie_list(request, tag_slug=None):
# Fetches the number of records using the `objects.all()`attribute on the model classes.
# Generate counts of some of the main objects
num_movies = Movie.objects.all().count()
# The 'all()' is implied by default.
num_ratings = Rating.objects.count()
# movie watched
num_movies_watched= Rating.objects.filter(rating_status__exact='h').count()

# if you are using django-taggit in your project
# https://pypi.org/project/django-taggit/
if tag_slug:
tag = get_object_or_404(Tag, slug=tag_slug)
object_list = Movie.objects.filter(tags__in=[tag])
else:
object_list = Movie.objects.all()
tag = None

# query, We'll talk more about this later
query = request.GET.get("q")
if query:
object_list = object_list.filter(
Q(movie_title__icontains=query) |
Q(director__director_name__icontains=query) |
Q(movie_summary__icontains=query) |
Q(movie_release_date_year__icontains=query) |
Q(tags__name__icontains=query)
).distinct()

# paginator, We'll talk more about this later
paginator = Paginator(object_list,25)
page = request.GET.get('page')
try:
movies = paginator.page(page)
except PageNotAnInteger:
movies = paginator.page(1)
except EmptyPage:
# last page
movies = paginator.page(paginator.num_pages)

context = {
'page': page,
'movies': movies,
'tag': tag,
'num_movies': num_movies,
'num_ratings': num_ratings,
'num_movies_watched': num_movies_watched,
}

return render(request, 'movies/movie_list.html', context)

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.

We’ll talk more about templates and the context variable in the next section.

Staticfiles

Django does not serve static files like CSS, JavaScript, and images by default, but it can be useful for the development web server to do so while you’re creating your site. As a final addition to this URL mapper, you can enable the serving of static files during development by appending the following lines.

Open fragments/urls.py:

1
2
3
4
5
6
7
8
...
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

# This helper function works only in debug mode and only if the given prefix is local (e.g. /static/) and not a URL (e.g. http://static.example.com/).

In addition, we included the import line (from django.urls import include) with the code that uses it (so it is easy to see what we’ve added), but it is common to include all your import lines at the top of a Python file.

Aside from the HTML generated by the server, web applications generally need to serve additional files — such as images, JavaScript, or CSS — necessary to render the complete web page. In Django, we refer to these files as “static files”.

For small projects, this isn’t a big deal, because you can just keep the static files somewhere your web server can find it. However, in bigger projects – especially those comprised of multiple apps – dealing with the multiple sets of static files provided by each application starts to get tricky.

That’s what django.contrib.staticfiles is for: it collects static files from each of your applications (and any other places you specify) into a single location that can easily be served in production.

First, create a directory called static in your project directory. Django will look for static files there, Django’s STATICFILES_FINDERS setting contains a list of finders that know how to discover static files from various sources.

1
2
3
{% load static %}

<link rel="stylesheet" type="text/css" href="{% static 'movie/css/style.css' %}">

The {% static %} template tag generates the absolute URL of static files,which means <link rel="stylesheet" type="text/css" href="/static/ratings/css/style.css">, you will find this file in http://127.0.0.1:8080/static/movie/css/style.css

That’s all you need to do for development.

Next, we’ll create a subdirectory for images. Create an images subdirectory in the movie/static/movie/images directory. In other words, put your image in movie/static/movie/images/yourname.jpg.

1
2
3
body {
background: white url("images/demo.jpg") no-repeat 0px 0px;
}

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.

Django will automatically look for templates in a directory named ‘templates’ in your application.

Referencing static files in templates

Your project is likely to use static resources, including JavaScript, CSS, and images. Because the location of these files might not be known (or might change), Django allows you to specify the location 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.

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

Base 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.

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 sample below includes common HTML with sections for a title, a sidebar, and main contents marked with the named block and endblock template tags, shown in bold. You can leave the blocks empty, or include default content to use when rendering pages derived from the template.

Create a new file movie_base.html in /movie/templates/movie and paste the following code to the file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
{% block title %}<title>My Movies</title>{% endblock %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.5.1/css/bootstrap.min.css" rel="stylesheet">
<!-- Add additional CSS in static file -->
{% load static %}
<link href="{% static 'css/movie.css' %}" rel="stylesheet" type="text/css" />
<script src="{% static 'js/movie.js' %}"></script>
</head>

<body>
<div class="container-fluid">
<div class="row">
<div class="col-sm-2">
{% block sidebar %}
<ul class="sidebar-nav">
<li><a href="">All Movies</a></li>
<li><a href="">All Directors</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 (movie.css) that provides additional styling. Create a movie.css file and paste the following code in the file:

1
2
3
4
5
.sidebar-nav {
margin-top: 20px;
padding: 0;
list-style: none;
}

Extending templates

Create a new HTML file movie_list.html in 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.

The template uses the for and endfor template tags to loop through the movie list, as shown below. Each iteration populates the book template variable with information for the current list item.

1
2
3
{% for movie in movies %}
<div> <!-- code here get information from each movieitem --> </div>
{% endfor %}

The code inside the loop creates a list item for each moviethat shows both the title (as a link to the yet-to-be-created detail view) and the director, and we access the fields of the associated movierecord using the “dot notation” in django.

1
<a href="{{ movie.get_absolute_url }}">{{ movie.movie_title }}</a> ({{movie.movie_director}})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{% extends 'movie/movie_base.html' %}
{% block content %}
<div class="card-columns text-center">
{% for movie in movies %}
<div class="card p-3">
<div class="text-center play">
<img class="card-img-top" src="{{ movie.movie_poster.url }}" alt="Card image" style="margin-top: 15px" />
</div>
<div class="card-body text-center">
<a class="btn btn-light" href="#">{{ movie.movie_title }}({{ movie.movie_title_CN}})</a>
<p class="card-text"><small class="text-muted">{{ movie.movie_release_date }}</small></p>
<p class="tags">
{% for tag in movie.movie_genres.all %}
<a href="{% url 'movie:movie_list_by_tag' tag.slug %}" class="badge badge-light">
{{ tag.name }}
</a>
{% if not forloop.last %}, {% endif %} {% endfor %}
</p>
<p class="card-text">
{{ movie.movie_summary|truncatewords:30|linebreaks }}</p>
</div>
</div>
{% empty %}
<a href="{% url 'movie:movie_list' %}">
<button class="btn btn-primary">
<i class="fas fa-send"></i>No results.
</button>
</a>
{% endfor %}
</div>
{% include 'pagination.html' with page=movies %}
{% endblock %}

We can also use the if, else, and endif template tags to check whether the movies has been defined and is not empty.

1
2
3
4
5
6
7
8
9
{% extends 'movie/movie_base.html' %}
{% block content %}
{% if movies %}
...
{% include 'pagination.html' with page=movies %}
{% else %}
<p>There are no movies here.</p>
{% endif %}
{% endblock %}

The base template below introduced the url template tag.

1
<a href="{% url 'movie:movie_list' %}">Home</a>

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.

The movie detail in the template above has an empty URL because we’ve not yet created a detail page. Once that exists, you should update the URL like this:

1
<a href="{% url 'movie-detail' movie.pk %}">{{ movie.title }}</a>

movie-list

Configuring where to find the templates

You need to point Django to search for your templates in the templates folder. To do that, add the templates dir to the TEMPLATES object by editing the settings.py file as shown in bold in the following code sample:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
],
'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',
],
},
},
]

TemplateTags

Cycle

Produces one of its arguments each time this tag is encountered. The first argument is produced on the first encounter, the second argument on the second encounter, and so forth. Once all arguments are exhausted, the tag cycles to the first argument and produces it again.

This tag is particularly useful in a loop:

1
2
3
4
5
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}

The first iteration produces HTML that refers to class row1, the second to row2, the third to row1 again, and so on for each iteration of the loop.

You can use variables, too. For example, if you have two template variables, rowvalue1 and rowvalue2, you can alternate between their values like this:

1
2
3
4
5
{% for o in some_list %}
<tr class="{% cycle rowvalue1 rowvalue2 %}">
...
</tr>
{% endfor %}

Variables included in the cycle will be escaped. You can disable auto-escaping with:

1
2
3
4
5
{% for o in some_list %}
<tr class="{% autoescape off %}{% cycle rowvalue1 rowvalue2 %}{% endautoescape %}">
...
</tr>
{% endfor %}

You can mix variables and strings:

1
2
3
4
5
{% for o in some_list %}
<tr class="{% cycle 'row1' rowvalue2 'row3' %}">
...
</tr>
{% endfor %}

In some cases you might want to refer to the current value of a cycle without advancing to the next value. To do this, give the {% cycle %} tag a name, using “as”, like this:

1
{% cycle 'row1' 'row2' as rowcolors %}

From then on, you can insert the current value of the cycle wherever you’d like in your template by referencing the cycle name as a context variable. If you want to move the cycle to the next value independently of the original cycle tag, you can use another cycle tag and specify the name of the variable. So, the following template:

1
2
3
4
5
6
7
8
<tr>
<td class="{% cycle 'row1' 'row2' as rowcolors %}">...</td>
<td class="{{ rowcolors }}">...</td>
</tr>
<tr>
<td class="{% cycle rowcolors %}">...</td>
<td class="{{ rowcolors }}">...</td>
</tr>

would output:

1
2
3
4
5
6
7
8
<tr>
<td class="row1">...</td>
<td class="row1">...</td>
</tr>
<tr>
<td class="row2">...</td>
<td class="row2">...</td>
</tr>

You can use any number of values in a cycle tag, separated by spaces. Values enclosed in single quotes (') or double quotes (") are treated as string literals, while values without quotes are treated as template variables.

By default, when you use the as keyword with the cycle tag, the usage of {% cycle %} that initiates the cycle will itself produce the first value in the cycle. This could be a problem if you want to use the value in a nested loop or an included template. If you only want to declare the cycle but not produce the first value, you can add a silent keyword as the last keyword in the tag. For example:

1
2
3
4
{% for obj in some_list %}
{% cycle 'row1' 'row2' as rowcolors silent %}
<tr class="{{ rowcolors }}">{% include "subtemplate.html" %}</tr>
{% endfor %}

This will output a list of <tr> elements with class alternating between row1 and row2. The subtemplate will have access to rowcolors in its context and the value will match the class of the <tr> that encloses it. If the silent keyword were to be omitted, row1 and row2 would be emitted as normal text, outside the <tr> element.

When the silent keyword is used on a cycle definition, the silence automatically applies to all subsequent uses of that specific cycle tag. The following template would output nothing, even though the second call to {% cycle %} doesn’t specify silent:

1
2
{% cycle 'row1' 'row2' as rowcolors silent %}
{% cycle rowcolors %}

You can use the resetcycle tag to make a {% cycle %} tag restart from its first value when it’s next encountered.

linebreaks

Replaces line breaks in plain text with appropriate HTML; a single newline becomes an HTML line break (<br>) and a new line followed by a blank line becomes a paragraph break (</p>).

For example:

1
{{ value|linebreaks }}

If value is Joel\nis a slug, the output will be <p>Joel<br>is a slug</p>.

Truncatewords

Truncates a string after a certain number of words.

Argument: Number of words to truncate after

For example:

1
{{ value|truncatewords:2 }}

If value is "Joel is a slug", the output will be "Joel is …".

Newlines within the string will be removed.

truncatewords_html is similar to truncatewords, except that it is aware of HTML tags. Any tags that are opened in the string and not closed before the truncation point, are closed immediately after the truncation.

This is less efficient than truncatewords, so should only be used when it is being passed HTML text.

For example:

1
{{ value|truncatewords_html:2 }}

If value is "<p>Joel is a slug</p>", the output will be "<p>Joel is …</p>".

Newlines in the HTML content will be preserved

REFERENCES