Commit f73b625c authored by Julien Muchembled's avatar Julien Muchembled

BT: don't export 'revision' anymore

Because on DVCS with branches, history is not always linear, we must stop
using increasing integer for revisions because they cause too many conflicts
and a higher revision number does not necessarily means the a BT newer.
They're now a cryptographic hash of all the contents of the Business Template,
and they're computed automatically:
- by genbt5list
- at download
- at building (by default)
- at export
parent ce483b26
......@@ -85,6 +85,8 @@ import posixpath
import transaction
import threading
from Products.ERP5.genbt5list import BusinessTemplateRevision, \
item_name_list, item_set
CACHE_DATABASE_PATH = None
try:
......@@ -306,6 +308,7 @@ class BusinessTemplateArchive(object):
"""
def __init__(self, path, **kw):
self.path = path
self.revision = BusinessTemplateRevision()
def addObject(self, obj, name, path=None, ext='.xml'):
if path:
......@@ -320,15 +323,23 @@ class BusinessTemplateArchive(object):
if not isinstance(obj, str):
obj.seek(0)
obj = obj.read()
self.revision.hash(path, obj)
self._writeString(obj, path)
else:
if isinstance(obj, str):
self.revision.hash(path, obj)
obj = StringIO(obj)
else:
obj.seek(0)
self.revision.hash(path, obj.read())
write(obj, path)
def finishCreation(self):
pass
def getRevision(self):
return self.revision.digest()
class BusinessTemplateFolder(BusinessTemplateArchive):
"""
Class archiving business template into a folder tree
......@@ -348,9 +359,8 @@ class BusinessTemplateFolder(BusinessTemplateArchive):
Import file from a local folder
"""
join = os.path.join
path = os.path.normpath(self.path)
class_name = item.__class__.__name__
root = join(path, class_name, '')
item_name = item.__class__.__name__
root = join(os.path.normpath(self.path), item_name, '')
root_path_len = len(root)
if CACHE_DATABASE_PATH:
try:
......@@ -362,9 +372,13 @@ class BusinessTemplateFolder(BusinessTemplateArchive):
for file_name in files:
file_name = join(root, file_name)
with open(file_name, 'rb') as f:
file_name = file_name[root_path_len:]
file_name = posixpath.normpath(file_name[root_path_len:])
if '%' in file_name:
file_name = unquote(file_name)
elif item_name == 'bt' and file_name == 'revision':
continue
self.revision.hash(item_name + '/' + file_name, f.read())
f.seek(0)
item._importFile(file_name, f)
finally:
if hasattr(cache_database, 'db'):
......@@ -411,10 +425,16 @@ class BusinessTemplateTarball(BusinessTemplateArchive):
Import all file from the archive to the site
"""
extractfile = self.tar.extractfile
for file_name, info in self.item_dict.get(item.__class__.__name__, ()):
item_name = item.__class__.__name__
for file_name, info in self.item_dict.get(item_name, ()):
if '%' in file_name:
file_name = unquote(file_name)
item._importFile(file_name, extractfile(info))
elif item_name == 'bt' and file_name == 'revision':
continue
f = extractfile(info)
self.revision.hash(item_name + '/' + file_name, f.read())
f.seek(0)
item._importFile(file_name, f)
class TemplateConditionError(Exception): pass
class TemplateConflictError(Exception): pass
......@@ -4782,63 +4802,6 @@ Business Template is a set of definitions, such as skins, portal types and categ
, 'filter_content_types' : 1
}
# This is a global variable
# Order is important for installation
# We want to have:
# * path after module, because path can be module content
# * path after categories, because path can be categories content
# * path after portal types roles so that roles in the current bt can be used
# * path before workflow chain, because path can be a portal type
# (until chains are set on portal types with categories)
# * skin after paths, because we can install a custom connection string as
# path and use it with SQLMethods in a skin.
# ( and more )
_item_name_list = [
'_registered_version_priority_selection_item',
'_product_item',
'_document_item',
'_property_sheet_item',
'_constraint_item',
'_extension_item',
'_test_item',
'_role_item',
'_tool_item',
'_message_translation_item',
'_workflow_item',
'_site_property_item',
'_portal_type_item',
#'_portal_type_workflow_chain_item',
'_portal_type_allowed_content_type_item',
'_portal_type_hidden_content_type_item',
'_portal_type_property_sheet_item',
'_portal_type_base_category_item',
'_category_item',
'_module_item',
'_portal_type_roles_item',
'_path_item',
'_skin_item',
'_registered_skin_selection_item',
'_preference_item',
'_action_item',
'_local_roles_item',
'_portal_type_workflow_chain_item',
'_catalog_method_item',
'_catalog_result_key_item',
'_catalog_related_key_item',
'_catalog_result_table_item',
'_catalog_search_key_item',
'_catalog_keyword_key_item',
'_catalog_datetime_key_item',
'_catalog_full_text_key_item',
'_catalog_request_key_item',
'_catalog_multivalue_key_item',
'_catalog_topic_key_item',
'_catalog_scriptable_key_item',
'_catalog_role_key_item',
'_catalog_local_role_key_item',
'_catalog_security_uid_column_item',
]
def __init__(self, *args, **kw):
XMLObject.__init__(self, *args, **kw)
self._clean()
......@@ -4870,23 +4833,10 @@ Business Template is a set of definitions, such as skins, portal types and categ
self.workflow_history[
'business_template_installation_workflow'] = None
security.declareProtected(Permissions.AccessContentsInformation,
'getRevision')
def getRevision(self):
"""returns the revision property.
This is a workaround for #461.
"""
return self._baseGetRevision()
def updateRevisionNumber(self):
"""Increment bt revision number.
"""
revision_number = self.getRevision()
if revision_number is None or revision_number.strip() == '':
revision_number = 1
else:
revision_number = int(revision_number)+1
self.setRevision(revision_number)
def getShortRevision(self):
"""Returned a shortened revision"""
r = self.getRevision()
return r and r[:5]
security.declareProtected(Permissions.ManagePortal, 'storeTemplateItemData')
def storeTemplateItemData(self):
......@@ -5009,7 +4959,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
pass
security.declareProtected(Permissions.ManagePortal, 'build')
def build(self, no_action=0):
def build(self, no_action=0, update_revision=True):
"""
Copy existing portal objects to self
"""
......@@ -5018,19 +4968,11 @@ Business Template is a set of definitions, such as skins, portal types and categ
# Make sure that everything is sane.
self.clean()
try:
from Products.ERP5VCS.WorkingCopy import NotAWorkingCopyError
try:
self.setRevision(self.getVcsTool().newRevision())
except NotAWorkingCopyError:
raise ImportError
except ImportError:
self.updateRevisionNumber()
self._setTemplateFormatVersion(1)
self.storeTemplateItemData()
# Build each part
for item_name in self._item_name_list:
for item_name in item_name_list:
item = getattr(self, item_name)
if item is None:
continue
......@@ -5039,6 +4981,8 @@ Business Template is a set of definitions, such as skins, portal types and categ
item.build(self)
# update _p_jar property of objects cleaned by removeProperties
transaction.savepoint(optimistic=True)
if update_revision:
self._export()
def publish(self, url, username=None, password=None):
"""
......@@ -5118,14 +5062,14 @@ Business Template is a set of definitions, such as skins, portal types and categ
return modified_object_list
elif installed_bt_format == 0 and new_bt_format == 1:
# return list of all object in bt
for item_name in self._item_name_list:
for item_name in item_name_list:
item = getattr(self, item_name, None)
if item is not None:
for path in item._objects.keys():
modified_object_list.update({path : ['New', item.__class__.__name__[:-12]]})
return modified_object_list
for item_name in self._item_name_list:
for item_name in item_name_list:
new_item = getattr(self, item_name, None)
installed_item = getattr(installed_bt, item_name, None)
if new_item is not None:
......@@ -5186,7 +5130,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
# Install everything
if len(object_to_update) or force:
for item_name in self._item_name_list:
for item_name in item_name_list:
item = getattr(self, item_name, None)
if item is not None:
item.install(self, force=force, object_to_update=object_to_update,
......@@ -5211,7 +5155,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
# remove object from old business template
if len(remove_object_dict):
# XXX: this code assumes that there is an installed_bt
for item_name in reversed(installed_bt._item_name_list):
for item_name in reversed(item_name_list):
item = getattr(installed_bt, item_name, None)
if item is not None:
item.remove(self, remove_object_dict=remove_object_dict, trashbin=trashbin)
......@@ -5255,7 +5199,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
not remove all items.
"""
# Trash everything
for item_name in self._item_name_list[::-1]:
for item_name in reversed(item_name_list):
item = getattr(self, item_name, None)
if item is not None:
item.trash(
......@@ -5268,7 +5212,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
"""
# Uninstall everything
# Trash everything
for item_name in self._item_name_list[::-1]:
for item_name in reversed(item_name_list):
item = getattr(self, item_name, None)
if item is not None:
item.uninstall(self, **kw)
......@@ -5296,7 +5240,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
if hasattr(self, attr):
delattr(self, attr)
# Secondly, make attributes empty.
for item_name in self._item_name_list:
for item_name in item_name_list:
setattr(self, item_name, None)
security.declareProtected(Permissions.ManagePortal, 'clean')
......@@ -5558,7 +5502,9 @@ Business Template is a set of definitions, such as skins, portal types and categ
if self.getBuildingState() != 'built':
raise TemplateConditionError, \
'Business Template must be built before export'
return self._export(path, local, bta)
def _export(self, path=None, local=0, bta=None):
if bta is None:
if local:
# we export into a folder tree
......@@ -5573,7 +5519,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
for prop in self.propertyMap():
prop_type = prop['type']
id = prop['id']
if id in ('id', 'uid', 'rid', 'sid', 'id_group', 'last_id',
if id in ('id', 'uid', 'rid', 'sid', 'id_group', 'last_id', 'revision',
'install_object_list_list', 'id_generator', 'bt_for_diff'):
continue
value = self.getProperty(id)
......@@ -5585,11 +5531,12 @@ Business Template is a set of definitions, such as skins, portal types and categ
bta.addObject('\n'.join(value), name=id, path='bt', ext='')
# Export each part
for item_name in self._item_name_list:
for item_name in item_name_list:
item = getattr(self, item_name, None)
if item is not None:
item.export(context=self, bta=bta)
self._setRevision(bta.getRevision())
return bta.finishCreation()
security.declareProtected(Permissions.ManagePortal, 'importFile')
......@@ -5630,7 +5577,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
setattr(module, template_id, type(template_id,
(SimpleItem.SimpleItem,), {'__module__': module_id}))
for item_name in self._item_name_list:
for item_name in item_name_list:
item_object = getattr(self, item_name, None)
# this check is due to backwards compatability when there can be a
# difference between install erp5_property_sheets (esp. BusinessTemplate
......@@ -5643,11 +5590,13 @@ Business Template is a set of definitions, such as skins, portal types and categ
for module_id in module_id_list:
del sys.modules[module_id]
self._setRevision(bta.getRevision())
def getItemsList(self):
"""Return list of items in business template
"""
items_list = []
for item_name in self._item_name_list:
for item_name in item_name_list:
item = getattr(self, item_name, None)
if item is not None:
items_list.extend(item.getKeys())
......@@ -6106,5 +6055,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
# Block acquisition on all _item_name_list properties by setting
# a default class value to None
for key in BusinessTemplate._item_name_list:
for key in item_name_list:
setattr(BusinessTemplate, key, None)
# Check naming convention of items.
assert item_set.issubset(globals()), item_set.difference(globals())
......@@ -44,6 +44,7 @@ from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type.Cache import transactional_cached
from Products.ERP5Type import Permissions
from Products.ERP5.Document.BusinessTemplate import BusinessTemplateMissingDependency
from Products.ERP5.genbt5list import generateInformation
from Acquisition import aq_base
from tempfile import mkstemp, mkdtemp
from Products.ERP5 import _dtmldir
......@@ -124,23 +125,19 @@ class TemplateTool (BaseTool):
# However, that unlikely happens, and using a Z SQL Method has a
# potential danger because business templates may exchange catalog
# methods, so the database could be broken temporarily.
latest_bt = None
latest_revision = 0
for bt in self.contentValues(filter={'portal_type':'Business Template'}):
last_bt = last_time = None
for bt in self.objectValues(portal_type='Business Template'):
if bt.getTitle() == title or title in bt.getProvisionList():
installation_state = bt.getInstallationState()
if installation_state == 'installed':
latest_bt = bt
break
elif strict is False and installation_state == 'replaced':
revision = bt.getRevision()
try:
revision = int(revision)
except ValueError:
continue
if revision > latest_revision:
latest_bt = bt
return latest_bt
state = bt.getInstallationState()
if state == 'installed':
return bt
if state == 'replaced' and not strict:
t = bt.workflow_history \
['business_template_installation_workflow'][-1]['time']
if last_time < t:
last_bt = bt
last_time = t
return last_bt
def getInstalledBusinessTemplatesList(self):
"""Deprecated.
......@@ -180,20 +177,12 @@ class TemplateTool (BaseTool):
return bt.getRevision()
return None
def getBuiltBusinessTemplatesList(self):
"""Deprecated.
"""
DeprecationWarning('getBuiltBusinessTemplatesList is deprecated; Use getBuiltBusinessTemplateList instead.', DeprecationWarning)
return self.getBuiltBusinessTemplateList()
def getBuiltBusinessTemplateList(self):
"""Get the list of built and not installed business templates.
"""
built_bts = []
for bt in self.contentValues(portal_type='Business Template'):
if bt.getInstallationState() == 'not_installed' and bt.getBuildingState() == 'built':
built_bts.append(bt)
return built_bts
return [bt for bt in self.objectValues(portal_type='Business Template')
if bt.getInstallationState() == 'not_installed' and
bt.getBuildingState() == 'built']
@property
def asRepository(self):
......@@ -201,7 +190,7 @@ class TemplateTool (BaseTool):
"""Export business template by their title
Provides a view of template tool allowing a user to download the last
revision of a business template with a URL like:
edited business template with a URL like:
http://.../erp5/portal_templates/asRepository/erp5_core
"""
def __before_publishing_traverse__(self, self2, request):
......@@ -213,9 +202,9 @@ class TemplateTool (BaseTool):
last_bt = None, None
for bt in self.aq_parent.searchFolder(title=title):
bt = bt.getObject()
revision = int(bt.getRevision())
if last_bt[0] < revision and bt.getInstallationState() != 'deleted':
last_bt = revision, bt
modified = bt.getModificationDate()
if last_bt[0] < modified and bt.getInstallationState() != 'deleted':
last_bt = modified, bt
if last_bt[1] is None:
return RESPONSE.notFoundError(title)
RESPONSE.setHeader('Content-type', 'application/data')
......@@ -342,18 +331,6 @@ class TemplateTool (BaseTool):
finally:
shutil.rmtree(svn_checkout_tmp_dir)
def assertBtPathExists(self, url):
"""
Check if bt is present on the system
"""
urltype, name = splittype(url)
# Windows compatibility
if WIN:
if os.path.isdir(os.path.normpath(url)) or \
os.path.isfile(os.path.normpath(url)):
name = os.path.normpath(url)
return os.path.exists(os.path.normpath(name))
security.declareProtected( 'Import/Export objects', 'download' )
def download(self, url, id=None, REQUEST=None):
"""
......@@ -368,13 +345,9 @@ class TemplateTool (BaseTool):
id = self.generateNewId()
urltype, name = splittype(url)
# Windows compatibility
if WIN:
if os.path.isdir(os.path.normpath(url)) or \
os.path.isfile(os.path.normpath(url)):
urltype = 'file'
name = os.path.normpath(url)
if WIN and urltype and '\\' in name:
urltype = None
name = url
if urltype and urltype != 'file':
if '/portal_templates/asRepository/' in url:
# In this case, the downloaded BT is already built.
......@@ -384,7 +357,7 @@ class TemplateTool (BaseTool):
return self[self._setObject(id, bt)]
bt = self._download_url(url, id)
else:
bt = self._download_local(name, id)
bt = self._download_local(os.path.normpath(name), id)
bt.build(no_action=True)
return bt
......@@ -592,9 +565,12 @@ class TemplateTool (BaseTool):
'updateRepositoryBusinessTemplateList' )
def updateRepositoryBusinessTemplateList(self, repository_list,
REQUEST=None, RESPONSE=None, **kw):
REQUEST=None, RESPONSE=None, genbt5list=0, **kw):
"""
Update the information on Business Templates from repositories.
For local repositories, if bt5list is missing or if genbt5list > 1,
bt5list is automatically generated (but not saved on disk).
"""
self.repository_dict = PersistentMapping()
property_list = ('title', 'version', 'revision', 'description', 'license',
......@@ -602,9 +578,19 @@ class TemplateTool (BaseTool):
#LOG('updateRepositoryBusiessTemplateList', 0,
# 'repository_list = %r' % (repository_list,))
for repository in repository_list:
url = '/'.join([repository, 'bt5list'])
f = urlopen(url)
property_dict_list = []
urltype, url = splittype(repository)
if WIN and urltype and '\\' in url:
urltype = None
url = repository
if urltype and urltype != 'file':
f = urlopen(repository + '/bt5list')
else:
bt5list = os.path.join(url, 'bt5list')
if genbt5list > os.path.exists(bt5list):
f = generateInformation(url)
f.seek(0)
else:
f = open(bt5list, 'rb')
try:
try:
doc = parse(f)
......@@ -618,6 +604,7 @@ class TemplateTool (BaseTool):
else:
raise RuntimeError, 'Invalid repository: %s' % repository
try:
property_dict_list = []
root = doc.documentElement
for template in root.getElementsByTagName("template"):
id = template.getAttribute('id')
......@@ -958,9 +945,6 @@ class TemplateTool (BaseTool):
update_only: return only bt that needs to be updated
template_list: only returns bt within the given list
"""
version_state_title_dict = { 'new' : 'New', 'present' : 'Present',
'old' : 'Old' }
from Products.ERP5Type.Document import newTempBusinessTemplate
result_list = []
template_set = None
......@@ -987,14 +971,8 @@ class TemplateTool (BaseTool):
# if this business template is newer.
previous_repository, previous_property_dict = \
template_item_dict[title]
diff_version = self.compareVersions(previous_property_dict['version'],
property_dict['version'])
if diff_version < 0:
template_item_dict[title] = (repository, property_dict)
elif diff_version == 0 \
and previous_property_dict['revision'] \
and property_dict['revision'] \
and int(previous_property_dict['revision']) < int(property_dict['revision']):
if self.compareVersions(previous_property_dict['version'],
property_dict['version']) < 0:
template_item_dict[title] = (repository, property_dict)
# Next, select only updated business templates.
if update_only:
......@@ -1007,9 +985,8 @@ class TemplateTool (BaseTool):
if diff_version < 0:
template_item_list.append((repository, property_dict))
elif diff_version == 0 \
and installed_bt.getRevision() \
and property_dict['revision'] \
and int(installed_bt.getRevision()) < int(property_dict['revision']):
and installed_bt.getRevision() != property_dict['revision']:
template_item_list.append((repository, property_dict))
elif template_list is not None:
template_item_list.append((repository, property_dict))
......@@ -1017,29 +994,24 @@ class TemplateTool (BaseTool):
# Create temporary Business Template objects for displaying.
for repository, property_dict in template_item_list:
property_dict = property_dict.copy()
id = property_dict['id']
filename = property_dict['id']
del property_dict['id']
revision = property_dict['revision']
version_state = 'new'
id = filename = property_dict.pop('id')
installed_bt = \
self.getInstalledBusinessTemplate(property_dict['title'])
if installed_bt is not None:
installed_version = installed_bt.getVersion()
installed_revision = installed_bt.getRevision()
result = self.compareVersions(installed_revision, revision)
if result == 0:
installed_revision = installed_bt.getShortRevision()
if installed_bt.getRevision() == property_dict['revision']:
version_state = 'present'
elif result < 0:
version_state = 'old'
else:
version_state = 'different'
else:
installed_version = ''
installed_revision = ''
version_state_title = version_state_title_dict[version_state]
version_state = 'new'
uid = self.encodeRepositoryBusinessTemplateUid(repository, id)
obj = newTempBusinessTemplate(self, 'temp_' + uid,
version_state = version_state,
version_state_title = version_state_title,
version_state_title=version_state.title(),
filename = filename,
installed_version = installed_version,
installed_revision = installed_revision,
......@@ -1104,10 +1076,9 @@ class TemplateTool (BaseTool):
return 0
def _getBusinessTemplateUrlDict(self, newest_only=False):
def _getBusinessTemplateUrlDict(self):
business_template_url_dict = {}
for bt in self.getRepositoryBusinessTemplateList(\
newest_only=newest_only):
for bt in self.getRepositoryBusinessTemplateList():
url, name = self.decodeRepositoryBusinessTemplateUid(bt.getUid())
if name.endswith('.bt5'):
name = name[:-4]
......@@ -1119,14 +1090,11 @@ class TemplateTool (BaseTool):
security.declareProtected(Permissions.ManagePortal,
'installBusinessTemplatesFromRepositories')
def installBusinessTemplatesFromRepositories(self, template_list,
only_newer=True, update_catalog=_MARKER, activate=False,
install_dependency=False):
def installBusinessTemplatesFromRepositories(self, *args, **kw):
"""Deprecated.
"""
DeprecationWarning('installBusinessTemplatesFromRepositories is deprecated; Use self.installBusinessTemplateListFromRepository instead.', DeprecationWarning)
return self.installBusinessTemplateListFromRepository(template_list,
only_newer, update_catalog, activate, install_dependency)
return self.installBusinessTemplateListFromRepository(*args, **kw)
security.declareProtected(Permissions.ManagePortal,
'resolveBusinessTemplateListDependency')
......@@ -1187,7 +1155,7 @@ class TemplateTool (BaseTool):
security.declareProtected(Permissions.ManagePortal,
'installBusinessTemplateListFromRepository')
def installBusinessTemplateListFromRepository(self, template_list,
only_newer=True, update_catalog=_MARKER, activate=False,
only_different=True, update_catalog=_MARKER, activate=False,
install_dependency=False):
"""Installs template_list from configured repositories by default only newest"""
# XXX-Luke: This method could replace
......@@ -1197,12 +1165,13 @@ class TemplateTool (BaseTool):
operation_log = []
resolved_template_list = self.resolveBusinessTemplateListDependency(
template_list)
installed_bt5_set = set([x.title
for x in self.getInstalledBusinessTemplatesList()])
installed_bt5_dict = dict((x.getTitle(), x.getRevision())
for x in self.getInstalledBusinessTemplateList())
if only_different:
template_url_dict = self._getBusinessTemplateUrlDict()
def checkAvailability(bt_title):
return bt_title in template_list or bt_title in installed_bt5_set
return bt_title in template_list or bt_title in installed_bt5_dict
missing_dependency_list = [i for i in resolved_template_list
if not checkAvailability(i[1].replace(".bt5", ""))]
......@@ -1211,17 +1180,14 @@ class TemplateTool (BaseTool):
"Impossible to install, please install the following dependencies before: %s" \
% [x[1] for x in missing_dependency_list]
template_url_dict = self._getBusinessTemplateUrlDict()
activate_kw = dict(activity="SQLQueue", tag="start_%s" % (time.time()))
for repository, bt_id in resolved_template_list:
if only_different:
bt = template_url_dict.get(bt_id)
if bt is not None and bt_id in installed_bt5_set:
revision = int(bt['revision'])
installed_bt5 = self.getInstalledBusinessTemplate(bt_id)
if int(installed_bt5.getRevision()) <= revision and only_newer:
if bt is not None and bt['revision'] == installed_bt5_dict.get(bt_id):
continue
bt_url = '%s/%s' % (repository, bt_id)
param_dict = dict(download_url=bt_url, only_newer=only_newer)
param_dict = dict(download_url=bt_url, only_different=only_different)
if update_catalog is not _MARKER:
param_dict["update_catalog"] = update_catalog
......@@ -1234,7 +1200,7 @@ class TemplateTool (BaseTool):
else:
document = self.updateBusinessTemplateFromUrl(**param_dict)
operation_log.append('Installed %s with revision %s' % (
document.getTitle(), document.getRevision()))
document.getTitle(), document.getShortRevision()))
return operation_log
......@@ -1248,7 +1214,7 @@ class TemplateTool (BaseTool):
reinstall=False,
active_process=None,
force_keep_list=None,
only_newer=True):
only_different=True):
"""
This method download and install a bt5, from a URL.
......@@ -1284,18 +1250,13 @@ class TemplateTool (BaseTool):
if reinstall:
install_kw = None
else:
if only_different:
previous_bt5 = self.getInstalledBusinessTemplate(bt_title)
if (previous_bt5 is not None) and only_newer:
try:
imported_revision = int(imported_bt5.getRevision())
previous_revision = int(previous_bt5.getRevision())
if imported_revision <= previous_revision:
log("%s is already installed with revision %i, which is same or "
"newer revision than new revision %i." % (bt_title,
previous_revision, imported_revision))
if previous_bt5 and \
imported_bt5.getRevision() == previous_bt5.getRevision():
log("%s is already installed with revision %s"
% (bt_title, imported_bt5.getShortRevision()))
return imported_bt5
except ValueError:
pass
install_kw = {}
for listbox_line in imported_bt5.BusinessTemplate_getModifiedObject():
......
......@@ -31,59 +31,147 @@
"""Generate repository information on Business Templates.
"""
import posixpath
import tarfile
import os
import sys
import cgi
from base64 import b64encode
from cStringIO import StringIO
from hashlib import sha1
from urllib import unquote
property_list = '''
# Order is important for installation
# We want to have:
# * path after module, because path can be module content
# * path after categories, because path can be categories content
# * path after portal types roles so that roles in the current bt can be used
# * path before workflow chain, because path can be a portal type
# (until chains are set on portal types with categories)
# * skin after paths, because we can install a custom connection string as
# path and use it with SQLMethods in a skin.
# ( and more )
item_name_list = (
'registered_version_priority_selection',
'product',
'document',
'property_sheet',
'constraint',
'extension',
'test',
'role',
'tool',
'message_translation',
'workflow',
'site_property',
'portal_type',
'portal_type_allowed_content_type',
'portal_type_hidden_content_type',
'portal_type_property_sheet',
'portal_type_base_category',
'category',
'module',
'portal_type_roles',
'path',
'skin',
'registered_skin_selection',
'preference',
'action',
'local_roles',
'portal_type_workflow_chain',
'catalog_method',
'catalog_result_key',
'catalog_related_key',
'catalog_result_table',
'catalog_search_key',
'catalog_keyword_key',
'catalog_datetime_key',
'catalog_full_text_key',
'catalog_request_key',
'catalog_multivalue_key',
'catalog_topic_key',
'catalog_scriptable_key',
'catalog_role_key',
'catalog_local_role_key',
'catalog_security_uid_column',
)
item_set = set(('CatalogDateTimeKey' if x == 'catalog_datetime_key' else
''.join(map(str.title, x.split('_')))) + 'TemplateItem'
for x in item_name_list)
item_set.add('bt')
item_name_list = tuple('_%s_item' % x for x in item_name_list)
class BusinessTemplateRevision(list):
def hash(self, path, text):
self.append((path, sha1(text).digest()))
def digest(self):
self.sort()
return b64encode(sha1('\0'.join(h + p for (h, p) in self)).digest())
class BusinessTemplate(dict):
property_list = frozenset('''
title
version
revision
description
license
dependency_list
provision_list
copyright_list
'''.split()
'''.split())
bt_title_path = os.path.join('bt', 'title')
def __init__(self):
self.revision = BusinessTemplateRevision()
def readProperty(property_dict, property_name, property_file):
def _read(self, path, file):
try:
text = property_file.read()
if property_name.endswith('_list'):
property_dict[property_name[:-5]] = text.splitlines()
else:
property_dict[property_name] = text
text = file.read()
finally:
property_file.close()
file.close()
if path.startswith('bt/'):
name = path[3:]
if name in self.property_list:
if name.endswith('_list'):
self[name[:-5]] = text.splitlines()
else:
self[name] = text
elif name == 'revision':
return
self.revision.hash(unquote(path) if '%' in path else path, text)
def readBusinessTemplate(tar):
"""Read an archived Business Template info.
"""
property_dict = {}
def __iter__(self):
self['revision'] = self.revision.digest()
return iter(sorted(self.iteritems()))
@classmethod
def fromTar(cls, tar):
"""Read an archived Business Template info"""
self = cls()
for info in tar:
name_list = info.name.split('/')
if len(name_list) == 3 and name_list[1] == 'bt' and name_list[2] in property_list:
property_file = tar.extractfile(info)
property_name = name_list[2]
readProperty(property_dict, property_name, property_file)
return property_dict
def readBusinessTemplateDirectory(dir):
"""Read Business Template Directory info.
"""
property_dict = {}
for property_name in property_list:
filename = os.path.join(dir, 'bt', property_name)
if os.path.isfile(filename):
property_file = open(filename, 'rb')
readProperty(property_dict, property_name, property_file)
return property_dict
if not info.isdir():
name = info.name.split('/', 1)[1]
if name.split('/', 1)[0] in item_set:
self._read(name, tar.extractfile(info))
return iter(self)
@classmethod
def fromDir(cls, dir):
"""Read Business Template Directory info"""
self = cls()
lstrip_len = len(dir + os.sep)
for root, dirs, files in os.walk(dir):
if root:
for path in files:
path = os.path.join(root, path)
self._read(posixpath.normpath(path[lstrip_len:]), open(path, 'rb'))
else:
dirs[:] = item_set.intersection(dirs)
return iter(self)
def generateInformation(dir, info=id, err=None):
xml = StringIO()
......@@ -100,16 +188,16 @@ def generateInformation(dir, info=id, err=None):
continue
raise
try:
property_dict = readBusinessTemplate(tar)
property_list = BusinessTemplate.fromTar(tar)
finally:
tar.close()
elif os.path.isfile(os.path.join(path, bt_title_path)):
elif os.path.isfile(os.path.join(path, 'bt', 'title')):
info('Reading Directory %s... ' % name)
property_dict = readBusinessTemplateDirectory(path)
property_list = BusinessTemplate.fromDir(path)
else:
continue
xml.write(' <template id="%s">\n' % name)
for k, v in sorted(property_dict.iteritems()):
for k, v in property_list:
for v in (v,) if type(v) is str else v:
xml.write(' <%s>%s</%s>\n' % (k, cgi.escape(v), k))
xml.write(' </template>\n')
......
......@@ -108,7 +108,7 @@
<string>my_id</string>
<string>my_title</string>
<string>my_version</string>
<string>my_revision</string>
<string>my_short_revision</string>
<string>my_translated_building_state_title</string>
<string>my_translated_installation_state_title</string>
<string>my_description</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StringField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_revision</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>Too much input was given.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>the number of revision used by the business template. This number increases each time we commit a modification</string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Revision Number</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>description</string>
<string>editable</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_short_revision</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>A shortened hash of the contents of the Business Template. The hash is computed at download, build and export.</string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Revision Number</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -349,7 +349,7 @@
<string>Version</string>
</tuple>
<tuple>
<string>revision</string>
<string>short_revision</string>
<string>Revision</string>
</tuple>
<tuple>
......
......@@ -340,7 +340,7 @@
<string>Version</string>
</tuple>
<tuple>
<string>revision</string>
<string>short_revision</string>
<string>Revision</string>
</tuple>
<tuple>
......
......@@ -343,14 +343,14 @@
<string>version</string>
<string>Version</string>
</tuple>
<tuple>
<string>revision</string>
<string>Revision</string>
</tuple>
<tuple>
<string>installed_version</string>
<string>Installed Version</string>
</tuple>
<tuple>
<string>short_revision</string>
<string>Revision</string>
</tuple>
<tuple>
<string>installed_revision</string>
<string>Installed Revision</string>
......@@ -367,10 +367,6 @@
<string>license</string>
<string>License</string>
</tuple>
<tuple>
<string>version_state_title</string>
<string>State</string>
</tuple>
</list>
</value>
</item>
......
bin/genbt5list
\ No newline at end of file
......@@ -2824,24 +2824,6 @@ class BusinessTemplateMixin(TestDeveloperMixin, ERP5TypeTestCase, LogInterceptor
self.failUnless(base_category_obj is not None)
self.assertEquals(len(base_category_obj.objectIds()), 0)
def stepCheckInitialRevision(self, sequence=None, **kw):
""" Check if revision of a new bt is an empty string
"""
bt = sequence.get('current_bt')
self.assertEqual(bt.getRevision(), '')
def stepCheckFirstRevision(self, sequence=None, **kw):
""" Check if revision of the bt is 1
"""
bt = sequence.get('current_bt')
self.assertEqual(bt.getRevision(), '1')
def stepCheckSecondRevision(self, sequence=None, **kw):
""" Check if revision of the bt is 2
"""
bt = sequence.get('current_bt')
self.assertEqual(bt.getRevision(), '2')
def stepCheckNoMissingDependencies(self, sequence=None, **kw):
""" Check if bt has no missing dependency
"""
......@@ -5126,26 +5108,6 @@ class TestBusinessTemplate(BusinessTemplateMixin):
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
# test of portal types
def test_22_RevisionNumberIsIncremented(self):
"""Test is revision number is incremented with the bt is built"""
sequence_list = SequenceList()
sequence_string = '\
CreatePortalType \
CreateNewBusinessTemplate \
UseExportBusinessTemplate \
CheckInitialRevision \
BuildBusinessTemplate \
CheckBuiltBuildingState \
stepCheckFirstRevision \
BuildBusinessTemplate \
stepCheckSecondRevision \
RemoveBusinessTemplate \
RemovePortalType \
'
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
def test_23_CheckNoDependencies(self):
"""Test if a new Business Template has no dependencies"""
sequence_list = SequenceList()
......
......@@ -31,7 +31,7 @@ import os
import shutil
import unittest
import random
import transaction
import tempfile
from App.config import getConfiguration
from Products.ERP5VCS.WorkingCopy import getVcsTool
......@@ -99,32 +99,23 @@ class TestTemplateTool(ERP5TypeTestCase):
def testUpdateBT5FromRepository(self, quiet=quiet, run=run_all_test):
""" Test the list of bt5 returned for upgrade """
# edit bt5 revision so that it will be marked as updatable
bt_list = self.templates_tool.searchFolder(title='erp5_base')
self.assertEquals(len(bt_list), 1)
erp5_base = bt_list[0].getObject()
try:
erp5_base.edit(revision=0)
erp5_base = self.templates_tool.getInstalledBusinessTemplate('erp5_base',
strict=True)
erp5_base._setRevision('')
updatable_bt_list = \
self.templates_tool.getRepositoryBusinessTemplateList(update_only=True)
self.assertEqual(
[i.title for i in updatable_bt_list if i.title == "erp5_base"],
["erp5_base"])
self.assertTrue("erp5_base" in (bt.getTitle() for bt in
self.templates_tool.getRepositoryBusinessTemplateList(update_only=True)))
erp5_base.replace()
updatable_bt_list = \
self.templates_tool.getRepositoryBusinessTemplateList(update_only=True)
self.assertEqual(
[i.title for i in updatable_bt_list if i.title == "erp5_base"],
[])
finally:
erp5_base.edit(revision=int(erp5_base.getRevision()) + 10)
self.assertFalse("erp5_base" in (bt.getTitle() for bt in
self.templates_tool.getRepositoryBusinessTemplateList(update_only=True)))
self.abort()
def test_download_http(self):
test_web = self.portal.portal_templates.download(
'http://www.erp5.org/dists/snapshot/test_bt5/test_web.bt5')
self.assertEquals(test_web.getPortalType(), 'Business Template')
self.assertEquals(test_web.getTitle(), 'test_web')
self.assertTrue(test_web.getRevision())
self.assertEqual(len(test_web.getRevision()), 28)
def _svn_setup_ssl(self):
"""
......@@ -148,20 +139,20 @@ class TestTemplateTool(ERP5TypeTestCase):
test_web = self.portal.portal_templates.download(bt5_url)
self.assertEquals(test_web.getPortalType(), 'Business Template')
self.assertEquals(test_web.getTitle(), 'test_web')
self.assertTrue(test_web.getRevision())
self.assertEqual(len(test_web.getRevision()), 28)
def test_updateBusinessTemplateFromUrl_simple(self):
"""
Test updateBusinessTemplateFromUrl method
By default if a new business template has revision >= previous one
By default if a new business template has revision != previous one
the new bt5 is not installed, only imported.
"""
self._svn_setup_ssl()
template_tool = self.portal.portal_templates
old_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style')
# change revision to an old revision
old_bt.setRevision(0.0001)
# fake different revision
old_bt.setRevision('')
url = 'https://svn.erp5.org/repos/public/erp5/trunk/bt5/erp5_csv_style'
template_tool.updateBusinessTemplateFromUrl(url)
new_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style')
......@@ -170,7 +161,7 @@ class TestTemplateTool(ERP5TypeTestCase):
# Test Another time with definning an ID
old_bt = new_bt
old_bt.setRevision(0.0002)
old_bt.setRevision('')
template_tool.updateBusinessTemplateFromUrl(url, id="new_erp5_csv_style")
new_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style')
self.assertNotEquals(old_bt, new_bt)
......@@ -184,8 +175,7 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertEquals(old_bt, new_bt)
self.assertEquals('erp5_csv_style', new_bt.getTitle())
self.assertEquals('new_erp5_csv_style', new_bt.getId())
not_installed_bt5 = getattr(template_tool, "not_installed_bt5", None)
self.assertNotEquals(not_installed_bt5, None)
not_installed_bt5 = template_tool['not_installed_bt5']
self.assertEquals('erp5_csv_style', not_installed_bt5.getTitle())
self.assertEquals(not_installed_bt5.getInstallationState(),
"not_installed")
......@@ -204,10 +194,8 @@ class TestTemplateTool(ERP5TypeTestCase):
keep_original_list=keep_original_list)
bt = template_tool.getInstalledBusinessTemplate('test_core')
self.assertNotEquals(None, bt)
erp5_test = getattr(self.portal.portal_skins, 'erp5_test', None)
self.assertNotEquals(None, erp5_test)
test_file = getattr(erp5_test, 'test_file', None)
self.assertEquals(None, test_file)
erp5_test = self.portal.portal_skins['erp5_test']
self.assertFalse(erp5_test.hasObject('test_file'))
def test_updateBusinessTemplateFromUrl_after_before_script(self):
"""
......@@ -248,48 +236,6 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertEquals(bt.getChangeLog(), 'MODIFIED')
self.assertEquals(portal.getTitle(), 'MODIFIED')
def test_updateBusinessTemplateFromUrl_stringCastingBug(self):
pt = self.getTemplateTool()
template = pt.newContent(portal_type='Business Template')
self.failUnless(template.getBuildingState() == 'draft')
self.failUnless(template.getInstallationState() == 'not_installed')
title = 'install_casting_to_int_bug_check'
template.edit(title=title,
version='1.0',
description='bt for unit_test')
self.commit()
template.build()
self.commit()
cfg = getConfiguration()
template_path = os.path.join(cfg.instancehome, 'tests', '%s' % (title,))
# remove previous version of bt it exists
if os.path.exists(template_path):
shutil.rmtree(template_path)
template.export(path=template_path, local=1)
self.failUnless(os.path.exists(template_path))
# setup version '9'
first_revision = '9'
open(os.path.join(template_path, 'bt', 'revision'), 'w').write(first_revision)
pt.updateBusinessTemplateFromUrl(template_path)
new_bt = pt.getInstalledBusinessTemplate(title)
self.assertEqual(new_bt.getRevision(), first_revision)
# setup revision '11', becasue: '11' < '9' (string comp), but 11 > 9 (int comp)
second_revision = '11'
self.assertTrue(second_revision < first_revision)
self.assertTrue(int(second_revision) > int(first_revision))
open(os.path.join(template_path, 'bt', 'revision'), 'w').write(second_revision)
pt.updateBusinessTemplateFromUrl(template_path)
newer_bt = pt.getInstalledBusinessTemplate(title)
self.assertNotEqual(new_bt, newer_bt)
self.assertEqual(newer_bt.getRevision(), second_revision)
def test_CompareVersions(self):
"""Tests compare version on template tool. """
compareVersions = self.getPortal().portal_templates.compareVersions
......@@ -320,12 +266,45 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertEquals(None, self.getPortal()\
.portal_templates.getInstalledBusinessTemplate('erp5_toto'))
def test_getInstalledBusinessTemplateRevision(self):
self.assertTrue(300 < self.getPortal()\
.portal_templates.getInstalledBusinessTemplateRevision('erp5_core'))
self.assertEquals(None, self.getPortal()\
.portal_templates.getInstalledBusinessTemplateRevision('erp5_toto'))
def test_revision(self):
template_tool = self.portal.portal_templates
getInstalledRevision = template_tool.getInstalledBusinessTemplateRevision
self.assertEqual(None, getInstalledRevision('erp5_toto'))
available_bt, = template_tool.getRepositoryBusinessTemplateList(
template_list=('test_core',))
revision = available_bt.getRevision()
self.assertEqual('PN8VPt52MbdHtxfjKvL+MBsNbzM=', revision)
installed_bt = template_tool.download("%s/%s" % (available_bt.repository,
available_bt.filename))
self.assertEqual(revision, installed_bt.getRevision())
installed_bt.install()
self.assertEqual(revision, getInstalledRevision('test_core'))
bt = installed_bt.Base_createCloneDocument(batch_mode=1)
bt.build(update_revision=False)
root = tempfile.mkdtemp()
try:
bt.export(root, local=1)
with open(os.path.join(root, 'bt', 'title')) as f:
self.assertTrue('test_core', f.read())
# We don't export revision anymore.
self.assertFalse(os.path.exists(os.path.join(root, 'bt', 'revision')))
# Computed at download ...
self.assertEqual(revision, template_tool.download(root).getRevision())
finally:
shutil.rmtree(root)
bt._setVersion("2.0")
# ... at building by default ...
bt.build()
revision = bt.getRevision()
self.assertEqual('tPNr/gGXaa0fYCsFUWe8nqzSNLc=', revision)
self.portal.portal_skins.erp5_test.manage_renameObject('test_file',
'test_file2')
bt.build(update_revision=False)
self.assertEqual(revision, bt.getRevision())
# ... and at export.
bt.export(str(random.random()))
self.assertEqual('Nup/xsO1xpsmdJ5GTdknuVJyOr8=', bt.getRevision())
self.abort()
def test_getInstalledBusinessTemplateList(self):
templates_tool = self.getPortal().portal_templates
......@@ -479,9 +458,9 @@ class TestTemplateTool(ERP5TypeTestCase):
bt_old = self.templates_tool.getInstalledBusinessTemplate(bt5_name, strict=True)
self.assertEquals(bt.getId(), bt_old.getId())
# Repeat operation, new bt5 should be inslalled due only_newer = False
# Repeat operation, new bt5 should be inslalled due only_different = False
operation_log = self.templates_tool.installBusinessTemplateListFromRepository(
[bt5_name], only_newer=False)
[bt5_name], only_different=False)
self.assertTrue("Installed %s with" % bt5_name in operation_log[-1])
bt_new = self.templates_tool.getInstalledBusinessTemplate(bt5_name,
......@@ -498,7 +477,7 @@ class TestTemplateTool(ERP5TypeTestCase):
bt = template_tool.getInstalledBusinessTemplate(bt5_name)
self.assertEquals(bt, None)
operation_log = template_tool.installBusinessTemplateListFromRepository([bt5_name],
only_newer=False, update_catalog=0)
only_different=False, update_catalog=0)
self.assertTrue("Installed %s with" % bt5_name in operation_log[0])
bt = template_tool.getInstalledBusinessTemplate(bt5_name)
......@@ -514,7 +493,7 @@ class TestTemplateTool(ERP5TypeTestCase):
bt5_name = 'erp5_odt_style'
operation_log = template_tool.installBusinessTemplateListFromRepository([bt5_name],
only_newer=False, update_catalog=1)
only_different=False, update_catalog=1)
self.assertTrue("Installed %s with" % bt5_name in operation_log[-1])
bt = template_tool.getInstalledBusinessTemplate(bt5_name)
self.assertEquals(bt.getTitle(), bt5_name)
......@@ -524,7 +503,7 @@ class TestTemplateTool(ERP5TypeTestCase):
# Install again should not force catalog to be updated
operation_log = template_tool.installBusinessTemplateListFromRepository(
[bt5_name], only_newer=False)
[bt5_name], only_different=False)
self.assertTrue("Installed %s with" % bt5_name in operation_log[-1])
bt = template_tool.getInstalledBusinessTemplate(bt5_name)
self.assertNotEquals(bt, None)
......@@ -609,7 +588,7 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertNotEquals(bt, None)
bt = template_tool.getInstalledBusinessTemplate("erp5_workflow")
self.assertNotEquals(bt, None)
transaction.abort()
self.abort()
# Same as above but also check that dependencies are properly resolved if
# one of the dependency is explicitly added to the list of bt5 to be
......@@ -625,22 +604,23 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertNotEquals(bt, None)
bt = template_tool.getInstalledBusinessTemplate("erp5_workflow")
self.assertNotEquals(bt, None)
transaction.abort()
self.abort()
def test_installBusinessTemplateListFromRepository_ignore_when_installed(self):
"""Check that install one business template, this method does not download
many business templates that are already installed
"""
template_tool = self.portal.portal_templates
# Delete not installed bt5 to check easily if more not installed was
# created
for bt5 in template_tool.getBuiltBusinessTemplateList():
bt5.delete()
bt5_name_list = ['erp5_calendar']
template_tool.installBusinessTemplateListFromRepository(bt5_name_list,
before = dict((bt.getTitle(), bt.getId())
for bt in template_tool.getInstalledBusinessTemplateList())
bt_title = 'erp5_calendar'
template_tool.installBusinessTemplateListFromRepository([bt_title],
install_dependency=True)
self.tic()
self.assertEquals(template_tool.getBuiltBusinessTemplateList(), [])
after = dict((bt.getTitle(), bt.getId())
for bt in template_tool.getInstalledBusinessTemplateList())
del after[bt_title]
self.assertEqual(before, after)
def test_sortBusinessTemplateList(self):
"""Check sorting of a list of business template by their dependencies
......
......@@ -461,43 +461,15 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase):
DeprecationWarning)
return self.createUserAssignment(user, assignment_kw)
def setupAutomaticBusinessTemplateRepository(self, accept_public=True,
searchable_business_template_list=None):
# Try to setup some valid Repository List by reusing ERP5TypeTestCase API.
# if accept_public we can accept public repository can be set, otherwise
# we let failure happens.
if searchable_business_template_list is None:
searchable_business_template_list = ["erp5_base"]
# Assume that the public official repository is a valid repository
public_bt5_repository_list = ['http://www.erp5.org/dists/snapshot/bt5/']
template_list = []
for bt_id in searchable_business_template_list:
bt_template_list = self._getBTPathAndIdList([bt_id])
if len(bt_template_list):
template_list.append(bt_template_list[0])
if len(template_list) > 0:
bt5_repository_path_list = ["/".join(x[0].split("/")[:-1])
for x in template_list]
if accept_public:
try:
self.portal.portal_templates.updateRepositoryBusinessTemplateList(
bt5_repository_path_list, None)
except (RuntimeError, IOError), e:
# If bt5 repository is not a repository use public one.
self.portal.portal_templates.updateRepositoryBusinessTemplateList(
public_bt5_repository_list)
else:
self.portal.portal_templates.updateRepositoryBusinessTemplateList(
bt5_repository_path_list, None)
elif accept_public:
self.portal.portal_templates.updateRepositoryBusinessTemplateList(
public_bt5_repository_list)
else:
raise ValueError("ERP5 was unable to determinate a valid local " + \
"repository, please check your environment or " + \
"use accept_public as True")
def setupAutomaticBusinessTemplateRepository(self,
searchable_business_template_list=("erp5_base",)):
template_tool = self.portal.portal_templates
bt_set = set(searchable_business_template_list).difference(x['title']
for x in template_tool.repository_dict.itervalues() for x in x)
if bt_set:
template_tool.updateRepositoryBusinessTemplateList(set(
os.path.dirname(x[0]) for x in self._getBTPathAndIdList(bt_set)),
genbt5list=1)
def failIfDifferentSet(self, a, b, msg=""):
if not msg:
......
......@@ -187,7 +187,7 @@ class WorkingCopy(Implicit):
"""
if business_template.getBuildingState() == 'draft':
business_template.edit()
business_template.build()
business_template.build(update_revision=False)
self._export(business_template)
def _export(self, business_template):
......@@ -200,16 +200,6 @@ class WorkingCopy(Implicit):
def update(self, keep=False):
raise NotAWorkingCopyError
def newRevision(self):
path = os.path.join('bt', 'revision')
try:
revision = int(self.showOld(path)) + 1
except NotVersionedError:
return 1
with open(os.path.join(self.working_copy, path), 'w') as file:
file.write(str(revision))
return revision
def hasDiff(self, path):
try:
hasDiff = aq_base(self).__hasDiff
......@@ -329,7 +319,7 @@ class WorkingCopy(Implicit):
title='tmp_bt_revert',
template_path_list=path_added_list)
tmp_bt.edit()
tmp_bt.build()
tmp_bt.build(update_revision=False)
# Install then uninstall it to remove objects from ZODB
tmp_bt.install()
tmp_bt.uninstall()
......
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