- PVSM.RU - https://www.pvsm.ru -
Сегодня я расскажу о том, как мы валидируем формы с использованием технологии Ajax. На стороне сервера будет использоваться Django.
Ничего нового в этом способе нет, в интернете есть несколько статей на эту тему, главное же отличие от этих статей заключается в том, что всю логику валидации формы через Ajax мы вынесем в отдельное представление (view). Это позволит нам писать все остальные представления без какой-либо дополнительной логики. При написании функционала будет использоваться Class Based Views подход.
При валидации форм с использованием Ajax мы получаем следующие преимущества:
Если заинтересовались, добро пожаловать под кат.
Коротко о том, как все работает:
В статье более подробно будет описана реализация серверной части валидации. При интересе со стороны хабрасообщества можно будет более подробно описать клиентскую часть.
Рассмотрим, как все работает, на примере формы авторизации.
Создадим представление, которое будет работать с формой авторизации:
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('/')
Как можно заметить, никакой дополнительной логики связанной с нашей Ajax
обработкой здесь нет.
Теперь напишем представление, которое будет обрабатывать Аjax запросы:
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')]
Здесь мы создаем представление AjaxValidation, которое в случае не валидности формы будет передавать ошибки клиентской стороне в виде списка объектов в формате Json следующего вида:
[{“key”: ”#id_email”, “desc”: ”Введен некоректный email адрес”}, {“key”: ”...”, “desc”: ”...”}]
В нем мы указываем идентификатор поля, в котором возникла ошибка, в соответствии со стандартным, генерируемым фреймворком форматом — “id_<название поля>”, или название формы, если эта ошибка не относится к определенным полям формы. Так же передаем текст ошибки, полученный на основании валидации формы.
В словаре form_dict мы указываем идентификатор формы и соответствующий класс формы для валидации.
Иногда, для правильной валидации, форме требуется передать дополнительные аргументы. Их можно передать переопределив метод get_form_kwargs, например, так:
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
Здесь используется статический метод. Статический метод позволяет хранить логику, относящуюся к форме, в классе формы без необходимости инициализации экземпляра для исполнения этого метода. Мы проверяем класс на наличие метода и, в случае, если метод в классе определен, обновляем словарь аргументов, передаваемый в форму.
Сам метод может выглядеть следующим образом:
@staticmethod
def get_arguments(arg):
user = arg.request.user
return {'instance': user}
В данном случае мы передаем в наш метод экземпляр AjaxValidation, из которого мы получаем объект пользователя. Затем передаем его в качестве аргумента в нашу форму.
Далее рассмотрим класс формы:
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
В качестве дополнительных аргументов передаем полям формы значения humanReadable, которые будем использовать при выводе ошибок в данной реализации.
В валидации все достаточно просто: проверяем есть ли email пользователя в базе, правильно ли указан пароль и не заблокирован ли пользователь. Так как мы работаем со значениями сразу нескольких полей то валидация осуществляется в методе clean.
Способ вывода информации в каждом проекте может варьироваться и ниже приведен самый простой шаблон для вывода формы:
<!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>
В блоке с классом validation_info будут выводиться название поля, которое мы получаем из атрибута humanReadable и текст ошибки, сгенерированный фреймворком.
Работа с Ajax валидацией в нашем случае требует, чтобы каждой форме был присвоен
идентификатор. С помощью этого идентификатора мы связываем форму с соответствующим классом в переменной form_dict. Каждая валидируемая форма должна иметь класс validForm, тогда javascript обработчик начнет с ней работать.
Сам javascript код выглядит следующим образом:
(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));
});
})();
Обработчик перехватывает событие submit у формы, выполняет $.serialize(),
собирая в строку идентификаторы и значения полей ввода, а также присоединяет идентификатор самой формы.
В случае, если в ответ пришло сообщение об ошибке, получаем идентификатор объекта с ошибкой и текст ошибки. Далее выводим удобочитаемое описание элемента (формы) и сам текст ошибки в элемент с классом validation_info.
У нас есть представление AjaxValidation, в которое мы вынесли все
взаимодействие с клиентской частью. В представлении мы регистрируем формы, которые необходимо валидировать. Дополнительные параметры можно передать в статическом методе формы.
Данное решение позволяет использовать все преимущества Ajax валидации и писать только необходимую нам функциональность, ни на что не отвлекаясь.
В соавторстве с flip89 [1]
Автор: erdmko
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/django-2/39808
Ссылки в тексте:
[1] flip89: http://habrahabr.ru/users/flip89/
[2] Источник: http://habrahabr.ru/post/188384/
Нажмите здесь для печати.