Django's Forms Set
New in 2.0 How to validate a Set of Forms
This example shows how to validate multiple Forms inside an AngularJS application.
djng-forms-set
's scope:
subscribe_data = {{ subscribe_data | json }} address_data = {{ address_data | json }}
How does it work?
In component based web development, it is quite common to arrange more than one form on the same
page. As opposed to form submissions via application/x-www-form-urlencoded
or
multipart/form-data
, we can, thanks to Ajax, submit the content of more than one form
using a single HTTP-request. This requires to dispatch the submitted data on the server to each
form class, but if we prefix them with unique identifiers (using scope_prefix
), that's
a no-brainer.
Directive djng-forms-set
To achieve this, we can reuse the same Form mixin classes as we have used in the previous examples.
The main difference is that we must wrap the set of forms into the AngularJS directive,
<djng-forms-set endpoint="/some/endpoint">...</djng-forms-set>
.
Inside this directive, we render the forms as usual, using
{{ form.as_div }}
.
Forms Submission
The submit button(s) now can be placed outside of the <form>...</form>
element. This allows us to submit the content from multiple forms altogether. However, we
must specify the common endpoint to accept our form submissions; this is, as you might have
expected, the attribute endpoint="/some/endpoint"
ng-click="do(update())"
do(...)
, in order to emulate the first promise (see below for a longer
explanation).
By itself, this however would not invoke any further action on the client. We therefore must
tell our directive, what we want to do next. For this, the django-angular's button
directive offers a few prepared targets, which can be chained. If we change the above to
ng-click="do(update()).then(reloadPage())"
For further options on how to chain actions, please refer to the previous chapter.
Forms Validation
All forms wrapped inside our djng-forms-set
ng-disabled="isDisabled()"
import re
from django.core.exceptions import ValidationError
from django.forms import widgets
from djng.forms import fields, NgModelFormMixin, NgFormValidationMixin
from djng.styling.bootstrap3.forms import Bootstrap3Form
def validate_full_name(value):
pattern = re.compile(r'^\S+\s+\S+')
if not pattern.match(value):
raise ValidationError("Please enter a first-, followed by a last name.")
class SubscribeForm(NgModelFormMixin, NgFormValidationMixin, Bootstrap3Form):
scope_prefix = 'subscribe_data'
form_name = 'subscribe_form'
use_required_attribute = False
sex = fields.ChoiceField(
choices=[('m', 'Male'), ('f', 'Female')],
widget=widgets.RadioSelect,
error_messages={'invalid_choice': 'Please select your sex'},
)
full_name = fields.CharField(
label='Full name',
validators=[validate_full_name],
help_text='Must consist of a first- and a last name',
)
def clean(self):
if self.cleaned_data.get('full_name', '').lower() == 'john doe':
raise ValidationError('The full name "John Doe" is rejected by the server.')
return super(SubscribeForm, self).clean()
class AddressForm(NgModelFormMixin, NgFormValidationMixin, Bootstrap3Form):
scope_prefix = 'address_data'
form_name = 'address_form'
use_required_attribute = False
street_name = fields.CharField(label='Street name')
import json
from django.http import JsonResponse
from django.views.generic import TemplateView
from django.core.urlresolvers import reverse_lazy
class SubscribeView(TemplateView):
template_name = 'forms-set.html'
success_url = reverse_lazy('form_data_valid')
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context['subscribe_form'] = SubscribeForm()
context['address_form'] = AddressForm()
return self.render_to_response(context)
def put(self, request, *args, **kwargs):
request_data = json.loads(request.body)
subscribe_form = SubscribeForm(data=request_data.get(SubscribeForm.scope_prefix, {}))
address_form = AddressForm(data=request_data.get(AddressForm.scope_prefix, {}))
response_data = {}
if subscribe_form.is_valid() and address_form.is_valid():
response_data.update({'success_url': self.success_url})
return JsonResponse(response_data)
# otherwise report form validation errors
response_data.update({
subscribe_form.form_name: subscribe_form.errors,
address_form.form_name: address_form.errors,
})
return JsonResponse(response_data, status=422)
<script type="text/javascript">
angular.module('djangular-demo', ['djng.forms']).config(function($httpProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
$httpProvider.defaults.headers.common['X-CSRFToken'] = '{{ csrf_token }}';
});
</script>
<djng-forms-set endpoint="/form_sets/">
<form name="subscribe_form" novalidate>
<fieldset>
<legend>Subscribe Form</legend>
{{ subscribe_form.as_div }}
</fieldset>
</form>
<form name="address_form" novalidate>
<fieldset>
<legend>Address Form</legend>
{{ address_form.as_div }}
</fieldset>
</form>
<button ng-click="do(update()).then(reloadPage())" type="button">
Forced Submission <i class="some-icon"></i>
</button>
<button ng-click="do(update()).then(redirectTo())" ng-disabled="isDisabled()" type="button">
Validated Submission <i class="some-icon"></i>
</button>
</djng-forms-set>