diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py
index 14065164d186fd22562ffc264c245861a0d11a4d..6f911bb8a0108cc7342140c24bf90cbf41a4cf6a 100755
--- a/product/ERP5/Document/BusinessTemplate.py
+++ b/product/ERP5/Document/BusinessTemplate.py
@@ -33,15 +33,25 @@ from AccessControl import ClassSecurityInfo
 from Products.CMFCore.utils import getToolByName
 from Products.CMFCore.WorkflowCore import WorkflowMethod
 from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
-from Products.ERP5Type.Utils import readLocalPropertySheet, writeLocalPropertySheet, importLocalPropertySheet, removeLocalPropertySheet
-from Products.ERP5Type.Utils import readLocalExtension, writeLocalExtension, removeLocalExtension
-from Products.ERP5Type.Utils import readLocalTest, writeLocalTest, removeLocalTest
-from Products.ERP5Type.Utils import readLocalDocument, writeLocalDocument, importLocalDocument, removeLocalDocument
+from Products.ERP5Type.Utils import readLocalPropertySheet, \
+                                    writeLocalPropertySheet, \
+                                    importLocalPropertySheet, \
+                                    removeLocalPropertySheet
+from Products.ERP5Type.Utils import readLocalExtension, writeLocalExtension, \
+                                    removeLocalExtension
+from Products.ERP5Type.Utils import readLocalTest, writeLocalTest, \
+                                    removeLocalTest
+from Products.ERP5Type.Utils import readLocalDocument, writeLocalDocument, \
+                                    importLocalDocument, removeLocalDocument
 from Products.ERP5Type.XMLObject import XMLObject
 import cStringIO
 import fnmatch
 import re
 from Products.ERP5Type.Cache import clearCache
+from DateTime import DateTime
+from OFS import XMLExportImport
+from cStringIO import StringIO
+import difflib
 
 from zLOG import LOG
 
@@ -72,6 +82,9 @@ class BaseTemplateItem(Implicit, Persistent):
     # trash is quite similar to uninstall.
     return self.uninstall(context, new_item=new_item, trash=1, **kw)
 
