
Today I will talk about how we validate forms using Ajax technology. On the server side will be used Django.
There is nothing new in this method, there are several articles on this topic on the Internet, the main difference from these articles is that we will put the whole logic of form validation through Ajax into a separate view (view). This will allow us to write all the other views without any additional logic. When writing functionality, the Class Based Views approach will be used.
When validating forms using Ajax, we get the following benefits:
- user page does not reload until data is valid;
- the form is not drawn again;
- The logic of form validation is described in one place.
If interested, welcome under cat.
Briefly about how everything works:
- The javascript handler intercepts the "submit" event for all forms on a page with a specific class, for example, validForm;
- when the "submit" event occurs, the handler sends the form data and its identifier to a specific address;
- A view that processes requests from this address:
- initializes the form;
- validates data;
- returns back a response about the success of validation or a list with errors, if any;
- Upon successful validation, the handler allows the submission of form data in the usual way; otherwise, it displays the error text.
The article will describe in more detail the implementation of the server-side validation. With interest from the community, it will be possible to describe the client part in more detail.
Consider how everything works, for example, the authorization form.
Server side
Representation
Let's create a view that will work with the authorization form:
class SignIn(FormView): form_class = SignInForm template_name = 'valid.html' def form_valid(self, form): user = authenticate(**form.cleaned_data) if user: login(self.request, user) return HttpResponseRedirect('/')
As you can see, there is no additional logic associated with our Ajax.
no processing here.
')
Now we will write a view that will process Ajax requests:
class AjaxValidation(FormView): form_dict = { 'signin': SignInForm, } def get(self, request, *args, **kwargs): return HttpResponseRedirect('/') def form_invalid(self, form): data = [] for k, v in form._errors.iteritems(): text = { 'desc': ', '.join(v), } if k == '__all__': text['key'] = '#%s' % self.request.POST.get('form') else: text['key'] = '#id_%s' % k data.append(text) return HttpResponse(json.dumps(data)) def form_valid(self, form): return HttpResponse("ok") def get_form_class(self): return self.form_dict[self.request.POST.get('form')]
Here we create the AjaxValidation view, which, if the form is not valid, will transmit errors to the client side in the form of a list of objects in the following Json format:
[{“key”: ”#id_email”, “desc”: ” email ”}, {“key”: ”...”, “desc”: ”...”}]
In it we indicate the identifier of the field in which the error occurred, in accordance with the standard format generated by the framework - “id_ <field name>”, or the name of the form, if this error does not apply to certain fields of the form. We also transmit the error text obtained on the basis of form validation.
In the form_dict dictionary, we specify the form identifier and the corresponding form class for validation.
Sometimes, for proper validation, the form needs to pass additional arguments. They can be passed by overriding the get_form_kwargs method, for example, as follows:
def get_form_kwargs(self): kwargs = super(AjaxValidation, self).get_form_kwargs() cls = self.get_form_class() if hasattr(cls, 'get_arguments'): kwargs.update(cls.get_arguments(self)) return kwargs
This uses the static method. The static method allows you to store logic related to the form in the form class without the need to initialize the instance to execute this method. We check the class for the presence of a method and, if the method is defined in the class, update the argument dictionary passed to the form.
The method itself may look like this:
@staticmethod def get_arguments(arg): user = arg.request.user return {'instance': user}
In this case, we pass an instance of AjaxValidation to our method, from which we get the user object. Then we pass it as an argument to our form.
Forms
Next, consider the form class:
class SignInForm(forms.Form): email = forms.EmailField(widget=forms.TextInput(attrs={'humanReadable': 'E-mail'}), label='E-mail') password = forms.CharField(min_length=6, max_length=32, widget=forms.PasswordInput(attrs={'humanReadable': ''}), label='') def clean(self): if not self._errors: cleaned_data = super(SignInForm, self).clean() email = cleaned_data.get('email') password = cleaned_data.get('password') try: user = User.objects.get(email=email) if not user.check_password(password): raise forms.ValidationError(u' e-mail \ .') elif not user.is_active: raise forms.ValidationError(u' e-mail .') except User.DoesNotExist: raise forms.ValidationError(u' e-mail .') return cleaned_data
As additional arguments, we pass the humanReadable values ​​to the form fields, which we will use when generating errors in this implementation.
In the validation, everything is quite simple: we check if the user's email is in the database, if the password is correct and if the user is not blocked. Since we work with the values ​​of several fields at once, validation is performed in the clean method.
Template
The way information is displayed in each project can vary and the simplest template for form output is given below:
<!DOCTYPE html> <html> <head> <title></title> </head> <body> <form class='validForm' id='signin' humanReadable=' ' method='post' action=''> <div class='validation_info'></div> {{ form.as_ul }} {% csrf_token %} <input type='submit'> </form> </body> </html>
In the block with the validation_info class, the name of the field that we get from the humanReadable attribute and the error text generated by the framework will be displayed.
Client part
Working with Ajax validation in our case requires that each form be assigned
identifier. Using this identifier, we associate the form with the corresponding class in the form_dict variable. Each validated form must have a validForm class, then the javascript handler will start working with it.
The javascript code itself looks like this:
(function(){ var _p = project; _p.setupValidator = function($f){ var targetInputs = $f.find('input,textarea,select'), infoElement = $f.find('.validation_info'); targetInputs.add($f).each(function(){ var $i = $(this), hR = $i.attr('humanReadable'); $i.data('showErrorMessage',function(msg){ infoElement.append($('<p/>').html(hR+': '+msg)); }); }); $f.on('submit', function(e, machineGenerated) { if(machineGenerated) return; infoElement.html(''); e.preventDefault(); var ser = targetInputs.serialize(); ser += '&form=' + $f.attr('id'); $.post('/ajaxValidation/', ser, function(info) { if (info != 'ok') { var errors = $.parseJSON(info); for (var i = 0; i < errors.length; i++) { var input = $f.find(errors[i].key); if($f.is(errors[i].key)){ input = $f; } if(input.data('showErrorMessage')){ input.data('showErrorMessage')(errors[i].desc); } else { console.log(input,errors[i].desc); } } } else { $f.trigger('submit', [true]); } }); }); } $(function (){ _p.setupValidator($('.'+_p.validateeFormClass)); }); })();
The handler intercepts the submit event from the form, executes $ .serialize (),
gathering the identifiers and values ​​of the input fields in a line, and also appends the identifier of the form itself.
If an error message was received in response, we get the object identifier with an error and the error text. Next, we print a readable description of the element (form) and the error text itself into the element with the class validation_info.
Total
We have a presentation AjaxValidation, in which we carried all
interaction with the client part. In the view, we register the forms that need to be validated. Additional parameters can be passed in the static form method.
This solution allows you to use all the benefits of Ajax validation and write only the functionality we need, without being distracted by anything.
Co-authored with
flip89