Как стать автором
Обновить

Комментарии 38

А почему вы не проверяете запрос на ajax? Вдруг у пользователя отключен js, будете писать отдельный view?
Согласен, в общем случае нужно проверять, но здесь не стал делать чтобы не усложнять код.
На самом деле именно в моём примере кода при отключенном JS сабмит формы вообще не произойдёт, но это легко исправляется заменой span на input.
Плюс, если вы хотите чтобы форма умела обрабатывать ошибки про отключенном JS, нужно будет написать ещё один темплейт, расширяющий базовый, чтобы он был в стиле сайта.
Кстати, я вот тут так делал и напоролся на проблему: в некоторых версиях оперы мини и в стандартном браузере на нокиях заголовок аякс-запроса не присоединяется, т.е. отправляется как обычный http-запрос (соответственно, вьюха просто возвращает конечную страницу или редирект). С тех пор на всякий случай привешиваю get-параметр ajax=y к строке адреса :(
Мне кажется вы забыли про ошибки, которые не привязаны к конкретному полю (form.non_field_error). В js они никак не обратаваются
Да, такие тоже есть, но при желании их можно добавить аналогичным образом.
это все хорошо но когда это аякс иногда валидация с сервера, даже при асинхронном запросе, работает мягко говоря мучительно, особенно если форма большая и сложная. мне в проекте пришлось дописывать еще клиент-сайд валидаторы, и только после валидности всех полей идет запрос аякса. хотелось бы видеть автоматическую генерацию таких вот правил, или хотя бы либу для прописывания их в формах а не отдельно, но пока это только мечты :)
Это интересная мысль кстати, за несколько минут поиска наткнулся вот на это
http://code.google.com/p/django-ajax-forms/
Может быть это то, что нужно?
но просто так он ведь не может «узнать» правила валидации, базовые, конечно, но ведь есть нюансы…
dajaxproject.com — писать на питоне, чтобы не писать на js? Я до сих пор не могу простить авторам джанги forms и widget, которые вообще никак не вписываются ни в mvt, ни в mvc — чтобы поменять css класс — надо лезть в forms.py, потом искать виджет, а если не повезет, то еще и в темплейт виджета.
Так… Нужно разобраться, что это нам дает. Зачем формы стали описывать на Питоне? Ведь это же очень неудобно: html-атрибуты, тексты кнопок, подписи к полям — все приходится держать внутри view, а не в шаблоне.

Все перенести в шаблон и только подставлять данные полей — не получится. Бывают динамические формы, собрать которые в шаблоне будет очень тяжело. Например, набор полей или список опций может зависеть от каких-то параметров:

{% if action == 'gift' or action == 'change' %}
    <dl>
        <dt>
            <label for="{{ prefix_id }}-conditions">{{ product_form.conditions.label }}</label>
        </dt>
        <dd>
            {{ product_form.conditions }}
            {{ product_form.conditions.errors }}
        </dd>
    </dl>
{% endif %}

О боже! Логика в шаблоне!!!

Бывают сложные формы, в которых редактируется сразу несколько объектов, иерархически связанных друг с другом. Нужно делать «размножаемые» поля. Кстати, все эти готовые ajax-решения начинают нервно курить в углу на таких формах.

Еще есть поля management_form, которые в шаблонах не нуждаются.

Получается, проще все перенести в Питон.

С другой стороны, если форму нужно вывести каким-то нестандартным образом (например, в две колонки), в шаблоне придется отказаться от вывода всей формы целиком и работать с отдельными полями.


Насчет widget-tweaks

Я не очень понимаю, чем в общем случае вот этот код:

{{ form.title|add_class:"css_class_1 css_class_2" }}

лучше простого вывода поля в виде html:

<input class="css_class_1 css_class_2" name="title" id="{{ prefix_id }}-title" />
<label for="{{ prefix_id }}-title">{{ form.title.label }}</label>


То есть, он лучше в том конкретном случае, когда вид формы совсем не статический и с ее формированием хорошо справляется чужой view, но очень нужно поменять какую-то мелочь (класс или атрибут добавить).
Про рассуждения насчет логики в шаблонах. Если часть полей не нужна, или какие-то зависимости, то их и проверять не нужно/нужно по-другому на сервере, и это логика не только вывода формы, но и проверки. Ее все равно т.е., получается, в питоне писать. Это конечно же не логика шаблона.

Я не очень понимаю, чем в общем случае вот этот код:

{{ form.title|add_class:"css_class_1 css_class_2" }}


лучше простого вывода поля в виде html:

<input class="css_class_1 css_class_2" name="title" id="{{ prefix_id }}-title" />
<label for="{{ prefix_id }}-title">{{ form.title.label }}</label>



