Convert a Django function-based-view into a class-based-view (DetailView)

doridoro

DoriDoro

Posted on September 25, 2024

Convert a Django function-based-view into a class-based-view (DetailView)

Introduction

To convert this function-based view (FBV) into a class-based view (CBV), I will use the Django's DetailView, which is specifically designed to display a single object from the database.

This is the code of the Post model which is used for both views:

# models.py

from django.db import models
from django.utils import timezone


class Post(models.Model):
    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250, unique_for_date="publish")
    author = models.ForeignKey(
        "account.User", on_delete=models.CASCADE, related_name="blog_posts"
    )
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)


    def __str__(self):
        return self.title
Enter fullscreen mode Exit fullscreen mode

Here's the function-based view I'm creating for a single post view. In the URL, the day/month/year/slug is displayed, such as blog/1/1/2024/test-blog/. With this way of displaying the URL, we provide the search engines with SEO-friendly URL's.

Function-Based view (FBV):

# views.py

from django.shortcuts import render, get_object_or_404

from .models import Post

def post_detail(request, day, month, year, post):
    post = get_object_or_404(
        Post,
        status=Post.Status.PUBLISHED,
        publish__day=day,
        publish__month=month,
        publish__year=year,
        slug=post,
    )
    return render(request, "post/detail.html", {"post": post})
Enter fullscreen mode Exit fullscreen mode

Here you can see the pattern of the URL.

# urls.py

from django.urls import path

from blog import views


urlpatterns = [
    path(
        "<int:day>/<int:month>/<int:year>/<slug:post>/",
        views.post_detail,
        name="post_detail",
    ),
]
Enter fullscreen mode Exit fullscreen mode

This is the html template of the list of all posts which will lead to a single detailed post.

# html template for listing all posts

{% extends "base.html" %}

{% block content %}
  <h1>My Blog</h1>
  {% for post in posts %}
    <h2>
      <a href="{% url 'blog:post_detail' day=post.publish.day month=post.publish.month year=post.publish.year post=post.slug %}">
        {{ post.title }}
      </a>
    </h2>
  <p class="date">
    Published {{ post.publish }} by {{ post.author }}
  </p>
  {{ post.body|truncatewords:30|linebreaks }}
  {% endfor %}
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

When you click on the link of the {{ post.title }} and been redirected to a detailed view of one post, the URL will be like: blog/1/1/2024/test-blog/ (blog/day/month/year/slug-of-the-post/)


Now, I will show you how the function-based view has been converted to a class-based view:

Class-Based View (CBV) Conversion:

# views.py

from django.views.generic.detail import DetailView
from django.shortcuts import get_object_or_404

from .models import Post

class PostDetailView(DetailView):
    model = Post
    template_name = "post/detail.html"
    context_object_name = "post"

    def get_object(self):
        return get_object_or_404(
            Post,
            status=Post.Status.PUBLISHED,
            publish__day=self.kwargs['day'],
            publish__month=self.kwargs['month'],
            publish__year=self.kwargs['year'],
            slug=self.kwargs['post']
        )
Enter fullscreen mode Exit fullscreen mode

Breakdown:

  • DetailView: Handles the logic of displaying a single object.
  • model: Defines the model (i.e., Post) to be used in the view.
  • template_name: Specifies the template file to be used ("post/detail.html").
  • context_object_name: Sets the context variable name for the object passed to the template ("post").
  • get_object: Customizes how the object is retrieved using the same logic from your FBV with get_object_or_404.

URL Pattern:

The URL pattern for this view, will be the same like before and you have to pass the day, month, year, and post parameters to the view:

from django.urls import path
from .views import PostDetailView

urlpatterns = [
    path('<int:year>/<int:month>/<int:day>/<slug:post>/', PostDetailView.as_view(), name='post_detail'),
]
Enter fullscreen mode Exit fullscreen mode

This approach keeps the view clean and leverages Django's class-based view system for more reusability and structure.

💖 💪 🙅 🚩
doridoro
DoriDoro

Posted on September 25, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Choosing the Right Relational Database
undefined Choosing the Right Relational Database

November 29, 2024

Can a Solo Developer Build a SaaS App?
undefined Can a Solo Developer Build a SaaS App?

November 29, 2024

This Week In Python
python This Week In Python

November 29, 2024