diff --git a/docs/HISTORY.txt b/docs/HISTORY.txt index 54a7d3d..c51b411 100644 --- a/docs/HISTORY.txt +++ b/docs/HISTORY.txt @@ -4,6 +4,10 @@ Changelog 2.0.1 (unreleased) ------------------ +- The default attribute accessor now also looks through subtypes + (behaviors) to find a field default. + [malthe] + - Added support in the FTI to look up behaviors by utility name when getting additional schemata (i.e. fields provided by behaviors). diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py index 4181617..2c6912b 100644 --- a/plone/dexterity/content.py +++ b/plone/dexterity/content.py @@ -53,7 +53,6 @@ class FTIAwareSpecification(ObjectSpecificationDescriptor): """ def __get__(self, inst, cls=None): - # We're looking at a class - fall back on default if inst is None: return getObjectSpecification(cls) @@ -186,7 +185,13 @@ class DexterityContent(DAVResourceMixin, PortalContent, DefaultDublinCoreImpl, C field = schema.get(name, None) if field is not None: return deepcopy(field.default) - + + # do the same for each subtype + for schema in SCHEMA_CACHE.subtypes(self.portal_type): + field = schema.get(name, None) + if field is not None: + return deepcopy(field.default) + raise AttributeError(name) # Let __name__ and id be identical. Note that id must be ASCII in Zope 2, @@ -316,16 +321,11 @@ class Container(DAVCollectionMixin, BrowserDefaultMixin, CMFCatalogAware, CMFOrd setattr(self, k, v) def __getattr__(self, name): - - # attribute was not found; try to look it up in the schema and return - # a default - - schema = SCHEMA_CACHE.get(self.portal_type) - if schema is not None: - field = schema.get(name, None) - if field is not None: - return deepcopy(field.default) - + try: + return DexterityContent.__getattr__(self, name) + except AttributeError: + pass + # Be specific about the implementation we use return CMFOrderedBTreeFolderBase.__getattr__(self, name) diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py index 4e18b50..eee4e29 100644 --- a/plone/dexterity/schema.py +++ b/plone/dexterity/schema.py @@ -77,12 +77,13 @@ class SchemaCache(object): if cached is None: subtypes = [] fti = queryUtility(IDexterityFTI, name=portal_type) - if fti is not None: - for behavior_name in fti.behaviors: - behavior = queryUtility(IBehavior, name=behavior_name) - if behavior is not None and behavior.marker is not None: - subtypes.append(behavior.marker) - cached = self.subtypes_cache[portal_type] = tuple(subtypes) + if fti is None: + return () + for behavior_name in fti.behaviors: + behavior = queryUtility(IBehavior, name=behavior_name) + if behavior is not None and behavior.marker is not None: + subtypes.append(behavior.marker) + cached = self.subtypes_cache[portal_type] = tuple(subtypes) return cached @synchronized(lock) diff --git a/plone/dexterity/tests/test_content.py b/plone/dexterity/tests/test_content.py index 38b2368..ff9fdd7 100644 --- a/plone/dexterity/tests/test_content.py +++ b/plone/dexterity/tests/test_content.py @@ -231,7 +231,7 @@ class TestContent(MockTestCase): pass class ISubtype(Interface): - pass + baz = zope.schema.TextLine(title=u"baz", default=u"baz") behavior1 = BehaviorRegistration(u"Behavior1", "", IBehavior1, None, None) behavior2 = BehaviorRegistration(u"Behavior2", "", IBehavior2, ISubtype, None) @@ -258,7 +258,10 @@ class TestContent(MockTestCase): # the cache. This is not the case, as evidenced by .count(1) above. self.assertEquals(True, ISubtype.providedBy(item)) self.assertEquals(True, ISchema.providedBy(item)) - + + # Subtypes provide field defaults. + self.assertEquals(u"baz", getattr(item, "baz", None)) + # We also need to ensure that the _v_ attribute doesn't hide any # interface set directly on the instance with alsoProvides() or # directlyProvides(). This is done by clearing the cache when these