~/TechPurAI
~/tutorials/django-from-scratch/deleting-objects-deleteview
intermediate·part 15 of 22·2 min read

Deleting objects: DeleteView and confirmation templates

Updated Aug 15, 2026Python · Django

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

python
# 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")
python
# 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

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

Common mistake

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

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

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous14. Handling POST requests and CSRF protectionnext →16. User authentication: login, logout, and signup