Client-Side Form Validation
Form validation with AngularJS using only a Django Form
This example shows how to let AngularJS validate input fields from a Django Form in a DRY manner.
How does it work?
The Django forms.Form
class offers many possibilities to validate a given form.
This for obvious reasons is done on the server. However, while typing, it is unacceptable to send
the form's content to the server for continuous validation. Therefore, adding client side form
validation is a good idea and commonly used. But since this validation easily can be bypassed by a
malicious client, the same validation has to occur a second time, when the server receives the
form's data for final processing.
With django-angular, we can handle this without having to re-implement any client side form validation. Apart from initializing the AngularJS application, no JavaScript is required to enable this useful feature.
from django.forms import widgets
from django.core.exceptions import ValidationError
from djng.forms import fields, NgFormValidationMixin
from djng.styling.bootstrap3.forms import Bootstrap3Form
def validate_password(value):
# Just for demo. Do not validate passwords like this!
if value != 'secret':
raise ValidationError('The password is wrong.')
class SubscribeForm(NgFormValidationMixin, 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}$',
label='Phone number',
error_messages={'invalid': 'Phone number have 4-25 digits and may start with +'})
birth_date = fields.DateField(
label='Date of birth',
widget=widgets.DateInput(attrs={'validate-date': '^(\d{4})-(\d{1,2})-(\d{1,2})$'}),
help_text='Allowed date format: yyyy-mm-dd')
continent = fields.ChoiceField(
label='Living on continent',
choices=CONTINENT_CHOICES,
error_messages={'invalid_choice': 'Please select your continent'})
weight = fields.IntegerField(
label='Weight in kg',
min_value=42,
max_value=95,
error_messages={'min_value': 'You are too lightweight'})
height = fields.FloatField(
label='Height in meters',
min_value=1.48,
max_value=1.95,
step=0.05,
error_messages={'max_value': 'You are too tall'})
traveling = fields.MultipleChoiceField(
label='Traveling by',
choices=TRAVELLING_BY,
help_text='Choose one or more carriers',
required=True)
notifyme = fields.MultipleChoiceField(
label='Notify by',
choices=NOTIFY_BY,
widget=widgets.CheckboxSelectMultiple, required=True,
help_text='Must choose at least one type of notification')
annotation = fields.CharField(
label='Annotation',
required=True,
widget=widgets.Textarea(attrs={'cols': '80', 'rows': '3'}))
agree = fields.BooleanField(
label='Agree with our terms and conditions',
initial=False,
required=True)
password = fields.CharField(
label='Password',
widget=widgets.PasswordInput,
validators=[validate_password],
help_text='The password is "secret"')
confirmation_key = fields.CharField(
max_length=40,
required=True,
widget=widgets.HiddenInput(),
initial='hidden value')
from django.core.urlresolvers import reverse_lazy
from django.views.generic.edit import FormView
class SubscribeView(FormView):
template_name = 'client-validation.html'
form_class = SubscribeForm
success_url = reverse_lazy('form_data_valid')
<script type="text/javascript">
angular.module('djangular-demo', ['djng.forms']);
</script>
<form name="{{ form.form_name }}" method="post" action="." novalidate>
{% csrf_token %}
{{ form.as_div }}
<button type="submit" ng-disabled="{{ form.form_name }}.$invalid" class="btn btn-primary">Subscribe</button>
</form>
Here the differences to the Classic Subscription example is, that the
Form now additionally must inherit from the mixin class NgFormValidationMixin
.
Furthermore, the browsers internal Form validation must be disabled. This is achieved by adding
the property novalidate
to the Form's HTML element.