Custom Error Messages on Model Deletion in the Django Admin

I have been working on a project where we might want to delete models even just for testing purposes, but we don't want to accidentally delete models.

Protect Model Deletion

Let's say you have a Django model:

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=255)
    email = models.EmailField()


class Rental(models.Model):
    name = models.CharField(max_length=255)
    current_renter = models.ForeignKey(
        User, on_delete=models.CASCADE, null=True, blank=True
    )

At some point, I might sell and delete my Rental object, but I don't want to do that if I have someone renting it at the moment.

Override Model delete() Method

We can put in our own custom logic to prevent this deletion if there's an active renter:

from django.core.exceptions import ValidationError  # new


class Rental(models.Model):
    # ...
    def delete(self, *args, **kwargs):
        # Check if someone is renting at the moment
        if self.user_set.exists():
            raise ValidationError(
                f"Cannot delete rental {self.name} while someone is in it."
            )

        # If no renter, proceed with deletion
        super().delete(*args, **kwargs)

Message the User by Overriding the delete_model() Method

In our Model's Admin class, we can override the delete_model() method to give the user feedback as to why we stopped their deletion so they aren't left totally confused:

from django.contrib import admin
from django.contrib import messages


@admin.register(Rental)
class RentalAdmin(admin.ModelAdmin):
    list_display = ("name", "current_renter")
    list_filter = ("current_renter",)
    search_fields = ("name", "current_renter__name", "current_renter__email")

    def delete_model(self, request, obj):
        try:
            return super().delete_model(request, obj)
        except ValidationError as e:
            self.message_user(request, str(e.message), messages.ERROR)
            return

This will pass along our error message through the Django Messages framework.

The ModelAdmin.delete_model() method normally calls the object's deletion. This is what we just overrode, and we can use the ValidationError we've raise to communicate across methods.

Neat.

Customize the Deletion HTTP Response by Overriding the response_delete() Method

You might notice that if you test this code, you'll get two messages: the one you wrote that says the deletion failed, and another one that said it succeeded.

Weird!

That's because Django's method version is cuing up a success message.

We can override this method an add a check related to our deletion logic to divert the response if it makes sense:

from django.http import HttpResponseRedirect  # new


@admin.register(Rental)
class RentalAdmin(admin.ModelAdmin):
    # ...
    def response_delete(self, request, obj_display, obj_id):
        if Rental.objects.filter(pk=obj_id).exists():
            # Return to the admin's Rental detail page
            return HttpResponseRedirect(
                reverse("admin:rentals_rental_change", args=[obj_id])
            )

        # Otherwise proceed as usual
        return super().response_delete(request, obj_display, obj_id)

This will stop Django from adding that confusing "successful deletion" message.

Voila.

Bulk Deletions Follow a Different Course of Action

To my knowledge, the bulk deletion option in the Django Admin uses different methods to handle the bulk deletion behavior.

If you're familiar with how to override this behavior, I'd be curious to hear!

You can disable bulk deletions by overriding the method ModelAdmin.get_actions():

admin.register(Rental)
class RentalAdmin(admin.ModelAdmin):
    # ...
    def get_actions(self, request):
        actions = super().get_actions(request)
        if "delete_selected" in actions:
            del actions["delete_selected"]
        return actions

Get Notified of New Posts

Sign up for the newsletter and I'll send you an email when there's a new post.