Как чем лучше? Меньше писать, меньше дублирования, нет необходимости передавать префиксы и следить за тем, как формируется параметры у формы (например, в name=«title» не учтен возможный префикс формы).

Не бывает общих случаев на практике) Конечно, это инструмент, он для решения определенных задач (imho встречающихся часто, особенно если версткой шаблонов занимается верстальщик). ACCOUNT задачу обозначил, по-моему django-widget-tweaks ее более-менее решает.

Поменять класс или атрибут — это же при удачной front-end части не мелочь, а все, что необходимо для смены внешнего вида и поведения поля.

Возьмем, например, кусок с dl/dt/dd. Берем и выносим его в шаблон:

{# fields/as_dl.html #}
<dl>
    <dt>
        {{ field.label_tag }}}
    </dt>
    <dd>
        {{ field }}
        {{ field.errors }}
    </dd>
</dl>


Делаем это для всех других видов верстки полей, встречающихся в проекте. Если каждое поле верстается по-своему — это уже клиника все-таки, или проект очень маленький. Да и что тут сделаешь, если каждое поле верстается по-своему. Но обычно во всех формах примерно одно и то же ведь, если верстка вменяемая.

Дальше мы поля выводим:

<form action='.' method='POST'> {% csrf_token %}
    {% include 'fields/as_dl.html' with field=product_form.title %}
    {% include 'fields/as_dl.html' with field=product_form.conditions %}
</form>


а потом хотим там заменить css-класс у одного из полей — по дизайну так нужно:

<form action='.' method='POST'> {% csrf_token %}
    {% include 'fields/as_dl.html' with field=form.title %}
    {% include 'fields/as_dl.html' with field=form.conditions|add_class:"foo" %}
</form>


можно поля и в цикле вывести:

{% for field in product_form %}
    {% include 'fields/as_dl.html' %}
{% endfor %}


Можно и для форм сниппеты сделать под свой проект.

В случаях посложнее — откатываемся назад на обычные методы. По-моему всяко лучше, чем безусловно все копипастить. И обошлись тут стандартными средствами, ну + еще маленькой библиотечкой (которая, по сути, тоже стандартные средства и использует, просто позволяет в шаблоне решения об атрибутах виджета принимать, а не в объявлении формы на питоне).
django-widget-tweaks — хорошая штука, да.

Я просто не люблю все, что с html-формами связано. Мне все решения кажутся неудачными, т.к. всегда найдется такой случай, для которого конкретное решение подходит плохо. Ваши примеры выглядят красиво. Умеете успешно применить стандартные средства.
спасибо) А так ведь всегда — с чем угодно можно найти случай, для которого решение плохо подойдет. При этом хорошо, когда есть возможность убрать мешающий слой абстракции — а с выводом формам возможность есть, написать html да и все. В django ORM то же самое хорошо — если запрос составляется с трудом, можно QuerySet API не гнуть, а на raw откатиться.

Проблема еще в том, что супер-универсальные абстрактные java-style решения обычно сложными получаются.

В GSoC'11 есть довольно перспективный proposal от Gregor Müllegger о том, как сделать вывод форм в django поудобнее и погибче. Если есть что сказать по этому поводу — почитайте обсуждения и напишите туда свои мысли, сейчас самое время.
а по-моему, суета) все это. нет ни какой радости от шаблонов, в которых 90% занимает логика. в таких случаях, dsl шаблонизатора ужасен и в разработке, и в отладке, и в поддержке. поэтому логика рисования форм изначально кодилась на python. чтобы понять, во что выльется сея безумная затея GSoC'11 достаточно посмотреть на шаблоны админки — code.djangoproject.com/browser/django/trunk/django/contrib/admin/templates/admin/change_form.html. собственно html'я в методах рендера форм на самом деле чуть — в основном логика: code.djangoproject.com/browser/django/trunk/django/forms/widgets.py. поэтому, на следующий год кто-нибудь решит переписать обратно на python. :)

эти студенческие метания могут прекратиться только в том случае, когда у страницы появится нормальное объектное представление, которое обеспечит возможность описывать и твикать его как из шаблона, так и из кода на python (по типу www.livinglogic.de/Python/xist/xsc/index.html). плюс AST-представление, чтобы дать возможность компилировать шаблоны не только в python, но и другие языки (например в javascript для рендера страниц на клиенте). примеры есть github.com/mkerrin/pwt.jinja2js, или в том же xist.
Было бы неплохо, если бы вы выложили файлы как готовый проект из коробки, потому как подобные вещи очень помогают новичкам Джанги.
На самом деле я причесал эти файлы из реального проекта специально для статьи, но я попробую что-нибудь придумать.
Я ошибки в джанго-формах проверяю следующим образом:

делаю

И в методе-обработчике формы проверяю — если метод get, то просто отобразить данные, если post — то проверить, и, в случае валидности формы, показать. Иначе вывести ошибки.

    def object_edit_or_add_post_save(self, request, entity_name, object_id=None):
        self.check_the_entity(entity_name)
        
        if request.method == 'GET':
            """ если GET, то запрашивается страница """
            return self.object_edit_or_add(request, entity_name, object_id)
        elif request.method == 'POST':
            """ если POST, то передаются данные """
            return self.object_save(request, entity_name, object_id)


    def object_save(self, request, entity_name, object_id=None):
        ...
        if f.is_valid():
            f.save()
            return HttpResponseRedirect(self.root + entity_name)
        else:
            # ищем ошибки
            for e, e_message in f.errors.items():
                field = f.fields[e]
                field.error = True
                field.error_message = e_message
        ...



	<form name="edit" method="post" action="">
	{% csrf_token %} 
	...
	</form>

Парсер съел теги. Я писал что делаю у формы action="" для ссылки страницы саму на себя.
Эм, раз форма постится на ту же страницу значит речь идёт о форме на отдельной странице всё-таки?
Да, именно так. Ну если на странице больше нет форм, которые надо проверять, и точно не появятся — то это как раз описанный мною случай.
Да, спасибо за пост — статьи по джанго радуют.
Судя по скриншотам у вас форма отображается во всплывающем окне. Так вот ещё один способ реализации «AJAX» формы — показывать iframe с обыкновенной страницей обработки формы, естественно базовый шаблон для такой страницы нужно сделать минималистичным и ещё нужно как-то обрабатывать успешность регистрации — закрывать это окошко с iframe, как минимум.
Спасибо, а можете просветить насчёт iframe, у меня почему-то очень давно укоренился стереотип что iframe — это плохо, его нормально вообще использовать сейчас?
Раз вы думаете, что «iframe — это плохо», что бы это ни значило, то расскажите для начала, чем плох iframe и для кого он плох :)
Так я же не помню, вот и хочу узнать бред ли это или есть какие-то проблемы с использованием iframe.
Я считаю, что как быстрое решение для того, чтобы сварганить форму во всплывающем окошке iframe хорошее решение. Есть проблема с оформлением размеров блока, содержащего iframe — после отправки формы с ошибочными данными, мы получим ту же форму + ошибки т.е. высота нового содержимого будет выше чем высота пустой формы, если javascript-библиотека не умеет изменять размер окна в случае измененения содержимого iframe, то мы получим полосу прокрутки или надо изначально задавать высоту окна с запасом. Главное преимущество iframe-решения в том, что мы работаем с обычными джанговскими вьюшкой и формой.
Спасибо, теперь понятно
Даже если и «не нормально», то залить асинхронно файлы яваскриптом на сервер никак по другому нельзя, поэтому как минимум для этого придется его использовать.
Привет всем, в своем веб-приложении мне необходимо изменять и добавлять данные по еще пока не известной модели, как через ajax так и через обычную форму.
Естественно я использовал modelform'ы, и очень удобную разработку под название dojango. Dojango элементарно устанавливается и теперь наши django виджеты превращаются на выходе в виде dojo виджетов c той валидацией что мы определили в модели\форме.
Теперь осталось просто вывести форму в виде html — form.as_ul(), и ждать ответа.

Dojo виджеты нам обеспечат валидацию на стороне клиента, из нашей формы без грамма кода. А серверную валидацию надо обеспечить нам.

Вот мой код для вывода формы\валидации и оправки ошибок\и если нет ошибок то перенаправления на объект.
Боюсь что код не прапарсится, вот ссылка на него
def show_form(request,oper,id=None):
#print request.path
import newdisp.whs.models as models # Все модели берем, и доставать нужную будем через getattr
try:
f=getattr(models,oper+'_form') # Берем
except AttributeError,e: #Ловим неправильные название форм
raise Http404('Page not found') # как вариант возвращять json({'status':'error'})

if 'id' in request.POST: # Переменые в запросах приходят строками, делаем в int
id = int(request.POST['id'])

if id is None: #Если нет айди значит добавление
m=getattr(models,oper)() # Берем модель, и вызываем ее без параметров, что означает пустому экземпляру.
else: # Значит изменение
id=int(id) #еще раз
try:
m=getattr(models,oper).objects.get(pk=id) # Берем модель с данными которые будем изменять
except ObjectDoesNotExist,e:
raise Http404('Page not found')


