=============
zeam.form.ztk
=============

Fields can be generated from zope.schema.

Creation from a Zope interface
------------------------------

You can create a full set of fields from a Zope interface::

  >>> from zope import schema, interface

  >>> class IPlanet(interface.Interface):
  ...     name = schema.TextLine(title=u"Name")
  ...     size = schema.Int(title=u"Size",
  ...                       description=u"Size in Km",
  ...                       readonly=True)

  >>> from zeam.form.base import Fields
  >>> s2 = Fields(IPlanet)
  >>> list(s2)
  [<TextLineSchemaField Name>, <IntSchemaField Size>]

Those are correct fields, filled with the interface definition
information::

  >>> f2, f3 = list(s2)
  >>> f2
  <TextLineSchemaField Name>
  >>> f2.title
  u'Name'
  >>> f2.identifier
  'name'
  >>> f2.description
  u''
  >>> f3
  <IntSchemaField Size>
  >>> f3.description
  u'Size in Km'

  >>> from zope.interface.verify import verifyObject
  >>> from zeam.form.base import interfaces
  >>> verifyObject(interfaces.IField, f2, f3)
  True

You can add fields via an interface after::

  >>> class IHabitationSupport(interface.Interface):
  ...     city = schema.TextLine(title=u"City name")
  ...     people = schema.Int(title=u"Number of people",
  ...                         required=False,
  ...                         description=u"Or population size")

  >>> s2.extend(IHabitationSupport)
  >>> list(s2)
  [<TextLineSchemaField Name>, <IntSchemaField Size>,
   <TextLineSchemaField City name>, <IntSchemaField Number of people>]

You can add a field from an interface alone::

  >>> class IFoodSupport(interface.Interface):
  ...      vegetables = schema.TextLine(title=u"Vegetables kind")
  ...      meat = schema.TextLine(title=u"Meat kind")

  >>> s2.extend(IFoodSupport['vegetables'])
  >>> list(s2.select('vegetables'))
  [<TextLineSchemaField Vegetables kind>]

  >>> list(Fields(IFoodSupport['meat']))
  [<TextLineSchemaField Meat kind>]

But it need to belong to an interface::

  >>> Fields(schema.TextLine(title=u"Quality of McDo restaurant"))
  Traceback (most recent call last):
     ...
  ValueError: Field has no interface or __name__
  >>> s2.extend(schema.TextLine(title=u"Quality of McDo restaurant"))
  Traceback (most recent call last):
     ...
  ValueError: Field has no interface or __name__

Or to specify at least a __name__:

  >>> fields_created_with_name = Fields(schema.TextLine(
  ...     __name__="quality",
  ...     title=u"Quality of McDo restaurant"))
  >>> list(fields_created_with_name)
  [<TextLineSchemaField Quality of McDo restaurant>]
  >>> fields_created_with_name['quality']
  <TextLineSchemaField Quality of McDo restaurant>

Description, required and readonly
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Those values are correctly collected from the schema field::

  >>> s2['city'].description
  u''
  >>> s2['city'].required
  True

  >>> s2['people'].description
  u'Or population size'
  >>> s2['people'].required
  False

  >>> s2['size'].description
  u'Size in Km'
  >>> s2['size'].required
  True

  >>> s2['size'].readonly
  True
  >>> s2['people'].readonly
  False

Cloning a field
~~~~~~~~~~~~~~~

You can clone a field and keep all settings::

  >>> s2['size'].mode = 'my-special-rendering-mode'
  >>> clone = s2['size'].clone(new_identifier='myclone')

  >>> clone
  <IntSchemaField Size>
  >>> clone.identifier
  'myclone'
  >>> clone.description
  u'Size in Km'
  >>> clone.required
  True
  >>> clone.mode
  'my-special-rendering-mode'


Data managers
-------------

The adapters-aware data manager
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Sometimes, it's necessary, while edition or adding an object, to work
with fields that are provided through more than one adapter. For these
special usecases, a special datamanager is used: the
`GenericAdaptiveDataManager`.

First, we define some interfaces, with at least one fields::

  >>> from zope.schema import TextLine
  >>> from zope.interface import Interface

  >>> class ITitledItem(Interface):
  ...     title = TextLine(title=u"A title")

  >>> class IDescriptiveItem(Interface):
  ...     description = TextLine(title=u"Some description")

Then, we create a `Fields` collection out of it::

  >>> fields = Fields(ITitledItem, IDescriptiveItem)

Now, we need an object that will be used as a context::

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

  >>> item = Item()

At this point, calling the GenericAdaptiveDataManager will provoke an
error, since no adapters are registered, yet::

  >>> from zeam.form.ztk.datamanager import makeGenericAdaptiveDataManager
  >>> factory = makeGenericAdaptiveDataManager(*fields)
  >>> print factory
  <class 'zeam.form.ztk.datamanager.GenericAdaptiveDataManager'>

  >>> gadm = factory(item)
  Traceback (most recent call last):
  ...
  TypeError: ('Could not adapt', <Item object at ...>,
              <InterfaceClass __builtin__.ITitledItem>)


Now, if we provide the correct adapters, we can build the data
manager::

  >>> import grokcore.component as grok

  >>> class TitleProvider(grok.Adapter):
  ...     grok.context(Item)
  ...     grok.implements(ITitledItem)
  ...
  ...     def get_title(self):
  ...         return getattr(self.context, '_title', None)
  ...
  ...     def set_title(self, value):
  ...         return setattr(self.context, '_title', value)
  ...
  ...     title = property(get_title, set_title)

  >>> class DescProvider(grok.Adapter):
  ...     grok.context(Item)
  ...     grok.implements(IDescriptiveItem)
  ...
  ...     def get_description(self):
  ...         return getattr(self.context, '_description', None)
  ...
  ...     def set_description(self, value):
  ...         return setattr(self.context, '_description', value)
  ...
  ...     description = property(get_description, set_description)

  >>> grok.testing.grok_component('title', TitleProvider)
  True
  >>> grok.testing.grok_component('desc', DescProvider)
  True

  >>> gadm = factory(item)
  >>> print gadm
  <GenericAdaptiveDataManager used for <Item object at ...>>

The data manager should provide all the necessary interfaces::

  >>> ITitledItem.providedBy(gadm), IDescriptiveItem.providedBy(gadm)
  (True, True)

We can, now, simply get and set our attributes::

  >>> print gadm.get("title")
  None

  >>> print gadm.get("description")
  None

  >>> print gadm.get("nothing")
  Traceback (most recent call last):
  ...
  AttributeError: 'Item' object has no attribute 'nothing'

  >>> gadm.set('title', u'My title')
  >>> title = gadm.get('title')
  >>> print title
  My title
  >>> title is item._title
  True

  >>> gadm.set('description', u'My description')
  >>> description = gadm.get('description')
  >>> print description
  My description
  >>> description is item._description
  True

To be thorough, we display the inner attributes that are created
during the data manager initialisation::

  >>> gadm.fields
  {'description': <InterfaceClass __builtin__.IDescriptiveItem>,
   'title': <InterfaceClass __builtin__.ITitledItem>}
  >>> sorted(gadm.adapters.items())
  [(<InterfaceClass __builtin__.IDescriptiveItem>, <DescProvider object at ...>),
   (<InterfaceClass __builtin__.ITitledItem>, <TitleProvider object at ...>)]
