GENERIC VIEWS = BEST VIEWS?

Posted on 14 September 2024 by admin

For me, views in Django come to arguably be the trickiest aspect to try and master. Perhaps it was because they seemed like a bit of an abstract intermediary object, or because there are multiple approaches that require some familiarity to decide which suits the current application best.

The very first views I used were the standard, function-based type:

from django.core.paginator import Paginator, EmptyPage
from django.http import Http404
from django.shortcuts import render
from django.utils import timezone
from .forms import SearchForm
from .models import Post


def blog_home(request):
    posts = Post.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")
    form = SearchForm()
    paginator = Paginator(posts, 5)
    page = request.GET.get("page")

    if not page:
        page = 1
    else:
        try:
            page = int(page)
        except ValueError:
            raise Http404()

    try:
        paginated_posts = paginator.page(page)
    except EmptyPage:
        raise Http404()
    return render(
        request,
        "blog/home.html",
        {"tree_posts": posts, "form": form, "paginated_posts": paginated_posts},
    )

As you can see, being the "messenger" between the model and template, views do take some setting up, depending on your requirements. In my case, the need for pagination of the post listing and the different scenarios that may occur with page requests need to be accommodated. Additionally, compiling the context variable dictionary for the render function is somewhat cumbersome, especially if the number of variables is large.

After learning more about views, I saw that they may alternatively be encapsulated in a class by subclassing the View parent class. I switched to this approach, as it seemed tidier and more modular:

from django.views import View


class BlogHomeView(View):
    def get(self, request):
        posts = Post.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")
        form = SearchForm()
        paginator = Paginator(posts, 5)
        page = request.GET.get("page")

        if not page:
            page = 1
        else:
            try:
                page = int(page)
            except ValueError:
                raise Http404()

        try:
            paginated_posts = paginator.page(page)
        except EmptyPage:
            raise Http404()
        return render(
            request,
            "blog/home.html",
            {"tree_posts": posts, "form": form, "paginated_posts": paginated_posts},
        )

Later down the line, when looking for ways to potential simplify or refactor my views, I encountered "generic" class-based views. These can be thought of as "off-the-shelf" views geared towards specific use cases, such as listings with ListView, or a single item/article with DetailView, etc., with much of the setup abstracted away.

The relevant attributes can be overridden in a plug-and-play fashion. My application also necessitated overriding a couple of methods to tailor the object list filtering and for additional context variables. This particular generic view even has a built-in paginator, enabling pagination without worrying about the logic:

from django.views import generic


class BlogHomeView(generic.ListView):
    model = Post
    template_name = "blog/home.html"
    context_object_name = "paginated_posts"
    paginate_by = 5
    ordering = "-pub_date"

    def get_queryset(self):
        return Post.objects.filter(pub_date__lte=timezone.now()).order_by(self.ordering)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context["form"] = SearchForm()
        context["tree_posts"] = self.object_list

        return context

In conclusion, generic class-based views proved to be incredibly useful for compact, easy-to-read views. My requirements were covered by exclusively using either the ListView or DetailView.