[Testbot] Plone 5.0 - Python 2.7 - Build # 2532 - Regression! - 1 failure(s)

jenkins at plone.org jenkins at plone.org
Fri May 30 19:04:15 UTC 2014


-------------------------------------------------------------------------------
Plone 5.0 - Python 2.7 - Build # 2532 - Still Failing!
-------------------------------------------------------------------------------

http://jenkins.plone.org/job/plone-5.0-python-2.7/2532/


-------------------------------------------------------------------------------
CHANGES
-------------------------------------------------------------------------------

Repository: plone.supermodel
Branch: refs/heads/master
Date: 2014-04-16T12:08:33-06:00
Author: Sean Upton (seanupton) <sdupton at gmail.com>
Commit: https://github.com/plone/plone.supermodel/commit/f16f480b6062a44bda6f729f03de4555c65c82b0

Fix parsing of empty Choice term to u'', not None, which addresses a cause of https://github.com/plone/plone.app.dexterity/issues/49 -- also explicitly construct SimpleTerm instances for each Choice field element, instead of relying on zope.schema constructors to do so.  This ensures that all terms have non-None title attributes.  Added tests for ChoiceHandler serialization and parsing.

Files changed:
M CHANGES.rst
M plone/supermodel/exportimport.py
M plone/supermodel/tests.py

diff --git a/CHANGES.rst b/CHANGES.rst
index 4f07a1d..2f7a55d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,7 +4,17 @@ Changelog
 1.2.5 (unreleased)
 ------------------
 
-- Nothing changed yet.
+- Fix parsing of empty Choice term to u'', not None, which addresses a
+  cause of https://github.com/plone/plone.app.dexterity/issues/49
+  [seanupton]
+
+- Explicitly construct SimpleTerm instances for each Choice field
+  element, instead of relying on zope.schema constructors to do so.
+  This ensures that all terms have non-None title attributes.
+  [seanupton]
+
+- Tests for ChoiceHandler serialization and parsing.
+  [seanupton]
 
 
 1.2.4 (2014-01-27)
diff --git a/plone/supermodel/exportimport.py b/plone/supermodel/exportimport.py
index 8fa7972..b502ee8 100644
--- a/plone/supermodel/exportimport.py
+++ b/plone/supermodel/exportimport.py
@@ -274,16 +274,16 @@ def __init__(self, klass):
     def _constructField(self, attributes):
         if 'values' in attributes:
             terms = []
-            unicode_found = False
             for value in attributes['values']:
-                encoded = value.encode('unicode_escape')
+                encoded = (value or '').encode('unicode_escape')
                 if value != encoded:
-                    unicode_found = True
-                term = SimpleTerm(token=encoded, value=value, title=value)
+                    value = value or u''
+                    term = SimpleTerm(token=encoded, value=value, title=value)
+                else:
+                    term = SimpleTerm(value=value, title=value)
                 terms.append(term)
-            if unicode_found:
-                attributes['vocabulary'] = SimpleVocabulary(terms)
-                del attributes['values']
+            attributes['vocabulary'] = SimpleVocabulary(terms)
+            del attributes['values']
         return super(ChoiceHandler, self)._constructField(attributes)
 
     def write(self, field, name, type, elementName='field'):
diff --git a/plone/supermodel/tests.py b/plone/supermodel/tests.py
index 8ab9e58..fa762c6 100644
--- a/plone/supermodel/tests.py
+++ b/plone/supermodel/tests.py
@@ -10,13 +10,31 @@
 from zope.schema import getFieldNamesInOrder
 from zope.schema.interfaces import IContextAwareDefaultFactory
 from zope.schema.interfaces import IContextSourceBinder
-from zope.schema.vocabulary import SimpleVocabulary
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
 from zope import schema
 
 from plone.supermodel import utils
+from plone.supermodel.exportimport import ChoiceHandler
 from plone.supermodel.interfaces import IDefaultFactory
 
 
