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:
DeleteView
?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.
Is it wrong to do model instance evaluation using
ModelForm
onDeleteView
?
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.