пятница, 4 апреля 2008 г.

Как в Zope3 генерируется нужная форма.

Допустим у нас есть интерфейс:

1.# -*- coding: utf-8 -*-
2.__docformat__ = 'restructuredtext'
3.
4.from zope.interface import Interface
5.from zope.schema import TextLine
6.
7.class IMark(Interface):
8.
9. data = TextLine(
10. title=u"Title",
11. description=u"Des",
12. required=False)

У него есть атрибут data который является экземпляром класса TextLine
импортированного из shema.


И есть реализация:

1.# -*- coding: utf-8 -*-
2.__docformat__ = 'restructuredtext'
3.
4.from zope.interface import implements
5.from zope.app.container.contained import Contained
6.
7.from boom.interfaces import IMark
8.
9.class Mark(Contained):
10. implements(IMark)

В реализации мы показываем что класс Mark представляет интерфейс IMark.

Конфигурация:

1.<configure
2. xmlns="http://namespaces.zope.org/zope"
3. xmlns:browser="http://namespaces.zope.org/browser">
4.
5. <interface
6. interface=".interfaces.IMark"
7. type="zope.app.content.interfaces.IContentType"
8. />
9.
10. <content class=".bookmarker.Mark">
11. <implements
12. interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
13. />
14. <factory
15. id="boom.bookmarker.Mark"
16. description="A book mark."
17. />
18. <require
19. permission="zope.ManageContent"
20. interface=".interfaces.IMark"/>
21. <require
22. permission="zope.ManageContent"
23. set_schema=".interfaces.IMark"
24. />
25. </content>
26.
27.<browser:addMenuItem
28. class="boom.bookmarker.Mark"
29. title="Mark"
30. description="Test"
31. permission="zope.ManageContent"
32. view="AddMark.html"
33. />
34.
35. <browser:addform
36. label="Add Mark"
37. name="AddMark.html"
38. schema="boom.interfaces.IMark"
39. content_factory="boom.bookmarker.Mark"
40. permission="zope.ManageContent"
41. />
42.</configure>

Строки 2742 как раз позволяют добавить нам наш объект и сгенерировать для него форму,
директива addform имеет имя которое совпадает с видом в директиве addMenuItem,
таким образом при добавлении объекта Mark будет на основе интерфейса boom.interfaces.IMark
прописанного в строке
38 сгенерирована нужная нам форма.
Строка 39 позволяет привязаться к нашей реализации.
Остальные параметры можно посмотреть в apidoc — Справка по ZCML.
В представлении HTML форма имеет следующий вид:

1.<form enctype="multipart/form-data" method="post"
action
="http://localhost:091/+/AddMark.html%3D">
2. <div>
3. <h3>Add Mark</h3>
4. <div class="row">
5.
6. <div class="label">
7. <label title="Des" for="field.data">Title</label>
8. </div>
9.
10. <div class="field"><input type="text" value="" size="20"
name
="field.data" id="field.data" class="textType"/>
11.</div>
12.
13. </div>
14. <div class="separator"/>
15. <div class="separator"/>
16.
17. </div>
18. <br/><br/>
19. <div class="row">
20. <div class="controls"><hr/>
21. <input type="submit" value="Обновить"/>
22. <input type="submit" name="UPDATE_SUBMIT" value="Добавить"/>
23.
24. <b>Название объекта</b>
25. <input type="text" value="" name="add_input_name"/>
26.
27. </div>
28. </div>
29.
30. <div class="separator"/>
31. </form>

Как было описано выше форма генерируется на основе нашего интерфейса IMark
в котором прописан TextLine, теперь посмотрим его реализацию.

TextLine импортируется из zope.shema, смотрим __init__.py:
from zope.schema._field import Text, TextLine, Bool, Int, Float
видно что импорт из _field. где он опять же импортируется:
from zope.schema._bootstrapfields import Text, TextLine, Bool, Int, Password

так же указыватся что он представляет интерфейс ITextLine:
classImplements(TextLine, ITextLine)

смотрим _bootstrapfields:

1.class TextLine(Text):
2. """A text field with no newlines."""
3.
4. def constraint(self, value):
5. return '\n' not in value and '\r' not in value

Там видно что TextLine наследуется от Text который в свою очередь наследуется от Field,
где как раз обьекту присваиваются параметры из нашей data.

...
1.self.title = title
2.self.description = description
3.self.required = required
4.self.readonly = readonly
...
Теперь собственно посмотрим реализацию форм.

Деректива addform обрабатывается в zope.app.form.browser.metaconfigure.AddFormDirective,

