Почему экземпляр модели не передается в `ModelForm` внутри `DeleteView`.
В Django 5.0.2
давайте начнем с простой модели:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
Я хочу сделать некоторую проверку формы внутри ModelForm
, чтобы определить, должно ли удаление происходить внутри DeleteView
. Проще говоря, я думаю, что форма уже способна показывать ошибки, не связанные с полем, и я хочу избежать использования messages.error()
для отправки сообщения об ошибке.
Так что я начал с 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
и затем 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())
Но self.instance
, похоже, находится None
внутри DeleteBookForm.deletable()
. Это указывает на то, что экземпляр модели Book
не поставляется/вставляется в ModelForm
при его создании. Я полагаю, что это не относится к UpdateView
.
Потратив некоторое время на чтение, я нашел этот код на BaseDeleteView.post()
(родитель DeleteView
). Обратите внимание, что он действительно не вставляет объект в форму:
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)
В итоге, чтобы заставить его работать, я вручную вставил экземпляр при создании формы:
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()
)
Опять же, экземпляр модели поставляется по умолчанию в UpdateView
.
Итак, мои вопросы таковы:
- Почему экземпляр модели не поставляется по умолчанию на
DeleteView
? - Неправильно ли делать оценку экземпляра модели с помощью
ModelForm
наDeleteView
? Я не уверен, но мне показалось, что Form - это наиболее логичный способ разместить оценку экземпляра модели, а не на View, например.
Вам нужно только добавить ModelFormMixin
[Django-doc], что позволит передать объект форме:
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.'
По умолчанию DeleteView
работает с FormMixin
, поэтому не передает объект в качестве модели. Обычно это связано с тем, что форма представляет собой скорее подтверждение, возможно, с чекбоксом, а не что-то связанное с объектом.
При импорте формы используется FormMixin, а методы используются следующие.
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
Информация об объекте не передается. Если вы хотите передать и проверить информацию об объекте, сделайте следующее.
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['instance'] = self.object
return kwargs