Hello everyone!
Все мы знаем что Django — очень мощный и динамично развивающийся фреймворк для создания веб-приложений. Однако, несмотря на наступление эпохи Веб 2.0, в нём всё ещё нет встроенных механизмов для работы с AJAX, в частности отправки и проверки форм. Возможно django просто не хочет навязывать пользователю какой-то js-фреймворк и хочет оставаться гибкой в этом вопросе, но так или иначе при разработке часто требуются формы, работающие через ajax, без перезагрузок страниц.
О создании таких форм и работе с ними и пойдёт речь в данной статье.
Сразу оговорюсь, что идея не нова, и существует несколько библиотек, реализующих требуемую функциональность, например одна из них — http://www.dajaxproject.com/.
Для тех же, кто предпочитает сам управлять взаимодействием клиента с сервером или тех, кто не хочет втягивать в проект дополнительную библиотеку и иметь дело с её багами, я расскажу какизобрести велосипед реализовать механизм самостоятельно и опишу различные способы решения проблемы.
Для примера возьмём простую форму регистрации пользователя на сайте:
В реальной жизни вы, скорее всего, будете наследовать эту форму от модели данных, но для нашего примера это несущественно.
Для отображения этой формы на странице у нас есть два варианта:
Преимущество первого варианта — в его скорости работы (не надо делать дополнительного запроса к серверу), зато во втором варианте можно использовать один и тот же view для обработки GET и POST запросов к форме (POST нам потребуется в обоих случаях), плюс мы делаем отдельный файл шаблона для вывода формы, что делает структуру кода более упорядоченной, хотя это конечно дело вкуса.
Лично я внедряю что-то в шаблон страницы только если это простой диалог типа да/нет, а для форм всегда использую отдельные view.
Поэтому остановимся здесь на отдельном представлении для формы, тогда код будет выглядеть следующим образом:
Переходим к обработке формы. Здесь необходимо учесть, что ajax-обработчик должен как-то понимать, была ли форма успешно проверена или в ней присутствуют ошибки. Логичным решением здесь, на мой взгляд, будет использование JSON. В ответе сервера будет содержаться 2 параметра, первый из которых — булевый, будет сообщать об успешности или неудаче проверки формы. Со вторым параметром опять же есть различные варианты:
Вот и всё. Прилагаю скриншоты получившейся формы в различных состояниях:
Буду рад услышать комментарии и узнать другие способы работы с ajax-формами.
Все мы знаем что Django — очень мощный и динамично развивающийся фреймворк для создания веб-приложений. Однако, несмотря на наступление эпохи Веб 2.0, в нём всё ещё нет встроенных механизмов для работы с AJAX, в частности отправки и проверки форм. Возможно django просто не хочет навязывать пользователю какой-то js-фреймворк и хочет оставаться гибкой в этом вопросе, но так или иначе при разработке часто требуются формы, работающие через ajax, без перезагрузок страниц.
О создании таких форм и работе с ними и пойдёт речь в данной статье.
Сразу оговорюсь, что идея не нова, и существует несколько библиотек, реализующих требуемую функциональность, например одна из них — http://www.dajaxproject.com/.
Для тех же, кто предпочитает сам управлять взаимодействием клиента с сервером или тех, кто не хочет втягивать в проект дополнительную библиотеку и иметь дело с её багами, я расскажу как
Форма
Для примера возьмём простую форму регистрации пользователя на сайте:
class RegisterForm(forms.Form):
email = forms.EmailField()
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput)
В реальной жизни вы, скорее всего, будете наследовать эту форму от модели данных, но для нашего примера это несущественно.
Вывод формы
Для отображения этой формы на странице у нас есть два варианта:
- Сделать контейнер с display:none внутри всех страниц, с которых можно вызывать форму (или внутри родительского шаблона), затем с помощью JS создавать диалог из этого контейнера.
- Подгружать форму через ajax с отдельного URL, и затем также создавать диалог.
Преимущество первого варианта — в его скорости работы (не надо делать дополнительного запроса к серверу), зато во втором варианте можно использовать один и тот же view для обработки GET и POST запросов к форме (POST нам потребуется в обоих случаях), плюс мы делаем отдельный файл шаблона для вывода формы, что делает структуру кода более упорядоченной, хотя это конечно дело вкуса.
Лично я внедряю что-то в шаблон страницы только если это простой диалог типа да/нет, а для форм всегда использую отдельные view.
Поэтому остановимся здесь на отдельном представлении для формы, тогда код будет выглядеть следующим образом:
- view:
def register(request): form = RegisterForm() return direct_to_template(request, "register.html", extra_context={'form': form })
- template:
{% load i18n %} <form id="register_form" method="post" action="{% url register %}"> {% csrf_token %} <ul> {{ form.as_ul }} <li><span id="register">{% trans "Register" %}</span></li> </ul> </form>
Обработка формы
Переходим к обработке формы. Здесь необходимо учесть, что ajax-обработчик должен как-то понимать, была ли форма успешно проверена или в ней присутствуют ошибки. Логичным решением здесь, на мой взгляд, будет использование JSON. В ответе сервера будет содержаться 2 параметра, первый из которых — булевый, будет сообщать об успешности или неудаче проверки формы. Со вторым параметром опять же есть различные варианты:
- В случае успешной проверки формы этот параметр может быть пустым, так как форма логина скорее всего перенаправляет пользователя на необходимый url после входа и нам неважно что там, либо это может быть строка, которую необходимо отобразить в диалоге подтверждения.
- В случае когда в форме присутствуют ошибки снова возможно 2 способа отображения:
- Первый способ состоит в том, чтобы заново отрендерить форму с ошибками через шаблон и весь html-ответ поместить в json-переменную, которая затем заменяет содержимое всей формы.
- Второй способ — создать массив ошибок для всех полей формы и разместить его в json-переменной, затем вывести ошибки для каждого поля в цикле.
- Первый способ:
Финальная версия view:
def register(request): if request.method == 'POST': form = RegisterForm(request.POST) if form.is_valid(): # Обработка # ... return HttpResponse(simplejson.dumps({'response': _("Email with a confirmation link has been sent"), 'result': 'success'})) else: t = loader.get_template('register.html') ctx = RequestContext(request, {'form': form}) response = t.render(ctx) return HttpResponse(simplejson.dumps({'response': unicode(response), 'result': 'error'})) form = RegisterForm() return direct_to_template(request, "register.html", extra_context={'form': form })
Обработка на стороне клиента, javascript:
$(document).ready(function() { $('#register').live('click', function() { $('#register_form').ajaxSubmit({ success: function(data, statusText, xhr, $form) { // Удаляем ошибки если были $form.find('.error').remove(); if (data['result'] == 'success') { // Делаем что-то полезное } else if (data['result'] == 'error') { // Показываем ошибки $form.replaceWith(data['response']); } }, dataType: 'json' }); }); }
- Второй способ:
Финальная версия view:
def register(request): if request.method == 'POST': form = RegisterForm(request.POST) if form.is_valid(): # Обработка # ... return HttpResponse(simplejson.dumps({'response': _("Email with a confirmation link has been sent"), 'result': 'success'})) else: # Заполняем словарь response ошибками формы, ключ - название поля response = {} for k in form.errors: # Теоретически у поля может быть несколько ошибок... response[k] = form.errors[k][0] return HttpResponse(simplejson.dumps({'response': response, 'result': 'error'})) form = RegisterForm() return direct_to_template(request, "register.html", extra_context={'form': form })
Обработка на стороне клиента, javascript:
function display_form_errors(errors, $form) { for (var k in errors) { $form.find('input[name=' + k + ']').after('<div class="error">' + errors[k] + '</div>'); } } $(document).ready(function() { $('#register').live('click', function() { $('#register_form').ajaxSubmit({ success: function(data, statusText, xhr, $form) { // Удаляем ошибки если были $form.find('.error').remove(); if (data['result'] == 'success') { // Делаем что-то полезное } else if (data['result'] == 'error') { // Показываем ошибки display_form_errors(data['response'], $form); } }, dataType: 'json' }); }); }
Вот и всё. Прилагаю скриншоты получившейся формы в различных состояниях:
Буду рад услышать комментарии и узнать другие способы работы с ajax-формами.