==============
Data Converter
==============

The data converter is the component that converts an internal data value as
described by a field to an external value as required by a widget and vice
versa. The goal of the converter is to avoid field and widget proliferation
solely to handle different types of values. The purpose of fields is to
describe internal data types and structures and that of widgets to provide one
particular mean of input.

The only two discriminators for the converter are the field and the widget.

Let's look at the ``Int`` field to ``TextWidget`` converter as an example:

  >>> import zope.schema
  >>> age = zope.schema.Int(
  ...     __name__='age',
  ...     title=u'Age',
  ...     min=0)

  >>> from z3c.form.testing import TestRequest
  >>> from z3c.form import widget
  >>> text = widget.Widget(TestRequest())

  >>> from z3c.form import converter
  >>> conv = converter.FieldDataConverter(age, text)

The converter can now convert any integer to a the value the test widget deals
with, which is an ASCII string:

  >>> conv.toWidgetValue(34)
  u'34'

When the missing value is passed in, an empty string should be returned:

  >>> conv.toWidgetValue(age.missing_value)
  u''

Of course, values can also be converted from a widget value to field value:

  >>> conv.toFieldValue('34')
  34

An empty string means simply that the value is missing and the missing value
of the field is returned:

  >>> age.missing_value = -1
  >>> conv.toFieldValue('')
  -1

Of course, trying to convert a non-integer string representation fails in a
conversion error:

  >>> conv.toFieldValue('3.4')
  Traceback (most recent call last):
  ...
  ValueError: invalid literal for int(): 3.4

Also, the conversion to the field value also validates the data; in this case
negative values are not allowed:

  >>> conv.toFieldValue('-34')
  Traceback (most recent call last):
  ...
  TooSmall: (-34, 0)

That's pretty much the entire API. When dealing with converters within the
component architecture, everything is a little bit simpler. So let's register
the converter:

  >>> import zope.component
  >>> zope.component.provideAdapter(converter.FieldDataConverter)

Once we ensure that our widget is a text widget, we can lookup the adapter:

  >>> import zope.interface
  >>> from z3c.form import interfaces
  >>> zope.interface.alsoProvides(text, interfaces.ITextWidget)

  >>> zope.component.getMultiAdapter((age, text), interfaces.IDataConverter)
  <DataConverter from Int to Widget>

For field-widgets there is a helper adapter that makes the lookup even
simpler:

  >>> zope.component.provideAdapter(converter.FieldWidgetDataConverter)

After converting our simple widget to a field widget,

  >>> fieldtext = widget.FieldWidget(age, text)

we can now lookup the data converter adapter just by the field widget itself:

  >>> interfaces.IDataConverter(fieldtext)
  <DataConverter from Int to Widget>


Date Data Converter
-------------------

Since the ``Date`` field does not provide ``IFromUnicode``, we have to provide
a custom data converter. This default one is not very sophisticated and is
inteded for use with the text widget:

  >>> date = zope.schema.Date()

  >>> ddc = converter.DateDataConverter(date, text)
  >>> ddc
  <DataConverter from Date to Widget>

Dates are simply converted to ISO format:

  >>> import datetime
  >>> bday = datetime.date(1980, 1, 25)

  >>> ddc.toWidgetValue(bday)
  u'1980-01-25'

The converter only knows how to convert this particular format back to a
datetime value:

  >>> ddc.toFieldValue(u'1980-01-25')
  datetime.date(1980, 1, 25)

By default the converter converts missing input to missin_input value:

  >>> ddc.toFieldValue(u'') is None
  True
  


Time Data Converter
-------------------

Since the ``Time`` field does not provide ``IFromUnicode``, we have to provide
a custom data converter. This default one is not very sophisticated and is
inteded for use with the text widget:

  >>> time = zope.schema.Time()

  >>> tdc = converter.TimeDataConverter(time, text)
  >>> tdc
  <DataConverter from Time to Widget>

Dates are simply converted to ISO format:

  >>> noon = datetime.time(12, 0, 0)

  >>> tdc.toWidgetValue(noon)
  u'12:00:00'

The converter only knows how to convert this particular format back to a
datetime value:

  >>> tdc.toFieldValue(u'12:00:00')
  datetime.time(12, 0)

By default the converter converts missing input to missin_input value:

  >>> tdc.toFieldValue(u'') is None
  True


