Validated Model-Form Demo

Combine client-side Form validation with two-way data-binding

In this example, client-side Form validation is combined with two-way data-binding.


    Please enter a valid email address
    Allowed date format: yyyy-mm-dd
    Choose one or more carriers
    Must choose at least one type of notification
    The password is "secret"

     

    Test submission of invalid Form data:
     

    MyFormCtrl's scope:
    subscribe_data = {{ subscribe_data | json }}

    How does it work?

    If a Form inherits from both mixin classes NgModelFormMixin and NgFormValidationMixin, then django-angular combines client-side validation with AngularJS's model scope.

    Refer to the previous examples for a detailed explanation.

    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_password(value):
        # Just for demo. Do not validate passwords like this!
        if value != 'secret':
            raise ValidationError('The password is wrong.')
    
    class SubscribeForm(NgModelFormMixin, NgFormValidationMixin, Bootstrap3Form):
        use_required_attribute = False
        scope_prefix = 'subscribe_data'
        form_name = 'my_form'
    
        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')
    
        def clean(self):
            if self.cleaned_data.get('first_name') == 'John' and self.cleaned_data.get('last_name') == 'Doe':
                raise ValidationError('The full name "John Doe" is rejected by the server.')
            return super(SubscribeForm, self).clean()
    
    import json
    from django.http import HttpResponse
    from django.core.urlresolvers import reverse_lazy
    from django.views.generic.edit import FormView
    from django.utils.encoding import force_text
    
    class SubscribeView(FormView):
        template_name = 'combined-validation.html'
        form_class = SubscribeForm
        success_url = reverse_lazy('form_data_valid')
    
        def post(self, request, **kwargs):
            if request.is_ajax():
                return self.ajax(request)
            return super(SubscribeView, self).post(request, **kwargs)
    
        def ajax(self, request):
            form = self.form_class(data=json.loads(request.body))
            response_data = {'errors': form.errors, 'success_url': force_text(self.success_url)}
            return HttpResponse(json.dumps(response_data), content_type="application/json")
    
    <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>
    
    <form name="{{ form.form_name }}" method="post" action="." novalidate ng-controller="MyFormCtrl">
        {% csrf_token %}
        {{ form.as_div }}
        <button type="submit" ng-disabled="{{ form.form_name }}.$invalid" class="btn btn-primary">Submit via Post</button>
        <button type="button" ng-disabled="{{ form.form_name }}.$invalid" ng-click="submit()" class="btn btn-primary">Submit via Ajax</button>
    </form>
    
    angular.module('djangular-demo').controller('MyFormCtrl', ['$scope', '$http', '$window', 'djangoForm',
                                                function($scope, $http, $window, djangoForm) {
    	$scope.submit = function() {
    		if ($scope.subscribe_data) {
    			$http.post(".", $scope.subscribe_data).then(function(response) {
    				if (!djangoForm.setErrors($scope.my_form, response.data.errors)) {
    					// on successful post, redirect onto success page
    					$window.location.href = response.data.success_url;
    				}
    			}, function() {
    				console.error('An error occured during submission');
    			});
    		}
    		return false;
    	};
    }]);
    

    This configuration is the most flexible one. Use it on productive web-sites.
    Note: The submit buttons are disabled, until the client-side Form validation has validated all the fields.

    Fork me on GitHub