# 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.
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.