Django Tutorial #7: Create Views and Templates

Django Tutorial #7: Create Views and Templates

Please note that this post may contain affiliate links, and for every purchase you make, at no extra cost to you, a commission fee will be rewarded to me.

You can download the source code of this tutorial here.

In this article, we’ll start by creating the URL configurations for our website.

URL Configurations

Django_31_Tutorial/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('blog.urls')),
]

blog/urls.py

from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from . import views

urlpatterns = [
    path('', views.home, name='home'),
    path('category/<slug:slug>', views.category, name='category'),
    path('tag/<slug:slug>', views.tag, name='tag'),
    path('post/<slug:slug>', views.post, name='post'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT,) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

In the last line, this is how we can use the media and static files during development, but it is not suitable for production.

Create Views

Each view corresponds to one page. For example, in our project, we need four views.

A home view which we created previously. A category view that corresponds to the category page, which shows a list of all posts belongs to a specific category. A tag view that corresponds to the tag page, which shows a list of all posts that has a specific tag.

And finally, the most important one is the post view, which corresponds to a page displaying the content of a post.

But, before we start, remember to import all the models for our project.

from .models import Category, Tag, Post, General

Update Home View

First, we need to update our home view. The one we just create is too simple, and we want our home page to display a list of all posts.

def home(request):
    general = General.objects.first()
    posts = Post.objects.all()
    categories = Category.objects.all()
    tags = Tag.objects.all()

    return render(request, 'blog/home.html', {
        'general': general,
        'posts': posts,
        'categories': categories,
        'tags': tags,
    })

Create Category View

def category(request, slug):
    general = General.objects.first()
    posts = Post.objects.filter(category__slug=slug)
    requested_category = Category.objects.get(slug=slug)
    categories = Category.objects.all()
    tags = Tag.objects.all()

    return render(request, 'blog/category.html', {
        'general': general,
        'posts': posts,
        'category': requested_category,
        'categories': categories,
        'tags': tags,
    })

The category view takes an extra argument slug, which is sent from the URL, and use this slug to find the specific category we need.

Line 3, filter() can be used to get a group of entries that fits a certain criteria. In this case, we use it to get all the posts whose category’s slug is slug.

Line 4, get() can be used to get one specific entry that fits a certain criteria. In this case, we use it to get the category whose slug is slug.

Create Tag View

def tag(request, slug):
    general = General.objects.first()
    posts = Post.objects.filter(tag__slug=slug)
    requested_tag = Tag.objects.get(slug=slug)
    categories = Category.objects.all()
    tags = Tag.objects.all()

    return render(request, 'blog/tag.html', {
        'general': general,
        'posts': posts,
        'tag': requested_tag,
        'categories': categories,
        'tags': tags,
    })

Create Post View

def post(request, slug):
    general = General.objects.first()
    requested_post = Post.objects.get(slug=slug)
    posts = Post.objects.all()
    categories = Category.objects.all()
    tags = Tag.objects.all()

    return render(request, 'blog/post.html', {
        'general': general,
        'post': requested_post,
        'posts': posts,
        'categories': categories,
        'tags': tags,
    })

Create Templates

Finally, it is time to create the templates. Let’s start with template inheritance.

In this tutorial, we will be using this Bootstrap template for our views.

img

You can download the source code here:

Assuming you understand how HTML, CSS and JS work. This is the view structure I designed:

Layout

  • templates
    • blog
      • vendor
        • posts-list.html
        • sidebar.html
      • master.html
      • home.html
      • category.html
      • tag.html
      • post.html

Master

<!DOCTYPE html>
<html lang="en">

<head>

    {% block meta %}

    {% endblock %}

    <title>{{ general.name }}</title>

    {% load static %}
    <!-- Bootstrap core CSS -->
    <link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="{% static "css/style.css" %}" rel="stylesheet">

</head>

<body>

<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
    <div class="container">
        <a class="navbar-brand" href="#">
            <img src="{{ general.logo.url }}" width="60" height="40" class="d-inline-block align-top" alt="" loading="lazy">
            {{ general.name }}</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
                aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarResponsive">
            <ul class="navbar-nav ml-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="{% url 'home' %}">Home</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="https://www.techjblog.com/index.php/django-tutorial-for-beginners/">Django Tutorial For Beginners</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/admin">Admin</a>
                </li>
            </ul>
        </div>
    </div>
</nav>

{% block content %}

{% endblock %}

<!-- Footer -->
<footer class="py-5 bg-dark">
    <div class="container">
        <p class="m-0 text-center text-white">Copyright &copy; {{ general.name }}</p>
    </div>
    <!-- /.container -->
</footer>

<!-- Bootstrap core JavaScript -->
<script src="{% static "jquery/jquery.min.js" %}"></script>
<script src="{% static "js/bootstrap.bundle.min.js" %}"></script>

</body>

</html>

Line 6 to 8, we define a block named meta, which we can use to store metadata of the web page.

Line 12 to 17, this is how we can use local static files. Remember, like we talked about in the beginning, all the static files for the blog app should be in the directory blog/static/ during development.

Line 36, {% url 'home' %} is the reverse of the URL pattern with the name “home”. In this case, it generates /. You can try to change this URL pattern see if the result changes.

Line 49 to 51, we define a block named content.

Post List

{% for post in posts %}
    <!-- Blog Post -->
    <div class="card mb-4">
        <img class="card-img-top" src="{{ post.featured_image.url }}" alt="{{ post.title }}">
        <div class="card-body">
            <h2 class="card-title">{{ post.title }}</h2>
            <p class="card-text">{{ post.content|striptags|truncatewords_html:50 }}</p>
            <a href="{% url 'post' post.slug %}" class="btn btn-primary">Read More &rarr;</a>
        </div>
        <div class="card-footer text-muted">
            Posted on {{ post.created_at }} by
            <a href="#">{{ post.user }}</a>
        </div>
    </div>
{% endfor %}

This is a for loop, it will go through all the posts that was sent by the view.

Sidebar

<!-- Sidebar Widgets Column -->
<div class="col-md-4">

    <!-- Search Widget -->
    <div class="card my-4">
        <h5 class="card-header">Search</h5>
        <form class="card-body" action="" method="GET" role="search">
            <div class="input-group">
                <label>
                    <input type="text" class="form-control" placeholder="Search for..." name="q">
                </label>
                <span class="input-group-btn">
                <button class="btn btn-secondary" type="submit">Go!</button>
              </span>
            </div>
        </form>
    </div>

    <!-- Categories Widget -->
    <div class="card my-4">
        <h5 class="card-header">Categories</h5>
        <div class="card-body">
            <div class="row">
                <div class="col-lg-6">
                    <ul class="list-unstyled mb-0">
                        {% for category in categories %}
                            <li>
                                <a href="{% url 'category' category.slug %}">{{ category.name }}</a>
                            </li>
                        {% endfor %}
                    </ul>
                </div>
            </div>
        </div>
    </div>

    <!-- Tags Widget -->
    <div class="card my-4">
        <h5 class="card-header">Tags</h5>
        <div class="card-body">
            <div class="row">
                <div class="col-lg-6">
                    <ul class="list-unstyled mb-0">
                        {% for tag in tags %}
                            <li>
                                <a href="{% url 'tag' tag.slug %}">{{ tag.name }}</a>
                            </li>
                        {% endfor %}
                    </ul>
                </div>
            </div>
        </div>
    </div>

    <!-- Side Widget -->
    <div class="card my-4">
        <h5 class="card-header">Side Widget</h5>
        <div class="card-body">
            You can put anything you want inside of these side widgets. They are easy to use, and feature the new
            Bootstrap 4 card containers!
        </div>
    </div>

</div>

Line 28, {% url 'category' category.slug %} is the reverse for the URL pattern whose name is “category”. This particular URL pattern requires an argument slug, which in this case is provided by category.slug.

If you have a URL pattern which require more than one argument, three for example, the syntax should be {% url 'name' arg1 arg2 arg3 %}.

Home Page

{% extends "blog/master.html" %}

{% block meta %}

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="{{ general.description }}">
    <meta name="author" content="">

{% endblock %}


{% block content %}

    <!-- Page Content -->
    <div class="container">
        <div class="row">
            <!-- Blog Entries Column -->
            <div class="col-md-8">
                <h1 class="my-4">{{ general.name }}
                    <small>Home Page</small>
                </h1>
                {% include 'blog/vendor/post_list.html' %}
            </div>
            {% include 'blog/vendor/sidebar.html' %}
        </div>
        <!-- /.row -->
    </div>
    <!-- /.container -->

{% endblock %}

Line 23, Django will find the post_list.html and put it here.

Category Page

{% extends "blog/master.html" %}

{% block meta %}

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="{{ category.description }}">
    <meta name="author" content="">

{% endblock %}


{% block content %}

    <!-- Page Content -->
    <div class="container">
        <div class="row">
            <!-- Blog Entries Column -->
            <div class="col-md-8">
                <h1 class="my-4">{{ category.name }}
                    <small>Category Page</small>
                </h1>
                <p>{{ category.description }}</p>
                {% include 'blog/vendor/post_list.html' %}
            </div>
            {% include 'blog/vendor/sidebar.html' %}
        </div>
        <!-- /.row -->
    </div>
    <!-- /.container -->

{% endblock %}

Tag Page

{% extends "blog/master.html" %}

{% block meta %}

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="{{ tag.description }}">
    <meta name="author" content="">

{% endblock %}


{% block content %}

    <!-- Page Content -->
    <div class="container">
        <div class="row">
            <!-- Blog Entries Column -->
            <div class="col-md-8">
                <h1 class="my-4">{{ tag.name }}
                    <small>Tag Page</small>
                </h1>
                <p>{{ tag.description }}</p>
                {% include 'blog/vendor/post_list.html' %}
            </div>
            {% include 'blog/vendor/sidebar.html' %}
        </div>
        <!-- /.row -->
    </div>
    <!-- /.container -->

{% endblock %}

Post Page

{% extends "blog/master.html" %}

{% block meta %}

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="{{ post.description }}">
    <meta name="author" content="">

{% endblock %}


{% block content %}

    <!-- Page Content -->
    <div class="container">

        <div class="row">

            <!-- Post Content Column -->
            <div class="col-lg-8">

                <!-- Title -->
                <h1 class="mt-4">{{ post.title }}</h1>

                <!-- Author -->
                <p class="lead">
                    by
                    <a href="#">{{ post.user }}</a>
                </p>

                <hr>

                <!-- Date/Time -->
                <p>Posted on {{ post.created_at }}</p>

                <hr>

                <!-- Preview Image -->
                <img class="img-fluid rounded" src="{{ post.featured_image.url }}" alt="">

                <hr>

                <!-- Post Content -->
                {{ post.content|safe }}
                <hr>

                <!-- Comments Form -->
                <div class="card my-4">
                    <h5 class="card-header">Leave a Comment:</h5>
                    <div class="card-body">
                        <form>
                            <div class="form-group">
                                <textarea class="form-control" rows="3"></textarea>
                            </div>
                            <button type="submit" class="btn btn-primary">Submit</button>
                        </form>
                    </div>
                </div>

                <!-- Single Comment -->
                <div class="media mb-4">
                    <img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">
                    <div class="media-body">
                        <h5 class="mt-0">Commenter Name</h5>
                        Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras
                        purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi
                        vulputate fringilla. Donec lacinia congue felis in faucibus.
                    </div>
                </div>

                <!-- Comment with nested comments -->
                <div class="media mb-4">
                    <img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">
                    <div class="media-body">
                        <h5 class="mt-0">Commenter Name</h5>
                        Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras
                        purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi
                        vulputate fringilla. Donec lacinia congue felis in faucibus.

                        <div class="media mt-4">
                            <img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">
                            <div class="media-body">
                                <h5 class="mt-0">Commenter Name</h5>
                                Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante
                                sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce
                                condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
                            </div>
                        </div>

                        <div class="media mt-4">
                            <img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">
                            <div class="media-body">
                                <h5 class="mt-0">Commenter Name</h5>
                                Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante
                                sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce
                                condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
                            </div>
                        </div>

                    </div>
                </div>

            </div>

            {% include 'blog/vendor/sidebar.html' %}

        </div>
        <!-- /.row -->

    </div>
    <!-- /.container -->

{% endblock %}

Line 45, the safe filter here is very important. If you don’t include this filter, the HTML tags will be displayed instead of being rendered into the page.

Leave a Reply

Your email address will not be published. Required fields are marked *