CodeOath
← All posts
Python65 min total · 18 parts

Django Fundamentals: The ORM, Migrations, and Shipping a Real App

Part 9 of 18 · ~2 min

Views: One Function, One Class, Same Job

# catalog/views.py — function-based: every step visible top to bottom
def tool_list(request):
    tools = Tool.objects.select_related("owner").all()
    return render(request, "catalog/tool_list.html", {"tools": tools})

def request_loan(request, pk):
    tool = get_object_or_404(Tool, pk=pk)
    if request.method == "POST":
        form = LoanRequestForm(request.POST)
        if form.is_valid():
            loan = form.save(commit=False)
            loan.tool = tool
            loan.borrower = request.user
            loan.save()
            return redirect("loan-detail", pk=loan.pk)
    else:
        form = LoanRequestForm()
    return render(request, "loans/request_form.html", {"form": form, "tool": tool})
# loans/views.py — class-based: the same CRUD shape, written once by Django itself
class MyActiveLoansView(LoginRequiredMixin, ListView):
    model = Loan
    template_name = "loans/my_active_loans.html"
    paginate_by = 20

    def get_queryset(self):
        return (
            Loan.objects.select_related("tool", "borrower")
            .filter(borrower=self.request.user, status="approved")
        )

The trade a class-based view is making is legibility for brevity. MyActiveLoansView above gets pagination, a place to plug in a custom queryset, and a working template out of maybe six lines — but reading those six lines doesn't tell you any of that; you have to already know that overriding get_queryset is the hook, and that ListView hands the template a variable called object_list unless you rename it. A function makes every one of those steps visible in exchange for typing more of them out by hand. request_loan, back at the top of this chapter, is the case where a function wins outright — fetching a specific tool, attaching both it and the logged-in user to a new Loan before saving — none of that has a matching generic view to inherit from, so writing it as a class would mean overriding nearly everything anyway.

Extending a CBV without breaking its built-in context

class MyActiveLoansView(LoginRequiredMixin, ListView):
    model = Loan

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)   # populates object_list, page_obj, etc. first
        context["overdue_count"] = self.get_queryset().filter(due_date__lt=date.today()).count()
        return context

Skip the super().get_context_data(**kwargs) call and context starts life as an empty dict instead of the one ListView builds — object_list, page_obj, everything the template already expects silently disappears, and the page renders broken in a way that looks nothing like the one line actually missing.