pythonpython-3.xdjangodjango-viewsdjango-forms

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.

Solution

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

    Is it wrong to do model instance evaluation using ModelForm on DeleteView?

    It might make more sense to limit the queryset, such that is simply not possible to go to the page where such object is removed, like:

    from django.contrib.messages.views import SuccessMessageMixin
    
    
    class DeleteBookView(SuccessMessageMixin, DeleteView):
        model = Book
        queryset = Book.objects.filter(title='delete me please')
        success_message = 'The object has been deleted successfully.'

    So now it can simply not even retrieve such Book, wich is typically more robust than validating the object through the form.