Skip to content
This repository has been archived by the owner on Apr 9, 2019. It is now read-only.

Commit

Permalink
Merge pull request #16 from zopefoundation/python3
Browse files Browse the repository at this point in the history
Python3
  • Loading branch information
pbauer committed Sep 19, 2018
2 parents 23089f7 + 5c2d9eb commit 35facfe
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 44 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -15,6 +15,9 @@ Bug fixes:

New features:

- Add support for Python 3
[pbauer]

- Prepare for Python 2 / 3 compatibility
[pbauer, davilima6]

Expand Down
3 changes: 3 additions & 0 deletions setup.py
Expand Up @@ -23,10 +23,13 @@ def description():
"Framework :: Plone :: 4.3",
"Framework :: Plone :: 5.0",
"Framework :: Plone :: 5.1",
"Framework :: Plone :: 5.2",
"Framework :: Zope2",
"Programming Language :: Python",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
],
keywords='zope cmf form widget',
author='Zope Foundation and Contributors',
Expand Down
34 changes: 17 additions & 17 deletions src/plone/z3cform/crud/README.txt
Expand Up @@ -59,7 +59,7 @@ This is all that we need to render a combined edit add form containing
all our items:

>>> from plone.z3cform.tests import TestRequest
>>> print MyForm(None, TestRequest())() \
>>> print(MyForm(None, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...Martha...Peter...</div>

Expand Down Expand Up @@ -107,7 +107,7 @@ Now that we renamed Peter to Franz, it would be also nice to have
Franz use 'Franz' as the id in the storage, wouldn't it?

>>> storage['Peter']
<Person with name=u'Franz', age=16>
<Person with name='Franz', age=16>

We can override the CrudForm's ``before_update`` method to perform a
rename whenever the name of a person is changed:
Expand Down Expand Up @@ -186,7 +186,7 @@ clicking the "Delete" button:
>>> 'Franz' in html
False
>>> storage
{'Maria': <Person with name=u'Maria', age=50>}
{'Maria': <Person with name='Maria', age=50>}

Add an item with our form
-------------------------
Expand All @@ -209,7 +209,7 @@ Added items should show up right away:
True

>>> storage['Daniel']
<Person with name=u'Daniel', age=28>
<Person with name='Daniel', age=28>
>>> log.pop().object == storage['Daniel']
True
>>> log
Expand Down Expand Up @@ -262,7 +262,7 @@ wanted the name of our persons to be viewable only in the table:
... view_schema = field.Fields(IPerson).select('name')
... add_schema = IPerson

>>> print MyAdvancedForm(None, TestRequest())() \
>>> print(MyAdvancedForm(None, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...Daniel...Maria...</div>

Expand Down Expand Up @@ -293,7 +293,7 @@ We can still add a Person using both name and age:
>>> len(storage)
3
>>> storage['Thomas']
<Person with name=u'Thomas', age=28>
<Person with name='Thomas', age=28>

Our form can also contain links to our items:

Expand All @@ -302,7 +302,7 @@ Our form can also contain links to our items:
... if field == 'name':
... return 'http://en.wikipedia.org/wiki/%s' % item.name

>>> print MyAdvancedLinkingForm(None, TestRequest())() \
>>> print(MyAdvancedLinkingForm(None, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Daniel"...
Expand All @@ -318,7 +318,7 @@ What if we wanted the name to be both used for linking to the item
... view_schema = field.Fields(IPerson).select('name')
... add_schema = IPerson

>>> print MyAdvancedLinkingForm(None, TestRequest())() \
>>> print(MyAdvancedLinkingForm(None, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Thomas"...Thomas...</a>...
Expand All @@ -329,12 +329,12 @@ Wikipedia link immediately:

>>> request = TestRequest()
>>> for name in 'Daniel', 'Maria', 'Thomas':
... request.form['crud-edit.%s.widgets.name' % name] = unicode(storage[name].name)
... request.form['crud-edit.%s.widgets.age' % name] = unicode(storage[name].age)
... request.form['crud-edit.%s.widgets.name' % name] = storage[name].name
... request.form['crud-edit.%s.widgets.age' % name] = storage[name].age
>>> request.form['crud-edit.Thomas.widgets.name'] = u'Dracula'
>>> request.form['crud-edit.form.buttons.edit'] = u'Apply Changes'

>>> print MyAdvancedLinkingForm(None, request)() \
>>> print(MyAdvancedLinkingForm(None, request)()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Dracula"...Dracula...</a>...
Expand Down Expand Up @@ -401,19 +401,19 @@ than the 'edit' and 'delete' ones:
>>> "Delete" in html, "Apply changes" in html, "Capitalize" in html
(False, False, True)
>>> pprint(storage)
{'Daniel': <Person with name=u'Daniel', age=35>,
'Maria': <Person with name=u'Maria', age=40>,
'Thomas': <Person with name=u'Thomas', age=28>}
{'Daniel': <Person with name='Daniel', age=35>,
'Maria': <Person with name='Maria', age=40>,
'Thomas': <Person with name='Thomas', age=28>}

>>> request.form['crud-edit.Thomas.widgets.select'] = ['selected']
>>> request.form['crud-edit.form.buttons.capitalize'] = u'Capitalize'
>>> html = MyCustomForm(None, request)()
>>> "Capitalized items" in html
True
>>> pprint(storage)
{'Daniel': <Person with name=u'Daniel', age=35>,
'Maria': <Person with name=u'Maria', age=40>,
'Thomas': <Person with name=u'THOMAS', age=28>}
{'Daniel': <Person with name='Daniel', age=35>,
'Maria': <Person with name='Maria', age=40>,
'Thomas': <Person with name='THOMAS', age=28>}

We *cannot* use any of the other buttons:

Expand Down
12 changes: 8 additions & 4 deletions src/plone/z3cform/inputs.txt
Expand Up @@ -68,20 +68,24 @@ Let's now look this up with some form data that is not encoded:
>>> testForm.update()
>>> data, errors = testForm.extractData()
>>> data
{'text': u'foo'}
{'text': 'foo'}
>>> errors
()

Let's now try with some encoded data. The default encoding is utf-8.

>>> context = Bar()
>>> request = make_request(form={'form.widgets.text': 'fo\xc3\xb8'})
>>> request = make_request(form={'form.widgets.text': b'fo\xc3\xb8'})

>>> testForm = getMultiAdapter((context, request), name=u"test-form")
>>> testForm.update()
>>> data, errors = testForm.extractData()
>>> data
{'text': u'fo\xf8'}
>>> import six
>>> if six.PY2:
... data == {'text': u'fo\xf8'}
... else:
... data == {'text': 'foø'}
True
>>> errors
()

2 changes: 1 addition & 1 deletion src/plone/z3cform/layout.txt
Expand Up @@ -91,7 +91,7 @@ layout. We define a custom layout template first:
>>> from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
>>> fd, path = tempfile.mkstemp()
>>> f = os.fdopen(fd, 'w')
>>> f.write("""
>>> _ = f.write("""
... <html>Hello, this is your layout speaking:
... <h1 tal:content="view/label">View Title</h1>
... <div tal:content="structure view/contents"></div>
Expand Down
28 changes: 22 additions & 6 deletions src/plone/z3cform/tests.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.testing import Layer, z2, zca
from z3c.form.interfaces import IFormLayer
Expand All @@ -6,8 +7,11 @@
from zope.component import testing
from zope.configuration import xmlconfig
from zope.publisher.browser import TestRequest

import doctest
import plone.z3cform.templates
import six
import re
import unittest


Expand Down Expand Up @@ -139,29 +143,41 @@ def tearDown(self):
name="plone.z3cform:Functional")


class Py23DocChecker(doctest.OutputChecker):
def check_output(self, want, got, optionflags):
if six.PY2:
got = re.sub('LocationError', 'zope.location.interfaces.LocationError', got)
got = re.sub("u'(.*?)'", "'\\1'", got)
return doctest.OutputChecker.check_output(self, want, got, optionflags)


def test_suite():
layout_txt = doctest.DocFileSuite('layout.txt')
layout_txt = doctest.DocFileSuite('layout.txt', checker=Py23DocChecker())
layout_txt.layer = FUNCTIONAL_TESTING

inputs_txt = doctest.DocFileSuite('inputs.txt')
inputs_txt = doctest.DocFileSuite('inputs.txt', checker=Py23DocChecker())
inputs_txt.layer = FUNCTIONAL_TESTING

fieldsets_txt = doctest.DocFileSuite('fieldsets/README.txt')
fieldsets_txt = doctest.DocFileSuite('fieldsets/README.txt', checker=Py23DocChecker())
fieldsets_txt.layer = FUNCTIONAL_TESTING

traversal_txt = doctest.DocFileSuite('traversal.txt')
traversal_txt = doctest.DocFileSuite('traversal.txt', checker=Py23DocChecker())
traversal_txt.layer = FUNCTIONAL_TESTING

return unittest.TestSuite([
layout_txt, inputs_txt, fieldsets_txt, traversal_txt,

doctest.DocFileSuite(
'crud/README.txt',
setUp=testing.setUp, tearDown=testing.tearDown,
setUp=testing.setUp,
tearDown=testing.tearDown,
checker=Py23DocChecker(),
),

doctest.DocTestSuite(
'plone.z3cform.crud.crud',
setUp=testing.setUp, tearDown=testing.tearDown,
setUp=testing.setUp,
tearDown=testing.tearDown,
checker=Py23DocChecker(),
),
])
24 changes: 12 additions & 12 deletions src/plone/z3cform/traversal.txt
Expand Up @@ -11,10 +11,10 @@ may get an error if you mix in Explicit in Zope 2.12 and the parent view
the case. To be compatible with both Zope 2.10 and 2.12, you'll need to
mix in Acquisition.Explicit, but make sure the widget does *not* provide the
IAcquirer interface. One way to do that is by using implementsOnly(), e.g.::

class MyWidget(Aquisition.Explicit):
implementsOnly(IMyWidget)

...

If you are only targeting Zope 2.12 and later, you can avoid mixing in any
Expand Down Expand Up @@ -86,7 +86,7 @@ adapter. For example, let's say we'd traversed to
>>> age_widget
<TextWidget 'form.widgets.age'>
>>> age_widget.value
u'48'
'48'

Can also specify form.widgets.age and get the same result.

Expand All @@ -96,7 +96,7 @@ Can also specify form.widgets.age and get the same result.
>>> age_widget
<TextWidget 'form.widgets.age'>
>>> age_widget.value
u'48'
'48'

Please note that this point, the form has been updated, but not rendered.

Expand Down Expand Up @@ -144,31 +144,31 @@ And traverse through to individual items.
>>> age_widget
<TextWidget 'form.widgets.list_field.1'>
>>> age_widget.value
u'49'
'49'

>>> traverser = get_traverser(Bar(),u"test-list-form")
>>> age_widget = traverser.traverse('list_field.2', [])
Updating test form
>>> age_widget
<TextWidget 'form.widgets.list_field.2'>
>>> age_widget.value
u'50'
'50'

Out of range errors are LocationErrors

>>> traverser = get_traverser(Bar(),u"test-list-form")
>>> age_widget = traverser.traverse('list_field.9', [])
Traceback (most recent call last):
File "<stdin>", line 1, in ?
LocationError: "'9' not in range"
zope.location.interfaces.LocationError: "'9' not in range"

Non-integer values are also LocationErrors

>>> traverser = get_traverser(Bar(),u"test-list-form")
>>> age_widget = traverser.traverse('list_field.camel', [])
Traceback (most recent call last):
File "<stdin>", line 1, in ?
LocationError: "'camel' not valid index"
zope.location.interfaces.LocationError: "'camel' not valid index"

Traversing object types
-----------------------
Expand Down Expand Up @@ -225,23 +225,23 @@ And traverse through to the form within
>>> age_widget
<TextWidget 'form.widgets.obj_field.widgets.list_field.0'>
>>> age_widget.value
u'48'
'48'

Missing widgets are LocationErrors

>>> traverser = get_traverser(Bar(),u"test-parent-form")
>>> list_widget = traverser.traverse('obj_field.widgets.camel_field', [])
Traceback (most recent call last):
File "<stdin>", line 1, in ?
LocationError: 'camel_field'
zope.location.interfaces.LocationError: 'camel_field'

Looking for anything other than 'widgets' is also an error

>>> traverser = get_traverser(Bar(),u"test-parent-form")
>>> list_widget = traverser.traverse('obj_field.wodgets', [])
Traceback (most recent call last):
File "<stdin>", line 1, in ?
LocationError: 'wodgets'
zope.location.interfaces.LocationError: 'wodgets'

Traversal on a layout wrapper view
-----------------------------------
Expand Down Expand Up @@ -320,4 +320,4 @@ When a form field does not exist a LocationError is raised.
>>> traverser.traverse('missing', [])
Traceback (most recent call last):
...
LocationError: 'missing'
zope.location.interfaces.LocationError: 'missing'
8 changes: 4 additions & 4 deletions src/plone/z3cform/z2.py
Expand Up @@ -39,12 +39,12 @@ def processInputs(request, charsets=None):

for name, value in request.form.items():
if not (isCGI_NAME(name) or name.startswith('HTTP_')):
if isinstance(value, str):
if isinstance(value, six.binary_type):
request.form[name] = _decode(value, charsets)
elif isinstance(value, (list, tuple,)):
newValue = []
for val in value:
if isinstance(val, str):
if isinstance(val, six.binary_type):
val = _decode(val, charsets)
newValue.append(val)

Expand All @@ -59,9 +59,9 @@ def processInputs(request, charsets=None):
def _decode(text, charsets):
for charset in charsets:
try:
text = six.text_type(text, charset)
text = text.decode(charset)
break
except UnicodeError:
except (UnicodeError, AttributeError):
pass
return text

Expand Down

0 comments on commit 35facfe

Please sign in to comment.