Tutorial: Django Forms with AngularJS

Django offers an excellent Form framework which is responsible for rendering and validating HTML forms. Since Django’s design philosophy is to be independent of the styling and JavaScript, this Form framework requires some adoption in order to play well with AngularJS and optionally Bootstrap.

A common technique to adopt the styling of a Django Form, is to extend the template so that each Form field is rendered by hand written HTML. This of course leads to code duplication and is a violation of the DRY principle.

An alternative technique is to add crispy-forms to your project and enrich the Django Forms with a helper class. Unfortunately, crispy-form does not work very well with django-angular. In order to add the same functionality in a “DRY” manner, a special Mixin class can be added to your forms, rather than having to work with helper-classes.

This tutorial attempts to explain how to combine the Django Form framework in combination with AngularJS and Bootstrap.

Basic Form Submission

Lets start with a very basic example, a functioning demo is available here: http://django-angular.awesto.com/classic_form/

Say, we have a simple but rather long Form definition, to subscribe a person wanting to visit us:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from __future__ import unicode_literals
from djng.forms import fields
class SubscribeForm(Bootstrap3Form):
    use_required_attribute = False

    CONTINENT_CHOICES = [('am', 'America'), ('eu', 'Europe'), ('as', 'Asia'), ('af', 'Africa'),
                         ('au', 'Australia'), ('oc', 'Oceania'), ('an', 'Antartica')]
    TRAVELLING_BY = [('foot', 'Foot'), ('bike', 'Bike'), ('mc', 'Motorcycle'), ('car', 'Car'),
                     ('public', 'Public Transportation'), ('train', 'Train'), ('air', 'Airplane')]
    NOTIFY_BY = [('email', 'EMail'), ('phone', 'Phone'), ('sms', 'SMS'), ('postal', 'Postcard')]

    first_name = fields.CharField(
        label='First name',
        min_length=3,
        max_length=20)

    last_name = fields.RegexField(
        r'^[A-Z][a-z -]?',
        label='Last name',
        error_messages={'invalid': 'Last names shall start in upper case'})

    sex = fields.ChoiceField(
        choices=(('m', 'Male'), ('f', 'Female')),
        widget=widgets.RadioSelect,
        error_messages={'invalid_choice': 'Please select your sex'})

    email = fields.EmailField(
        label='E-Mail',
        required=True,
        help_text='Please enter a valid email address')

    subscribe = fields.BooleanField(
        label='Subscribe Newsletter',
        initial=False, required=False)

    phone = fields.RegexField(
        r'^\+?[0-9 .-]{4,25}$',

Since we want to render this form in a DRY manner, our favorite template syntax is something such as:

<form name="{{ form.form_name }}" method="post" action=".">
        {% csrf_token %}
        {{ form }}
        <input type="submit" value="Subscribe">
</form>

In this example, the whole form is rendered in one pass including all HTML elements, such as <label>, <select>, <option> and <input>. Additionally, bound forms are rendered with their preset values and a list of errors, if the previous Form validation did not succeed.

The Django Form framework comes with three different rendering methods: as_p(), as_ul() and as_table() (the default). Unfortunately, these three rendering methods are not suitable for nowadays needs, such as Bootstrap styling and Ajax based Form validation.

In order to be more flexible without having to abandon the “DRY” principle, the above SubscriptionForm has been enriched by the Mixin class Bootstrap3FormMixin. This class adds an additional method as_div(), which is responsible for rendering the Form suitable for Bootstrap styling.

Additionally, this Mixin class wraps the list of validation errors occurred during the last Form validation into the AngularJS directive ng-show, so that error messages disappear after the user starts typing, and thus puts the Form into a “dirty”, or in other words “non-pristine”, state.

You can test this yourself, by leaving out some fields or entering invalid values and submitting the Form.

Bound Form in AngularJS

AngularJS does not take into account the concept of bound Forms. Therefore, input fields rendered with preset values, are displayed as empty fields. To circumvent this, the django-angular Form directive re-enables the rendering of the bound field values.

Dynamically Hiding Form Fields for Bootstrap

A common use case is to hide a form field based on the value of another. For example, to hide the phone field if the user selects Female within SubscriptionForm, overwrite field_css_classes on SubscriptionForm:

field_css_classes = {
    '*': 'form-group has-feedback',
    'phone': "ng-class:{'ng-hide':sex==='f'}",
}

field_css_classes adds css classes to the wrapper div surrounding individual fields in bootstrap. In the above example, '*' adds the classes form-group has-feedback to all fields within the form and 'ng-class:{"ng-hide":sex==="f"}' is added only to the phone field. Only Angular directives that can be used as CSS classes are allowed within field_css_classes. Additionally, if specified as a string, the string may not contain any spaces or double quotes. However, if specified as a list, spaces can be used, and the above example can be rewritten as:

field_css_classes = {
    '*': 'form-group has-feedback',
    'phone': ["ng-class: {'ng-hide': sex==='f'};"],
}

By adding the keyword '__default__' to this list, the CSS classes for the default entry, ie. '*', are merged with the CSS classes for the current field.

Adding an asterisk for required fields

An asterisk is often added after labels on required fields (like with django-crispy-forms for example). This can be accomplished by using native Django and CSS. To do this, set the required_css_class attribute on SubscriptionForm (see Django documentation).

required_css_class = 'djng-field-required'

Next, add the CSS:

label.djng-field-required::after {
    content: "\00a0*";
}

Client-side Form validation

To enable client-side Form validation, simply add the mixin class NgFormValidationMixin to the SubscriptionForm class:

class SubscriptionForm(NgFormValidationMixin, Bootstrap3FormMixin, forms.Form):
    # form fields as usual

Here the rendered Form contains all the AngularJS directives as required for client side Form validation. If an entered value does not match the criteria as defined by the definition of SubscriptionForm, AngularJS will notify the user immediately