diff --git a/product/ERP5Type/__init__.py b/product/ERP5Type/__init__.py
index f5623e4af4a0ebd710ecbcf5fe6575d1a895f7de..c85e218f385b3dbb5b42ea9abad43954bb05b751 100644
--- a/product/ERP5Type/__init__.py
+++ b/product/ERP5Type/__init__.py
@@ -162,6 +162,9 @@ def initialize( context ):
   Timeout.publisher_timeout = getattr(erp5_conf, 'publisher_timeout', None)
   Timeout.activity_timeout = getattr(erp5_conf, 'activity_timeout', None)
 
+  initialized.append(True)
+
+initialized = []
 from AccessControl.SecurityInfo import allow_module
 from AccessControl.SecurityInfo import ModuleSecurityInfo
 
diff --git a/product/ERP5Type/dynamic/component_package.py b/product/ERP5Type/dynamic/component_package.py
index 9352c71181454df9d637c784a56077b1f89dc52b..0988031dfde1aa4fc4d280dce4441882fa42236c 100644
--- a/product/ERP5Type/dynamic/component_package.py
+++ b/product/ERP5Type/dynamic/component_package.py
@@ -34,6 +34,7 @@ import sys
 import imp
 import collections
 
+from Products.ERP5Type import initialized as Products_ERP5Type_initialized
 from Products.ERP5.ERP5Site import getSite
 from . import aq_method_lock
 from types import ModuleType
@@ -80,6 +81,10 @@ class ComponentDynamicPackage(ModuleType):
     self._portal_type = portal_type
     self.__version_suffix_len = len('_version')
     self.__fullname_source_code_dict = {}
+    # A mapping of legacy documents (Products.*.Document.{name}) to redirect to the
+    # new component name (erp5.component.document.{name}). We remember this to be
+    # able to clean up theses modules on reset.
+    self.__legacy_document_mapping = {}
 
     # Add this module to sys.path for future imports
     sys.modules[namespace] = self
@@ -109,11 +114,20 @@ class ComponentDynamicPackage(ModuleType):
     perhaps because the Finder of another Component Package could do it or
     because this is a filesystem module...
     """
-    # Ignore imports with a path which are filesystem-only and any
-    # absolute imports which does not start with this package prefix,
-    # None there means that "normal" sys.path will be used
-    if path or not fullname.startswith(self._namespace_prefix):
-      return None
+
+    # TODO can't we do better than this Products_ERP5Type_initialized ? (register this loader later ?)
+    if fullname.startswith('Products.') and Products_ERP5Type_initialized:
+      # Dynamically handle Products.*.Document namespace for compatibility, when an import
+      # for Products.*.Document.X is requested and there's a document component X, use the component instead.
+      names = fullname.split('.')
+      if not (len(names) == 4 and names[2] == 'Document'):
+        return None
+    else:
+      # Ignore imports with a path which are filesystem-only and any
+      # absolute imports which does not start with this package prefix,
+      # None there means that "normal" sys.path will be used
+      if path or not fullname.startswith(self._namespace_prefix):
+        return None
 
     import_lock_held = True
     try:
@@ -127,7 +141,8 @@ class ComponentDynamicPackage(ModuleType):
       # __import__ will first try a relative import, for example
       # erp5.component.XXX.YYY.ZZZ where erp5.component.XXX.YYY is the current
       # Component where an import is done
-      name = fullname[len(self._namespace_prefix):]
+      name = fullname[len(self._namespace_prefix):] if fullname.startswith(self._namespace_prefix) else ''
+
       # name=VERSION_version.REFERENCE
       if '.' in name:
         try:
@@ -174,7 +189,29 @@ class ComponentDynamicPackage(ModuleType):
                                                                           'validated'):
             break
         else:
-          return None
+          # maybe a legacy import in the form Products.*.Document.{name}
+          # If we have a document component which was created to replace this, use the
+          # component instead.
+          names = fullname.split('.')
+          if not (names[0] == 'Products' and len(names) == 4 and names[2] == 'Document'):
+            return None
+          name = names[-1]
+          for version in site.getVersionPriorityNameList():
+            id_ = "%s.%s.%s" % (self._id_prefix, version, name)
+            component = getattr(component_tool, id_, None)
+            if component is not None and component.getValidationState() in ('modified',
+                                                                            'validated'):
+              # Products.ERP5Type.Document is a special case here, because historically
+              # all documents were also dynamically loaded on this module.
+              if names[1] == 'ERP5Type' or component.getSourceReference() == fullname:
+                self.__legacy_document_mapping[fullname] = 'erp5.component.document.%s' % name
+                # TODO maybe it would be more performant to use the versionned module here, since we already know it
+                # but it seems erp5.component.document.X_version.Y and erp5.component.document.Y are not the same module
+                # and modules are loaded twice (a assertIs() test is failing)
+                # self.__legacy_document_mapping[fullname] = 'erp5.component.document.%s_version.%s' % (version, name)
+                break
+          else:
+            return None
 
       return self
 
@@ -218,6 +255,16 @@ class ComponentDynamicPackage(ModuleType):
     As per PEP-302, raise an ImportError if the Loader could not load the
     module for any reason...
     """
+    if fullname in self.__legacy_document_mapping:
+      module = self.__load_module(self.__legacy_document_mapping[fullname])
+      # TODO: do we need this lock ? is this deadlock-safe ?
+      imp.acquire_lock()
+      try:
+        sys.modules[fullname] = module
+      finally:
+        imp.release_lock()
+      return module
+
     site = getSite()
     name = fullname[len(self._namespace_prefix):]
 
@@ -428,6 +475,10 @@ class ComponentDynamicPackage(ModuleType):
       del sys.modules[module_name]
 
       delattr(package, name)
+    for module_name in list(self.__legacy_document_mapping):
+      sys.modules.pop(module_name, None)
+    self.__legacy_document_mapping.clear()
+
 
 class ToolComponentDynamicPackage(ComponentDynamicPackage):
   def reset(self, *args, **kw):
diff --git a/product/ERP5Type/tests/testDynamicClassGeneration.py b/product/ERP5Type/tests/testDynamicClassGeneration.py
index b8bf9b791f252bbb94f6441fea2e9e5bc93425f7..8cfcbab07732d8186a9592ee1e32cd5af5511bbf 100644
--- a/product/ERP5Type/tests/testDynamicClassGeneration.py
+++ b/product/ERP5Type/tests/testDynamicClassGeneration.py
@@ -2902,6 +2902,103 @@ class TestGC(XMLObject):
        'gc: collectable <Implements 0x%x>\n' % Implements_id],
       sorted(found_line_list))
 
+  def testProductsERP5TypeDocumentCompatibility(self):
+    """Check that document class also exist in Products.ERP5Type.Document namespace
+    for compatibility.
+
+    We also check that this module is properly reloaded when a document component
+    is modified.
+    """
+    self.failIfModuleImportable('TestProductsERP5TypeDocumentCompatibility')
+
+    test_component = self._newComponent(
+        'TestProductsERP5TypeDocumentCompatibility',
+        """\
+from Products.ERP5Type.Base import Base
+class TestProductsERP5TypeDocumentCompatibility(Base):
+  portal_type = 'Test ProductsERP5TypeDocument Compatibility'
+  generation = 1
+"""
+    )
+    test_component.validate()
+    self.tic()
+
+    self.assertModuleImportable('TestProductsERP5TypeDocumentCompatibility')
+
+    from Products.ERP5Type.Document.TestProductsERP5TypeDocumentCompatibility import TestProductsERP5TypeDocumentCompatibility  # pylint:disable=import-error,no-name-in-module
+    self.assertEqual(TestProductsERP5TypeDocumentCompatibility.generation, 1)
+
+    test_component.setTextContent(
+        """\
+from Products.ERP5Type.Base import Base
+class TestProductsERP5TypeDocumentCompatibility(Base):
+  portal_type = 'Test ProductsERP5TypeDocument Compatibility'
+  generation = 2
+""")
+    self.tic()
+    self.assertModuleImportable('TestProductsERP5TypeDocumentCompatibility')
+    from Products.ERP5Type.Document.TestProductsERP5TypeDocumentCompatibility import TestProductsERP5TypeDocumentCompatibility  # pylint:disable=import-error,no-name-in-module
+    self.assertEqual(TestProductsERP5TypeDocumentCompatibility.generation, 2)
+
+  def testProductsERP5DocumentCompatibility(self):
+    """Check that document class also exist in its original namespace (source_reference)
+
+    Document Component that were moved from file system Products/*/Document needs
+    to be still importable from their initial location, as there might be classes
+    in the database of these instances.
+    """
+    self.failIfModuleImportable('TestProductsERP5DocumentCompatibility')
+
+    test_component = self._newComponent(
+        'TestProductsERP5DocumentCompatibility',
+        """\
+from Products.ERP5Type.Base import Base
+class TestProductsERP5DocumentCompatibility(Base):
+  portal_type = 'Test ProductsERP5Document Compatibility'
+  test_attribute = 'TestProductsERP5DocumentCompatibility'
+"""
+    )
+    test_component.setSourceReference('Products.ERP5.Document.TestProductsERP5DocumentCompatibility')
+    test_component.validate()
+    self.tic()
+
+    self.assertModuleImportable('TestProductsERP5DocumentCompatibility')
+
+    from Products.ERP5.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility  # pylint:disable=import-error,no-name-in-module
+    self.assertEqual(TestProductsERP5DocumentCompatibility.test_attribute, 'TestProductsERP5DocumentCompatibility')
+
+    # this also exist in Products.ERP5Type.Document
+    from Products.ERP5Type.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility as TestProductsERP5DocumentCompatibility_from_ProductsERP5Type  # pylint:disable=import-error,no-name-in-module
+    self.assertIs(TestProductsERP5DocumentCompatibility_from_ProductsERP5Type, TestProductsERP5DocumentCompatibility)
+
+    # another component can also import the migrated component from its original name
+    test_component_importing = self._newComponent(
+        'TestComponentImporting',
+        """\
+from Products.ERP5.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility
+class TestComponentImporting(TestProductsERP5DocumentCompatibility):
+  pass
+"""
+    )
+    test_component_importing.validate()
+    self.tic()
+
+    self.assertModuleImportable('TestComponentImporting')
+    from erp5.component.document.TestComponentImporting import TestComponentImporting  # pylint:disable=import-error,no-name-in-module
+
+    from Products.ERP5.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility  # pylint:disable=import-error,no-name-in-module
+    self.assertTrue(issubclass(TestComponentImporting, TestProductsERP5DocumentCompatibility))
+
+    test_component.invalidate()
+    self.tic()
+
+    # after invalidating the component, the legacy modules are no longer importable
+    with self.assertRaises(ImportError):
+      from Products.ERP5.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility  # pylint:disable=import-error,no-name-in-module
+    with self.assertRaises(ImportError):
+      from Products.ERP5Type.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility  # pylint:disable=import-error,no-name-in-module
+
+
 from Products.ERP5Type.Core.TestComponent import TestComponent
 
 class TestZodbTestComponent(_TestZodbComponent):
