Reputation: 928
I created a custom validation error and it worked just fine before abstracting my class based views. For some reason, whenever there's an error, the error is just blank, e.g [{}]
Validation Error logic:
class BaseFormSetValidation(BaseModelFormSet):
def clean(self):
super().clean()
days = ['day_1', 'day_2', 'day_3', 'day_4', 'day_5', 'day_6', 'day_7']
for form in self.forms:
for day in days:
current_day = form.cleaned_data.get(day)
if current_day is not None and current_day > 24:
raise ValidationError([{day: ["Submitted hours per day cannot exceed 24 hours"]}])
class DivErrorList(ErrorList):
def __str__(self):
return self.as_divs()
def as_divs(self):
if not self: return ''
return '%s' % ''.join([e for e in self])
TimesheetModelFormSet = modelformset_factory(Timesheet, formset=BaseFormSetValidation, exclude=("year", "week", "project", "user"), extra=0)
TemplateView Class
class TimesheetEditorView(BaseTimesheet, TemplateView):
form_class = TimesheetModelFormSet
template_name = "timesheets/timesheet.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
timesheet = Timesheet.objects.filter(year=context["year"], week=context["week"], user=self.request.user).order_by("project_id")
timesheet_formset = self.form_class(queryset=timesheet, error_class=DivErrorList)
create_timesheet_form = TimesheetModelForm(self.request.user)
context.update(
timesheet=timesheet,
timesheet_formset=timesheet_formset,
create_timesheet_form=create_timesheet_form
)
return context
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
timesheet_formset = self.form_class(request.POST)
if timesheet_formset.is_valid():
messages.info(request, "Weekly timesheet updated", extra_tags='timesheet')
timesheet_formset.save()
success_url = reverse("timesheets:current-week", args=(context["year"], context["week"]))
return HttpResponseRedirect(success_url)
return render(request, "timesheets/timesheet.html", self.get_context_data(**kwargs))
Which inherits from
class BaseTimesheet(object):
...
def get_context_data(self, **kwargs):
context = super(BaseTimesheet, self).get_context_data(**kwargs)
year = kwargs.get("year") or datetime.datetime.now().year
week = kwargs.get("week") or Week.thisweek().week
user = self.request.user
next_year, next_week = calc_next(year, week)
previous_year, previous_week = calc_previous(year, week)
calc_full_week = self.calc_full_week(year, week)
context.update(
week=week,
year=year,
next_week=next_week,
previous_week=previous_week,
next_year=next_year,
previous_year=previous_year,
current_week=calc_full_week
)
return context
And the template with error handling:
{% for row in timesheet_formset %}
{% if row.errors %}
<div class="col-lg-10 offset-lg-1 text-center">
<div class="alert alert-warning alert-dismissible fade show">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
Error on project {{ row.instance.project }}
{% endif %}
{% for field in row %}
{% if field.errors %}
{% for error in field.errors %}
<p>{{ field.label }} - {{ error }}</p>
{% endfor %}
{% endif %}
{% endfor %}
{% if row.errors %}
</div>
</div>
{% endif %}
{% endfor %}
{% if timesheet_formset.non_form_errors %}
<div class="col-lg-10 offset-lg-1 text-center">
<div class="alert alert-warning alert-dismissible fade show">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
{{ timesheet_formset.non_form_errors }}
</div>
</div>
{% endif %}
Do I need to pass in error_class=DivErrorList
elsewhere? Right now, whenever an error occurs, the form just "resets" so to say
This is what TimesheetEditorView
looked like before my refactor and where the errors would be output correctly in the template. No actual "change" to the logic has happened - all I tried to do was refactor the code so my view wouldn't be so "thick"
@method_decorator(login_required, name='dispatch')
class TimesheetEditorView(View):
form_class = TimesheetModelFormSet
def get(self, request, *args, **kwargs):
year = kwargs.get("year") or datetime.datetime.now().year
week = kwargs.get("week") or Week.thisweek().week
user = request.user
if invalid_week(week, year):
raise Http404("Invalid week / year")
next_year, next_week = calc_next(year, week)
previous_year, previous_week = calc_previous(year, week)
calc_full_week = self.calc_full_week(year, week)
timesheet = Timesheet.objects.filter(year=year, week=week, user=user).order_by("project_id") # only show timesheet rows that belongs to logged in user
timesheet_formset = self.form_class(queryset=timesheet)
create_timesheet_form = TimesheetModelForm(user)
context = {
"create_timesheet_form": create_timesheet_form,
"timesheet_formset": timesheet_formset,
"week": week,
"year": year,
"next_week": next_week,
"next_year": next_year,
"previous_week": previous_week,
"previous_year": previous_year,
"current_week": calc_full_week,
}
return render(request, "timesheets/timesheet.html", context)
def post(self, request, *args, **kwargs):
year = kwargs.get("year") or datetime.datetime.now().year
week = kwargs.get("week") or Week.thisweek().week
if invalid_week(week, year):
raise Http404("Invalid week / year")
next_year, next_week = calc_next(year, week)
previous_year, previous_week = calc_previous(year, week)
timesheet_formset = self.form_class(request.POST, error_class=DivErrorList)
if timesheet_formset.is_valid():
messages.info(request, "Weekly timesheet updated", extra_tags='timesheet')
timesheet_formset.save()
success_url = reverse("timesheets:current-week", args=(year, week))
return HttpResponseRedirect(success_url)
create_timesheet_form = TimesheetModelForm(request.user)
context = {
"create_timesheet_form": create_timesheet_form,
"timesheet_formset": timesheet_formset,
"week": week,
"year": year,
"next_week": next_week,
"next_year": next_year,
"previous_week": previous_week,
"previous_year": previous_year,
}
return render(request, "timesheets/timesheet.html", context)
Upvotes: 0
Views: 146
Reputation: 919
The issue is that you aren't handing the form over:
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
timesheet_formset = self.form_class(request.POST)
if timesheet_formset.is_valid():
messages.info(request, "Weekly timesheet updated", extra_tags='timesheet')
timesheet_formset.save()
success_url = reverse("timesheets:current-week", args=(context["year"], context["week"]))
return HttpResponseRedirect(success_url)
return render(request, "timesheets/timesheet.html", self.get_context_data(**kwargs))
There is no need to call the self.get_context_data(**kwargs)
with the same kwargs they will be the same. And you have to hand over the timesheet_formset
and create_timesheet_form
as you did in the "original" view.
This would result in something like this:
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
timesheet_formset = self.form_class(request.POST)
if timesheet_formset.is_valid():
messages.info(request, "Weekly timesheet updated", extra_tags='timesheet')
timesheet_formset.save()
success_url = reverse("timesheets:current-week", args=(context["year"], context["week"]))
return HttpResponseRedirect(success_url)
context['timesheet_formset'] = timesheet_formset
create_timesheet_form = TimesheetModelForm(request.user)
context['create_timesheet_form '] = create_timesheet_form
return render(request, "timesheets/timesheet.html", )
You have to test if you need both timesheet_formset
and create_timesheet_form
.
Upvotes: 1