2015-11-17

Django Form - Confirm Before Saving (Part 1)

How can I ask the user to confirm before saving a form if the form meets certain conditions? I tried different approaches and settled on the following.
This example is about saving an invoice. I would like to ask the user to confirm (by checking a checkbox) before saving an invoice if the invoice date falls on a Sunday. The checkbox will only appear if confirmation is required.
Step 1: Add a checkbox in the form as a hidden input.
class InvoiceForm(forms.ModelForm):
    confirm = forms.BooleanField(initial=False, required=False, widget=forms.HiddenInput)
    class Meta:
        model = Invoice
        fields = ('data', ..., 'confirm',)
Step 2: Check for the condition in the form.clean() method. The required data are retrieved from the form cleaned_data dictionary. Note that it is a straightly a read only access. If the condition is met, raise a validation error thereby invalidating the form.
MSG001 is a string constant used as the error message. For example something like "The invoice date does not fall on a Sunday. Please check confirm to proceed."
class InvoiceForm(forms.ModelForm):
    ...
    def clean(self):
        super().clean()
        ...
        if all((key in self.cleaned_data for key in ['date', 'confirm'])):
            if self.cleaned_data['date'].weekday() == 6 and not self.cleaned_data['confirm']:
                raise forms.ValidationError(MSG001)
Step 3: Check for the error condition in the __init__() method of the form, and dynamically change the checkbox from a hidden input to a regular checkbox on the fly. This effectively adds a checkbox to the form and unless it is checked, the form is invalid.
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        ...
        if any(e == MSG001 for e in self.errors.get('__all__', [])):
            self.fields['confirm'].widget = forms.CheckboxInput()
Step 4: In the HTML template, add conditional logic to show the checkbox either as a hidden input or as a checkbox as appropriate.
{% if form.confirm in form.hidden_fields %}
  {{ form.confirm }}
{% else %}
  {{ form.confirm.label_tag }}{{ form.confirm }}
{% endif %}
Final thoughts:
This approach does not require AJAX or any third party packages. The reason is to minimize Javascript knowledge and external dependency.
This approach uses validation error as a signal to add a checkbox to the form.
This approach modifies a form field dynamically in the form.__init__() method.
Also, in another approach, I tried to to update the form.cleaned_data dict without success. The changes I made are lost somewhere in the call chain. It is evident that I do not have sufficient knowledge in how Django forms work.
Continue to Part 2 of this blog for working with multiple confirmations.

No comments: