I have a form for a job opening model + inline formset for each desired skills. Something like this:
# forms.py
class SkillInlineFormSet(forms.models.BaseInlineFormSet):
def clean(self):
"""Do not allow Job Openings to be created without desired skills"""
if any(self.errors):
# Don't bother validating the formset unless each form is valid on its own
return
has_filled_forms = any([form.has_changed() for form in self.forms])
if not has_filled_forms:
raise ValidationError(
_("You should select at least one desired skill"), code="invalid"
)
DesiredSkillIFormSet = forms.models.inlineformset_factory(
JobOpening
Skill,
formset=SkillInlineFormSet,
fields=("skill", "is_required"),
can_delete=False,
extra=2,
)
Then my view is as follows:
# views.py
class TalentProfileCreate(LoginRequiredMixin, UpdateView):
model = JobOpening
fields = ("name", "description")
success_url = "/"
def form_invalid(self, form, skills_formset):
"""If any of the forms is invalid, render the invalid forms."""
return self.render_to_response(
self.get_context_data(
form=form, skills_formset=skills_formset
)
)
def get_object(self, queryset=None):
try:
return super().get_object(queryset)
except AttributeError:
# Treat as the new object
return None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["skills_formset"] = DesiredSkillIFormSet(instance=self.object)
return context
def get_success_message(self, created=False) -> str:
if created:
message = _("The profile {name} was created successfully!")
else:
message = _("The profile {name} was updated!")
return message
@transaction.atomic
def post(self, request, *args, **kwargs):
# Are we creating or updating a talent profile?
self.object = self.get_object()
created = self.object is None
form = self.get_form()
form.instance.user = request.user
skills_formset = DesiredSkillIFormSet(request.POST, instance=self.object)
if form.is_valid() and skills_formset.is_valid():
self.object = form.save()
skills_formset.instance = self.object
skills_formset.save()
messages.success(self.request, self.get_success_message(created))
return HttpResponseRedirect(self.get_success_url())
else:
return self.form_invalid(form, skills_formset)
And the template:
<!-- jobopening_form.html -->
<form method="POST">
{% csrf_token %}
{{ form.as_ul }}
{{ skills_formset.management_form }}
{{ skills_formset }}
<input type="submit" value="Save">
</form>
The validation above works: no job openings with no desired skills return form.is_valid == False
. My problem is that I can’t print that validation error to the template.
Using {skills_formset.non_form_errors}
yield nothing.
However, when I print it from the views.py
, I get the following:
<ul class="errorlist">
<li>You should select at least one desired skill</li>
</ul>
Weird, right? I mean, why are there ul tags? Should I be explicitly adding that to the view’s context?
When I inspected the formset’s __dict__
method, I see that the error message is actually stored on an attribute called _non_form_errors
but I can’t assess that from the template. I get the following error:
# TemplateSyntaxError at /jobopening/new
Variables and attributes may not begin with underscores: 'skills_formset._non_form_errors'
What am I missing here? Saving and updating instances with valid entries work fine. The only thing I can’t do is the render the error message I’ve defined on forms.py
.
Incidentally, when I try to access the formset’s error_message
, I see a missing_management_form
error (despite having it on the template). Could this be hinting to a deeper problem at play?