===========================
Schema and Field Management
===========================

  >>> from cybertools.composer.schema import Schema
  >>> from cybertools.composer.schema import Field


Working with predefined schemas
===============================

We start with setting up a schema with fields.

  >>> serviceSchema = Schema(
  ...     Field(u'title', renderFactory=None),
  ...     Field(u'description'),
  ...     Field(u'start'),
  ...     Field(u'end'),
  ...     Field(u'capacity'),
  ... )

For using a schema we need some class that we can use for creating
objects.

  >>> class Service(object):
  ...     pass

The schema will be connected with an object via an instance adapter.
In addition, we need a field instance adapter that cares for  the
correct conversion of input data to context attributes.

  >>> from cybertools.composer.schema.instance import Editor
  >>> from cybertools.composer.schema.field import FieldInstance
  >>> from zope import component
  >>> component.provideAdapter(Editor, (Service,), name="service.edit")
  >>> component.provideAdapter(FieldInstance)

  >>> srv = Service()
  >>> inst = component.getAdapter(srv, name='service.edit')
  >>> inst.template = serviceSchema
  >>> inst.applyTemplate(data=dict(title='Service', capacity='30'))
  <...FormState object ...>

  >>> srv.title, srv.description, srv.capacity
  (u'Service', u'', u'30')

Field types
-----------

  >>> from cybertools.composer.schema.interfaces import fieldTypes
  >>> sorted(t.token for t in fieldTypes)
  ['checkbox', 'checkboxes', 'date', 'display', 'dropdown', 'email', 'explanation',
   'fileupload', 'heading', 'html', 'list', 'number', 'password', 'radiobuttons',
   'spacer', 'textarea', 'textline']

  >>> from zope.schema.vocabulary import SimpleVocabulary
  >>> textFieldTypes = SimpleVocabulary([t for t in fieldTypes if t.token in
  ...                                       ('textline', 'textarea',)])
  >>> sorted(t.token for t in textFieldTypes)
  ['textarea', 'textline']

Dynamic default values
----------------------

  >>> idField = Field(u'id', default='user/title|string:???', defaultValueType='tales')
  >>> idField.getDefaultValue()
  '???'


Creating a schema from an interface
===================================

  >>> from zope.interface import Interface, implements
  >>> import zope.schema
  >>> from cybertools.composer.schema.factory import SchemaFactory
  >>> component.provideAdapter(SchemaFactory)

  >>> class IPerson(Interface):
  ...    firstName = zope.schema.TextLine(title=u'First name')
  ...    lastName = zope.schema.TextLine(title=u'Last name')
  ...    age = zope.schema.Int(title=u'Age')

  >>> class Person(object):
  ...     implements(IPerson)
  ...     def __init__(self, firstName=u'', lastName=u'', age=None):
  ...         self.firstName, self.lastName, self.age = firstName, lastName, age

  >>> from cybertools.composer.schema.interfaces import ISchemaFactory
  >>> factory = ISchemaFactory(Person())

  >>> schema = factory(IPerson)
  >>> for f in schema.fields:
  ...     print f.name, f.title, f.fieldType
  firstName First name textline
  lastName Last name textline
  age Age number

Using a more specialized schema factory
---------------------------------------

  >>> class PersonSchemaFactory(SchemaFactory):
  ...     def __call__(self, interface, **kw):
  ...         schema = super(PersonSchemaFactory, self).__call__(interface)
  ...         if 'firstName' in schema.fields.keys():
  ...             del schema.fields['firstName']  # don't show first name
  ...         return schema
  >>> component.provideAdapter(PersonSchemaFactory, (IPerson,))

  >>> factory = ISchemaFactory(Person())
  >>> schema = factory(IPerson)
  >>> for f in schema.fields:
  ...     print f.name, f.title, f.fieldType
  lastName Last name textline
  age Age number


Access and update a context object using a schema-based form
============================================================

  >>> from zope.publisher.browser import TestRequest
  >>> from cybertools.composer.schema.browser.form import Form

We first have to provide adapters for special field types ('number' in
this case) and an instance adapter that manages the access to the
context object.

  >>> from cybertools.composer.schema.field import NumberFieldInstance
  >>> component.provideAdapter(NumberFieldInstance, name='number')

  >>> from cybertools.composer.schema.instance import Instance
  >>> component.provideAdapter(Instance)

  >>> person = Person(u'John', u'Miller', 33)

Note that the first name is not shown as we excluded it via the schema
factory above. The age field is a number, but is shown here as a
string as the instance is accessed using 'edit' mode, i.e. provide
data suitable for showing on an HTML form.

  >>> form = Form(person, TestRequest())
  >>> form.interface = IPerson
  >>> form.data
  {'lastName': u'Miller', 'age': '33'}

For editing we have to provide another instance adapter.

  >>> from cybertools.composer.schema.instance import Editor
  >>> component.provideAdapter(Editor, name='editor')

  >>> input = dict(lastName='Miller', age='40', action='update')
  >>> request = TestRequest(form=input)
  >>> form = Form(person, request)
  >>> form.interface = IPerson
  >>> form.nextUrl = 'dummy_url'  # avoid hassle with IAbsoluteURL view...

  >>> form.update()
  False

  >>> person.age
  40

Create a new object using a schema-based form
---------------------------------------------

  >>> from cybertools.composer.schema.browser.form import CreateForm
  >>> container = dict()

  >>> input = dict(lastName=u'Smith', age='28', action='update')
  >>> form = CreateForm(container, TestRequest(form=input))
  >>> form.interface = IPerson
  >>> form.factory = Person

  >>> form.nextUrl = 'dummy_url'  # avoid hassle with IAbsoluteURL view...
  >>> form.getName = lambda x: x.lastName.lower()

  >>> form.data
  {'lastName': u'Smith', 'age': '28'}

  >>> form.update()
  False

  >>> p2 = container['smith']
  >>> p2.lastName, p2.age
  (u'Smith', 28)

Macros / renderers
------------------

  >>> fieldRenderers = form.fieldRenderers
  >>> sorted(fieldRenderers.keys())
  [u'field', u'field_spacer', u'fields', u'form', u'input_checkbox',
   u'input_date', u'input_dropdown', u'input_fileupload', u'input_html',
   u'input_list', u'input_password', u'input_textarea', u'input_textline']


Special Field Types
===================

Grids, Records, Key Tables
--------------------------

  >>> from cybertools.composer.schema.grid.field import KeyTableFieldInstance

  >>> ktfield = Field('data')
  >>> ktfield.column_types = [zope.schema.Text(__name__='key', title=u'Key',),
  ...                         zope.schema.Text(__name__='value', title=u'Value')]

  >>> ktfi = KeyTableFieldInstance(ktfield)
  >>> ktfi.unmarshall([dict(key='0001', value='First row')])
  {u'0001': [u'First row']}

  >>> ktfi.marshall({u'0001': [u'First row']})
  [{'value': u'First row', 'key': u'0001'}]

Now with some real stuff, using a field instance that takes the column types
from the context object of the edit form.

  >>> from cybertools.composer.schema.grid.interfaces import KeyTable
  >>> from cybertools.composer.schema.grid.field import \
  ...                               ContextBasedKeyTableFieldInstance
  >>> component.provideAdapter(ContextBasedKeyTableFieldInstance, name='keytable')

  >>> class IDataTable(Interface):
  ...     title = zope.schema.TextLine(title=u'Title', required=False)
  ...     columnNames = zope.schema.List(title=u'Column Names', required=False)
  ...     data = KeyTable(title=u'Data', required=False)
  >>> IDataTable['columnNames'].nostore = True

  >>> class DataTable(object):
  ...     implements(IDataTable)
  ...     def __init__(self, title, columnNames):
  ...         self.title = title
  ...         self.columnNames = columnNames

  >>> dt = DataTable('Account Types', ['identifier', 'label', 'info'])

  >>> input = dict(title='Account Types',
  ...              columnNames=['identifier', 'label', 'info'],
  ...              data=[dict(identifier='0001', label='Standard', info='')],
  ...              action='update')
  >>> form = Form(dt, TestRequest(form=input))
  >>> form.interface = IDataTable
  >>> form.nextUrl = 'dummy_url'
  >>> form.update()
  False

  >>> dt.data
  {u'0001': [u'Standard', u'']}