+  def diff(self, **kw):
+    return ''
+
 class ObjectTemplateItem(BaseTemplateItem):
   """
     This class is used for generic objects and as a subclass.
@@ -112,7 +125,9 @@ class ObjectTemplateItem(BaseTemplateItem):
       container_path = relative_url.split('/')[0:-1]
       object_id = relative_url.split('/')[-1]
       container = portal.unrestrictedTraverse(container_path)
-      #LOG('Installing' , 0, '%s in %s with %s' % (self.id, container.getPhysicalPath(), self.export_string))
+#       LOG('Installing' , 0, 
+#           '%s in %s with %s' % \
+#               (self.id, container.getPhysicalPath(), self.export_string))
       container_ids = container.objectIds()
       if object_id in container_ids:    # Object already exists
         self._backupObject(container, object_id)
@@ -125,8 +140,10 @@ class ObjectTemplateItem(BaseTemplateItem):
       object.manage_afterClone(object)
       object.wl_clearLocks()
       if object.meta_type in ('Z SQL Method',):
-        # It is necessary to make sure that the sql connection in this method is valid.
-        sql_connection_list = portal.objectIds(spec=('Z MySQL Database Connection',))
+        # It is necessary to make sure that the sql connection 
+        # in this method is valid.
+        sql_connection_list = portal.objectIds(
+                                 spec=('Z MySQL Database Connection',))
         if object.connection_id not in sql_connection_list:
           object.connection_id = sql_connection_list[0]
 
@@ -145,9 +162,107 @@ class ObjectTemplateItem(BaseTemplateItem):
             container.manage_delObjects([object_id])
       except:
         pass
-
     BaseTemplateItem.uninstall(self, context, **kw)
 
+  def _compareObjects(self, object1, object2, btsave_object_included=0):
+    """
+      Execute a diff between 2 objects, 
+      and return a string diff.
+    """
+    xml_dict = {
+      str(object1): None,
+      str(object2): None,
+    }
+    # Generate XML
+    for object in (object1, object2):
+      string_io = StringIO()
+      XMLExportImport.exportXML(object._p_jar, object._p_oid, string_io)
+      object_xml = string_io.getvalue()
+      string_io.close()
+      object_xml_lines = object_xml.splitlines(1)
+      xml_dict[str(object)] = object_xml_lines
+    # Make diff between XML
+    diff_instance = difflib.Differ()
+    diff_list = list(diff_instance.compare(xml_dict[str(object1)],
+                                           xml_dict[str(object2)]))
+    diff_list = [x for x in diff_list if x[0] != ' ']
+    # Dirty patch to remove useless diff message (id different)
+    if btsave_object_included==1:
+      if len(diff_list) == 3:
+        if '_btsave_' in diff_list[1]:
+          diff_list = []
+    # Return string 
+    result = '%s' % ''.join(diff_list)
+    return result
+
+  def diff(self, archive_variable='_archive', max_deep=0, verbose=0):
+    """
+      Show all __btsave__ created, and make a diff between
+      the current and the old version.
+    """
+    result = ''
+    portal = self.getPortalObject()
+    # Browse all items stored
+    for relative_url, object in getattr(self, archive_variable).items():
+      object = portal.unrestrictedTraverse(relative_url)
+      container_path = relative_url.split('/')[0:-1]
+      object_id = relative_url.split('/')[-1]
+      container = portal.unrestrictedTraverse(container_path)
+      container_ids = container.objectIds()
+      # Search _btsave_ object
+      compare_object_couple_list = []
+      btsave_id_list = []
+      n = 1
+      new_object_id = '%s_btsave_%s' % (object_id, n)
+      while new_object_id in container_ids:
+        # Found _btsave_ object
+        btsave_id_list.append(new_object_id)
+        compare_object_couple_list.append(
+              (object, portal.unrestrictedTraverse(
+                                  container_path+[new_object_id])))
+        n += 1
+        new_object_id = '%s_btsave_%s' % (object_id, n)
+      if n == 1:
+        result += "$$$ Added: %s $$$\n" % \
+              ('/'.join(container_path+[object_id]))
+        result += '%s\n' % ('-'*80)
+      # Find all objects to compare
+      deep = 0
+      while deep != max_deep:
+        new_compare_object_couple_list = []
+        for new_object, btsave_object in compare_object_couple_list:
+          btsave_object_content_id_list = btsave_object.objectIds()
+          for new_object_content_id in new_object.objectIds():
+            if new_object_content_id in btsave_object_content_id_list:
+              new_compare_object_couple_list.append(
+                  (getattr(new_object, new_object_content_id),
+                   getattr(btsave_object, new_object_content_id)))
+              btsave_object_content_id_list.remove(new_object_content_id)
+            else:
+              result += "$$$ Added: %s/%s $$$\n" % \
+                    (new_object.absolute_url(), new_object_content_id)
+              result += '%s\n' % ('-'*80)
+          for btsave_object_id in btsave_object_content_id_list:
+            result += "$$$ Removed: %s/%s $$$\n" % \
+                  (btsave_object.absolute_url(), btsave_object_id)
+            result += '%s\n' % ('-'*80)
+        if new_compare_object_couple_list == []:
+          deep = max_deep
+        else:
+          compare_object_couple_list = new_compare_object_couple_list
+          deep += 1
+      # Now, we can compare all objects requested
+      for new_object, btsave_object in compare_object_couple_list:
+        tmp_diff = self._compareObjects(new_object, btsave_object,
+                                        btsave_object_included=1)
+        if tmp_diff != '':
+          result += "$$$ %s $$$\n$$$ %s $$$\n" % \
+              (new_object.absolute_url(),
+               btsave_object.absolute_url())
+          if verbose == 1:
+            result += tmp_diff  
+          result += '%s\n' % ('-'*80)
+    return result
 
 class PathTemplateItem(ObjectTemplateItem):
   """
@@ -308,12 +423,14 @@ class SkinTemplateItem(ObjectTemplateItem):
 
     ObjectTemplateItem.uninstall(self, context, **kw)
 
+  def diff(self, max_deep=1, **kw):
+    return ObjectTemplateItem.diff(self, max_deep=max_deep, **kw)
 
-class WorkflowTemplateItem(ObjectTemplateItem):
 
-  def __init__(self, id_list, **kw):
-    ObjectTemplateItem.__init__(self, id_list, tool_id='portal_workflow', **kw)
+class WorkflowTemplateItem(ObjectTemplateItem):
 
+  def __init__(self, id_list, tool_id='portal_workflow', **kw):
+    return ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
 
 class PortalTypeTemplateItem(ObjectTemplateItem):
 
