Использование модификатора "method:" в Zope3
В статье описывается как в Zope3 создать форму, имеющую более одной кнопки submit и как обслуживать ее, используя модификатор :method запроса.
Использование модификатора "method:" в Zope3:
Широко известно, что записывая запросы к Zope3 можно указывать различные модификаторы в ключах аргументов запроса. В соответствии с ними значения аргументов преобразуются к определенным типам и формируются в определенные структуры. Разбор модификаторов и соответствующие преобразования значений выполняются этим методом:
zope.publisher.browser.BrowserRequest.processInputs
Подробному рассказу об этом место в другой статье, а здесь расскажем о модификаторе :method. Модификатор method широко использовался в Zope2, но в Zope3 используется достаточно редко, возможно, из-за низкой информированности разработчиков.
Попробуем восполнить этот пробел приведя пример, удобный для копирования: ответим на три вопроса: зачем, идея решения и пример.
Зачем? или постановка задачи :
Пусть существует форма, в которой присутствует более одной кнопки submit. Нажатие на каждую кнопку должен обрабатывать независимый обработчик, желательно реализованный на языке питон.
Характерный пример такой формы - управление различными контейнерами: от папки до корзины покупок.
Идея решения :
Свяжем с каждой кнопкой submit свое собственное имя, например:
<input type="submit" name="ww0" value="..."/> ... ... ... ... ... ... <input type="submit" name="wwn" value="..."/>
Тогда нажатие на любую из кнопок сформирует уникальный запрос, в котором существует только ключ, указанный в декларации именно той кнопки, на которую нажали. Очевидно, несложная проверка на наличие ключа, позволяет обработать нажатие на конкретную кнопку особым образом.
Так пишут все - от perl:CGI до PHP, но разработчики Zope3 (точнее говоря, Zope2), вынесли проверку этого ключа в специальный диспетчер. Как это работает: если в запросе обнаруживается ключ с модификтором method (например ww0:method), то по окончании процедуры траверса, будет вызван не последний объект, найденный в траверсе, а метод ww0 этого объекта.
Причем, безразлично о каком объекте идет речь: контент-объекте или адаптере вида. Важно, что бы аттрибут ww0 был и его можно было вызвать.
Пример 1: Простой и неправильный :
Что бы протестировать вышеописанный диспетчер, напишем два zpt-метода: qq и ww.
Метод qq:
<html> <body> <h1>QQ</H1> <form action="" tal:attributes="action context/@@absolute_url"> <input type="submit" name="qq:method" value="QQ"/> <input type="submit" name="ww:method" value="WW"/> </form> </body> </html>
Метод ww:
<html> <body> <h1>WW</H1> <form action="" tal:attributes="action context/@@absolute_url"> <input type="submit" name="qq:method" value="QQ"/> <input type="submit" name="ww:method" value="WW"/> </form> </body> </html>
Создав оба метода в одной папке и вызвав один из них, можно понажимать на кнопки WW & QQ и убедится, что происходит вызов метода, соответствующего нажатой кнопке.
Раз все работает, то почему пример неверный? Потому, что в программировании под Zope3 принято разделять контент-объект и адаптер вида, причем вся логика взаимодействия с пользователем выносится именно в адаптер вида, сущетственная часть которого, зачастую, пишется на питоне.
Пример 2: Сложный и из реальной жизни:
Это адаптер вида, некоторого объекта, для которого нужно просматривать кой-какую статистическую информацию, а кроме того выполнять два действия: "перегенерацию" и "очистку". В чем смысл этих действий - не суть, контент объект мы рассматривать не будем.
Начнем с темлейта statistic.pt:
<html metal:use-macro="views/standard_macros/view"> <body metal:fill-slot="body"> <h1>Cache Statistics</H1> <dl> <dt>Size:</dt><dd tal:content="context/__len__">0</dd> <tal:block tal:condition="options/count | nothing"> <dt>Regenerated:</dt> <dd tal:content="options/count | nothing">0</dd> </tal:block> <tal:block tal:condition="options/count | nothing"> <dt>Elapsed:</dt> <dd tal:content="options/elapsed | nothing">0</dd> </tal:block> </dl> <form action="" tal:attributes="action string: ${context/@@absolute_url}/"> <input type="submit" name="regenerate_all:method" value="Regenerate All"/> <input type="submit" name="clean:method" value="Clean"/> </form> </body> </html>
В первой части темплейта выводится вышеупомянутая статистика, в т.ч. статистика выполняемых методов (обратите внимание на использование пространства имен options, который будут упомянут ниже). Вторая часть - это уже знакомая по предыдущему примеру формочка с двумя кнопками, вызывающие два разных метода. Разумеется, эти методы должны возвращать нечто, читабельное через браузер: удобно, если нечто основано на том же самом темплейте.
Методы должны быть сконфигурены, приведем кусок из configure.zcml:
<pages permission="zope.ManageContent" class=".statistic.Statistic" for="..interfaces.ICachestore" > <page name="statistic_view" attribute="statistic_view" menu="zmi_views" title="Statistic" /> <page name="clean" attribute="clean" /> <page name="regenerate_all" attribute="regenerate_all" /> /> </pages>
Эта конфигурация предполагает, что основанный на классе .statistic.Statistic адаптер вида, предоставляет три метода, ориентированных на вызов через веб: statistic_view (вид по умолчанию) и clean & regenerate_all : выполняющие действия по кнопкам в темплейте.
Последняя часть - тот самый питоновский код, в который, благодаря такому подходу, удалось без проблем вынести обработчики кнопок.
Модуль statistic.py:
from zope.app.pagetemplate.viewpagetemplatefile \ import ViewPageTemplateFile class Statistic(object) : # Темлейт который используется для возврата # результата во всех трех вызовах statistic_view = ViewPageTemplateFile("statistic.pt") # Обработчик кнопки regenerate_all:method def regenerate_all(self,*kv,**kw) : count,elapsed = self.context.regenerate_all() # Обратите внимание, результаты вычислений передаются # в темлейт как ключевые аргументы: в темплейте они попадут # в пространство имен options return self.statistic_view(self,count=count,elapsed=elapsed,*kv,**kw) def clean(self,*kv,**kw) : self.context.clean() return self.statistic_view(self,*kv,**kw)
Вот такой простой класс позволяет эффективно запрограммировать на питоне всю логику взаимодействия адаптера вида с контент-объектом и оставить в темплейте только отображение.
Заключение:
Разумеется, вместо такого подхода, основанного на использовании диспетчера, встроенного в Zope3, можно добавить в код темплейта собственноручно изготовленный диспетчер примерно такого вида:
<tal:block tal:condition="request/forms/clean | nothing" tal:content="view/clean"/> <tal:block tal:condition="request/forms/regenerator_all | nothing" tal:content="view/regenerator_all"/>
Не длиннее, ни короче: дело вкуса, хотя на мой вкус, использование модификатора method оставляет больше свободы.