+def configure():
+    zope.component.testing.setUp()
+    configuration = """\
+    <configure
+         xmlns="http://namespaces.zope.org/zope"
+         i18n_domain="plone.supermodel.tests">
+
+        <include package="zope.component" file="meta.zcml" />
+
+        <include package="plone.supermodel" />
+
+    </configure>
+    """
+    from zope.configuration import xmlconfig
+    xmlconfig.xmlconfig(StringIO(configuration))
+
+
 class IBase(Interface):
     title = schema.TextLine(title=u"Title")
     description = schema.TextLine(title=u"Description")
@@ -304,21 +322,7 @@ class ISchema(IBase1, IBase2, IBase3):
 class TestValueToElement(unittest.TestCase):
 
     def setUp(self):
-        zope.component.testing.setUp()
-        configuration = """\
-        <configure
-             xmlns="http://namespaces.zope.org/zope"
-             i18n_domain="plone.supermodel.tests">
-
-            <include package="zope.component" file="meta.zcml" />
-
-            <include package="plone.supermodel" />
-
-        </configure>
-        """
-        from zope.configuration import xmlconfig
-        xmlconfig.xmlconfig(StringIO(configuration))
-
+        configure()
 
     tearDown = zope.component.testing.tearDown
 
@@ -386,10 +390,57 @@ def test_nested_dicts(self):
             )
 
 
+class TestChoiceHandling(unittest.TestCase):
+   
+    def setUp(self):
+        configure()
+        self.handler = ChoiceHandler(schema.Choice)
+
+    def _choice(self):
+        vocab = SimpleVocabulary([SimpleTerm(t, title=t) for t in (u'a', u'b', u'c')])
+        expected = '<field name="myfield" type="zope.schema.Choice">'\
+            '<values>'\
+            '<element>a</element><element>b</element><element>c</element>'\
+            '</values>'\
+            '</field>'
+        return (schema.Choice(vocabulary=vocab), expected)
+
+    def _choice_with_empty(self):
+        # add an empty string term to vocabulary
+        vocab = SimpleVocabulary([SimpleTerm(t, title=t) for t in (u'a', u'')])
+        expected = '<field name="myfield" type="zope.schema.Choice">'\
+            '<values>'\
+            '<element>a</element>'\
+            '<element></element>'\
+            '</values>'\
+            '</field>'
+        return (schema.Choice(vocabulary=vocab), expected)
+ 
+    def test_choice_serialized(self):
+        field, expected = self._choice()
+        el = self.handler.write(field, 'myfield', 'zope.schema.Choice')
+        self.assertEquals(etree.tostring(el), expected)
+        # now with an empty string term in vocab:
+        field, expected = self._choice_with_empty()
+        el = self.handler.write(field, 'myfield', 'zope.schema.Choice')
+        self.assertEquals(etree.tostring(el), expected)
+
+    def test_choice_parsing(self):
+        _termvalues = lambda vocab: tuple((t.value, t.title) for t in vocab)
+        for field, expected in (self._choice(), self._choice_with_empty()):
+            el = etree.fromstring(expected)
+            imported_field = self.handler.read(el)
+            self.assertEquals(
+                _termvalues(imported_field.vocabulary),
+                _termvalues(field.vocabulary),
+                )
+
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(TestUtils),
         unittest.makeSuite(TestValueToElement),
