Class-based Views

Django Class-based Views offers built-in functionality that handles backend logic.

There are different types of class-based views:

  • Detail Views
    • DetailView
  • List Views
    • ListView
  • Editing views
    • FormView
    • CreateView
    • UpdateView
    • DeleteView
  1. Since homepage is listing all blog post, we’ll use ListView to update blog/ views.py: class PostListView(ListView):

Within the ListView, we’ll need to create a model called Post: model = Post

Add template for our homepage: template_name = 'blog/index.html'

To change the order of the post from newest to oldest, we need the - sign: ordering = ['-date']

  1. Open blog/urls.py

import: from .views import PostListView Replace homeview with PostListView:

path('', PostListView.as_view(), name='blog-home')

When we use a class-based view, it needs to be converted into an actual view, so we use a method called as_view().

  1. Create a detailview in views.py
class PostDetailView(DetailView):
    model = Post

in urls.py

  • import PostDetailView
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),

In this step, we’ll create a template that will display our PostDetail.

Create a post_detail.html in blog/templates It will be similar to index.html, but all the post will become object, and we don’t need to loop over the article.

{% extends "blog/base.html" %} {% block content %}
<article class="media content-section">
  <img
    class="rounded-circle article-img"
    src="{{ object.author.profile.image.url }}"
  />
  <div class="media-body">
    <div class="article-metadata">
      <a class="mr-2" href="#">{{ object.author }}</a>
      <small class="text-muted">{{ object.date|date:"F d, Y" }}</small>
      <!-- Add button for Update and Delete-->
      {% if object.author == user %}
      <div>
        <a
          class="btn btn-secondary btn-sm mt-1 mb-1"
          href="{% url 'post-update' object.id %}"
          >Update</a
        >
        <a
          class="btn btn-danger btn-sm mt-1 mb-1"
          href="{% url 'post-delete' object.id %}"
          >Delete</a
        >
      </div>
      {% endif %}
    </div>
    <h2 class="article-title">{{ object.title }}</h2>
    <p class="article-content">{{ object.content }}</p>
  </div>
</article>
{% endblock content %}
  1. Add a link to the route of an individual post

Right now, they are just dead links. So we need to update the link to the post title.

<h2>
  <a class="article-title" href="{% url 'post-detail' post.id%}"
    >{{ post.title }}</a
  >
</h2>
  1. Create a CreatView In views.py
  • Import CreateView In urls.py
  • Import PostCreateView
  • Add urlpatterns
path('post/new/', PostCreateView.as_view(), name='post-create'),

create post post_form.html template

It’s quite similar to register.html template

{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
    <div class="content-section">
        <form method="POST">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Blog Post</legend>
                {{ form|crispy }}
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-info" type="submit">Post</button>
            </div>
        </form>
    </div>
{% endblock content %}

Tell Django how to find the URL of a model object is to create a get absolute URL method in our model that returns that path.

Add this blog/models.py

def get_absolute_url(self):
        return reverse('post-detail', kwargs={'pk': self.pk})

It becomes:

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    date = models.DateTimeField(default=timezone.now)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('post-detail', kwargs={'pk': self.pk})
  • Update Navbar, so it has a Create new post button under {% if user.is_authenticated %}
<a class="nav-item nav-link" href="{% url 'post-create' %}">New Post</a>
  1. Create a UpdateView in views.py
  • import UserPassesTestMixin to check only the user can update the post.
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Post
    fields = ['title', 'content']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

    def test_func(self):
        post = self.get_object()
        if self.request.user == post.author:
            return True
        return False

in urls.py

path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
  1. Create a DeleteView

In views.py

  • Import DeleteView
  • Add PostDeleteView class
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Post
    success_url = '/' # when post is deleted, redirect user to Homepage

    def test_func(self):
        post = self.get_object()
        if self.request.user == post.author:
            return True
        return False

in urls.py

path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),

Create delete form template: post_confirm_delete.html.

{% extends "blog/base.html" %} {% block content %}
<div class="content-section">
  <form method="POST">
    {% csrf_token %}
    <fieldset class="form-group">
      <legend class="border-bottom mb-4">Delete Post</legend>
      <h2>Are you sure you want to delete the post "{{ object.title }}"</h2>
    </fieldset>
    <div class="form-group">
      <button class="btn btn-outline-danger" type="submit">Yes, Delete</button>
      <a
        class="btn btn-outline-secondary"
        href="{% url 'post-detail' object.id %}"
        >Cancel</a
      >
    </div>
  </form>
</div>
{% endblock content %}

Overall, here is the final version of our urls.py.

from django.urls import path
from .views import (
    PostListView,
    PostDetailView,
    PostCreateView,
    PostUpdateView,
    PostDeleteView
)
from . import views

urlpatterns = [
    path('', PostListView.as_view(), name='blog-home'),
    path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
    path('post/new/', PostCreateView.as_view(), name='post-create'),
    path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
    path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),
    path('about/', views.about, name='blog-about'),
]

and views.py

from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import (
    ListView,
    DetailView,
    CreateView,
    UpdateView,
    DeleteView
)
from .models import Post


def home(request):
    context = {
        'posts': Post.objects.all()
    }
    return render(request, 'blog/home.html', context)


class PostListView(ListView):
    model = Post
    template_name = 'blog/home.html'  # <app>/<model>_<viewtype>.html
    context_object_name = 'posts'
    ordering = ['-date_posted']


class PostDetailView(DetailView):
    model = Post


class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ['title', 'content']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)


class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Post
    fields = ['title', 'content']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

    def test_func(self):
        post = self.get_object()
        if self.request.user == post.author:
            return True
        return False


class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Post
    success_url = '/'

    def test_func(self):
        post = self.get_object()
        if self.request.user == post.author:
            return True
        return False


def about(request):
    return render(request, 'blog/about.html', {'title': 'About'})