Where to validate calculated model field

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