I have a calculated field on my model that is generated by an external program based on form input.
Where should I put the validation code?
My form accepts either a charfield or filefield. In the form.clean() I check that either of the fields are set (or else validation error) and set the string required to calculate the calculated field. At this point I would call the form valid since the success/fail of the external program is more a property of the model?
At the moment I overrode the model.save() and set the calculated field to “%%ERROR%%” on error because the model.save() doesn’t seem to have a way to return the validation error if the subprocess call fails. So I’m assuming I am doing this wrong? Do i override model.clean() and call model.full_clean() in Formview.is_valid()? Stackoverflow seems to say you can’t/shouldn’t return a ValidationError inside is_valid(). Do I call model.full_clean() in the form.clean()? or do i move the subprocess call to the form.clean() method.
Thanks
Edit:
Found Creating forms from models | Django documentation | Django
Reading through it…
Example code
class UserInfoForm(forms.ModelForm):
userinfoFile = forms.FileField(label="User Info File", required=False)
userinfo_txt = forms.CharField(required=False)
class Meta:
model = UserInfo
fields = ('userinfo_txt',)
def clean(self):
cd = super().clean()
#prefer txt over file
if cd['userinfo_txt'] != '':
return cd
elif cd['userinfoFile']:
cd['userinfo_txt'] = cd.cleaned_data["userinfoFile"].read().decode()
else:
raise ValidationError('Either text or file must be given', code='needuinfo')
class UserInfo(models.Model):
userinfo_txt = models.TextField()
userinfo_calculated = models.TextField(editable=False)
def save(self, *args, **kwargs):
#subprocess call and save to calculated
class UserInfoView(LoginRequiredMixin, FormView):
template_name = 'products/userinfo_create.html'
form_class = UserInfoForm
def form_valid(self, form):
#something here
That example code looks generally good. I would be a little more defensive about checking the values in cleaned_data
though.
def clean(self):
cd = super().clean()
#prefer txt over file
if cd.get('userinfo_txt'):
pass # fall through to the return statement
elif cd.get('userinfoFile'):
cd['userinfo_txt'] = cd.cleaned_data["userinfoFile"].read().decode()
else:
raise ValidationError('Either text or file must be given', code='needuinfo')
return cd
Since your fields aren’t required, if they aren’t included in the post, then the cleaned_data will not have the keys. cleaned_data.get
avoids an unnecessary unhandled exception