Add new API of id tool:

- add the id generators generic
- add the zodb continuous increasing id generator
- add the sql non continuous increasing id generator
- change id tool for the id generators and the compatiblity with the old api
- change the business template to not modify the generator dictionaries
  during the export and the install of bt
- change the test of id tool



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@34543 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 874be360
...@@ -55,7 +55,7 @@ from Products.ERP5Type.Utils import readLocalTest, \ ...@@ -55,7 +55,7 @@ from Products.ERP5Type.Utils import readLocalTest, \
writeLocalTest, \ writeLocalTest, \
removeLocalTest removeLocalTest
from Products.ERP5Type.Utils import convertToUpperCase from Products.ERP5Type.Utils import convertToUpperCase
from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.XMLObject import XMLObject from Products.ERP5Type.XMLObject import XMLObject
from OFS.Traversable import NotFound from OFS.Traversable import NotFound
from OFS import SimpleItem, XMLExportImport from OFS import SimpleItem, XMLExportImport
...@@ -591,6 +591,10 @@ class BaseTemplateItem(Implicit, Persistent): ...@@ -591,6 +591,10 @@ class BaseTemplateItem(Implicit, Persistent):
obj.deletePdfContent() obj.deletePdfContent()
elif meta_type == 'Script (Python)': elif meta_type == 'Script (Python)':
obj._code = None obj._code = None
elif interfaces.IIdGenerator.providedBy(obj):
for dict_name in ('last_max_id_dict', 'last_id_dict'):
if getattr(obj, dict_name, None) is not None:
setattr(obj, dict_name, None)
return obj return obj
def getTemplateTypeName(self): def getTemplateTypeName(self):
...@@ -1064,6 +1068,14 @@ class ObjectTemplateItem(BaseTemplateItem): ...@@ -1064,6 +1068,14 @@ class ObjectTemplateItem(BaseTemplateItem):
obj._setProperty( obj._setProperty(
'business_template_registered_skin_selections', 'business_template_registered_skin_selections',
skin_selection_list, type='tokens') skin_selection_list, type='tokens')
# in case the portal ids, we want keep the property dict
elif interfaces.IIdGenerator.providedBy(obj) and \
old_obj is not None:
for dict_name in ('last_max_id_dict', 'last_id_dict'):
# Keep previous last id dict
if getattr(old_obj, dict_name, None) is not None:
old_dict = getattr(old_obj, dict_name, None)
setattr(obj, dict_name, old_dict)
recurse(restoreHook, obj) recurse(restoreHook, obj)
# now put original order group # now put original order group
......
##############################################################################
#
# Copyright (c) 2010 Nexedi SARL and Contributors. All Rights Reserved.
# Daniele Vanbaelinghem <daniele@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import zope.interface
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces
from Products.ERP5Type.Cache import caching_instance_method
from Products.ERP5Type.Base import Base
from Products.CMFCore.utils import getToolByName
from zLOG import LOG, INFO
class IdGenerator(Base):
"""
Generator of Ids
"""
zope.interface.implements(interfaces.IIdGenerator)
# CMF Type Definition
meta_type = 'ERP5 Id Generator'
portal_type = 'Id Generator'
add_permission = Permissions.AddPortalContent
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative property
property_sheets = ( PropertySheet.Base,
PropertySheet.Version,
PropertySheet.Reference)
security.declareProtected(Permissions.AccessContentsInformation,
'getLatestVersionValue')
def getLatestVersionValue(self, **kw):
"""
Return the last generator with the reference
"""
id_tool = getToolByName(self, 'portal_ids', None)
last_id = id_tool._getLatestIdGenerator(self.getReference())
last_version = id_tool._getOb(last_id)
return last_version
security.declareProtected(Permissions.AccessContentsInformation,
'generateNewId')
def generateNewId(self, id_group=None, default=None,):
"""
Generate the next id in the sequence of ids of a particular group
Use int to store the last_id, use also a persistant mapping for to be
persistent.
"""
try:
specialise = self.getSpecialiseValue()
except AttributeError:
raise AttributeError, 'specialise is not defined'
if specialise is None:
raise ValueError, "the id generator %s doesn't have specialise value" %\
self.getReference()
return specialise.getLatestVersionValue().generateNewId(id_group=id_group,
default=default)
security.declareProtected(Permissions.AccessContentsInformation,
'generateNewIdList')
def generateNewIdList(self, id_group=None, id_count=1, default=None):
"""
Generate a list of next ids in the sequence of ids of a particular group
Store the last id on a database in the portal_ids table
If stored in zodb is enable, to store the last id use Length inspired
by BTrees.Length to manage conflict in the zodb, use also a persistant
mapping to be persistent
"""
# For compatibilty with sql data, must not use id_group as a list
if not isinstance(id_group, str):
raise AttributeError, 'id_group is not a string'
try:
specialise = self.getSpecialiseValue()
except AttributeError:
raise AttributeError, 'specialise is not defined'
if specialise is None:
raise ValueError, "the id generator %s doesn't have specialise value" %\
self.getReference()
return specialise.getLatestVersionValue().generateNewIdList(id_group=id_group, \
id_count=id_count, default=default)
security.declareProtected(Permissions.AccessContentsInformation,
'initializeGenerator')
def initializeGenerator(self):
"""
Initialize generator. This is mostly used when a new ERP5 site
is created. Some generators will need to do some initialization like
creating SQL Database, prepare some data in ZODB, etc
"""
specialise = self.getSpecialiseValue()
if specialise is None:
raise ValueError, "the id generator %s doesn't have specialise value" %\
self.getReference()
specialise.getLatestVersionValue().initializeGenerator()
security.declareProtected(Permissions.AccessContentsInformation,
'clearGenerator')
def clearGenerator(self):
"""
Clear generators data. This can be usefull when working on a
development instance or in some other rare cases. This will
loose data and must be use with caution
This can be incompatible with some particular generator implementation,
in this case a particular error will be raised (to be determined and
added here)
"""
specialise = self.getSpecialiseValue()
if specialise is None:
raise ValueError, "the id generator %s doesn't have specialise value" %\
self.getReference()
specialise.getLatestVersionValue().clearGenerator()
##############################################################################
#
# Copyright (c) 2010 Nexedi SARL and Contributors. All Rights Reserved.
# Daniele Vanbaelinghem <daniele@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import zope.interface
from Acquisition import aq_base
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Globals import PersistentMapping
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5.Document.IdGenerator import IdGenerator
from _mysql_exceptions import ProgrammingError
from zLOG import LOG, INFO
import persistent
class LastMaxGeneratedId(persistent.Persistent):
"""
Store the last id generated
The object support application-level conflict resolution
"""
def __init__(self, value=0):
self.value = value
def __getstate__(self):
return self.value
def __setstate__(self, value):
self.value = value
def set(self, value):
self.value = value
def _p_resolveConflict(self, first_id, second_id):
return max(first_id, second_id)
class SQLNonContinuousIncreasingIdGenerator(IdGenerator):
"""
Generate some ids with mysql storage and also zodb is enabled
by the checkbox : StoredInZodb
"""
zope.interface.implements(interfaces.IIdGenerator)
# CMF Type Definition
meta_type = 'ERP5 SQL Non Continous Increasing Id Generator'
portal_type = 'SQL Non Continous Increasing Id Generator'
add_permission = Permissions.AddPortalContent
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative property
property_sheets = (PropertySheet.SQLIdGenerator,
) + IdGenerator.property_sheets
def _generateNewId(self, id_group, id_count=1, default=None):
"""
Return the next_id with the last_id with the sql method
Store the last id on a database in the portal_ids table
If stored in zodb is enable, to store the last id use LastMaxGeneratedId inspired
by BTrees.Length to manage conflict in the zodb, use also a persistant
mapping to be persistent
"""
# Check the arguments
if id_group in (None, 'None'):
raise ValueError, '%s is not a valid group Id.' % (repr(id_group), )
if default is None:
default = 0
# Retrieve the zsql method
portal = self.getPortalObject()
generate_id_method = getattr(portal, 'IdTool_zGenerateId', None)
commit_method = getattr(portal, 'IdTool_zCommit', None)
get_last_id_method = getattr(portal, 'IdTool_zGetLastId', None)
if None in (generate_id_method, commit_method, get_last_id_method):
raise AttributeError, 'Error while generating Id: ' \
'idTool_zGenerateId and/or IdTool_zCommit and/or idTool_zGetLastId' \
'could not be found.'
result_query = generate_id_method(id_group=id_group, id_count=id_count, \
default=default)
try:
# Tries of generate the new_id
new_id = result_query[0]['LAST_INSERT_ID()']
# Commit the changement of new_id
commit_method()
except ProgrammingError:
# If the database not exist, initialise the generator
self.initializeGenerator()
if self.getStoredInZodb():
# Store the new_id on ZODB if the checkbox storedInZodb is enabled
self.last_max_id_dict = getattr(aq_base(self), \
'last_max_id_dict', None)
if self.last_max_id_dict is None:
# If the dictionary not exist, initialize the generator
self.initializeGenerator()
# Store the new value id
if self.last_max_id_dict.get(id_group, None) is None:
self.last_max_id_dict[id_group] = LastMaxGeneratedId(new_id)
self.last_max_id_dict[id_group].set(new_id)
return new_id
security.declareProtected(Permissions.AccessContentsInformation,
'generateNewId')
def generateNewId(self, id_group=None, default=None):
"""
Generate the next id in the sequence of ids of a particular group
"""
new_id = self._generateNewId(id_group=id_group, default=default)
return new_id
security.declareProtected(Permissions.AccessContentsInformation,
'generateNewIdList')
def generateNewIdList(self, id_group=None, id_count=1, default=None):
"""
Generate a list of next ids in the sequence of ids of a particular group
"""
new_id = self._generateNewId(id_group=id_group, id_count=id_count, \
default=default)
return range(new_id - id_count + 1, new_id + 1)
security.declareProtected(Permissions.AccessContentsInformation,
'initializeGenerator')
def initializeGenerator(self):
"""
Initialize generator. This is mostly used when a new ERP5 site
is created. Some generators will need to do some initialization like
prepare some data in ZODB
"""
LOG('initialize SQL Generator', INFO, 'Id Generator: %s' % (self,))
# Check the dictionnary
if getattr(self, 'last_max_id_dict', None) is None:
self.last_max_id_dict = PersistentMapping()
# Create table portal_ids if not exists
portal = self.getPortalObject()
get_value_list = getattr(portal, 'IdTool_zGetValueList', None)
if get_value_list is None:
raise AttributeError, 'Error while initialize generator:' \
'idTool_zGetValueList could not be found.'
try:
get_value_list()
except ProgrammingError:
drop_method = getattr(portal, 'IdTool_zDropTable', None)
create_method = getattr(portal, 'IdTool_zCreateEmptyTable', None)
if None in (drop_method, create_method):
raise AttributeError, 'Error while initialize generator: ' \
'idTool_zDropTable and/or idTool_zCreateTable could not be found.'
drop_method()
create_method()
# XXX compatiblity code below, dump the old dictionnaries
# Retrieve the zsql_method
portal_ids = getattr(self, 'portal_ids', None)
get_last_id_method = getattr(portal, 'IdTool_zGetLastId', None)
set_last_id_method = getattr(portal, 'IdTool_zSetLastId', None)
if None in (get_last_id_method, set_last_id_method):
raise AttributeError, 'Error while generating Id: ' \
'idTool_zGetLastId and/or idTool_zSetLastId could not be found.'
storage = self.getStoredInZodb()
# Recovery last_max_id_dict datas in zodb if enabled and is in mysql
if len(self.last_max_id_dict) != 0:
dump_dict = self.last_max_id_dict
elif getattr(portal_ids, 'dict_length_ids', None) is not None:
dump_dict = portal_ids.dict_length_ids
for id_group, last_id in dump_dict.items():
last_insert_id = get_last_id_method(id_group=id_group)
if len(last_insert_id) != 0:
last_insert_id = last_insert_id[0]['LAST_INSERT_ID()']
if last_insert_id > last_id.value:
# Check value in dict
if storage and (not self.last_max_id_dict.has_key(id_group) or \
self.last_max_id_dict.has_key[id_group] != last_insert_id):
self.last_max_id_dict[id_group] = LastMaxGeneratedId(last_insert_id)
self.last_max_id_dict[id_group].set(last_insert_id)
continue
last_id = int(last_id.value)
set_last_id_method(id_group=id_group, last_id=last_id)
if storage:
self.last_max_id_dict[id_group] = LastMaxGeneratedId(last_id)
self.last_max_id_dict[id_group].set(last_id)
security.declareProtected(Permissions.AccessContentsInformation,
'clearGenerator')
def clearGenerator(self):
"""
Clear generators data. This can be usefull when working on a
development instance or in some other rare cases. This will
loose data and must be use with caution
This can be incompatible with some particular generator implementation,
in this case a particular error will be raised (to be determined and
added here)
"""
# Remove dictionary
self.last_max_id_dict = PersistentMapping()
# Remove and recreate portal_ids table
portal = self.getPortalObject()
drop_method = getattr(portal, 'IdTool_zDropTable', None)
create_method = getattr(portal, 'IdTool_zCreateEmptyTable', None)
if None in (drop_method, create_method):
raise AttributeError, 'Error while clear generator: ' \
'idTool_zDropTable and/or idTool_zCreateTable could not be found.'
drop_method()
create_method()
##############################################################################
#
# Copyright (c) 2010 Nexedi SARL and Contributors. All Rights Reserved.
# Daniele Vanbaelinghem <daniele@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import zope.interface
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Globals import PersistentMapping
from Products.ERP5Type import Permissions, interfaces
from Products.ERP5.Document.IdGenerator import IdGenerator
from zLOG import LOG, INFO
class ZODBContinuousIncreasingIdGenerator(IdGenerator):
"""
Create some Ids with the zodb storage
"""
zope.interface.implements(interfaces.IIdGenerator)
# CMF Type Definition
meta_type = 'ERP5 ZODB Continous Increasing Id Generator'
portal_type = 'ZODB Continous Increasing Id Generator'
add_permission = Permissions.AddPortalContent
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
def _generateNewId(self, id_group, id_count=1, default=None):
"""
Return the new_id from the last_id of the zodb
Use int to store the last_id, use also a persistant mapping for to be
persistent.
"""
if id_group in (None, 'None'):
raise ValueError, '%s is not a valid group Id.' % (repr(id_group), )
if default is None:
default = 0
self.last_id_dict = getattr(self, 'last_id_dict', None)
if self.last_id_dict is None:
# If the dictionary not exist initialize generator
self.initializeGenerator()
marker = []
# Retrieve the last id
last_id = self.last_id_dict.get(id_group, marker)
if last_id is marker:
new_id = default
if id_count > 1:
# If create a list use the default and increment
new_id = new_id + id_count - 1
else:
# Increment the last_id
new_id = last_id + id_count
# Store the new_id in the dictionary
self.last_id_dict[id_group] = new_id
return new_id
security.declareProtected(Permissions.AccessContentsInformation,
'generateNewId')
def generateNewId(self, id_group=None, default=None):
"""
Generate the next id in the sequence of ids of a particular group
"""
new_id = self._generateNewId(id_group=id_group, default=default)
return new_id
security.declareProtected(Permissions.AccessContentsInformation,
'generateNewIdList')
def generateNewIdList(self, id_group=None, id_count=1, default=None):
"""
Generate a list of next ids in the sequence of ids of a particular group
"""
new_id = self._generateNewId(id_group=id_group, id_count=id_count, \
default=default)
return range(new_id - id_count + 1, new_id + 1)
security.declareProtected(Permissions.AccessContentsInformation,
'initializeGenerator')
def initializeGenerator(self):
"""
Initialize generator. This is mostly used when a new ERP5 site
is created. Some generators will need to do some initialization like
prepare some data in ZODB
"""
LOG('initialize ZODB Generator', INFO, 'Id Generator: %s' % (self,))
if getattr(self, 'last_id_dict', None) is None:
self.last_id_dict = PersistentMapping()
# XXX compatiblity code below, dump the old dictionnaries
portal_ids = getattr(self, 'portal_ids', None)
# Dump the dict_ids dictionary
if getattr(portal_ids, 'dict_ids', None) is not None:
for id_group, last_id in portal_ids.dict_ids.items():
if self.last_id_dict.has_key(id_group) and \
self.last_id_dict[id_group] > last_id:
continue
self.last_id_dict[id_group] = last_id
security.declareProtected(Permissions.AccessContentsInformation,
'clearGenerator')
def clearGenerator(self):
"""
Clear generators data. This can be usefull when working on a
development instance or in some other rare cases. This will
loose data and must be use with caution
This can be incompatible with some particular generator implementation,
in this case a particular error will be raised (to be determined and
added here)
"""
# Remove dictionary
self.last_id_dict = PersistentMapping()
...@@ -1848,15 +1848,11 @@ class ERP5Generator(PortalGenerator): ...@@ -1848,15 +1848,11 @@ class ERP5Generator(PortalGenerator):
# we don't want to make it crash # we don't want to make it crash
if p.erp5_sql_connection_type is not None: if p.erp5_sql_connection_type is not None:
setattr(p, 'isIndexable', ConstantGetter('isIndexable', value=True)) setattr(p, 'isIndexable', ConstantGetter('isIndexable', value=True))
portal_catalog = p.portal_catalog
# Clear portal ids sql table, like this we do not take # Clear portal ids sql table, like this we do not take
# ids for a previously created web site # ids for a previously created web site
# XXX It's temporary, a New API will be implemented soon p.portal_ids.initializeGenerator(all=True)
# the code will be change
p.IdTool_zDropTable()
p.IdTool_zCreateTable()
# Then clear the catalog and reindex it # Then clear the catalog and reindex it
portal_catalog.manage_catalogClear() p.portal_catalog.manage_catalogClear()
# Calling ERP5Site_reindexAll is useless. # Calling ERP5Site_reindexAll is useless.
def setupUserFolder(self, p): def setupUserFolder(self, p):
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
# Daniele Vanbaelinghem <daniele@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.CMFCore.Expression import Expression
class SQLIdGenerator:
"""
Id Generator provides properties to check the storage on ZODB
"""
_properties = (
{'id' :'stored_in_zodb',
'label' : 'A flag indicating if the last_id is stored in the ZODB',
'type' :'boolean',
'mode' :'w'
},
)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
# Daniele Vanbaelinghem <daniele@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.CMFCore.Expression import Expression
class SQLIdGenerator:
"""
Id Generator provides properties to check the storage on ZODB
"""
_properties = (
{'id' :'stored_in_zodb',
'label' : 'A flag indicating if the last_id is stored in the ZODB',
'type' :'boolean',
'mode' :'w'
},
)
This diff is collapsed.
...@@ -129,6 +129,14 @@ class TemplateTool (BaseTool): ...@@ -129,6 +129,14 @@ class TemplateTool (BaseTool):
installed_bts.append(bt) installed_bts.append(bt)
return installed_bts return installed_bts
def getInstalledBusinessTemplateRevision(self, title, **kw):
"""
Return the revision of business template installed with the title
given
"""
bt = self.getInstalledBusinessTemplate(title)
return bt.getRevision()
def getBuiltBusinessTemplatesList(self): def getBuiltBusinessTemplatesList(self):
"""Deprecated. """Deprecated.
""" """
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment