I have this model:
class IeltsExam(Model):
student = OneToOneField(Student, on_delete=CASCADE)
has_taken_exam = BooleanField(default=False,)
listening = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
reading = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
exam_date = DateField(null=True, blank=True, )
non_empty_fields = \
{
'listening': 'please enter your listening score',
'reading': 'please enter your reading score',
'exam_date': 'please specify your exam date',
}
def clean(self):
errors = {}
if self.has_taken_exam:
for field_name, field_error in self.non_empty_fields.items():
if getattr(self, field_name) is None:
errors[field_name] = field_error
if errors:
raise ValidationError(errors)
and have this modelform
class IeltsExamForm(ModelForm):
class Meta:
model = IeltsExam
fields = ('has_taken_exam', 'listening', 'reading', )
when I submit this form in template, I get the below error:
ValueError at /
'ExamForm' has no field named 'exam_date'.
and
During handling of the above exception ({'listening': ['please enter your listening score'], 'reading': ['please enter your reading score'], 'exam_date': ['please specify your exam date']}), another exception occurred:
The error happens in my view where I am validating the form. My database logic is such that I need to have an exam_date field and it should be mandatory to fill if has_taken_exam is checked. However, in ExamForm, for business reasons, I do not need the exam_date. How can I tell ExamForm to turn a blind eye to the exam_date, as I am not saving the modelform instance?
After the ModelForm
is initialised, it has an instance
attribute which is the model instance on which clean()
will be called. So if you remove exam_date
from the instance's non_empty_fields
dictionary, it won't use it in clean
:
class IeltsExamForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.instance.non_empty_fields.pop('exam_date')
And you could do that for each field in self._meta.exclude
.
However, when doing that, the attribute non_empty_fields
should not be a class attribute but an instance property. Modifying the instance's non_empty_fields
actually modifies the class attribute (it's a dictionary so it's mutable), which will have unintended side-effects (once removed, it's removed for any subsequent instance you create). Change your model to set the attribute in the init method:
class IeltsExam(Model):
# ...
# remove the class attribute non_empty_fields
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.non_empty_fields = { ... }
In general, I would advise you to only use a ModelForm
if you're actually going to save the model, in which case a class attribute is the cleaner approach. Instead of doing all this, if your form isn't going to save the actual model, you should not use a ModelForm
but a Form
and define all fields and cleaning in the form itself.
Perform the validation on model's save()
Consider the following model:
class Exam(Model):
student = OneToOneField(Student, on_delete=CASCADE)
has_taken_exam = BooleanField(default=False)
score = FloatField(choices=SCORE_CHOICES, null=True, blank=True)
exam_date = DateField(null=True, blank=True)
def save(self, *a, **kw):
if self.has_taken_exam and not self.exam_date:
raise ValidationError("Exam date must be set when has_taken_exam is True")
return super().save()
来源:https://stackoverflow.com/questions/58254333/how-to-handle-the-validation-of-the-model-form-when-the-model-has-a-clean-method