Недавно и до меня дошло озарения(лучше поздно, чем никогда), и я понял, что УЖЕ пользуюсь асинхронной валидацией форм, меня оно устраивало - его было легко внедрять, и кода было минимум. Основой был django-ajax-validation проект, на его основе и построен следующий пример.
Для реализации определил следующие критерии:
- Универсальный механизм сбора данных на клиенте.
- Минимум кода при внедрении.
1. Серверная часть
В реализации немного дорабатываем и расширяем серверную часть обработки запроса. Для работы понадобиться simplejson, его можно скачать тут.Поправив странный момент с перетиранием дополнительных входящих параметров в функцию обрабатывающую запрос получился следующий код(submit_form.py):
from django.utils.functional import Promise
from django.utils.encoding import force_unicode
from simplejson import JSONEncoder
from django import forms
from django.http import HttpResponse
from django.views.decorators.http import require_POST
class LazyEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
def submit_form(request, *args, **kwargs):
form_class = kwargs.pop('form_class')
complete_func = kwargs.pop('complete_func', None)
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
kwargs_form_data = extra_args_func(request, *args, **kwargs)
kwargs_form_data['data'] = request.POST
form = form_class(**kwargs_form_data)
if form.is_valid():
result_data = {
'saved': True,
}
if complete_func:
complete_func(request,result_data,kwargs_form_data,*args,**kwargs)
else:
if request.POST.getlist('fields'):
fields = request.POST.getlist('fields') + ['__all__']
errors = dict([(key, val) for key, val in form.errors.iteritems() if key in fields])
else:
errors = form.errors
final_errors = {}
for key, val in errors.iteritems():
if not isinstance(form.fields[key], forms.FileField):
html_id = form.fields[key].widget.attrs.get('id') or form[key].auto_id
html_id = form.fields[key].widget.id_for_label(html_id)
final_errors[html_id] = val
result_data = {
'saved': False,
'errors': final_errors,
}
json_serializer = LazyEncoder()
return HttpResponse(json_serializer.encode(result_data), mimetype='application/json')
submit_form = require_POST(submit_form)
В метод получает на вход объект запроса и дополнительные аргументы, данные проверяются на валидность и в случае успешного завершения вызывают функцию сохранения данных, в случае ошибки собирается текст ошибок и передается в качестве ответа. Останавливаться на описании алгоритма не буду, тут вроде бы и так все понятно, все тонкости будут понятны на примере (см. 3. Внедрение)2. Клиентская часть
Дорабатываем клиентскую часть с использованием jquery 1.3.2(jquery-ajax-submit.js):(function($) {
function form_data(form) {
return form.find("input[checked], input[type='text'], input[type='hidden'], input[type='password'], input[type='submit'], option[selected], textarea").filter(':enabled');
}
function inputs(form) {
return form.find("input, select, textarea")
}
$.fn.last_submit_data = null;
$.fn.submit_form = function(url, settings) {
settings = $.extend({
type: 'table',
callback: false,
fields: false,
}, settings);
var form = $(this);
var params = {};
form_data(form).each(function() {
params[ this.name || this.id || this.parentNode.name || this.parentNode.id ] = this.value;
});
var status = false;
if (settings.fields) {
params.fields = settings.fields;
}
$.ajax({
async: false,
data: params,
dataType: 'json',
error: function(XHR, textStatus, errorThrown) {
status = false;
},
success: function(data, textStatus) {
status = data.saved;
if (!status) {
if (settings.callback) {
settings.callback(data, form);
}
else{
if (settings.type == 'p') {
inputs(form).parent().prev('ul').remove();
inputs(form).parent().prev('ul').remove();
$.each(data.errors, function(key, val){
if (key == '__all__'){
var error = inputs(form).filter(':first').parent();
if (error.prev().is('ul.errorlist')) {
error.prev().before('- ' + val + '
- ' + val +'
- ' + val + '
- ' + val + '.
- ' + val + '
- ' + val + '
- ' + val + '
Если пролистать код по диагонали, то можно заметить следующее объявление "$.fn.last_submit_data = null", в данной переменной будет содержаться результат последнего выполнения функции.
3. Внедрение
Перейдем к непосредственному использованию. Пол дела сделано, теперь внедрим в приложение и посмотрим на сколько стало удобнее :) или не стало, такое тоже бывает. Для начала опишем стандартный разделы приложения.models.py:
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=150)
description = models.TextField
forms.py
from django import forms
from myapp.models import MyModel
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['name', 'description']
Для сохранения результатов запроса давайте в файл views.py добавим функцию сохранения значений.views.py
from django.shortcuts import render_to_response
from django.template import RequestContext
from myapp.forms import MyForm
from myapp.models import MyModel
def mymodel_form(request):
# какой то код логики
return render_to_response("mymodel_form.html", {'form' : MyForm}, RequestContext(request))
def item_save(request,result_data,kwargs_form_data,*args,**kwargs):
item = MyModel();
form = MyForm(instance = item, **kwargs_form_data)
form.save(commit=False)
try:
# Код по дополнительной обработке данных
item.save()
except Exception:
result_data['valid'] = False
result_data['error'] = 'Вознилка ошибка при сохранении'
Как вы уже заметили в функции сохранения нет нечего особенного, она лишь реализует самый простой механизм обработки и сохранения данных, гдеrequest - непосредственно объект запроса;
result_data - словарь данных которые будут возврашены на клиент, его можно дополнить своими значениями, или установить флаг о ошибке сохранения. При возвращении сериализуеться в JSON;
kwargs_form_data - данные с формы;*args - дополнительные аргументы;
**kwargs - дополнительные именованные аргументы.
Зарегистрируем эти функции в файле url.py.
url.py
from django.conf.urls.defaults import *
from myapp.forms import MyForm
from myapp.views import *
from myapp.submit_form import submit_form
urlpatterns = patterns('',
url(r'^form/$', mymodel_form, name='from'),
url(r'^submit/$', submit_form, {'form_class': MyForm,
'callback': lambda request, *args, **kwargs: { },
'complete_func': item_save,
}, 'submit'),
)
Немного разберем что за параметры необходимо передать для функции submit_form:form_class - класс формы;
callback - функция для обработки дополнительных параметров переда валидацией, результатом выполнения ожидается словарь содержащий данные которые будут переданы в форму;
complete_func - функция по обработки результата при успешной валидации;
Пример не будет закончен без описание шаблона.
mymodel_form.html
...Внимание: не забудте подключить на страницу jquery скрипты!...{{ form }}
Ну вот и все, решение готово, запускаем и убеждаемся что работает как часы. Подсумировав можно сказать, что для внедрения данного метода вам понадобиться определить функцию по генерированию основного html контента(view.py), функцию по обработке результата синхронного запроса(view.py), подключить их в url конфиге, и дописать javascript функцию обработки результата ответа на стороне клиента. Считаю что поставленная задача выполнена. На последок можно сказать что есть тонкости и недоработки в данной реализации :), без этого жить было бы скучно. А как вы решаете данную задачу?!
0 коммент.:
Отправить комментарий