Why model instance is not being supplied to `ModelForm` inside a `DeleteView`

With Django 5.0.2, let's start with a simple model:

from django.db import models


class Book(models.Model):
    title = models.CharField(max_length=100)

I want to do some form validation within ModelForm to determine whether or not the deletion should happen inside DeleteView. Simply put, I think the form is already capable of showing non-field errors and I want to avoid using messages.error() to sending back an error message.

So I started with ModelForm:

from django.forms import ModelForm


class DeleteBookForm(ModelForm):
    class Meta:
        model = Book
        fields = []

    def clean(self):
        super().clean()
        if not self.deletable():
            self.add_error(None, 'This object can not be deleted')

    def deletable(self) -> bool:
        # do some evaluation on Book here
        book = self.instance
        if book.title == 'delete me please':
            return True
        else:
            return False

and then the DeleteView:

from django.views.generic import DeleteView
from django.http import HttpResponseRedirect


class DeleteBookView(DeleteView):
    model = Book
    form_class = DeleteBookForm

    def form_valid(self, form):
        self.object.delete()
        messages.success(self.request, 'The object has been deleted successfully.')
        return HttpResponseRedirect(self.get_success_url())

But self.instance appears to be None inside DeleteBookForm.deletable(). Indicating that the Book model instance is not being supplied/inserted to the ModelForm when it is being created. I believe this is not the case with UpdateView.

Spent some time reading, I found this code on BaseDeleteView.post() (DeleteView's parent). Note that it is indeed doesn't insert the object to the form:

class BaseDeleteView(DeletionMixin, FormMixin, BaseDetailView):
    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

Eventually, what I did to make it work is by manually inserting the instance when the form is created:

class DeleteBookView(DeleteView):
    def get_form(self, form_class=None):
        # insert instance so it can be read inside the form
        return self.form_class(
            instance=self.object,
            **self.get_form_kwargs()
        )

Again, the model instance is being supplied by default in UpdateView.

So my questions are:

  1. Why the model instance is not being supplied by default on DeleteView?
  2. Is it wrong to do model instance evaluation using ModelForm on DeleteView? I'm not sure, but I felt like Form is the most logical way to place model instance evaluation rather than on a View, for example.

You only need to mix in the ModelFormMixin [Django-doc], that will thus pass the object to the form:

from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import ModelFormMixin


class DeleteBookView(SuccessMessageMixin, ModelFormMixin, DeleteView):
    model = Book
    form_class = DeleteBookForm
    success_message = 'The object has been deleted successfully.'

By default, the DeleteView works with a FormMixin, it thus does not passes the object as model. Usually because the form is more a confirmation, perhaps with a checkbox, not something instance related.

When importing a form, FormMixin is used, and the methods used are as follows.


    def get_form_class(self):
        """Return the form class to use."""
        return self.form_class

    def get_form(self, form_class=None):
        """Return an instance of the form to be used in this view."""
        if form_class is None:
            form_class = self.get_form_class()
        return form_class(**self.get_form_kwargs())

    def get_form_kwargs(self):
        """Return the keyword arguments for instantiating the form."""
        kwargs = {
            "initial": self.get_initial(),
            "prefix": self.get_prefix(),
        }

        if self.request.method in ("POST", "PUT"):
            kwargs.update(
                {
                    "data": self.request.POST,
                    "files": self.request.FILES,
                }
            )
        return kwargs

https://github.com/django/django/blob/main/django/views/generic/edit.py

Object information is not transmitted. If you want to pass and check object information, you should do the following.

def get_form_kwargs(self):
  kwargs = super().get_form_kwargs()
  kwargs['instance'] = self.object
  return kwargs
Back to Top