Deleting objects: DeleteView and confirmation templates
Create, read, and update are done — DeleteView completes CRUD. It's also the simplest generic view in this series, deliberately: deletion should never involve more moving parts than absolutely necessary.
DeleteView
# blog/views.py
from django.views.generic.edit import DeleteView
from django.urls import reverse_lazy
class PostDeleteView(DeleteView):
model = Post
template_name = "blog/post_confirm_delete.html"
slug_field = "slug"
slug_url_kwarg = "slug"
success_url = reverse_lazy("post_list")# blog/urls.py
path("post/<slug:slug>/delete/", views.PostDeleteView.as_view(), name="post_delete"),DeleteView handles GET (show a confirmation page) and POST (actually delete) with no form class needed — there's no data to validate, only a decision to confirm.
The confirmation template
{# blog/templates/blog/post_confirm_delete.html #}
{% extends "blog/base.html" %}
{% block content %}
<p>Delete "{{ post.title }}"? This can't be undone.</p>
<form method="post">
{% csrf_token %}
<button type="submit">Yes, delete it</button>
</form>
{% endblock %}Visiting the delete URL (a GET request) only ever shows this confirmation — it never deletes anything by itself. The actual deletion only happens when this form's POST is submitted, protected by the same {% csrf_token %} from part 14.
Why deletion is never a GET
A link like <a href="/post/1/delete/">Delete</a> that deletes immediately on click would also delete the moment a search engine crawler, a link preview generator, or a browser's prefetch-on-hover feature follows that link — none of which are a person confirming intent. HTTP's own spec treats GET as "safe" — expected to never change anything — specifically so tools built on that assumption (crawlers, caches, prefetchers) can follow links freely. A confirmation page reached by GET, with the real deletion behind a POST a person has to actively submit, is what keeps that assumption true.
Wiring a delete button straight to an <a href> instead of a form. It happens to work in casual manual testing — until a crawler, a browser extension, or a coworker's link preview quietly deletes something nobody meant to touch.
Linking it from the detail page
{# post_detail.html #}
{% if user == post.author %}
<a href="{% url 'post_update' post.slug %}">Edit</a>
<a href="{% url 'post_delete' post.slug %}">Delete</a>
{% endif %}{% if user == post.author %} is a preview of part 16 and 17: user is available in every template automatically, and checking it before showing edit/delete links is the first, template-level layer of the access control those parts build properly.
Full CRUD is done. Next: real user accounts — login, logout, and signup — so request.user in views like PostCreateView means something.