+        unittest.makeSuite(TestChoiceHandling),
         doctest.DocFileSuite('schema.txt',
             setUp=zope.component.testing.setUp,
             tearDown=zope.component.testing.tearDown,


Repository: plone.supermodel
Branch: refs/heads/master
Date: 2014-04-17T00:10:11-06:00
Author: Sean Upton (seanupton) <sdupton at gmail.com>
Commit: https://github.com/plone/plone.supermodel/commit/1e84a6b34394ddbe4c23a124a781d6156d36f270

Support Choice fields with terms containing distinct title from value as option, while preserving backward-compatible round-trip for all Choice fields where title is not distinct from value.  Includes tests for serialize, parse/read.  Uses OrderedDict as a mechanism to re-use IDict field rendering semantics for output of Choice field values where terms have distinct titles (py2.6 compatibility should be possible via zope.schema>=4.1.0).

Files changed:
M CHANGES.rst
M plone/supermodel/exportimport.py
M plone/supermodel/fields.txt
M plone/supermodel/tests.py
M plone/supermodel/utils.py
M setup.py

diff --git a/CHANGES.rst b/CHANGES.rst
index 2f7a55d..64b0e33 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,11 @@ Changelog
 1.2.5 (unreleased)
 ------------------
 
+- Support Choice fields with terms containing distinct title from value
+  as option, while preserving backward-compatible round-trip for all
+  Choice fields where title is not distinct from value.
+  [seanupton]
+
 - Fix parsing of empty Choice term to u'', not None, which addresses a
   cause of https://github.com/plone/plone.app.dexterity/issues/49
   [seanupton]
diff --git a/plone/supermodel/exportimport.py b/plone/supermodel/exportimport.py
index b502ee8..e18273e 100644
--- a/plone/supermodel/exportimport.py
+++ b/plone/supermodel/exportimport.py
@@ -17,6 +17,15 @@
 from plone.supermodel.utils import noNS, valueToElement, elementToValue
 from plone.supermodel.debug import parseinfo
 
+try:
+    from collections import OrderedDict
+except:
+    from zope.schema import OrderedDict  # <py27
+
+
+class OrderedDictField(zope.schema.Dict):
+    _type = OrderedDict
+
 
 class BaseHandler(object):
     """Base class for import/export handlers.
@@ -184,7 +193,6 @@ def readAttribute(self, element, attributeField):
         """Read a single attribute from the given element. The attribute is of
         a type described by the given Field object.
         """
-
         return elementToValue(attributeField, element)
 
     def writeAttribute(self, attributeField, field, ignoreDefault=True):
@@ -271,16 +279,30 @@ def __init__(self, klass):
         self.fieldAttributes['source'] = \
             zope.schema.Object(__name__='source', title=u"Source", schema=Interface)
 
+    def readAttribute(self, element, attributeField):
+        if element.tag == 'values':
+            if any([child.get('key') for child in element]):
+                attributeField = OrderedDictField(
+                    key_type=zope.schema.TextLine(),
+                    value_type=zope.schema.TextLine(),
+                    )
+        return elementToValue(attributeField, element)
+
     def _constructField(self, attributes):
         if 'values' in attributes:
+            if isinstance(attributes['values'], OrderedDict):
+                attributes['values'] = attributes['values'].items()
             terms = []
             for value in attributes['values']:
+                title = (value or u'')
+                if isinstance(value, tuple):
+                    value, title = value
                 encoded = (value or '').encode('unicode_escape')
                 if value != encoded:
                     value = value or u''
-                    term = SimpleTerm(token=encoded, value=value, title=value)
+                    term = SimpleTerm(token=encoded, value=value, title=title)
                 else:
-                    term = SimpleTerm(value=value, title=value)
+                    term = SimpleTerm(value=value, title=title)
                 terms.append(term)
             attributes['vocabulary'] = SimpleVocabulary(terms)
             del attributes['values']
@@ -306,9 +328,19 @@ def write(self, field, name, type, elementName='field'):
                     or term.token != term.value.encode('unicode_escape')):
                     raise NotImplementedError(u"Cannot export a vocabulary that is not "
                                                "based on a simple list of values")
-                value.append(term.value)
+                if term.title and term.title != term.value:
+                    value.append((term.value, term.title))
+                else:
+                    value.append(term.value)
 
             attributeField = self.fieldAttributes['values']
+            if any(map(lambda v: isinstance(v, tuple), value)):
+                _pair = lambda v: v if len(v) == 2 else (v[0],) * 2
+                value = OrderedDict(map(_pair, value))
+                attributeField = OrderedDictField(
+                    key_type=zope.schema.TextLine(),
+                    value_type=zope.schema.TextLine(),
+                    )
             child = valueToElement(attributeField, value, name='values', force=True)
             element.append(child)
 
diff --git a/plone/supermodel/fields.txt b/plone/supermodel/fields.txt
index 3e82204..c4ba642 100644
--- a/plone/supermodel/fields.txt
+++ b/plone/supermodel/fields.txt
@@ -1321,6 +1321,34 @@ tokens are the utf8-encoded values).
     >>> [t.value for t in reciprocal.vocabulary]
     [u'a', u'\xe7']
 
+
+Additionally, it is possible for Choice fields with a values vocabulary
+whose terms contain values distinct from term titles for each
+respective term.  This is accomplished by using the 'key' attribute
+of each contained 'element' of the values element (this is consistent
+with how Dict fields are output, only for Choices, order is guaranteed).
+
+    >>> from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
+    >>> vocab = SimpleVocabulary([
+    ...     SimpleTerm(value=u'a', title=u'A'),
+    ...     SimpleTerm(value=u'b', title=u'B'),
+    ...     ])
+    >>> field = schema.Choice(
+    ...     __name__="dummy",
+    ...     title=u"Test",
+    ...     vocabulary=vocab,
+    ...     )
+    >>> handler = getUtility(IFieldExportImportHandler, name=fieldType)
+    >>> element = handler.write(field, 'dummy', fieldType)
+    >>> print prettyXML(element)
+    <field name="dummy" type="zope.schema.Choice">
+      <title>Test</title>
+      <values>
+        <element key="a">A</element>
+        <element key="b">B</element>
+      </values>
+    </field>
+
 3. Sources and source binders
 
 We cannot export choice fields with a source or context source binder:
diff --git a/plone/supermodel/tests.py b/plone/supermodel/tests.py
index fa762c6..f0ccaa3 100644
--- a/plone/supermodel/tests.py
+++ b/plone/supermodel/tests.py
@@ -397,7 +397,9 @@ def setUp(self):
         self.handler = ChoiceHandler(schema.Choice)
 
     def _choice(self):
-        vocab = SimpleVocabulary([SimpleTerm(t, title=t) for t in (u'a', u'b', u'c')])
+        vocab = SimpleVocabulary(
+            [SimpleTerm(t, title=t) for t in (u'a', u'b', u'c')]
+            )
         expected = '<field name="myfield" type="zope.schema.Choice">'\
             '<values>'\
             '<element>a</element><element>b</element><element>c</element>'\
@@ -415,6 +417,21 @@ def _choice_with_empty(self):
             '</values>'\
             '</field>'
         return (schema.Choice(vocabulary=vocab), expected)
+
+    def _choice_with_term_titles(self):
+        # two terms with distinct titles, one with same as value:
+        vocab = SimpleVocabulary(
+            [SimpleTerm(t, title=t.upper()) for t in (u'a', u'b')] +
+            [SimpleTerm(u'c', title=u'c')],
+            )
+        expected = '<field name="myfield" type="zope.schema.Choice">'\
+            '<values>'\
+            '<element key="a">A</element>'\
+            '<element key="b">B</element>'\
+            '<element key="c">c</element>'\
+            '</values>'\
+            '</field>'
+        return (schema.Choice(vocabulary=vocab), expected)
  
     def test_choice_serialized(self):
         field, expected = self._choice()
@@ -424,10 +441,19 @@ def test_choice_serialized(self):
         field, expected = self._choice_with_empty()
         el = self.handler.write(field, 'myfield', 'zope.schema.Choice')
         self.assertEquals(etree.tostring(el), expected)
+        # now with terms that have titles:
+        field, expected = self._choice_with_term_titles()
+        el = self.handler.write(field, 'myfield', 'zope.schema.Choice')
+        self.assertEquals(etree.tostring(el), expected)
 
     def test_choice_parsing(self):
         _termvalues = lambda vocab: tuple((t.value, t.title) for t in vocab)
-        for field, expected in (self._choice(), self._choice_with_empty()):
+        cases = (
+            self._choice(),
+            self._choice_with_empty(),
+            self._choice_with_term_titles(),
+            )
+        for field, expected in cases:
             el = etree.fromstring(expected)
             imported_field = self.handler.read(el)
             self.assertEquals(
diff --git a/plone/supermodel/utils.py b/plone/supermodel/utils.py
index 24cf9d8..d66515f 100644
--- a/plone/supermodel/utils.py
+++ b/plone/supermodel/utils.py
@@ -11,6 +11,12 @@
 from plone.supermodel.interfaces import XML_NAMESPACE, I18N_NAMESPACE, IToUnicode
 from plone.supermodel.debug import parseinfo
 
+try:
+    from collections import OrderedDict
+except:
+    from zope.schema import OrderedDict  # <py27
+
+
 _marker = object()
 noNS_re = re.compile('^{\S+}')
 
@@ -87,7 +93,7 @@ def elementToValue(field, element, default=_marker):
 
     if IDict.providedBy(field):
         key_converter = IFromUnicode(field.key_type)
-        value = {}
+        value = OrderedDict()
         for child in element.iterchildren(tag=etree.Element):
             if noNS(child.tag.lower()) != 'element':
                 continue
diff --git a/setup.py b/setup.py
index 3c2359e..756ef54 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,14 @@
 import os
+import sys
 from setuptools import setup, find_packages
 
 
+# if <= Python 2.6 or less, specify minimum zope.schema compatible:
+ZOPESCHEMA = 'zope.schema'
+if sys.version_info < (2, 7):
+    ZOPESCHEMA += '>=4.1.0'
+
+
 def read(*rnames):
     return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
 
@@ -39,7 +46,7 @@ def read(*rnames):
           'lxml',
           'zope.component',
           'zope.interface',
-          'zope.schema',
+          ZOPESCHEMA,
           'zope.deferredimport',
           'zope.dottedname',
           'z3c.zcmlhook',


Repository: plone.supermodel
Branch: refs/heads/master
Date: 2014-05-30T12:11:23-06:00
Author: Sean Upton (seanupton) <sdupton at gmail.com>
Commit: https://github.com/plone/plone.supermodel/commit/0bb35825de9b5180f4d6a9be232e775c9dcecd96

Merge pull request #7 from seanupton/master

Fix parsing of empty Choice term, explicit term construction, support terms with title!=value, new tests

Files changed:
M CHANGES.rst
M plone/supermodel/exportimport.py
M plone/supermodel/fields.txt
M plone/supermodel/tests.py
M plone/supermodel/utils.py
M setup.py

diff --git a/CHANGES.rst b/CHANGES.rst
index d90c4c6..53f9152 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,7 +4,22 @@ Changelog
 1.2.5 (unreleased)
 ------------------
 
-- Nothing changed yet.
+- Support Choice fields with terms containing distinct title from value
+  as option, while preserving backward-compatible round-trip for all
+  Choice fields where title is not distinct from value.
+  [seanupton]
+
+- Fix parsing of empty Choice term to u'', not None, which addresses a
+  cause of https://github.com/plone/plone.app.dexterity/issues/49
+  [seanupton]
+
+- Explicitly construct SimpleTerm instances for each Choice field
+  element, instead of relying on zope.schema constructors to do so.
+  This ensures that all terms have non-None title attributes.
+  [seanupton]
+
+- Tests for ChoiceHandler serialization and parsing.
+  [seanupton]
 
 
 1.2.4 (2014-01-27)
diff --git a/plone/supermodel/exportimport.py b/plone/supermodel/exportimport.py
index 8fa7972..e18273e 100644
--- a/plone/supermodel/exportimport.py
+++ b/plone/supermodel/exportimport.py
@@ -17,6 +17,15 @@
 from plone.supermodel.utils import noNS, valueToElement, elementToValue
 from plone.supermodel.debug import parseinfo
 
+try:
+    from collections import OrderedDict
+except:
+    from zope.schema import OrderedDict  # <py27
+
+
+class OrderedDictField(zope.schema.Dict):
+    _type = OrderedDict
+
 
 class BaseHandler(object):
     """Base class for import/export handlers.
@@ -184,7 +193,6 @@ def readAttribute(self, element, attributeField):
         """Read a single attribute from the given element. The attribute is of
         a type described by the given Field object.
         """
-
         return elementToValue(attributeField, element)
 
     def writeAttribute(self, attributeField, field, ignoreDefault=True):
@@ -271,19 +279,33 @@ def __init__(self, klass):
         self.fieldAttributes['source'] = \
             zope.schema.Object(__name__='source', title=u"Source", schema=Interface)
 
+    def readAttribute(self, element, attributeField):
+        if element.tag == 'values':
+            if any([child.get('key') for child in element]):
+                attributeField = OrderedDictField(
+                    key_type=zope.schema.TextLine(),
+                    value_type=zope.schema.TextLine(),
+                    )
+        return elementToValue(attributeField, element)
+
     def _constructField(self, attributes):
         if 'values' in attributes:
+            if isinstance(attributes['values'], OrderedDict):
+                attributes['values'] = attributes['values'].items()
             terms = []
-            unicode_found = False
             for value in attributes['values']:
-                encoded = value.encode('unicode_escape')
+                title = (value or u'')
+                if isinstance(value, tuple):
+                    value, title = value
+                encoded = (value or '').encode('unicode_escape')
                 if value != encoded:
-                    unicode_found = True
-                term = SimpleTerm(token=encoded, value=value, title=value)
+                    value = value or u''
+                    term = SimpleTerm(token=encoded, value=value, title=title)
+                else:
+                    term = SimpleTerm(value=value, title=title)
                 terms.append(term)
-            if unicode_found:
-                attributes['vocabulary'] = SimpleVocabulary(terms)
-                del attributes['values']
+            attributes['vocabulary'] = SimpleVocabulary(terms)
+            del attributes['values']
         return super(ChoiceHandler, self)._constructField(attributes)
 
     def write(self, field, name, type, elementName='field'):
@@ -306,9 +328,19 @@ def write(self, field, name, type, elementName='field'):
                     or term.token != term.value.encode('unicode_escape')):
                     raise NotImplementedError(u"Cannot export a vocabulary that is not "
                                                "based on a simple list of values")
-                value.append(term.value)
+                if term.title and term.title != term.value:
+                    value.append((term.value, term.title))
+                else:
+                    value.append(term.value)
 
             attributeField = self.fieldAttributes['values']
+            if any(map(lambda v: isinstance(v, tuple), value)):
+                _pair = lambda v: v if len(v) == 2 else (v[0],) * 2
+                value = OrderedDict(map(_pair, value))
+                attributeField = OrderedDictField(
+                    key_type=zope.schema.TextLine(),
+                    value_type=zope.schema.TextLine(),
+                    )
             child = valueToElement(attributeField, value, name='values', force=True)
             element.append(child)
 
diff --git a/plone/supermodel/fields.txt b/plone/supermodel/fields.txt
index 3e82204..c4ba642 100644
--- a/plone/supermodel/fields.txt
+++ b/plone/supermodel/fields.txt
@@ -1321,6 +1321,34 @@ tokens are the utf8-encoded values).
     >>> [t.value for t in reciprocal.vocabulary]
     [u'a', u'\xe7']
 
+
+Additionally, it is possible for Choice fields with a values vocabulary
+whose terms contain values distinct from term titles for each
+respective term.  This is accomplished by using the 'key' attribute
+of each contained 'element' of the values element (this is consistent
+with how Dict fields are output, only for Choices, order is guaranteed).
+
+    >>> from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
+    >>> vocab = SimpleVocabulary([
+    ...     SimpleTerm(value=u'a', title=u'A'),
+    ...     SimpleTerm(value=u'b', title=u'B'),
+    ...     ])
+    >>> field = schema.Choice(
+    ...     __name__="dummy",
+    ...     title=u"Test",
+    ...     vocabulary=vocab,
+    ...     )
+    >>> handler = getUtility(IFieldExportImportHandler, name=fieldType)
+    >>> element = handler.write(field, 'dummy', fieldType)
+    >>> print prettyXML(element)
+    <field name="dummy" type="zope.schema.Choice">
+      <title>Test</title>
+      <values>
+        <element key="a">A</element>
+        <element key="b">B</element>
+      </values>
+    </field>
+
 3. Sources and source binders
 
 We cannot export choice fields with a source or context source binder:
diff --git a/plone/supermodel/tests.py b/plone/supermodel/tests.py
index aeb41a9..ebf4d89 100644
--- a/plone/supermodel/tests.py
+++ b/plone/supermodel/tests.py
@@ -11,14 +11,32 @@
 from zope.schema import getFieldNamesInOrder
 from zope.schema.interfaces import IContextAwareDefaultFactory
 from zope.schema.interfaces import IContextSourceBinder
-from zope.schema.vocabulary import SimpleVocabulary
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
 from zope import schema
 
 from plone.supermodel import utils
+from plone.supermodel.exportimport import ChoiceHandler
 from plone.supermodel.interfaces import IDefaultFactory
 from plone.supermodel.interfaces import IInvariant
 
 
+def configure():
+    zope.component.testing.setUp()
+    configuration = """\
+    <configure
+         xmlns="http://namespaces.zope.org/zope"
+         i18n_domain="plone.supermodel.tests">
+
+        <include package="zope.component" file="meta.zcml" />
+
+        <include package="plone.supermodel" />
+
+    </configure>
+    """
+    from zope.configuration import xmlconfig
+    xmlconfig.xmlconfig(StringIO(configuration))
+
+
 class IBase(Interface):
     title = schema.TextLine(title=u"Title")
     description = schema.TextLine(title=u"Description")
@@ -321,21 +339,7 @@ class ISchema(IBase1, IBase2, IBase3):
 class TestValueToElement(unittest.TestCase):
 
     def setUp(self):
-        zope.component.testing.setUp()
-        configuration = """\
-        <configure
-             xmlns="http://namespaces.zope.org/zope"
-             i18n_domain="plone.supermodel.tests">
-
-            <include package="zope.component" file="meta.zcml" />
-
-            <include package="plone.supermodel" />
-
-        </configure>
-        """
-        from zope.configuration import xmlconfig
-        xmlconfig.xmlconfig(StringIO(configuration))
-
+        configure()
 
     tearDown = zope.component.testing.tearDown
 
@@ -403,10 +407,83 @@ def test_nested_dicts(self):
             )
 
 
+class TestChoiceHandling(unittest.TestCase):
+   
+    def setUp(self):
+        configure()
+        self.handler = ChoiceHandler(schema.Choice)
+
+    def _choice(self):
+        vocab = SimpleVocabulary(
+            [SimpleTerm(t, title=t) for t in (u'a', u'b', u'c')]
+            )
+        expected = '<field name="myfield" type="zope.schema.Choice">'\
+            '<values>'\
+            '<element>a</element><element>b</element><element>c</element>'\
+            '</values>'\
+            '</field>'
+        return (schema.Choice(vocabulary=vocab), expected)
+
+    def _choice_with_empty(self):
+        # add an empty string term to vocabulary
+        vocab = SimpleVocabulary([SimpleTerm(t, title=t) for t in (u'a', u'')])
+        expected = '<field name="myfield" type="zope.schema.Choice">'\
+            '<values>'\
+            '<element>a</element>'\
+            '<element></element>'\
+            '</values>'\
+            '</field>'
+        return (schema.Choice(vocabulary=vocab), expected)
+
+    def _choice_with_term_titles(self):
+        # two terms with distinct titles, one with same as value:
+        vocab = SimpleVocabulary(
+            [SimpleTerm(t, title=t.upper()) for t in (u'a', u'b')] +
+            [SimpleTerm(u'c', title=u'c')],
+            )
+        expected = '<field name="myfield" type="zope.schema.Choice">'\
+            '<values>'\
+            '<element key="a">A</element>'\
+            '<element key="b">B</element>'\
+            '<element key="c">c</element>'\
+            '</values>'\
+            '</field>'
+        return (schema.Choice(vocabulary=vocab), expected)
+ 
+    def test_choice_serialized(self):
+        field, expected = self._choice()
+        el = self.handler.write(field, 'myfield', 'zope.schema.Choice')
+        self.assertEquals(etree.tostring(el), expected)
+        # now with an empty string term in vocab:
+        field, expected = self._choice_with_empty()
+        el = self.handler.write(field, 'myfield', 'zope.schema.Choice')
+        self.assertEquals(etree.tostring(el), expected)
+        # now with terms that have titles:
+        field, expected = self._choice_with_term_titles()
+        el = self.handler.write(field, 'myfield', 'zope.schema.Choice')
+        self.assertEquals(etree.tostring(el), expected)
+
+    def test_choice_parsing(self):
+        _termvalues = lambda vocab: tuple((t.value, t.title) for t in vocab)
+        cases = (
+            self._choice(),
+            self._choice_with_empty(),
+            self._choice_with_term_titles(),
+            )
+        for field, expected in cases:
+            el = etree.fromstring(expected)
+            imported_field = self.handler.read(el)
+            self.assertEquals(
+                _termvalues(imported_field.vocabulary),
+                _termvalues(field.vocabulary),
+                )
+
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(TestUtils),
         unittest.makeSuite(TestValueToElement),
+        unittest.makeSuite(TestChoiceHandling),
         doctest.DocFileSuite('schema.txt',
             setUp=zope.component.testing.setUp,
             tearDown=zope.component.testing.tearDown,
diff --git a/plone/supermodel/utils.py b/plone/supermodel/utils.py
index 24cf9d8..d66515f 100644
--- a/plone/supermodel/utils.py
+++ b/plone/supermodel/utils.py
@@ -11,6 +11,12 @@
 from plone.supermodel.interfaces import XML_NAMESPACE, I18N_NAMESPACE, IToUnicode
 from plone.supermodel.debug import parseinfo
 
+try:
+    from collections import OrderedDict
+except:
+    from zope.schema import OrderedDict  # <py27
+
+
 _marker = object()
 noNS_re = re.compile('^{\S+}')
 
@@ -87,7 +93,7 @@ def elementToValue(field, element, default=_marker):
 
     if IDict.providedBy(field):
         key_converter = IFromUnicode(field.key_type)
-        value = {}
+        value = OrderedDict()
         for child in element.iterchildren(tag=etree.Element):
             if noNS(child.tag.lower()) != 'element':
                 continue
diff --git a/setup.py b/setup.py
index 3c2359e..756ef54 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,14 @@
 import os
+import sys
 from setuptools import setup, find_packages
 
 
+# if <= Python 2.6 or less, specify minimum zope.schema compatible:
+ZOPESCHEMA = 'zope.schema'
+if sys.version_info < (2, 7):
+    ZOPESCHEMA += '>=4.1.0'
+
+
 def read(*rnames):
     return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
 
@@ -39,7 +46,7 @@ def read(*rnames):
           'lxml',
           'zope.component',
           'zope.interface',
-          'zope.schema',
+          ZOPESCHEMA,
           'zope.deferredimport',
           'zope.dottedname',
           'z3c.zcmlhook',




-------------------------------------------------------------------------------
-------------- next part --------------
A non-text attachment was scrubbed...
Name: CHANGES.log
Type: application/octet-stream
Size: 31088 bytes
Desc: not available
URL: <http://lists.plone.org/pipermail/plone-testbot/attachments/20140530/7629fcba/attachment-0002.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: build.log
Type: application/octet-stream
Size: 88148 bytes
Desc: not available
URL: <http://lists.plone.org/pipermail/plone-testbot/attachments/20140530/7629fcba/attachment-0003.obj>


More information about the Testbot mailing list