Datetime Data Converter
-----------------------

Since the ``Datetime`` field does not provide ``IFromUnicode``, we have to
provide a custom data converter. This default one is not very sophisticated
and is inteded for use with the text widget:

  >>> dtField = zope.schema.Datetime()

  >>> dtdc = converter.DatetimeDataConverter(dtField, text)
  >>> dtdc
  <DataConverter from Datetime to Widget>

Dates are simply converted to ISO format:

  >>> bdayNoon = datetime.datetime(1980, 1, 25, 12, 0, 0)

  >>> dtdc.toWidgetValue(bdayNoon)
  u'1980-01-25 12:00:00'

The converter only knows how to convert this particular format back to a
datetime value:

  >>> dtdc.toFieldValue(u'1980-01-25 12:00:00')
  datetime.datetime(1980, 1, 25, 12, 0)

By default the converter converts missing input to missin_input value:

  >>> dtdc.toFieldValue(u'') is None
  True


Timedelta Data Converter
------------------------

Since the ``Timedelta`` field does not provide ``IFromUnicode``, we have to
provide a custom data converter. This default one is not very sophisticated
and is inteded for use with the text widget:

  >>> timedelta = zope.schema.Timedelta()

  >>> tddc = converter.TimedeltaDataConverter(timedelta, text)
  >>> tddc
  <DataConverter from Timedelta to Widget>

Dates are simply converted to ISO format:

  >>> allOnes = datetime.timedelta(1, 3600+60+1)

  >>> tddc.toWidgetValue(allOnes)
  u'1 day, 1:01:01'

The converter only knows how to convert this particular format back to a
datetime value:

  >>> tddc.toFieldValue(u'1 day, 1:01:01')
  datetime.timedelta(1, 3661)

By default the converter converts missing input to missin_input value:

  >>> tddc.toFieldValue(u'') is None
  True


FileUpload Data Converter
-------------------------

Since the ``Bytes`` field can contain a FileUpload object, we have to make
sure we can convert FileUpload objects to bytes too.

  >>> import z3c.form.browser.file
  >>> fileWidget = z3c.form.browser.file.FileWidget(TestRequest())
  >>> bytes = zope.schema.Bytes()

  >>> fudc = converter.FileUploadDataConverter(bytes, fileWidget)
  >>> fudc
  <DataConverter from Bytes to FileWidget>

Bytes are converted to unicode:

  >>> simple = 'foobar'
  >>> fudc.toFieldValue(simple)
  u'foobar'

The converter can also convert FileUpload objects. Setup a fields storage 
stub...

  >>> class FieldStorageStub:
  ...     def __init__(self, file):
  ...         self.file = file
  ...         self.headers = {}
  ...         self.filename = 'foo.bar'

build a FileUpload...

  >>> import cStringIO
  >>> from zope.publisher.browser import FileUpload
  >>> myfile = cStringIO.StringIO('File upload contents.')
  >>> aFieldStorage = FieldStorageStub(myfile)
  >>> myUpload = FileUpload(aFieldStorage)

and try to convert:

  >>> fudc.toFieldValue(myUpload)
  'File upload contents.'

By default the converter converts missing input to missin_input value:

  >>> fudc.toFieldValue(u'') is None
  True

If we get a emtpy filename for a fileupload, we also get the missing_value,
but this means there was a error somewhere in the upload, normaly yo are
not able to uppload a file without a filename:

  >>> class EmptyFilenameFieldStorageStub:
  ...     def __init__(self, file):
  ...         self.file = file
  ...         self.headers = {}
  ...         self.filename = ''
  >>> myfile = cStringIO.StringIO('')
  >>> aFieldStorage = EmptyFilenameFieldStorageStub(myfile)
  >>> myUpload = FileUpload(aFieldStorage)
  >>> bytes = zope.schema.Bytes()
  >>> fudc = converter.FileUploadDataConverter(bytes, fileWidget)
  >>> fudc.toFieldValue(myUpload) is None
  True

There is also a ValueError if we don't get a seekable file from the FieldStorage 
during the upload:

  >>> myfile = ''
  >>> aFieldStorage = FieldStorageStub(myfile)
  >>> myUpload = FileUpload(aFieldStorage)
  >>> bytes = zope.schema.Bytes()
  >>> fudc = converter.FileUploadDataConverter(bytes, fileWidget)
  >>> fudc.toFieldValue(myUpload) is None
  Traceback (most recent call last):
  ...
  ValueError: (u'Bytes data is not a file object', <...AttributeError...>)




Bytes Data Converter
--------------------

The IBytes field uses a file upload widget which uses a file upload data 
converter. This requires that we test the IBytes converter for normal IWidget.
Note that IBytes always will get a FileUpload widget, if you need to use a
text input widget for bytes, you have to register a custom widget using the 
widgetFactory attribute of the z3c.form.interfaces.IField. e.g.
``fields['foobar'].widgetFactory = TextWidget``

  >>> bytes = zope.schema.Bytes()

  >>> fdc = converter.FieldDataConverter(bytes, text)
  >>> fdc
  <DataConverter from Bytes to Widget>

BytesLine are simply converted to string:

  >>> value = 'foobar'

  >>> fdc.toWidgetValue(value)
  u'foobar'

The converter only knows how to convert this particular format back to a
datetime value:

  >>> fdc.toFieldValue(u'foobar')
  'foobar'

By default the converter converts missing input to missin_input value:

  >>> fdc.toFieldValue(u'') is None
  True


Sequence Data Converter
-----------------------

For widgets and fields that work with choices of a sequence, a special data
converter is required that works with terms. A prime example is a choice
field. Before we can use the converter, we have to register the terms adapter:

  >>> from z3c.form import term
  >>> zope.component.provideAdapter(term.ChoiceTerms)

Let's now create a choice field and a widget:

  >>> from zope.schema.vocabulary import SimpleVocabulary

  >>> gender = zope.schema.Choice(
  ...     vocabulary = SimpleVocabulary([
  ...              SimpleVocabulary.createTerm(0, 'm', u'male'),
  ...              SimpleVocabulary.createTerm(1, 'f', u'female'),
  ...              ]) )

  >>> from z3c.form import widget
  >>> seqWidget = widget.SequenceWidget(TestRequest())

We now use the field and widget to instantiate the converter:

  >>> sdv = converter.SequenceDataConverter(gender, seqWidget)

We can now convert a real value to a widget value, which will be the term's
token:

  >>> sdv.toWidgetValue(0)
  ['m']

The result is always a sequence, since sequence widgets only deal collections
of values. Of course, we can convert the widget value back to an internal
value:

  >>> sdv.toFieldValue(['m'])
  0

Sometimes a field is not required. In those cases, the internalvalue is the
missing value of the field. The converter interprets that as no value being
selected:

  >>> gender.missing_value = 'missing'

  >>> sdv.toWidgetValue(gender.missing_value)
  []

If "no value" has been specified in the widget, the missing value
of the field is returned:

  >>> sdv.toFieldValue([u'--NOVALUE--'])
  'missing'

An empty list will also cause the missing value to be returned:

  >>> sdv.toFieldValue([])
  'missing'


Collection Sequence Data Converter
----------------------------------

For widgets and fields that work with a sequence of choices, another data
converter is required that works with terms. A prime example is a list
field. Before we can use the converter, we have to register the terms adapter:

  >>> from z3c.form import term
  >>> zope.component.provideAdapter(term.CollectionTerms)

Let's now create a set field and a widget:

  >>> genders = zope.schema.List(value_type=gender)
  >>> seqWidget = widget.SequenceWidget(TestRequest())

We now use the field and widget to instantiate the converter:

  >>> csdv = converter.CollectionSequenceDataConverter(genders, seqWidget)

We can now convert a real value to a widget value, which will be the term's
token:

  >>> csdv.toWidgetValue([0])
  ['m']

The result is always a sequence, since sequence widgets only deal collections
of values. Of course, we can convert the widget value back to an internal
value:

  >>> csdv.toFieldValue(['m'])
  [0]

For some field, like the ``Set``, the collection type is a tuple. Sigh. In
these cases we use the last entry in the tuple as the type to use:

  >>> genders = zope.schema.Set(value_type=gender)
  >>> seqWidget = widget.SequenceWidget(TestRequest())

  >>> csdv = converter.CollectionSequenceDataConverter(genders, seqWidget)

  >>> csdv.toWidgetValue(set([0]))
  ['m']

  >>> csdv.toFieldValue(['m'])
  set([0])
