python

Рецепты Django. Part 1 — AJAX формы

  • вторник, 28 октября 2014 г. в 02:10:48
http://habrahabr.ru/post/241646/

Здравствуй, хабраюзер!

Возможно, у меня уже начался кризис среднего возраста, но летом я начал работу над крупным Open Source проектом. Впрочем, о нём немного позже, когда за код не будет стыдно. Итак, хочу поделиться рядом сниппетов, которые мне приходилось писать, чтобы соответсовать концепции DRYDon't Repeat Youself. Посему собираюсь написать несколько статей.
Кстати, можете обратить внимание на предыдущую мою статью.

Начну с реализации AJAX.


Сразу хочу оговориться — не так давно я нашёл на сайте джанги пример для реализации ajax формы. Как выяснилось, я сделал практически то же самое, однако я это сделал сам и я доволен=) Ниже я приведу свой пример и разберу его.
image

Прошу под кат.

Говоря об обработке форм в джанге, с учётом появления модуля django.views.generic ещё в релизе 1.5, нельзя не обратить внимание на класс FormView. Поскольку именно от него наследуются все остальные generic классы для обработки форм, его я и избрал объектом своих экспериментов.

Итак, поехали:



Backend


За обработку возвращаемого результата отвечают методы is_valid(self, form) и is_invalid(self, form) в случае соответствия формы требованиям is_valid() и нет, соотсветственно. Пробираясь наверх по предкам до FormMixin мы видим следующее:
    def form_valid(self, form):
        """
        If the form is valid, redirect to the supplied URL.
        """
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form):
        """
        If the form is invalid, re-render the context data with the
        data-filled form and errors.
        """
        return self.render_to_response(self.get_context_data(form=form))


В случае с методом is_valid() нам мешает только редирект. Поэтому мы смело можем заменить этот код необходимым нам ответом:
from django.shortcuts import HttpResponse

def form_valid(self, form):
        return HttpResponse('OK')

В итоге мы получаем код HTTP 200 на выходе, который ожидает jquery.

Также я позволил себе подсмотреть в класс ModelFormMixin и добавить сохранение формы перед ответом. В итоге получился вот такой гибрид:
    def form_valid(self, form):
        form.save()
        return HttpResponse('OK')


С ответом в случае ошибки формы несколько сложнее — нам надо вернуть словарь ошибок и отобразить их для пользователя. Впрочем, задача легко выполняется с использованием стандартного аттрибута errors формы:
import json
from django.http import HttpResponseBadRequest

    def form_invalid(self, form):
        errors_dict = json.dumps(dict([(k, [e for e in v]) for k, v in form.errors.items()]))
        return HttpResponseBadRequest(json.dumps(errors_dict))


AjaxFormMixin


Таким образом мы получаем следующий класс:
from django.views.generic import FormView

class AjaxFormMixin(FormView):

    template_name = 'form_ajax.html'  # Поскольку существенная часть форм в проекте рендерятся одним шаблоном, то зачем его везде указывать=) Впрочем, эта строка несущественна.

    def form_valid(self, form):
        form.save()
        return HttpResponse('OK')

    def form_invalid(self, form):
        errors_dict = json.dumps(dict([(k, [e for e in v]) for k, v in form.errors.items()]))
        return HttpResponseBadRequest(json.dumps(errors_dict))


У нас теперь есть класс, который мы можем смело наследовать, как любой другой типа Class-based views. Вызов будет выглядеть примерно следующим:
from django.contrib.auth.forms import PasswordChangeForm # если кому-то будет полезно;-)

class PasswordChange(AjaxFormMixin):

    form_class = PasswordChangeForm


Он может быть использован в качестве CreateView лишь с той оговоркой, что мы метод __init__() родительского класса FormMixin не воспринимает аргумент model и нам придётся проделать эту работу самостоятельно:
from django.forms.models import modelform_factory

class TaskUserAssign(AjaxFormMixin):
    form_class = modelform_factory(models.TaskRole)


AjaxUpdateFormMixin


Итак, у нас есть базовый класс для обработки ajax форм. Почему бы не последовать django-way и не создать для себя ещё один generic «по образу и подобию» для обновления. В том же модуле django.views.generic мы находим класс UpdateView и ответ для нас — нам нужен аттрибут object для нашего класса… Вуаля:
class AjaxUpdateFormMixin(AjaxFormMixin, UpdateView):

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(AjaxFormMixin, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(AjaxFormMixin, self).post(request, *args, **kwargs)

Без затей мы копируем код, вот только наследуем всё остальное от уже имеющегося у нас класса AjaxFormMixin. Кстати, у данного класса уже есть аттрибут model, поскольку унаследовали мы его от UpdateView, корни которого уходят к ModelFormMixin.

В итоге у нас есть набор классов, поддерживающих создание, изменение объектов и другие с ними действия (по необходимости переопределить is_valid() а также /dev/brain и /dev/hands по вкусу) с ajax ответами, для свободного не запрещённого законом наследования.


P.S. Цель статьи была не столько в демонстрации моих достижений (хотя чего уж тут лукавить), а в разборе самого процесса. Немного позже, когда мой ассистент доработает код морды приложения, мы дополним статью ещё и кодом front-end составляющей. А пока всем спасибо за внимание.