if request.method == 'POST': # Если пост то обрабатываем данные, если гет то вывод html формы
post=request.POST.copy() # Копируем массив, ибо request — read only
if 'price' in post.keys(): # Заменяем замечательные русские запятые в float числах
post['price']=post['price'].replace(',','.')
if 'delivery' in post.keys():
post['delivery']=post['delivery'].replace(',','.')

form = f(post,instance=m) # Создаем форму и наполняем ее даными из модели

if form.is_valid(): # Если ошибок нет, то сохраняем и редиректив на ссылку
# где ей отдадут json данные модели
inst = form.save()
return HttpResponseRedirect('/whs/id/%s/%d' % (oper,inst.id))
else: # Если ошибки, вернуть json со статусом ошибки и сообщения ошибки
re={'status':'error','message':form.errors}
return HttpResponse(json(re),mimetype=«application/json»)
else:
form = f(instance=m) # вывод html формы
if request.is_ajax(): # Если jax то вернуть просто html, я оберну ее в форму на стороне клиента
return HttpResponse(form.as_ul(),mimetype=«text/html») #Если ajax срем говый вариантик

# Если обычный, то обрабатываем. Это надо сделать через шаблон.
re=u' '
re+=u''
re+=form.as_ul() # Вывести ее как список
#re+=u''
re+=u''
re+=u''
return HttpResponse(re,mimetype=«text/html»)

На стоне клиента надо написать код на dojo, чтобы забрать форму, пропарсить его dojo.parser, и создать xhtPost формы.
При попытке отправки формы, dojo проверит данные еще раз на соответсвие типам, и отправит данные.
Дальше их поймает django, проверит на логичность(например повторение уникальных значений) и сгенерирует текст ошибки на чистом русском языке, что мы и отправим обратно форме в виде json
Например:
{
status: «error»
-message: {
-number: [
«Накладная с таким № документа уже существует.»
]
}
}
Осталось растолкать это по полям формы dojo.

if (result.status == 'error') {
for (var i in result.message) {
var input = dijit.byNode(dojo.query('.dijit [name="' + i + '"]', this.form.containerNode)[0].parentNode.parentNode);
input.state = 'Error';
input._setStateClass();
dijit.setWaiState(input, 'invalid', 'true');
input._maskValidSubsetError = true;
dijit.showTooltip(result.message[i], input.domNode, input.tooltipPosition);
}
}
else {
alert('СХОРАНЕНО');
//return result;
}
Вот клиентская валидация:

А вот уже ответ с сервера(заметьте сообщения создано автоматически и «из коробки» django)
Перепутаны местами варианты в описании по отношению к заявленным пунктам выше в случае описания двух вариантов отображения ошибок в форме.
Спасибо, исправил.
Всё это, конечно, здорово, но маленькую форму можно и без специальных извращений завернуть в ajax, а большую лучше честно отправлять на сервер без всякого ajax-а. Там и файлы могут быть, и ошибки проще так выводить, и всё равно на новую страницу редиректить после обработки действия, так зачем ajax?

Вот, что действительно было бы интересно, это валидация форм на клиенте javascript-ом, автоматически генерирующаяся по джанговой форме. Пусть и частичная.
А что значит маленькую форму завернуть в ajax без извращений? Вроде бы у меня вполне нормальный способ и подходит для любых форм. И редирект после обработки совсм необязателен, таким образом достигается бОльшая гибкость.
Насчёт валидации форм на клиенте посмотрите комментарии выше, вот здесь например:
http://habrahabr.ru/blogs/django/117876/#comment_3839476
Маленькая форма — та, которая выполняет стороннее действие не относящееся к основной цели страницы, форма подписки на рассылку на странице новости, например. Она обычно имеет кастомную вёрстку, может появлятся/прятаться и соответственно общий подход к показу ошибок к ней всё равно не подойдёт, лучше написать небольшую обработку специально для неё, чем пытаться общее решение подогнать.

Если же это страница формы, то и отправляться форма должна по-нормальному и редирект на страницу успеха после поста формы обязателен.
Это всё понятно, кастомная вёрстка и т.д., но я так и не услышал как вы предлагаете её в аякс завернуть? разве не придётся делать так же через JSON?
Ну да, где ajax, там и JSON. Просто как удобный формат передачи. Но, во-первых, кастомная вёрстка, следовательно не подходит общий метод показа ошибок, плюс для простеньких форм, джанго форма тоже излишня, можно просто вьюху написать, так будет и короче и логичнее (ни к чему лишние сущности). И, таким образом, ничего не остаётся из вашей статьи.

В случае большой формы или формы модели ваш подход смысл как раз имеет. Просто я считаю, что Ajax для отправки формы в таком случае использовать ни к чему. Ни для пользователя, ни для разработчика плюсов-то особых нет. Для разработчика как раз наоборот, лишние телодвижения.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории