Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add readonly, copying, and supermodel handler

svn path=/plone.app.textfield/trunk/; revision=29704
  • Loading branch information...
commit c61061da7fb3a45e337755387e96bad6fd888012 1 parent 8771c2f
@optilude optilude authored
View
9 plone/app/textfield/TODO.txt
@@ -1,14 +1,15 @@
- [ ] Handling of 'default'
- - need to make sure we don't modify a shared copy if used on multiple
- objects
+ [ ] Test the following
+
+ - copying an object (needs to get a new blob)
+ - placing a value in an annotation (needs to remain persistent)
+ - versioning an object and rolling back
[ ] Supermodel handler
- should support import and export of 'default'
[ ] Tests
- - handler.txt
- transform.txt
[ ] Documentation
View
6 plone/app/textfield/__init__.py
@@ -33,6 +33,7 @@ def __init__(self,
default = kw['default']
if isinstance(default, unicode):
kw['default'] = self.fromUnicode(default)
+ kw['default'].readonly = True
super(RichText, self).__init__(schema=schema, **kw)
@@ -46,7 +47,10 @@ def _validate(self, value):
def set(self, object, value):
if not self.readonly:
- value.__parent__ = object
+ if value.readonly:
+ value = value.copy(object)
+ else:
+ value.__parent__ = object
if value.defaultOutputMimeType is None:
value.defaultOutputMimeType = self.output_mime_type
super(RichText, self).set(object, value)
View
7 plone/app/textfield/configure.zcml
@@ -4,10 +4,13 @@
xmlns:zcml="http://namespaces.zope.org/zcml"
i18n_domain="plone.app.textfield">
- <adapter zcml:condition="installed plone.supermodel"
- factory=".handlers.RichTextHandler"
+ <utility zcml:condition="installed plone.supermodel"
+ component=".handler.RichTextHandler"
name="plone.app.textfield.RichText"
/>
+ <adapter zcml:condition="installed plone.supermodel"
+ factory=".handler.RichTextToUnicode"
+ />
<adapter zcml:condition="installed Products.PortalTransforms"
factory=".transform.PortalTransformsTransformer"
View
53 plone/app/textfield/field.txt
@@ -186,7 +186,8 @@ The 'default' parameter can be passed to the field upon construction as a
unicode string. It will then be converted to a RichTextValue with default
MIME types.
- >>> default_field = RichText(title=u"Rich text",
+ >>> default_field = RichText(__name__='default_field',
+ ... title=u"Rich text",
... default_mime_type='text/plain',
... output_mime_type='text/x-uppercase',
... allowed_mime_types=('text/plain', 'text/html',),
@@ -202,7 +203,57 @@ MIME types.
'text/x-uppercase'
>>> default_field.default.mimeType
'text/plain'
+
+Copying and read-only
+---------------------
+
+A value may be set to be readonly. In this case, it is not possible to set a
+new raw string. One example of a readonly value is the default value of a
+field.
+
+ >>> default_field.default.readonly
+ True
+
+ >>> default_field.default.raw = u"New value"
+ Traceback (most recent call last):
+ ...
+ TypeError: Value is readonly. Use copy() first.
+
+ >>> default_field.default.mimeType = 'text/foo'
+ Traceback (most recent call last):
+ ...
+ TypeError: Value is readonly. Use copy() first.
+
+ >>> default_field.default.defaultOutputMimeType = 'text/foo'
+ Traceback (most recent call last):
+ ...
+ TypeError: Value is readonly. Use copy() first.
+A field may be copied, in which case the new value will not be readonly.
+
+ >>> clone = default_field.default.copy()
+ >>> clone._blob is not default_field.default._blob
+ True
+ >>> clone.raw
+ u'Default value'
+
+The copy() method may be passed a new parent object.
+
+ >>> content = Content()
+ >>> clone = default_field.default.copy(content)
+ >>> clone.__parent__ is content
+ True
+
+Alternatively, when using set() on the field, the object will be cloned if
+necessary.
+
+ >>> content = Content()
+ >>> default_field.set(content, default_field.default)
+ >>> content.default_field.__parent__ is content
+ True
+ >>> content.default_field._blob is not default_field.default._blob
+ True
+
Persistence
-----------
View
15 plone/app/textfield/handler.py
@@ -7,7 +7,11 @@
if HAVE_SUPERMODEL:
+ from zope.interface import implements
+ from zope.component import adapts
from plone.app.textfield import RichText
+ from plone.supermodel.interfaces import IToUnicode
+ from plone.app.textfield.interfaces import IRichText
class RichTextHandler_(BaseHandler):
"""Special handling for the RichText field, to deal with 'default'
@@ -21,7 +25,14 @@ class RichTextHandler_(BaseHandler):
def __init__(self, klass):
super(RichTextHandler_, self).__init__(klass)
- # TODO: when reading 'default', allow a string; when writing 'default',
- # skip unless of default mime type
+ class RichTextToUnicode(object):
+ implements(IToUnicode)
+ adapts(IRichText)
+
+ def __init__(self, context):
+ self.context = context
+
+ def toUnicode(self, value):
+ return value.raw
RichTextHandler = RichTextHandler_(RichText)
View
77 plone/app/textfield/handler.txt
@@ -15,7 +15,12 @@ First, let's wire up the package.
... <include package="zope.app.component" file="meta.zcml" />
...
... <include package="plone.supermodel" />
- ... <include package="plone.app.textfield" />
+ ...
+ ... <utility
+ ... component="plone.app.textfield.handler.RichTextHandler"
+ ... name="plone.app.textfield.RichText"
+ ... />
+ ... <adapter factory="plone.app.textfield.handler.RichTextToUnicode" />
...
... </configure>
... """
@@ -27,7 +32,7 @@ First, let's wire up the package.
Then, let's test the field
>>> from zope.component import getUtility
- >>> from zope import schema
+ >>> from plone.app.textfield import RichText
>>> from plone.supermodel.interfaces import IFieldExportImportHandler
>>> from plone.supermodel.interfaces import IFieldNameExtractor
@@ -38,4 +43,70 @@ Then, let's test the field
>>> from elementtree import ElementTree
-TODO
+ >>> field = RichText(__name__=u"dummy", title=u"Test",
+ ... description=u"Test desc", required=False, readonly=True,
+ ... default=u"Test default",
+ ... default_mime_type='text/plain',
+ ... output_mime_type='text/html',
+ ... allowed_mime_types=('text/plain', 'text/html',))
+ >>> fieldType = IFieldNameExtractor(field)()
+ >>> handler = getUtility(IFieldExportImportHandler, name=fieldType)
+ >>> element = handler.write(field, u'dummy', fieldType) #doctest: +ELLIPSIS
+ >>> print prettyXML(element)
+ <field name="dummy" type="plone.app.textfield.RichText">
+ <allowed_mime_types>
+ <element>text/plain</element>
+ <element>text/html</element>
+ </allowed_mime_types>
+ <default>Test default</default>
+ <default_mime_type>text/plain</default_mime_type>
+ <description>Test desc</description>
+ <output_mime_type>text/html</output_mime_type>
+ <readonly>True</readonly>
+ <required>False</required>
+ <title>Test</title>
+ </field>
+
+To import:
+
+ >>> element = ElementTree.XML("""\
+ ... <field name="dummy" type="plone.app.textfield.RichText">
+ ... <default>Test default</default>
+ ... <description>Test desc</description>
+ ... <missing_value />
+ ... <readonly>True</readonly>
+ ... <required>False</required>
+ ... <schema>plone.supermodel.tests.IDummy</schema>
+ ... <title>Test</title>
+ ... <default_mime_type>text/plain</default_mime_type>
+ ... <output_mime_type>text/html</output_mime_type>
+ ... <allowed_mime_types>
+ ... <element>text/plain</element>
+ ... <element>text/html</element>
+ ... </allowed_mime_types>
+ ... </field>
+ ... """)
+
+ >>> reciprocal = handler.read(element)
+ >>> reciprocal.__class__
+ <class 'plone.app.textfield.RichText'>
+ >>> reciprocal.__name__
+ u'dummy'
+ >>> reciprocal.title
+ u'Test'
+ >>> reciprocal.description
+ u'Test desc'
+ >>> reciprocal.required
+ False
+ >>> reciprocal.readonly
+ True
+ >>> reciprocal.default.raw
+ u'Test default'
+ >>> reciprocal.missing_value is None
+ True
+ >>> reciprocal.default_mime_type
+ 'text/plain'
+ >>> reciprocal.output_mime_type
+ 'text/html'
+ >>> reciprocal.allowed_mime_types
+ ('text/plain', 'text/html')
View
19 plone/app/textfield/interfaces.py
@@ -22,6 +22,7 @@ class IRichText(IObject):
description=u"Set to None to disable checking",
default=None,
required=False,
+ value_type=schema.ASCIILine(title=u"MIME type"),
)
class IRichTextValue(Interface):
@@ -46,7 +47,7 @@ class IRichTextValue(Interface):
title=u"Default output MIME type"
)
- value = schema.Text(
+ output = schema.Text(
title=u"Transformed value in the target MIME type",
readonly=True
)
@@ -54,6 +55,22 @@ class IRichTextValue(Interface):
raw = schema.Text(
title=u"Raw value in the original MIME type"
)
+
+ readonly = schema.Bool(
+ title=u"Is the value readonly? If so, setting the raw data will raise a TypeError."
+ )
+
+ def modified():
+ """Notify the parent (if set) that this object has been modified
+ """
+
+ def update():
+ """Updated the cached output value
+ """
+
+ def copy(parent=None):
+ """Return a copy of this value, with the given parent
+ """
class TransformError(Exception):
"""Exception raised if a value could not be transformed. This is normally
View
6 plone/app/textfield/tests.py
@@ -5,6 +5,6 @@
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite('field.txt', optionflags=doctest.ELLIPSIS, tearDown=zope.component.testing.tearDown),
- doctest.DocFileSuite('transform.txt', optionflags=doctest.ELLIPSIS, tearDown=zope.component.testing.tearDown),
- # doctest.DocFileSuite('handler.txt', optionflags=doctest.ELLIPSIS, tearDown=zope.component.testing.tearDown),
- ))
+ doctest.DocFileSuite('handler.txt', optionflags=doctest.ELLIPSIS, tearDown=zope.component.testing.tearDown),
+ # doctest.DocFileSuite('transform.txt', optionflags=doctest.ELLIPSIS, tearDown=zope.component.testing.tearDown),
+ ))
View
2  plone/app/textfield/transform.py
@@ -2,7 +2,7 @@
from zope.component import adapts
from zope.app.component.hooks import getSite
-from zExceptions import ConflictError
+from ZODB.POSException import ConflictError
from plone.app.textfield.interfaces import ITransformer, TransformError
View
48 plone/app/textfield/value.py
@@ -19,7 +19,8 @@ class RichTextValue(object):
implements(IRichTextValue)
- def __init__(self, parent=None, defaultOutputMimeType=None, raw=None, mimeType=None, encoding='utf-8'):
+ def __init__(self, parent=None, defaultOutputMimeType=None, raw=None,
+ mimeType=None, encoding='utf-8', readonly=False):
self.__parent__ = parent
self._blob = Blob()
self._defaultOutputMimeType = defaultOutputMimeType
@@ -27,6 +28,7 @@ def __init__(self, parent=None, defaultOutputMimeType=None, raw=None, mimeType=N
self._mimeType = mimeType
self._output = None
self._encoding = encoding
+ self._readonly = readonly
if raw:
self.raw = raw
@@ -51,6 +53,10 @@ def raw(self):
@setproperty
def raw(self, value):
assert isinstance(value, unicode)
+
+ if self.readonly:
+ raise TypeError("Value is readonly. Use copy() first.")
+
fp = self._blob.open('w')
try:
fp.write(value.encode(self._encoding))
@@ -77,6 +83,10 @@ def mimeType(self):
@setproperty
def mimeType(self, value):
+
+ if self.readonly:
+ raise TypeError("Value is readonly. Use copy() first.")
+
self._mimeType = value
self.update()
self.modified()
@@ -89,10 +99,24 @@ def defaultOutputMimeType(self):
@setproperty
def defaultOutputMimeType(self, value):
+
+ if self.readonly:
+ raise TypeError("Value is readonly. Use copy() first.")
+
self._defaultOutputMimeType = value
self.update()
self.modified()
+ # is the object readonly?
+ @getproperty
+ def readonly(self):
+ return self._readonly
+
+ @setproperty
+ def readonly(self, value):
+ self._readonly = value
+ self.modified()
+
# cached output
def update(self):
@@ -121,5 +145,27 @@ def modified(self):
if self.__parent__ is not None:
self.__parent__._p_changed = 1
+ def copy(self, parent=None, readonly=False):
+ """Return a copy of this value, without the given parent.
+ """
+ newvalue = RichTextValue(parent)
+ newvalue._blob = Blob()
+ newvalue._defaultOutputMimeType = self.defaultOutputMimeType
+ newvalue._mimeType = self.mimeType
+ newvalue._output = self._output
+ newvalue._encoding = self._encoding
+ newvalue._readonly = readonly
+
+ fp = newvalue._blob.open('w')
+ try:
+ fp.write(self.raw_encoded)
+ finally:
+ fp.close()
+
+ newvalue._set = True
+ newvalue.modified()
+
+ return newvalue
+
def __repr__(self):
return u"RichTextValue object. (Did you mean <attribute>.raw or <attribute>.output?)"
Please sign in to comment.
Something went wrong with that request. Please try again.