Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Dexterity Support #4

Merged
merged 9 commits into from

2 participants

@jone
Owner
  • Adds a new Dexterity field data collector with namedfile support
  • zc.relation support is not yet implemented and raises
  • Register some other collectors for Interface so that they work for Dexterity too.

\cc @maethu

@jone jone referenced this pull request in 4teamwork/ftw.publisher.receiver
Merged

Dexterity Support #2

@maethu maethu merged commit 4389b62 into from
@maethu maethu deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
18 ftw/publisher/core/adapters/backreferences.py
@@ -1,4 +1,6 @@
from AccessControl.SecurityInfo import ClassSecurityInformation
+from Acquisition import aq_base
+from Products.Archetypes.interfaces.referenceable import IReferenceable
from Products.CMFCore.utils import getToolByName
from ftw.publisher.core import getLogger
from ftw.publisher.core.interfaces import IDataCollector
@@ -35,7 +37,21 @@ def getData(self):
"""
data = {}
- for ref in self.context.getBackReferenceImpl():
+
+ if hasattr(aq_base(self.context), 'getBackReferenceImpl'):
+ referenceable = self.context
+
+ else:
+ try:
+ referenceable = IReferenceable(self.context)
+
+ except TypeError:
+ # could not adapt
+ # this means we have a dexterity object without
+ # plone.app.referenceablebehavior activated.
+ return data
+
+ for ref in referenceable.getBackReferenceImpl():
# get source object
src = ref.getSourceObject()
suid = src.UID()
View
14 ftw/publisher/core/adapters/configure.zcml
@@ -1,5 +1,6 @@
<configure
xmlns="http://namespaces.zope.org/zope"
+ xmlns:zcml="http://namespaces.zope.org/zcml"
xmlns:five="http://namespaces.zope.org/five">
<adapter
@@ -9,25 +10,30 @@
name="field_data_adapter" />
<adapter
- for="Products.Archetypes.interfaces.IBaseObject"
+ zcml:condition="installed plone.dexterity"
+ factory="ftw.publisher.core.adapters.dx_field_data.DexterityFieldData"
+ name="dx_field_data_adapter" />
+
+ <adapter
+ for="*"
provides="ftw.publisher.core.interfaces.IDataCollector"
factory="ftw.publisher.core.adapters.properties_data.PropertiesData"
name="properties_data_adapter" />
<adapter
- for="Products.Archetypes.interfaces.IBaseObject"
+ for="*"
provides="ftw.publisher.core.interfaces.IDataCollector"
factory="ftw.publisher.core.adapters.portlet_data.PortletsData"
name="portlet_data_adapter" />
<adapter
- for="Products.Archetypes.interfaces.IBaseObject"
+ for="*"
provides="ftw.publisher.core.interfaces.IDataCollector"
factory="ftw.publisher.core.adapters.interface_data.InterfaceData"
name="interface_data_adapter" />
<adapter
- for="Products.Archetypes.interfaces.IBaseObject"
+ for="*"
provides="ftw.publisher.core.interfaces.IDataCollector"
factory="ftw.publisher.core.adapters.backreferences.Backreferences"
name="backreferences_adapter" />
View
140 ftw/publisher/core/adapters/dx_field_data.py
@@ -0,0 +1,140 @@
+from AccessControl.SecurityInfo import ClassSecurityInformation
+from ftw.publisher.core.interfaces import IDataCollector
+from plone.dexterity.interfaces import IDexterityContent
+from plone.dexterity.utils import iterSchemata
+from zope import schema
+from zope.component import adapts
+from zope.interface import implements
+import DateTime
+import base64
+import pkg_resources
+
+
+try:
+ pkg_resources.get_distribution('z3c.relationfield')
+
+except pkg_resources.DistributionNotFound:
+ HAS_RELATIONS = False
+
+else:
+ HAS_RELATIONS = True
+ from z3c.relationfield.interfaces import IRelation
+ from z3c.relationfield.interfaces import IRelationChoice
+ from z3c.relationfield.interfaces import IRelationList
+
+
+try:
+ pkg_resources.get_distribution('plone.namedfile')
+
+except pkg_resources.DistributionNotFound:
+ HAS_NAMEDFILE = False
+
+else:
+ HAS_NAMEDFILE = True
+ from plone.namedfile.interfaces import INamedFileField
+
+
+_marker = object()
+
+
+class DexterityFieldData(object):
+ implements(IDataCollector)
+ adapts(IDexterityContent)
+
+ security = ClassSecurityInformation()
+
+ def __init__(self, context):
+ self.context = context
+
+ def getData(self):
+ data = {}
+
+ for schemata in iterSchemata(self.context):
+ subdata = {}
+ repr = schemata(self.context)
+ for name, field in schema.getFieldsInOrder(schemata):
+ value = getattr(repr, name, _marker)
+ if value == _marker:
+ value = getattr(self.context, name, None)
+ value = self.pack(name, field, value)
+ subdata[name] = value
+
+ assert schemata.getName() not in data.keys(), \
+ 'Duplacte behavior names are not supported'
+
+ data[schemata.getName()] = subdata
+ return data
+
+ def setData(self, data, metadata):
+ """Inserts the field data on self.context
+ """
+
+ for schemata in iterSchemata(self.context):
+ repr = schemata(self.context)
+ subdata = data[schemata.getName()]
+ for name, field in schema.getFieldsInOrder(schemata):
+ value = subdata[name]
+ value = self.unpack(name, field, value)
+ if value != _marker:
+ setattr(repr, name, value)
+
+ def pack(self, name, field, value):
+ """Packs the field data and makes it ready for transportation with
+ json, which does only support basic data types.
+ """
+ if self._provided_by_one_of(field, [
+ schema.interfaces.IDate,
+ schema.interfaces.ITime,
+ schema.interfaces.IDatetime,
+ ]):
+ if value:
+ return str(value)
+
+ elif HAS_NAMEDFILE and self._provided_by_one_of(field, [
+ INamedFileField,
+ ]):
+ if value:
+ return {
+ 'filename': value.filename,
+ 'data': base64.encodestring(value.data),
+ }
+
+ elif HAS_RELATIONS and self._provided_by_one_of(field, (
+ IRelation,
+ IRelationChoice,
+ IRelationList,)):
+ # XXX RELATION SUPPORT
+ raise NotImplementedError()
+
+ return value
+
+ def unpack(self, name, field, value):
+ """Unpacks the value from the basic json types to the objects which
+ are stored on the field later.
+ """
+
+ if self._provided_by_one_of(field, [
+ schema.interfaces.IDate,
+ schema.interfaces.ITime,
+ schema.interfaces.IDatetime,
+ ]):
+ if value:
+ return DateTime.DateTime(value).asdatetime()
+
+ if HAS_NAMEDFILE and self._provided_by_one_of(
+ field, [INamedFileField]):
+ if value and isinstance(value, dict):
+ filename = value['filename']
+ data = base64.decodestring(value['data'])
+ return field._type(data=data, filename=filename)
+ return value
+
+ def _provided_by_one_of(self, obj, ifaces):
+ """Checks if at least one interface of the list `ifaces` is provied
+ by the `obj`.
+ """
+
+ for ifc in ifaces:
+ if ifc.providedBy(obj):
+ return True
+ return False
View
12 ftw/publisher/core/tests/interfaces.py
@@ -0,0 +1,12 @@
+from plone.directives import form
+from plone.namedfile.field import NamedFile
+
+
+class IFoo(form.Schema):
+ pass
+
+
+class IFileSchema(form.Schema):
+
+ form.primary('file')
+ file = NamedFile(title=u'File')
View
17 ftw/publisher/core/tests/profiles/dexterity.zcml
@@ -0,0 +1,17 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+ xmlns:i18n="http://namespaces.zope.org/i18n"
+ i18n_domain="ftw.publisher.sender">
+
+ <include package="Products.GenericSetup" file="meta.zcml" />
+
+ <genericsetup:registerProfile
+ name="dexterity"
+ title="ftw.publisher.sender.tests:dexterity"
+ directory="dexterity"
+ description=""
+ provides="Products.GenericSetup.interfaces.EXTENSION"
+ />
+
+</configure>
View
4 ftw/publisher/core/tests/profiles/dexterity/types.xml
@@ -0,0 +1,4 @@
+<object name="portal_types">
+ <object name="ExampleDxType" meta_type="Dexterity FTI" />
+ <object name="DXFile" meta_type="Dexterity FTI" />
+</object>
View
58 ftw/publisher/core/tests/profiles/dexterity/types/DXFile.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<object name="DXFile"
+ meta_type="Dexterity FTI"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ i18n:domain="ftw.publisher.core.tests" >
+
+ <!-- Basic metadata -->
+ <property name="title">DXFile</property>
+ <property name="global_allow">True</property>
+ <property name="add_permission">cmf.AddPortalContent</property>
+
+ <!-- schema interface -->
+ <property name="schema">ftw.publisher.core.tests.interfaces.IFileSchema</property>
+
+ <!-- class used for content items -->
+ <property name="klass">plone.dexterity.content.Item</property>
+
+ <!-- enabled behaviors -->
+ <property name="behaviors">
+ <element value="plone.app.dexterity.behaviors.metadata.IBasic" />
+ <element value="plone.app.content.interfaces.INameFromTitle" />
+ </property>
+
+ <!-- View information -->
+ <property name="default_view">view</property>
+ <property name="default_view_fallback">False</property>
+ <property name="view_methods">
+ <element value="view"/>
+ </property>
+
+ <!-- Method aliases -->
+ <alias from="(Default)" to="(dynamic view)"/>
+ <alias from="edit" to="@@edit"/>
+ <alias from="sharing" to="@@sharing"/>
+ <alias from="view" to="(selected layout)"/>
+
+ <!-- Actions -->
+ <action
+ action_id="view"
+ title="View"
+ category="object"
+ condition_expr=""
+ url_expr="string:${object_url}"
+ visible="True">
+ <permission value="View"/>
+ </action>
+
+ <action
+ action_id="edit"
+ title="Edit"
+ category="object"
+ condition_expr=""
+ url_expr="string:${object_url}/edit"
+ visible="True">
+ <permission value="Modify portal content"/>
+ </action>
+
+</object>
View
58 ftw/publisher/core/tests/profiles/dexterity/types/ExampleDxType.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<object name="ExampleDxType"
+ meta_type="Dexterity FTI"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ i18n:domain="ftw.publisher.core.tests" >
+
+ <!-- Basic metadata -->
+ <property name="title">ExampleDxType</property>
+ <property name="global_allow">True</property>
+ <property name="add_permission">cmf.AddPortalContent</property>
+
+ <!-- schema interface -->
+ <property name="schema">ftw.publisher.core.tests.interfaces.IFoo</property>
+
+ <!-- class used for content items -->
+ <property name="klass">plone.dexterity.content.Item</property>
+
+ <!-- enabled behaviors -->
+ <property name="behaviors">
+ <element value="plone.app.dexterity.behaviors.metadata.IBasic" />
+ <element value="plone.app.content.interfaces.INameFromTitle" />
+ </property>
+
+ <!-- View information -->
+ <property name="default_view">view</property>
+ <property name="default_view_fallback">False</property>
+ <property name="view_methods">
+ <element value="view"/>
+ </property>
+
+ <!-- Method aliases -->
+ <alias from="(Default)" to="(dynamic view)"/>
+ <alias from="edit" to="@@edit"/>
+ <alias from="sharing" to="@@sharing"/>
+ <alias from="view" to="(selected layout)"/>
+
+ <!-- Actions -->
+ <action
+ action_id="view"
+ title="View"
+ category="object"
+ condition_expr=""
+ url_expr="string:${object_url}"
+ visible="True">
+ <permission value="View"/>
+ </action>
+
+ <action
+ action_id="edit"
+ title="Edit"
+ category="object"
+ condition_expr=""
+ url_expr="string:${object_url}/edit"
+ visible="True">
+ <permission value="Modify portal content"/>
+ </action>
+
+</object>
View
105 ftw/publisher/core/tests/test_dexterity.py
@@ -0,0 +1,105 @@
+from ftw.publisher.core import utils
+from ftw.publisher.core.interfaces import IDataCollector
+from ftw.publisher.core.testing import PUBLISHER_EXAMPLE_CONTENT_FIXTURE
+from json import dumps
+from json import loads
+from plone.app.testing import IntegrationTesting
+from plone.app.testing import PloneSandboxLayer
+from plone.app.testing import applyProfile
+from plone.dexterity.utils import createContentInContainer
+from plone.namedfile.file import NamedFile
+from unittest2 import TestCase
+from zope.component import getAdapter
+from zope.configuration import xmlconfig
+
+
+class DexterityLayer(PloneSandboxLayer):
+
+ defaultBases = (PUBLISHER_EXAMPLE_CONTENT_FIXTURE, )
+
+ def setUpZope(self, app, configurationContext):
+ import plone.app.dexterity
+ xmlconfig.file('configure.zcml', plone.app.dexterity,
+ context=configurationContext)
+
+ import ftw.publisher.core.tests
+ xmlconfig.file('profiles/dexterity.zcml', ftw.publisher.core.tests,
+ context=configurationContext)
+
+ def setUpPloneSite(self, portal):
+ applyProfile(portal, 'ftw.publisher.core.tests:dexterity')
+
+
+DX_FIXTURE = DexterityLayer()
+DX_INTEGRATION_TESTING = IntegrationTesting(
+ bases=(DX_FIXTURE, ), name="ftw.publisher.core:dexterity integration")
+
+
+class TestDexterityFieldData(TestCase):
+
+ layer = DX_INTEGRATION_TESTING
+
+ def setUp(self):
+ super(TestDexterityFieldData, self).setUp()
+ self.portal = self.layer['portal']
+
+ def _get_field_data(self, obj, json=False):
+ collector = getAdapter(obj, IDataCollector,
+ name='dx_field_data_adapter')
+ data = collector.getData()
+
+ if json:
+ data = utils.decode_for_json(data)
+ data = dumps(data)
+
+ return data
+
+
+ def _set_field_data(self, obj, data, metadata=None, json=False):
+ collector = getAdapter(obj, IDataCollector,
+ name='dx_field_data_adapter')
+
+ if json:
+ data = loads(data)
+ data = utils.encode_after_json(data)
+
+ return collector.setData(data, metadata or {})
+
+ def test_dexterity_data_extraction(self):
+ obj = createContentInContainer(
+ self.portal, 'ExampleDxType', title=u'My Object')
+
+ self.assertEquals({'IBasic': {'description': u'',
+ 'title': u'My Object'},
+ 'IFoo': {}},
+
+ self._get_field_data(obj))
+
+ def test_extract_and_set_data(self):
+ foo = createContentInContainer(
+ self.portal, 'ExampleDxType', title=u'Foo')
+ data = self._get_field_data(foo, json=True)
+
+ bar = createContentInContainer(
+ self.portal, 'ExampleDxType', title=u'Bar')
+
+ self.assertEquals('Bar', bar.Title())
+ self._set_field_data(bar, data, json=True)
+ self.assertEquals('Foo', bar.Title())
+
+ def test_namedfile_files(self):
+ filedata = u'**** filedata ****'
+
+ foo = createContentInContainer(
+ self.portal, 'DXFile', title=u'Foo')
+ foo.file = NamedFile(data=filedata, filename=u'fuu.txt')
+ data = self._get_field_data(foo, json=True)
+
+ bar = createContentInContainer(
+ self.portal, 'DXFile', title=u'Bar')
+
+ self.assertEquals(bar.file, None)
+ self._set_field_data(bar, data, json=True)
+ self.assertTrue(bar.file, 'File data missing')
+
+ self.assertEquals(u'fuu.txt', bar.file.filename, 'Filename wrong')
View
19 setup.py
@@ -4,7 +4,14 @@
version = '2.0.dev0'
maintainer = 'Jonas Baumann'
-tests_require=[
+
+extras_require={
+ 'dexterity': [
+ 'plone.app.dexterity',
+ ]}
+
+
+extras_require['tests'] = tests_require = [
'Acquisition',
'Plone',
@@ -12,11 +19,14 @@
'ftw.testing',
'plone.app.blob',
'plone.app.testing',
+ 'plone.directives.form',
+ 'plone.namedfile',
'unittest2',
'zope.annotation',
'zope.configuration',
- ]
+ ] + reduce(list.__add__, extras_require.values())
+
setup(name='ftw.publisher.core',
version=version,
@@ -66,11 +76,8 @@
],
- extras_require={
- 'tests': tests_require,
- },
-
tests_require=tests_require,
+ extras_require=extras_require,
entry_points="""
# -*- Entry points: -*-
Something went wrong with that request. Please try again.