is there a way to validate that at least 1 of the many different formsets are not empty inside forms.py
?
I have a working validation inside views.py
but this validation don't apply to admin form. I want my code to be DRY and so my idea is to write the validation inside forms.py
, so that both admin and HTML form share the same validation.
models.py
, Person
can have multiple PersonAddress
and PersonEmail
. In my actual models.py
, I have many more related class, not just PersonAddress
, PersonEmail
, I just simplify it to 2 to make this example simpler.
class Person(models.Model):
...
class PersonAddress(models.Model):
person = models.ForeignKey(
Person, null=True, on_delete=models.SET_NULL, related_name="person_address",
)
...
class PersonEmail(models.Model):
person = models.ForeignKey(
Person, null=True, on_delete=models.SET_NULL, related_name="person_email",
)
...
forms.py
, PersonAddressFormSet
and PersonEmailFormSet
will become inline formsets of PersonForm
class PersonForm(forms.ModelForm):
...
class PersonAddressForm(forms.ModelForm):
...
class PersonEmailForm(forms.ModelForm):
...
PersonAddressFormSet = forms.models.inlineformset_factory(
Person, PersonAddress, form=PersonAddressForm, can_delete=False, extra=1,
)
PersonEmailFormSet = forms.models.inlineformset_factory(
Person, PersonEmail, form=PersonEmailForm, can_delete=False, extra=1,
)
views.py
, under form_valid
method, if both address and email formset are not has_changed
, I will return form error
class PersonCreateView(SuccessMessageMixin, MetadataMixin, LoginRequiredMixin, CreateView):
model = Person
form_class = PersonForm
template_name = "person/person_form.html"
def get_context_data(self, **kwargs):
context = super(PersonCreateView, self).get_context_data(**kwargs)
if self.request.POST:
context["addresses"] = PersonAddressFormSet(self.request.POST, self.request.FILES)
context["emails"] = PersonEmailFormSet(self.request.POST, self.request.FILES)
else:
context["addresses"] = PersonAddressFormSet()
context["emails"] = PersonEmailFormSet()
return context
def form_valid(self, form):
form.instance.created_by = self.request.user
context = self.get_context_data()
addresses = context["addresses"]
emails = context["emails"]
personfs = [addresses, emails]
if (
all(fs.is_valid() for fs in personfs)
):
if not any(fs.has_changed() for fs in personfs):
form.add_error(
None, ValidationError(
_("""
Please fill up either address / email
""")
)
)
return self.form_invalid(form)
else:
self.object = form.save()
for fs in personfs:
fs.instance = self.object
fs.save()
else:
return self.form_invalid(form)
return super(PersonCreateView, self).form_valid(form)
Now, I want the same validation for admin form, through forms.py
. I thought of using PersonForm.clean
def clean(self):
cleaned_data = super().clean()
print(f"PersonForm.clean.cleaned_data: {cleaned_data}")
return cleaned_data
but PersonAddress
and PersonEmail
is not found under cleaned_data
dict. So I am not sure if this is possible?
If my idea of putting validation inside forms.py
is not possible. Is there a way I can write validation that works on both admin and HTML form?
Thanks!