@@ -387,7 +504,6 @@ class PortalTypeTemplateItem(ObjectTemplateItem):
       chain_dict['chain_%s' % portal_type] = self._workflow_chain_archive[portal_type]
     context.portal_workflow.manage_changeWorkflows(default_chain,props=chain_dict)
 
-
 class CatalogMethodTemplateItem(ObjectTemplateItem):
 
   def __init__(self, id_list, tool_id='portal_catalog', **kw):
@@ -550,7 +666,6 @@ class CatalogMethodTemplateItem(ObjectTemplateItem):
 
     ObjectTemplateItem.uninstall(self, context, **kw)
 
-
 class ActionTemplateItem(BaseTemplateItem):
 
   def _splitPath(self, path):
@@ -668,6 +783,8 @@ class SitePropertyTemplateItem(BaseTemplateItem):
 
 class ModuleTemplateItem(BaseTemplateItem):
 
+  def diff(self, max_deep=1, **kw):
+    return ''
   def build(self, context, **kw):
     BaseTemplateItem.build(self, context, **kw)
     p = context.getPortalObject()
@@ -720,93 +837,55 @@ class ModuleTemplateItem(BaseTemplateItem):
     pass
 
 class DocumentTemplateItem(BaseTemplateItem):
+  local_file_reader_name = 'readLocalDocument'
+  local_file_writer_name = 'writeLocalDocument'
+  local_file_importer_name = 'importLocalDocument'
+  local_file_remover_name = 'removeLocalDocument'
 
   def build(self, context, **kw):
     BaseTemplateItem.build(self, context, **kw)
     for id in self._archive.keys():
-      self._archive[id] = readLocalDocument(id)
-
-  def install(self, context, **kw):
-    BaseTemplateItem.install(self, context, **kw)
-    for id,text in self._archive.items():
-      writeLocalDocument(id, text, create=1) # This raises an exception if the file exists.
-      importLocalDocument(id)
-
-  def uninstall(self, context, **kw):
-    for id in self._archive.keys():
-      try:
-        removeLocalDocument(id)
-      except OSError:
-        pass
-    BaseTemplateItem.uninstall(self, context, **kw)
-
-
-class PropertySheetTemplateItem(BaseTemplateItem):
-
-  def build(self, context, **kw):
-    BaseTemplateItem.build(self, context, **kw)
-    for id in self._archive.keys():
-      self._archive[id] = readLocalPropertySheet(id)
+      self._archive[id] = globals()[self.local_file_reader_name](id)
 
   def install(self, context, **kw):
     BaseTemplateItem.install(self, context, **kw)
     for id,text in self._archive.items():
-      writeLocalPropertySheet(id, text, create=1) # This raises an exception if the file exists.
-      importLocalPropertySheet(id)
+      # This raises an exception if the file exists.
+      globals()[self.local_file_writer_name](id, text, create=1)
+      if self.local_file_importer_name is not None:
+        globals()[self.local_file_importer_name](id)
 
   def uninstall(self, context, **kw):
     for id in self._archive.keys():
       try:
-        removeLocalPropertySheet(id)
+        globals()[self.local_file_importer_name](id)
       except OSError:
         pass
     BaseTemplateItem.uninstall(self, context, **kw)
 