где как раз устанавливается шаблон по умолчанию add.pt
...
1.<html metal:use-macro="context/@@standard_macros/page"
2. i18n:domain="zope">
3. <body>
4. <div metal:fill-slot="body">
5. <div metal:define-macro="addform">
6. <form action="." tal:attributes="action request/URL"
7. method="post" enctype="multipart/form-data">
8. <div metal:define-macro="formbody">
9. <h3 tal:condition="view/label"
10. tal:content="view/label"
11. metal:define-slot="heading"
12. i18n:translate=""
13. >Edit something</h3>
14. <p tal:define="status view/update"
15. tal:condition="status"
16. tal:content="status"
17. i18n:translate=""/>
18. <p tal:condition="view/errors" i18n:translate="">
19. There are <strong tal:content="python:len(view.errors)"
20. i18n:name="num_errors">6</strong> input errors.
21. </p>
22. <div metal:define-slot="extra_info" tal:replace="nothing">
23. </div>
24. <div class="row" metal:define-slot="extra_top" tal:replace="nothing">
25. <div class="label">Extra top</div>
26. <div class="label"><input type="text"
style
="width:100%" /></div>
27. </div>
28. <div metal:use-macro="context/@@form_macros/widget_rows" />
29. <div class="separator"></div>
30. <div class="row"
31. metal:define-slot="extra_bottom" tal:replace="nothing">
32. <div class="label">Extra bottom</div>
33. <div class="field"><input type="text"
style
="width:100%" /></div>
34. </div>
35. <div class="separator"></div>
36. </div>
37. <br/><br/>
38. <div class="row">
39. <div class="controls"><hr />
40. <input type='submit' value='Refresh'
41. i18n:attributes='value refresh-button' />
42. <input type='submit' value='Add' name='UPDATE_SUBMIT'
43. i18n:attributes='value add-button' />
44. <span tal:condition="context/nameAllowed|nothing" tal:omit-tag="">
45. &nbsp;&nbsp;<b i18n:translate="">Object Name</b>&nbsp;&nbsp;
46. <input type='text' name='add_input_name'
47. tal:attributes="value context/contentName" />
48. </span>
49. </div>
50. </div>
51. <div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
52. </div>
53. <div class="separator"></div>
54. </form>
55. </div>
56. </div>
57. </body>
58.</html>
...

В шаблоне генерируются кнопки для нашего объекта а в строке 28 вызывается макрос
widget_rows который объявлен в widget_macros.pt


1.<html i18n:domain="want an empty string, but zpt would ignore it :(">
2. <body>
3. <metal:block define-macro="widget_rows">
4. <div class="row" tal:repeat="widget view/widgets">
5. <metal:block define-macro="widget_row">
6. <div class="label">
7. <label for="field.name" title="The widget's hint"
8. tal:attributes="for widget/name; title widget/hint"
9. tal:content="widget/label" i18n:attributes="title"
10. i18n:translate=""
11. >The Label</label>
12. </div>
13. <tal:block define="error widget/error"
14. condition="error" content="structure error"
15. i18n:translate=""
16. >
17. The Error
18. </tal:block>
19. <div class="field" tal:content="structure widget">
20. <input type="text" style="width:100%"/>
21. </div>
22. </metal:block>
23. </div>
24. </metal:block>
25. </body>
26.</html>

смотрим что у них в configure.zcml
...
1.<browser:page
2. for="*"
3. name="form_macros"
4. permission="zope.Public"
5. class=".macros.FormMacros"
6. allowed_interface="zope.interface.common.mapping.IItemMapping"
7. />
8.
9. <browser:page
10. for="*"
11. name="widget_macros"
12. permission="zope.Public"
13. template="widget_macros.pt"
14. />
15.
16. <browser:page
17. for="*"
18. name="addform_macros"
19. permission="zope.Public"
20. template="add.pt"
21. />
...
Регистрируются имена для вызова макросов.
...
1.<view
2. type="zope.publisher.interfaces.browser.IBrowserRequest"
3. for="zope.schema.interfaces.ITextLine"
4. provides="zope.app.form.interfaces.IInputWidget"
5. factory=".TextWidget"
6. permission="zope.Public"
7. />
...

Видно что для интерфейса ITextLine зарегистрирован вид,посмотрим фабрику TextWidget:

1.class TextWidget(SimpleInputWidget):
2.
3. implements(ITextBrowserWidget)
4.
5. default = ''
6. displayWidth = 20
7. displayMaxWidth = ""
8. extra = ''
9. style = ''
10. convert_missing_value = True
11.
12. def __init__(self, *args):
13. super(TextWidget, self).__init__(*args)
14.
15. def __call__(self):
16. value = self._getFormValue()
17. if value is None or value == self.context.missing_value:
18. value = ''
19.
20. kwargs = {'type': self.type,
21. 'name': self.name,
22. 'id': self.name,
23. 'value': value,
24. 'cssClass': self.cssClass,
25. 'style': self.style,
26. 'size': self.displayWidth,
27. 'extra': self.extra}
28. if self.displayMaxWidth:
29. kwargs['maxlength'] = self.displayMaxWidth # TODO This is untested.
30.
31. return renderElement(self.tag, **kwargs)
32.
33. def _toFieldValue(self, input):
34. if self.convert_missing_value and input == self._missing:
35. value = self.context.missing_value
36. else:
37. # We convert everything to unicode. This might seem a bit crude,
38. # but anything contained in a TextWidget should be representable
39. # as a string. Note that you always have the choice of overriding
40. # the method.
41. try:
42. value = unicode(input)
43. except ValueError, v:
44. raise ConversionError(_("Invalid text data"), v)
45. return value

Здесь как раз возвращается при вызове отрендеренный self.tag
Небольшой тест:
>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import TextLine
>>> field = TextLine(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Bob'})
>>> from zope.app.form.browser.textwidgets import TextWidget
>>> widget = TextWidget(field, request)
>>> widget()

<input
class="textType"
id="field.foo"
name="field.foo"
size="20"
type="text"
value="Bob"
/>
Кстати type устанавливается в SimpleInputWidget от которого наследуется TextWidget
Таким образом становится ясно что сама форма ввода генерируется виджетом.

Комментариев нет: