DEV Community

DoriDoro
DoriDoro

Posted on

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.

Top comments (0)