From 33170cecea172a650611855d38c5c6cb0789a7b3 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Fri, 1 Dec 2017 20:56:22 +0100
Subject: [PATCH 01/29] Make the DX factory work in a GS baseline import work.

ps resolveDottedName either returns or raises
---
 CHANGES.rst                | 4 +++-
 plone/dexterity/factory.py | 2 +-
 plone/dexterity/fti.py     | 8 +++++---
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index d20d9c87..fa17f0d4 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -15,7 +15,9 @@ New features:
 
 Bug fixes:
 
-- *add item here*
+- Fix ft._updateProperty so it doesn't break when receiving an empty value.
+  This happens when an DX FTI is part of a Generic Setup baseline import.
+  [jaroel]
 
 
 2.6.0 (2018-04-03)
diff --git a/plone/dexterity/factory.py b/plone/dexterity/factory.py
index 9ee459c8..9949213e 100644
--- a/plone/dexterity/factory.py
+++ b/plone/dexterity/factory.py
@@ -30,7 +30,7 @@ def description(self):
     def __call__(self, *args, **kw):
         fti = getUtility(IDexterityFTI, name=self.portal_type)
 
-        klass = resolveDottedName(fti.klass)
+        klass = resolveDottedName(fti.klass) if fti.klass else None
         if klass is None or not callable(klass):
             raise ValueError(
                 "Content class %s set for type %s is not valid" %
diff --git a/plone/dexterity/fti.py b/plone/dexterity/fti.py
index 265a84df..b544f216 100644
--- a/plone/dexterity/fti.py
+++ b/plone/dexterity/fti.py
@@ -186,7 +186,7 @@ def __init__(self, *args, **kwargs):
 
         # Set the content_meta_type from the klass
 
-        klass = utils.resolveDottedName(self.klass)
+        klass = utils.resolveDottedName(self.klass) if self.klass else None
         if klass is not None:
             self.content_meta_type = getattr(klass, 'meta_type', None)
 
@@ -218,7 +218,7 @@ def Metatype(self):
         if self.content_meta_type:
             return self.content_meta_type
         # BBB - this didn't use to be set
-        klass = utils.resolveDottedName(self.klass)
+        klass = utils.resolveDottedName(self.klass) if self.klass else None
         if klass is not None:
             self.content_meta_type = getattr(klass, 'meta_type', None)
         return self.content_meta_type
@@ -291,7 +291,9 @@ def _updateProperty(self, id, value):
 
             # Update meta_type from klass
             if id == 'klass':
-                klass = utils.resolveDottedName(new_value)
+                klass = None
+                if new_value:
+                    klass = utils.resolveDottedName(new_value)
                 if klass is not None:
                     self.content_meta_type = getattr(klass, 'meta_type', None)
 

From 9e036ea59eaf205beb16564b0efcd1725fd6e846 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sat, 23 Jun 2018 23:42:45 +0200
Subject: [PATCH 02/29] Pass on attribute access to item access + log a bunch

---
 plone/dexterity/content.py | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index efcf0147..e19b1726 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -48,6 +48,7 @@
 from zope.security.interfaces import IPermission
 
 import six
+import warnings
 
 
 _marker = object()
@@ -704,6 +705,10 @@ def __init__(self, id=None, **kwargs):
         DexterityContent.__init__(self, id, **kwargs)
 
     def __getattr__(self, name):
+        if name in self:
+            msg = "Trying to access item '{}' in {} by attribute".format(name, self)
+            warnings.warn(msg)
+
         try:
             return DexterityContent.__getattr__(self, name)
         except AttributeError:
@@ -712,6 +717,36 @@ def __getattr__(self, name):
         # Be specific about the implementation we use
         return CMFOrderedBTreeFolderBase.__getattr__(self, name)
 
+    def __setattr__(self, name, value):
+        try:
+            has_item_by_name = name in self
+        except:
+            has_item_by_name = False
+        if has_item_by_name:
+            msg = (
+                "An item with the same name already exists. Use item access, for example: obj['{}'] = value. "
+                "We are setting the item instead of the attribute!"
+            ).format(name)
+            warnings.warn(msg)
+            # We remove the old one and then add the new one so the old one is
+            # unindexed and the new is indexed.
+            del self[name]
+            self[name] = value
+        else:
+            super(Container, self).__setattr__(name, value)
+
+    def _delObject(self, name, *args, **kwargs):
+        super(Container, self)._delObject(name, *args, **kwargs)
+
+        # This will trigger when an item was deleted from this container and
+        # attribute by the same name exists. ie obj['my_id'] and obj.my_id.
+        if getattr(aq_base(self), name, _marker) is not _marker:
+            msg = (
+                "Item '{}' contained in {} was shadowed by an attribute."
+                "You might want to delete the attribute as well."
+            ).format(name, self)
+            warnings.warn(msg)
+
     @security.protected(permissions.DeleteObjects)
     def manage_delObjects(self, ids=None, REQUEST=None):
         """Delete the contained objects with the specified ids.

From 71796f65a27c1d9b130441df71cadc6b8ec95151 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sat, 16 Feb 2019 16:32:01 +0100
Subject: [PATCH 03/29] Check __dict__ as getattr will use descriptors - ie
 FieldProperties

---
 plone/dexterity/content.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index d457327d..93389f0f 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -752,7 +752,7 @@ def _delObject(self, name, *args, **kwargs):
 
         # This will trigger when an item was deleted from this container and
         # attribute by the same name exists. ie obj['my_id'] and obj.my_id.
-        if getattr(aq_base(self), name, _marker) is not _marker:
+        if name in aq_base(self).__dict__:
             msg = (
                 "Item '{}' contained in {} was shadowed by an attribute."
                 "You might want to delete the attribute as well."

From 20fdb8aa54d8a12e650c22a4944d33c5c08f0bc9 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 3 Mar 2019 12:21:13 +0100
Subject: [PATCH 04/29] Donn't warning about accessing items by attribute.

---
 plone/dexterity/content.py | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index 93389f0f..6645d48f 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -717,10 +717,6 @@ def __init__(self, id=None, **kwargs):
         DexterityContent.__init__(self, id, **kwargs)
 
     def __getattr__(self, name):
-        if name in self:
-            msg = "Trying to access item '{}' in {} by attribute".format(name, self)
-            warnings.warn(msg)
-
         try:
             return DexterityContent.__getattr__(self, name)
         except AttributeError:

From 44523c93f5e9cd327115869a79089e8cd3d39d04 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 3 Mar 2019 13:50:56 +0100
Subject: [PATCH 05/29] Pass in knnown prefix

---
 plone/dexterity/fti.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/plone/dexterity/fti.py b/plone/dexterity/fti.py
index 7794936d..7b144497 100644
--- a/plone/dexterity/fti.py
+++ b/plone/dexterity/fti.py
@@ -264,8 +264,11 @@ def lookupSchema(self):
         # Otherwise, look up a dynamic schema. This will query the model for
         # an unnamed schema if it is the first time it is looked up.
         # See schema.py
-
-        schemaName = portalTypeToSchemaName(self.getId())
+        try:
+            prefix = self.__parent__.__name__
+        except AttributeError:
+            prefix = None
+        schemaName = portalTypeToSchemaName(self.getId(), prefix=prefix)
         return getattr(plone.dexterity.schema.generated, schemaName)
 
     def lookupModel(self):

From d341c7138bc4fde066c33b5ab931e0367fb9b14f Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 3 Mar 2019 22:40:02 +0100
Subject: [PATCH 06/29] fix up, look sharp

---
 plone/dexterity/content.py | 26 ++++++++++++++------------
 1 file changed, 14 insertions(+), 12 deletions(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index 6645d48f..9f352142 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -725,21 +725,23 @@ def __getattr__(self, name):
         # Be specific about the implementation we use
         return CMFOrderedBTreeFolderBase.__getattr__(self, name)
 
-    def __setattr__(self, name, value):
+    def __delattr__(self, name):
         try:
-            has_item_by_name = name in self
-        except:
-            has_item_by_name = False
-        if has_item_by_name:
-            msg = (
-                "An item with the same name already exists. Use item access, for example: obj['{}'] = value. "
-                "We are setting the item instead of the attribute!"
-            ).format(name)
-            warnings.warn(msg)
-            # We remove the old one and then add the new one so the old one is
-            # unindexed and the new is indexed.
+            super(Container, self).__delattr__(name)
+        except AttributeError:  # delete the item instead
             del self[name]
+
+    def __setattr__(self, name, value):
+        # If we have an existing attribute, just set it.
+        # We'll check this first, so we don't check the tree unneeded.
+        if name in self.__dict__:
+            super(Container, self).__setattr__(name, value)
+
+        # if we have a n item, set that
+        elif '_tree' in self.__dict__ and name in self:
             self[name] = value
+
+        # else we'll set an attribute.
         else:
             super(Container, self).__setattr__(name, value)
 

From 4877506ccb65d8a3875381f6f8d0a833432bf5c2 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 3 Mar 2019 23:09:10 +0100
Subject: [PATCH 07/29] fu. We do need to delete the item first, or we'll get
 doublicate ids.

---
 plone/dexterity/content.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index 9f352142..94ad5797 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -739,6 +739,7 @@ def __setattr__(self, name, value):
 
         # if we have a n item, set that
         elif '_tree' in self.__dict__ and name in self:
+            del self[name]
             self[name] = value
 
         # else we'll set an attribute.

From bde13fcb802f9dff19186bd1f18b5f934952b4f6 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 21 Apr 2019 23:18:33 +0200
Subject: [PATCH 08/29] prevent some loop where getUtility(ISiteRoot) tiggers
 gettings the schema, which triggers portalTypeToSchemaName, etc.

---
 plone/dexterity/fti.py    | 9 +++++----
 plone/dexterity/schema.py | 6 ++++--
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/plone/dexterity/fti.py b/plone/dexterity/fti.py
index 7b144497..bcdca294 100644
--- a/plone/dexterity/fti.py
+++ b/plone/dexterity/fti.py
@@ -265,10 +265,10 @@ def lookupSchema(self):
         # an unnamed schema if it is the first time it is looked up.
         # See schema.py
         try:
-            prefix = self.__parent__.__name__
+            siteroot = self.__parent__.__parent__
         except AttributeError:
-            prefix = None
-        schemaName = portalTypeToSchemaName(self.getId(), prefix=prefix)
+            siteroot = None
+        schemaName = portalTypeToSchemaName(self.getId(), siteroot=siteroot)
         return getattr(plone.dexterity.schema.generated, schemaName)
 
     def lookupModel(self):
@@ -558,7 +558,8 @@ def ftiModified(object, event):
         if (fti.model_source or fti.model_file) \
            and ('model_source' in mod or 'model_file' in mod or 'schema_policy' in mod):
 
-            schemaName = portalTypeToSchemaName(portal_type)
+            siteroot = fti.__parent__.__parent__
+            schemaName = portalTypeToSchemaName(portal_type, siteroot=siteroot)
             schema = getattr(plone.dexterity.schema.generated, schemaName)
 
             model = fti.lookupModel()
diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py
index 93565ea4..38e8d41a 100644
--- a/plone/dexterity/schema.py
+++ b/plone/dexterity/schema.py
@@ -281,11 +281,13 @@ def split(self, s):
         return [self.decode(a) for a in s.split('_0_')]
 
 
-def portalTypeToSchemaName(portal_type, schema=u"", prefix=None):
+def portalTypeToSchemaName(portal_type, schema=u"", prefix=None, siteroot=None):
     """Return a canonical interface name for a generated schema interface.
     """
     if prefix is None:
-        prefix = '/'.join(getUtility(ISiteRoot).getPhysicalPath())[1:]
+        if siteroot is None:
+            siteroot = getUtility(ISiteRoot)
+        prefix = '/'.join(siteroot.getPhysicalPath())[1:]
 
     encoder = SchemaNameEncoder()
     return encoder.join(prefix, portal_type, schema)

From 48b0bc5aa2c40c0c4fbae2402ccccdd33e833fa8 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 21 Apr 2019 23:48:39 +0200
Subject: [PATCH 09/29] blergh

---
 plone/dexterity/fti.py | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/plone/dexterity/fti.py b/plone/dexterity/fti.py
index bcdca294..e47ebf6d 100644
--- a/plone/dexterity/fti.py
+++ b/plone/dexterity/fti.py
@@ -264,10 +264,11 @@ def lookupSchema(self):
         # Otherwise, look up a dynamic schema. This will query the model for
         # an unnamed schema if it is the first time it is looked up.
         # See schema.py
-        try:
-            siteroot = self.__parent__.__parent__
-        except AttributeError:
-            siteroot = None
+        siteroot = self
+        while True:
+            if siteroot is None or ISiteRoot.providedBy(siteroot):
+                break
+            siteroot = getattr(siteroot, '__parent__', None)
         schemaName = portalTypeToSchemaName(self.getId(), siteroot=siteroot)
         return getattr(plone.dexterity.schema.generated, schemaName)
 
@@ -558,7 +559,11 @@ def ftiModified(object, event):
         if (fti.model_source or fti.model_file) \
            and ('model_source' in mod or 'model_file' in mod or 'schema_policy' in mod):
 
-            siteroot = fti.__parent__.__parent__
+            siteroot = fti
+            while True:
+                if siteroot is None or ISiteRoot.providedBy(siteroot):
+                    break
+                siteroot = getattr(siteroot, '__parent__', None)
             schemaName = portalTypeToSchemaName(portal_type, siteroot=siteroot)
             schema = getattr(plone.dexterity.schema.generated, schemaName)
 

From 32b8912ce1a33cb0ec19e16efdcac131550c31f3 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 28 Apr 2019 23:54:11 +0200
Subject: [PATCH 10/29] kekekekekek

---
 plone/dexterity/fti.py    | 14 ++------------
 plone/dexterity/schema.py |  6 +++---
 2 files changed, 5 insertions(+), 15 deletions(-)

diff --git a/plone/dexterity/fti.py b/plone/dexterity/fti.py
index e47ebf6d..888f34ea 100644
--- a/plone/dexterity/fti.py
+++ b/plone/dexterity/fti.py
@@ -264,12 +264,7 @@ def lookupSchema(self):
         # Otherwise, look up a dynamic schema. This will query the model for
         # an unnamed schema if it is the first time it is looked up.
         # See schema.py
-        siteroot = self
-        while True:
-            if siteroot is None or ISiteRoot.providedBy(siteroot):
-                break
-            siteroot = getattr(siteroot, '__parent__', None)
-        schemaName = portalTypeToSchemaName(self.getId(), siteroot=siteroot)
+        schemaName = portalTypeToSchemaName(self.getId())
         return getattr(plone.dexterity.schema.generated, schemaName)
 
     def lookupModel(self):
@@ -559,12 +554,7 @@ def ftiModified(object, event):
         if (fti.model_source or fti.model_file) \
            and ('model_source' in mod or 'model_file' in mod or 'schema_policy' in mod):
 
-            siteroot = fti
-            while True:
-                if siteroot is None or ISiteRoot.providedBy(siteroot):
-                    break
-                siteroot = getattr(siteroot, '__parent__', None)
-            schemaName = portalTypeToSchemaName(portal_type, siteroot=siteroot)
+            schemaName = portalTypeToSchemaName(portal_type)
             schema = getattr(plone.dexterity.schema.generated, schemaName)
 
             model = fti.lookupModel()
diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py
index 38e8d41a..e0e271f9 100644
--- a/plone/dexterity/schema.py
+++ b/plone/dexterity/schema.py
@@ -16,6 +16,7 @@
 from zope.component import getAllUtilitiesRegisteredFor
 from zope.component import getUtility
 from zope.component import queryUtility
+from zope.component.hooks import getSite
 from zope.dottedname.resolve import resolve
 from zope.interface import alsoProvides
 from zope.interface import implementer
@@ -281,12 +282,11 @@ def split(self, s):
         return [self.decode(a) for a in s.split('_0_')]
 
 
-def portalTypeToSchemaName(portal_type, schema=u"", prefix=None, siteroot=None):
+def portalTypeToSchemaName(portal_type, schema=u"", prefix=None):
     """Return a canonical interface name for a generated schema interface.
     """
     if prefix is None:
-        if siteroot is None:
-            siteroot = getUtility(ISiteRoot)
+        siteroot = getSite()
         prefix = '/'.join(siteroot.getPhysicalPath())[1:]
 
     encoder = SchemaNameEncoder()

From 123cb87a771dd2e24d58b00e9a21a5e87295abc6 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sat, 22 Jun 2019 10:15:20 +0200
Subject: [PATCH 11/29] just use getUtility

---
 plone/dexterity/schema.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py
index e0e271f9..6a4afd61 100644
--- a/plone/dexterity/schema.py
+++ b/plone/dexterity/schema.py
@@ -286,7 +286,7 @@ def portalTypeToSchemaName(portal_type, schema=u"", prefix=None):
     """Return a canonical interface name for a generated schema interface.
     """
     if prefix is None:
-        siteroot = getSite()
+        siteroot = getUtility(ISiteRoot)
         prefix = '/'.join(siteroot.getPhysicalPath())[1:]
 
     encoder = SchemaNameEncoder()

From b9233a9024ee08c0a57df8be0756b1a353141342 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sat, 22 Jun 2019 15:25:42 +0200
Subject: [PATCH 12/29] special case Plone Site to we don't loop

---
 plone/dexterity/schema.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py
index 6a4afd61..39a8e896 100644
--- a/plone/dexterity/schema.py
+++ b/plone/dexterity/schema.py
@@ -286,7 +286,11 @@ def portalTypeToSchemaName(portal_type, schema=u"", prefix=None):
     """Return a canonical interface name for a generated schema interface.
     """
     if prefix is None:
-        siteroot = getUtility(ISiteRoot)
+        if portal_type == 'Plone Site':
+            fti = getUtility(IDexterityFTI, name=portal_type)
+            siteroot = fti.__parent__
+        else:
+            siteroot = getUtility(ISiteRoot)
         prefix = '/'.join(siteroot.getPhysicalPath())[1:]
 
     encoder = SchemaNameEncoder()

From e6bd7fe602efb6b3ef1f3c49ce7e22ac2f08f2b6 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Tue, 10 Dec 2019 17:31:46 +0100
Subject: [PATCH 13/29] maybe you're gonna be that one that

---
 plone/dexterity/content.py | 60 ++++++++++++++++++++------------------
 1 file changed, 31 insertions(+), 29 deletions(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index f9286d88..a5dc04c1 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -766,35 +766,37 @@ def __init__(self, id=None, **kwargs):
         CMFOrderedBTreeFolderBase.__init__(self, id)
         DexterityContent.__init__(self, id, **kwargs)
 
-    def __getattr__(self, name):
-        try:
-            return DexterityContent.__getattr__(self, name)
-        except AttributeError:
-            pass
-
-        # Be specific about the implementation we use
-        return CMFOrderedBTreeFolderBase.__getattr__(self, name)
-
-    def __delattr__(self, name):
-        try:
-            super(Container, self).__delattr__(name)
-        except AttributeError:  # delete the item instead
-            del self[name]
-
-    def __setattr__(self, name, value):
-        # If we have an existing attribute, just set it.
-        # We'll check this first, so we don't check the tree unneeded.
-        if name in self.__dict__:
-            super(Container, self).__setattr__(name, value)
-
-        # if we have a n item, set that
-        elif '_tree' in self.__dict__ and name in self:
-            del self[name]
-            self[name] = value
-
-        # else we'll set an attribute.
-        else:
-            super(Container, self).__setattr__(name, value)
+    # def __getattr__(self, name):
+    #     # if name[0] != '_':
+    #     #     print('Container.__getattr__', self, name)
+    #     try:
+    #         return DexterityContent.__getattr__(self, name)
+    #     except AttributeError:
+    #         pass
+
+    #     # Be specific about the implementation we use
+    #     return CMFOrderedBTreeFolderBase.__getattr__(self, name)
+
+    # def __delattr__(self, name):
+    #     try:
+    #         super(Container, self).__delattr__(name)
+    #     except AttributeError:  # delete the item instead
+    #         del self[name]
+
+    # def __setattr__(self, name, value):
+    #     # If we have an existing attribute, just set it.
+    #     # We'll check this first, so we don't check the tree unneeded.
+    #     if name in self.__dict__:
+    #         super(Container, self).__setattr__(name, value)
+
+    #     # if we have a n item, set that
+    #     elif '_tree' in self.__dict__ and name in self:
+    #         del self[name]
+    #         self[name] = value
+
+    #     # else we'll set an attribute.
+    #     else:
+    #         super(Container, self).__setattr__(name, value)
 
     def _delObject(self, name, *args, **kwargs):
         super(Container, self)._delObject(name, *args, **kwargs)

From 9d40507e3c6d3568ed4a33cb73eec64e64a29c8f Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Wed, 11 Dec 2019 12:47:02 +0100
Subject: [PATCH 14/29] i won't know

---
 plone/dexterity/content.py | 40 +++++++++++++++++++++++---------------
 1 file changed, 24 insertions(+), 16 deletions(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index a5dc04c1..bd17529f 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -766,22 +766,20 @@ def __init__(self, id=None, **kwargs):
         CMFOrderedBTreeFolderBase.__init__(self, id)
         DexterityContent.__init__(self, id, **kwargs)
 
-    # def __getattr__(self, name):
-    #     # if name[0] != '_':
-    #     #     print('Container.__getattr__', self, name)
-    #     try:
-    #         return DexterityContent.__getattr__(self, name)
-    #     except AttributeError:
-    #         pass
-
-    #     # Be specific about the implementation we use
-    #     return CMFOrderedBTreeFolderBase.__getattr__(self, name)
-
-    # def __delattr__(self, name):
-    #     try:
-    #         super(Container, self).__delattr__(name)
-    #     except AttributeError:  # delete the item instead
-    #         del self[name]
+    def __getattr__(self, name):
+        try:
+            return DexterityContent.__getattr__(self, name)
+        except AttributeError:
+            pass
+
+        # Be specific about the implementation we use
+        return CMFOrderedBTreeFolderBase.__getattr__(self, name)
+
+    def __delattr__(self, name):
+        try:
+            super(Container, self).__delattr__(name)
+        except AttributeError:  # delete the item instead
+            del self[name]
 
     # def __setattr__(self, name, value):
     #     # If we have an existing attribute, just set it.
@@ -798,6 +796,16 @@ def __init__(self, id=None, **kwargs):
     #     else:
     #         super(Container, self).__setattr__(name, value)
 
+    # def __setattr__(self, name, value):
+    #     # if we have an item, set that
+    #     if '_tree' in self.__dict__ and name in self:
+    #                 # If an object by the given id already exists, remove it.
+    #         self._delObject(name)
+    #         self._setObject(name, value)
+    #     # else we'll set an attribute.
+    #     else:
+    #         super(Container, self).__setattr__(name, value)
+
     def _delObject(self, name, *args, **kwargs):
         super(Container, self)._delObject(name, *args, **kwargs)
 

From c23174c2788883dcd71f1ad685074cc61541b11e Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Mon, 30 Dec 2019 22:53:05 +0100
Subject: [PATCH 15/29] don't think we need this

---
 plone/dexterity/content.py | 42 --------------------------------------
 1 file changed, 42 deletions(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index 84b77b3e..1c271121 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -776,48 +776,6 @@ def __getattr__(self, name):
         # Be specific about the implementation we use
         return CMFOrderedBTreeFolderBase.__getattr__(self, name)
 
-    def __delattr__(self, name):
-        try:
-            super(Container, self).__delattr__(name)
-        except AttributeError:  # delete the item instead
-            del self[name]
-
-    # def __setattr__(self, name, value):
-    #     # If we have an existing attribute, just set it.
-    #     # We'll check this first, so we don't check the tree unneeded.
-    #     if name in self.__dict__:
-    #         super(Container, self).__setattr__(name, value)
-
-    #     # if we have a n item, set that
-    #     elif '_tree' in self.__dict__ and name in self:
-    #         del self[name]
-    #         self[name] = value
-
-    #     # else we'll set an attribute.
-    #     else:
-    #         super(Container, self).__setattr__(name, value)
-
-    # def __setattr__(self, name, value):
-    #     # if we have an item, set that
-    #     if '_tree' in self.__dict__ and name in self:
-    #                 # If an object by the given id already exists, remove it.
-    #         self._delObject(name)
-    #         self._setObject(name, value)
-    #     # else we'll set an attribute.
-    #     else:
-    #         super(Container, self).__setattr__(name, value)
-
-    def _delObject(self, name, *args, **kwargs):
-        super(Container, self)._delObject(name, *args, **kwargs)
-
-        # This will trigger when an item was deleted from this container and
-        # attribute by the same name exists. ie obj['my_id'] and obj.my_id.
-        if name in aq_base(self).__dict__:
-            msg = (
-                "Item '{}' contained in {} was shadowed by an attribute."
-                "You might want to delete the attribute as well."
-            ).format(name, self)
-            warnings.warn(msg)
 
     @security.protected(permissions.DeleteObjects)
     def manage_delObjects(self, ids=None, REQUEST=None):

From ef2e534aee67f46b41f2a93f27fbfd82913aa5d2 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Mon, 30 Dec 2019 22:56:43 +0100
Subject: [PATCH 16/29] Special case for Plone Site in lookup_fti

---
 plone/dexterity/schema.py | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py
index 41a246b5..4fad1d5a 100644
--- a/plone/dexterity/schema.py
+++ b/plone/dexterity/schema.py
@@ -66,9 +66,19 @@ def lookup_fti(portal_type, cache=True):
                 if fti_cache is None:
                     fti_cache = dict()
                     setattr(request, FTI_CACHE_KEY, fti_cache)
-                if portal_type in fti_cache:
-                    fti = fti_cache[portal_type]
-                else:
+                fti = fti_cache.get(portal_type)
+                if fti is not None:
+                    # The first time we get the Plone Site FTI it doesn't know
+                    # everything by the looks of it. Maybe because of behavior registration
+                    # or something like that. *hrugs*
+                    if portal_type == 'Plone Site':
+                        if not getattr(request, '_dx_FTI_CACHE_seen_plonesite_fti', False):
+                            del fti_cache[portal_type]
+                            fti = None
+                        else:
+                            request._dx_FTI_CACHE_seen_plonesite_fti = True
+
+                if fti is None:
                     fti_cache[portal_type] = fti = queryUtility(
                         IDexterityFTI,
                         name=portal_type

From bded94333a38db3aac69b082fa33fda1bebdcd1a Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sat, 30 May 2020 18:28:41 +0200
Subject: [PATCH 17/29] We no longer seem to be needing this

---
 plone/dexterity/schema.py | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)

diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py
index 4fad1d5a..657dd20e 100644
--- a/plone/dexterity/schema.py
+++ b/plone/dexterity/schema.py
@@ -65,20 +65,11 @@ def lookup_fti(portal_type, cache=True):
                 fti_cache = getattr(request, FTI_CACHE_KEY, None)
                 if fti_cache is None:
                     fti_cache = dict()
+                    fti_cache['Plone Site'] = None
                     setattr(request, FTI_CACHE_KEY, fti_cache)
-                fti = fti_cache.get(portal_type)
-                if fti is not None:
-                    # The first time we get the Plone Site FTI it doesn't know
-                    # everything by the looks of it. Maybe because of behavior registration
-                    # or something like that. *hrugs*
-                    if portal_type == 'Plone Site':
-                        if not getattr(request, '_dx_FTI_CACHE_seen_plonesite_fti', False):
-                            del fti_cache[portal_type]
-                            fti = None
-                        else:
-                            request._dx_FTI_CACHE_seen_plonesite_fti = True
-
-                if fti is None:
+                if portal_type in fti_cache:
+                    fti = fti_cache[portal_type]
+                else:
                     fti_cache[portal_type] = fti = queryUtility(
                         IDexterityFTI,
                         name=portal_type

From 4c505b3652101f9c979c91d962716482ce45a55b Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 14 Jun 2020 13:27:58 +0200
Subject: [PATCH 18/29] i'm an idiot.

'mmm why would Plone Site have no fields?'
---
 plone/dexterity/schema.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py
index 657dd20e..53593bcb 100644
--- a/plone/dexterity/schema.py
+++ b/plone/dexterity/schema.py
@@ -65,11 +65,12 @@ def lookup_fti(portal_type, cache=True):
                 fti_cache = getattr(request, FTI_CACHE_KEY, None)
                 if fti_cache is None:
                     fti_cache = dict()
-                    fti_cache['Plone Site'] = None
                     setattr(request, FTI_CACHE_KEY, fti_cache)
+                fti = None
                 if portal_type in fti_cache:
                     fti = fti_cache[portal_type]
-                else:
+                
+                if fti is None:
                     fti_cache[portal_type] = fti = queryUtility(
                         IDexterityFTI,
                         name=portal_type

From 8ca5a1db73aa191390fd55d1f792e3cace2e3f44 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 19 Jul 2020 12:56:32 +0200
Subject: [PATCH 19/29] Ignore 'utilities' in DX getattr

---
 plone/dexterity/content.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index 1c271121..63eb07f0 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -72,6 +72,7 @@
     'portal_placeful_workflow',
     'portal_properties',
     'translation_service',
+    'utilities',
 )
 
 ASSIGNABLE_CACHE_KEY = '__plone_dexterity_assignable_cache__'

From 49d33b22ccbd5fae3f9aa76e9144bd0453a4910d Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sun, 19 Jul 2020 14:11:31 +0200
Subject: [PATCH 20/29] Revert "Ignore 'utilities' in DX getattr" An utilities
 attibute could be anything for non-site root objects

This reverts commit 8ca5a1db73aa191390fd55d1f792e3cace2e3f44.
---
 plone/dexterity/content.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index 63eb07f0..1c271121 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -72,7 +72,6 @@
     'portal_placeful_workflow',
     'portal_properties',
     'translation_service',
-    'utilities',
 )
 
 ASSIGNABLE_CACHE_KEY = '__plone_dexterity_assignable_cache__'

From 617111d28237322c363f46a58ad13b375133f36f Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sat, 22 Aug 2020 21:52:36 +0200
Subject: [PATCH 21/29] Don't allow shadowing items in the btree by attributes

---
 plone/dexterity/content.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index 1c271121..4fb45c1b 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -776,6 +776,10 @@ def __getattr__(self, name):
         # Be specific about the implementation we use
         return CMFOrderedBTreeFolderBase.__getattr__(self, name)
 
+    def __setattr__(self, name, obj):
+        if self._tree is not None and name in self:
+            raise ValueError("Trying to set an item via attribute.")
+        super().__setattr__(name, obj)
 
     @security.protected(permissions.DeleteObjects)
     def manage_delObjects(self, ids=None, REQUEST=None):

From 66753b30bcb37e4e1b0ee58f0c2fef57df02dc5f Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sat, 22 Aug 2020 22:11:12 +0200
Subject: [PATCH 22/29] herf

---
 plone/dexterity/content.py | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index 4fb45c1b..d8dc6c95 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -774,11 +774,15 @@ def __getattr__(self, name):
             pass
 
         # Be specific about the implementation we use
-        return CMFOrderedBTreeFolderBase.__getattr__(self, name)
+        if self._tree is not None:
+            return CMFOrderedBTreeFolderBase.__getattr__(self, name)
+
+        raise AttributeError(name)
 
     def __setattr__(self, name, obj):
         if self._tree is not None and name in self:
-            raise ValueError("Trying to set an item via attribute.")
+            del self[name]
+            self[name] = obj
         super().__setattr__(name, obj)
 
     @security.protected(permissions.DeleteObjects)

From ed3db7ee4bdb21f1b07d1cfa16be9e91117ec66a Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sat, 26 Sep 2020 18:28:39 +0200
Subject: [PATCH 23/29] make py2 tests happy

---
 plone/dexterity/content.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index d8dc6c95..4fe6567d 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -781,9 +781,10 @@ def __getattr__(self, name):
 
     def __setattr__(self, name, obj):
         if self._tree is not None and name in self:
+            # We're trying to set an item via dotted name...
             del self[name]
             self[name] = obj
-        super().__setattr__(name, obj)
+        super(Container, self).__setattr__(name, obj)
 
     @security.protected(permissions.DeleteObjects)
     def manage_delObjects(self, ids=None, REQUEST=None):

From 4ca53ebc29552051430017a9f5cecb611e8be29a Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Wed, 9 Dec 2020 21:45:48 +0100
Subject: [PATCH 24/29] Try to come up with a prefix only when there is none

---
 plone/dexterity/schema.py | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py
index 82609828..ededc346 100644
--- a/plone/dexterity/schema.py
+++ b/plone/dexterity/schema.py
@@ -328,12 +328,13 @@ def split(self, s):
 def portalTypeToSchemaName(portal_type, schema=u"", prefix=None, suffix=None):
     """Return a canonical interface name for a generated schema interface.
     """
-    if portal_type == 'Plone Site':
-        fti = getUtility(IDexterityFTI, name=portal_type)
-        siteroot = fti.__parent__
-    else:
-        siteroot = getUtility(ISiteRoot)
-    prefix = '/'.join(siteroot.getPhysicalPath())[1:]
+    if prefix is None:
+        if portal_type == 'Plone Site':
+            fti = getUtility(IDexterityFTI, name=portal_type)
+            siteroot = fti.__parent__
+        else:
+            siteroot = getUtility(ISiteRoot)
+        prefix = '/'.join(siteroot.getPhysicalPath())[1:]
     if suffix:
         prefix = '|'.join([prefix, suffix])
 

From 472d60c30bb9a5c527d3b9fae8a1c3a9f6d3b5c5 Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Sat, 12 Dec 2020 15:38:45 +0100
Subject: [PATCH 25/29] Don't try to be smart?

---
 plone/dexterity/content.py | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index 4fe6567d..dcb619f3 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -779,12 +779,14 @@ def __getattr__(self, name):
 
         raise AttributeError(name)
 
-    def __setattr__(self, name, obj):
-        if self._tree is not None and name in self:
-            # We're trying to set an item via dotted name...
-            del self[name]
-            self[name] = obj
-        super(Container, self).__setattr__(name, obj)
+    # XXX do we really want to handle this kind of cases, or is this a ID-10-T
+    # thingy?
+    # def __setattr__(self, name, obj):
+    #     if self._tree is not None and name in self:
+    #         # We're trying to set an item via dotted name...
+    #         del self[name]
+    #         self[name] = obj
+    #     super(Container, self).__setattr__(name, obj)
 
     @security.protected(permissions.DeleteObjects)
     def manage_delObjects(self, ids=None, REQUEST=None):

From a375f3c7eb36e46dc278de182fbd411e149181ef Mon Sep 17 00:00:00 2001
From: Roel Bruggink <roel@jaroel.nl>
Date: Thu, 17 Dec 2020 14:52:18 +0100
Subject: [PATCH 26/29] Move changelog entry to news/85.bugfix

---
 CHANGES.rst    | 5 +----
 news/85.bugfix | 3 +++
 2 files changed, 4 insertions(+), 4 deletions(-)
 create mode 100644 news/85.bugfix

diff --git a/CHANGES.rst b/CHANGES.rst
index 9e3ea6e2..774f8c5b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -23,7 +23,7 @@ Bug fixes:
 
 
 - Fixes test to work clean with zope.interface.
-  Interfaces are hashed based on just their name and module. 
+  Interfaces are hashed based on just their name and module.
   So every one of these local `IBlank` interfaces will hash the same way, and be treated the same for purposes of zope.interface's `_dependents`.
   Thus in tests mock interfaces must not be used under the same name in the same module.
   [jensens] (#135)
@@ -179,9 +179,6 @@ New features:
 
 Bug fixes:
 
-- Fix ft._updateProperty so it doesn't break when receiving an empty value.
-  This happens when an DX FTI is part of a Generic Setup baseline import.
-  [jaroel]
 - Other Python 3 compatibility fixes
   [ale-rt, pbauer, jensens]
 
diff --git a/news/85.bugfix b/news/85.bugfix
new file mode 100644
index 00000000..e1e0943d
--- /dev/null
+++ b/news/85.bugfix
@@ -0,0 +1,3 @@
+Fix ft._updateProperty so it doesn't break when receiving an empty value.
+This happens when an DX FTI is part of a Generic Setup baseline import.
+[jaroel]

From 805c92a9d4df055b8a83d01e294ac00a05de4308 Mon Sep 17 00:00:00 2001
From: ale-rt <alessandro.pisa@gmail.com>
Date: Wed, 10 Feb 2021 15:22:19 +0100
Subject: [PATCH 27/29] Remove commented code

---
 plone/dexterity/content.py | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/plone/dexterity/content.py b/plone/dexterity/content.py
index dcb619f3..433c1edd 100644
--- a/plone/dexterity/content.py
+++ b/plone/dexterity/content.py
@@ -779,15 +779,6 @@ def __getattr__(self, name):
 
         raise AttributeError(name)
 
-    # XXX do we really want to handle this kind of cases, or is this a ID-10-T
-    # thingy?
-    # def __setattr__(self, name, obj):
-    #     if self._tree is not None and name in self:
-    #         # We're trying to set an item via dotted name...
-    #         del self[name]
-    #         self[name] = obj
-    #     super(Container, self).__setattr__(name, obj)
-
     @security.protected(permissions.DeleteObjects)
     def manage_delObjects(self, ids=None, REQUEST=None):
         """Delete the contained objects with the specified ids.

From d5a68965255a0bce3770ced6a725fcdf3b798a30 Mon Sep 17 00:00:00 2001
From: Maurits van Rees <maurits@vanrees.org>
Date: Wed, 18 Aug 2021 14:52:47 +0200
Subject: [PATCH 28/29] Use queryUtility to get Plone Site dexterity FTI in
 portalTypeToSchemaName.

That makes this code usable when Plone Site is not (yet) a dexterity item.
I doubt that this code path is ever reached when the Plone Site FTI is not dexterity, but let's be careful.
---
 plone/dexterity/schema.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/plone/dexterity/schema.py b/plone/dexterity/schema.py
index 22e6b18e..7a0ef351 100644
--- a/plone/dexterity/schema.py
+++ b/plone/dexterity/schema.py
@@ -324,10 +324,12 @@ def split(self, s):
 def portalTypeToSchemaName(portal_type, schema=u"", prefix=None, suffix=None):
     """Return a canonical interface name for a generated schema interface."""
     if prefix is None:
+        siteroot = None
         if portal_type == "Plone Site":
-            fti = getUtility(IDexterityFTI, name=portal_type)
-            siteroot = fti.__parent__
-        else:
+            fti = queryUtility(IDexterityFTI, name=portal_type)
+            if fti is not None:
+                siteroot = fti.__parent__
+        if siteroot is None:
             siteroot = getUtility(ISiteRoot)
         prefix = "/".join(siteroot.getPhysicalPath())[1:]
     if suffix:

From 1fbd4947c309aa8200b33547ea8667e7a62f115e Mon Sep 17 00:00:00 2001
From: Maurits van Rees <maurits@vanrees.org>
Date: Wed, 18 Aug 2021 14:54:27 +0200
Subject: [PATCH 29/29] More info in news snippet.

---
 news/85.bugfix | 1 +
 1 file changed, 1 insertion(+)

diff --git a/news/85.bugfix b/news/85.bugfix
index e1e0943d..e690b818 100644
--- a/news/85.bugfix
+++ b/news/85.bugfix
@@ -1,3 +1,4 @@
 Fix ft._updateProperty so it doesn't break when receiving an empty value.
 This happens when an DX FTI is part of a Generic Setup baseline import.
+Update more code to work when the Plone Site is a dexterity item.
 [jaroel]