-
-class ExtensionTemplateItem(BaseTemplateItem):
-
-  def build(self, context, **kw):
-    BaseTemplateItem.build(self, context, **kw)
-    for id in self._archive.keys():
-      self._archive[id] = readLocalExtension(id)
-
-  def install(self, context, **kw):
-    BaseTemplateItem.install(self, context, **kw)
-    for id,text in self._archive.items():
-      writeLocalExtension(id, text, create=1) # This raises an exception if the file exists.
-      importLocalPropertySheet(id)
-
-  def uninstall(self, context, **kw):
-    for id in self._archive.keys():
-      try:
-        removeLocalExtension(id)
-      except OSError:
-        pass
-    BaseTemplateItem.uninstall(self, context, **kw)
-
-class TestTemplateItem(BaseTemplateItem):
-
-  def build(self, context, **kw):
-    BaseTemplateItem.build(self, context, **kw)
-    for id in self._archive.keys():
-      self._archive[id] = readLocalTest(id)
-
-  def install(self, context, **kw):
-    BaseTemplateItem.install(self, context, **kw)
-    for id,text in self._archive.items():
-      writeLocalTest(id, text, create=1) # This raises an exception if the file exists.
-
-  def uninstall(self, context, **kw):
-    for id in self._archive.keys():
-      try:
-        removeLocalTest(id)
-      except OSError:
-        pass
-    BaseTemplateItem.uninstall(self, context, **kw)
-
-
-class ProductTemplateItem(BaseTemplateItem): pass # Not implemented yet
-
+class PropertySheetTemplateItem(DocumentTemplateItem):
+  local_file_reader_name = 'readLocalPropertySheet'
+  local_file_writer_name = 'writeLocalPropertySheet'
+  local_file_importer_name = 'importLocalPropertySheet'
+  local_file_remover_name = 'removeLocalPropertySheet'
+
+class ExtensionTemplateItem(DocumentTemplateItem):
+  local_file_reader_name = 'readLocalExtension'
+  local_file_writer_name = 'writeLocalExtension'
+  # XXX is this method a error or ?
+  local_file_importer_name = 'importLocalPropertySheet'
+  local_file_remover_name = 'removeLocalExtension'
+
+class TestTemplateItem(DocumentTemplateItem):
+  local_file_reader_name = 'readLocalTest'
+  local_file_writer_name = 'writeLocalTest'
+  # XXX is this a error ?
+  local_file_importer_name = None
+  local_file_remover_name = 'removeLocalTest'
+
+class ProductTemplateItem(BaseTemplateItem):
+  # XXX Not implemented yet
+  pass
 
 class RoleTemplateItem(BaseTemplateItem):
 
@@ -844,7 +923,6 @@ class RoleTemplateItem(BaseTemplateItem):
         del roles[role]
     p.__ac_roles__ = tuple(roles.keys())
 
-
 class CatalogResultKeyTemplateItem(BaseTemplateItem):
 
   def install(self, context, **kw):
@@ -958,7 +1036,6 @@ class CatalogResultTableTemplateItem(BaseTemplateItem):
     catalog.sql_search_tables = sql_search_tables
     BaseTemplateItem.uninstall(self, context, **kw)
 
-
 class MessageTranslationTemplateItem(BaseTemplateItem):
 
   def build(self, context, **kw):
@@ -966,11 +1043,14 @@ class MessageTranslationTemplateItem(BaseTemplateItem):
     localizer = context.getPortalObject().Localizer
     for lang in self._archive.keys():
       self._archive[lang] = PersistentMapping()
-      # Export only erp5_ui at the moment. This is safer against information leak.
+      # Export only erp5_ui at the moment. 
+      # This is safer against information leak.
       for catalog in ('erp5_ui', ):
-        LOG('MessageTranslationTemplateItem build', 0, 'catalog = %r' % (catalog,))
+        LOG('MessageTranslationTemplateItem build', 0, 
+            'catalog = %r' % (catalog,))
         mc = localizer._getOb(catalog)
-        LOG('MessageTranslationTemplateItem build', 0, 'mc = %r' % (mc,))
+        LOG('MessageTranslationTemplateItem build', 0, 
+            'mc = %r' % (mc,))
         self._archive[lang][catalog] = mc.manage_export(lang)
 
   def install(self, context, **kw):
@@ -986,7 +1066,6 @@ class MessageTranslationTemplateItem(BaseTemplateItem):
           mc.manage_addLanguage(lang)
         mc.manage_import(lang, po)
 
-
 class BusinessTemplate(XMLObject):
     """
     A business template allows to construct ERP5 modules
@@ -1389,14 +1468,16 @@ class BusinessTemplate(XMLObject):
       """
       return self._getOrderedList('template_message_translation')
 
-    def diff(self):
+    def diff(self, verbose=0):
       """
-        Return a 'diff' of the business template compared to the 
+        Return a 'diff' of the business template compared to the
         __btsave__ version.
       """
+      diff_message = '%s : %s\n%s\n' % (self.getPath(), DateTime(),
+                                        '='*80)
       # Diff everything
-#       for item_name in self._item_name_list[::-1]:
-#         item = getattr(self, item_name)
-#         if item is not None:
-#           item.uninstall(local_configuration)
-      pass
+      for item_name in self._item_name_list:
+        item = getattr(self, item_name)
+        if item is not None:
+          diff_message += item.diff(verbose=verbose)
+      return diff_message