diff --git a/product/ERP5Type/tests/testERP5Type.py b/product/ERP5Type/tests/testERP5Type.py
index c5d1fc869d85d306d0004e02de3e0f60211ab758..d5495fe9f3daf8c7f005ecdef9ef4daee57feb8c 100644
--- a/product/ERP5Type/tests/testERP5Type.py
+++ b/product/ERP5Type/tests/testERP5Type.py
@@ -51,7 +51,7 @@ from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
 from AccessControl.SecurityManagement import newSecurityManager
 from AccessControl import getSecurityManager
 from AccessControl import Unauthorized
-from AccessControl.ZopeGuards import guarded_getattr, guarded_hasattr
+from AccessControl.ZopeGuards import guarded_getattr, guarded_hasattr, guarded_import
 from Products.ERP5Type.tests.utils import createZODBPythonScript
 from Products.ERP5Type.tests.utils import removeZODBPythonScript
 from Products.ERP5Type import Permissions
@@ -261,6 +261,15 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
       self.assertEqual(b.isTempObject(), 1)
       self.assertEqual(b.getId(), str(2))
 
+      # Products.ERP5Type.Document.newTempBase is another (not recommended) way
+      # of creating temp objects
+      import Products.ERP5Type.Document
+      o = Products.ERP5Type.Document.newTempBase(self.portal, 'id')
+      self.assertEqual(o.getId(), 'id')
+      self.assertEqual(o.getPortalType(), 'Base Object')
+      self.assertTrue(o.isTempObject())
+      self.assertTrue(guarded_import("Products.ERP5Type.Document", fromlist=["newTempBase"]))
+
       # Test newContent with the temp_object parameter and where a non-temp_object would not be allowed
       o = portal.person_module.newContent(portal_type="Organisation", temp_object=1)
       o.setTitle('bar')
@@ -3320,6 +3329,31 @@ return [
           '<Organisation at /%s/organisation_module/organisation_id>' % self.portal.getId(),
           repr(document))
 
+    def test_products_document_legacy(self):
+      """check document classes defined in Products/*/Document/*.py
+      """
+      # note: this assertion below checks Alarm is really a legacy document class.
+      # if one day Alarm is moved to component, then this test needs to be updated
+      # with another module that lives on the file system.
+      import Products.ERP5.Document.Alarm
+      self.assertIn('product/ERP5/Document/Alarm.py', Products.ERP5.Document.Alarm.__file__)
+
+      # document classes are also dynamically loaded in Products.ERP5Type.Document module
+      from Products.ERP5Type.Document.Alarm import Alarm as Alarm_from_ERP5Type  # pylint:disable=import-error,no-name-in-module
+      self.assertIs(Alarm_from_ERP5Type, Products.ERP5.Document.Alarm.Alarm)
+
+      # a new temp constructor is created
+      from Products.ERP5Type.Document import newTempAlarm  # pylint:disable=import-error,no-name-in-module
+      self.assertIn(Alarm_from_ERP5Type, newTempAlarm(self.portal, '').__class__.mro())
+
+      # temp constructors are deprecated, they issue a warning when called
+      with mock.patch('Products.ERP5Type.Utils.warnings.warn') as warn:
+          newTempAlarm(self.portal, '')
+      warn.assert_called_with(
+          'newTemp*(self, ID) will be removed, use self.newContent(temp_object=True, id=ID, portal_type=...)',
+          DeprecationWarning, 2)
+
+
 class TestAccessControl(ERP5TypeTestCase):
   # Isolate test in a dedicaced class in order not to break other tests
   # when this one fails.