diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py index b9273d13b3b84c52e288246bb5f7bbdc70aa3ee4..4ab62b0cab025658169d34c50d80ca61a48bd2dd 100644 --- a/product/ERP5/Document/BusinessTemplate.py +++ b/product/ERP5/Document/BusinessTemplate.py @@ -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()) diff --git a/product/ERP5/Tool/TemplateTool.py b/product/ERP5/Tool/TemplateTool.py index 2a337b9b2136abe87c255096b20d1a899b234132..439291d5dff6c43f53625fcb62af1755c2e45857 100644 --- a/product/ERP5/Tool/TemplateTool.py +++ b/product/ERP5/Tool/TemplateTool.py @@ -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,15 +971,9 @@ 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: + if self.compareVersions(previous_property_dict['version'], + property_dict['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']): - template_item_dict[title] = (repository, property_dict) # Next, select only updated business templates. if update_only: for repository, property_dict in template_item_dict.values(): @@ -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: - 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 only_different: + bt = template_url_dict.get(bt_id) + 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: - 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)) - return imported_bt5 - except ValueError: - pass + if only_different: + previous_bt5 = self.getInstalledBusinessTemplate(bt_title) + 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 install_kw = {} for listbox_line in imported_bt5.BusinessTemplate_getModifiedObject(): diff --git a/product/ERP5/bin/genbt5list b/product/ERP5/bin/genbt5list index f3367044f986f77fba1f1b1252552253d2926f82..2608602dc4798c860f9ade53fcb72b6fbab878a9 100755 --- a/product/ERP5/bin/genbt5list +++ b/product/ERP5/bin/genbt5list @@ -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() - -def readBusinessTemplate(tar): - """Read an archived Business Template info. - """ - property_dict = {} - 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 + 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 __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: + 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') diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view.xml index 1d4b69b39414cbd83a796769fa0cf30a9244e7d1..83f050f8fa7b0b01c33a4d6d5f388dc8b5e84f26 100644 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view.xml +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view.xml @@ -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> diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view/my_revision.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view/my_revision.xml deleted file mode 100644 index 3d15e1286e071575d45bfb850c11f8842e4a27e8..0000000000000000000000000000000000000000 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view/my_revision.xml +++ /dev/null @@ -1,260 +0,0 @@ -<?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> diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view/my_short_revision.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view/my_short_revision.xml new file mode 100644 index 0000000000000000000000000000000000000000..c39cd9c9e7c6ff1db9749119968c6739cd30ca14 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/BusinessTemplate_view/my_short_revision.xml @@ -0,0 +1,106 @@ +<?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> diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewBusinessTemplateList/listbox.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewBusinessTemplateList/listbox.xml index e89be774746715414206e7efaf8420781b767c26..ed173bff20b48dce2b13b3a47d9e204cbfd68f3a 100644 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewBusinessTemplateList/listbox.xml +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewBusinessTemplateList/listbox.xml @@ -349,7 +349,7 @@ <string>Version</string> </tuple> <tuple> - <string>revision</string> + <string>short_revision</string> <string>Revision</string> </tuple> <tuple> diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewInstallRepositoryBusinessTemplateListDialog/listbox.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewInstallRepositoryBusinessTemplateListDialog/listbox.xml index 71cd4082ac498255bfee3454f3b24506f5bfed01..1e9e616b82051f0c5317f61e78f6a2dc0b4af921 100644 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewInstallRepositoryBusinessTemplateListDialog/listbox.xml +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewInstallRepositoryBusinessTemplateListDialog/listbox.xml @@ -340,7 +340,7 @@ <string>Version</string> </tuple> <tuple> - <string>revision</string> + <string>short_revision</string> <string>Revision</string> </tuple> <tuple> diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewUpgradeRepositoryBusinessTemplateListDialog/listbox.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewUpgradeRepositoryBusinessTemplateListDialog/listbox.xml index 6e0a3ece833a226fad2a8ada1efb92742faeadff..d8f1a26e2a7594e5428507f36f8d337dbd563260 100644 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewUpgradeRepositoryBusinessTemplateListDialog/listbox.xml +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/TemplateTool_viewUpgradeRepositoryBusinessTemplateListDialog/listbox.xml @@ -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> diff --git a/product/ERP5/genbt5list.py b/product/ERP5/genbt5list.py new file mode 120000 index 0000000000000000000000000000000000000000..938efd27c7148d704f79269d15774049b183c302 --- /dev/null +++ b/product/ERP5/genbt5list.py @@ -0,0 +1 @@ +bin/genbt5list \ No newline at end of file diff --git a/product/ERP5/tests/testBusinessTemplate.py b/product/ERP5/tests/testBusinessTemplate.py index be7e6234fa5f99297c92f65958e6a4b0f652e4c2..a36589aed9e834f1d4958ca9a9633124ce664312 100644 --- a/product/ERP5/tests/testBusinessTemplate.py +++ b/product/ERP5/tests/testBusinessTemplate.py @@ -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() diff --git a/product/ERP5/tests/testTemplateTool.py b/product/ERP5/tests/testTemplateTool.py index cd0119895292d874ef0c24916a59e3096c8afbd9..d9d84fb4b98afd7a5f49e787d30b34544d156357 100644 --- a/product/ERP5/tests/testTemplateTool.py +++ b/product/ERP5/tests/testTemplateTool.py @@ -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) - - 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"]) - 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) + erp5_base = self.templates_tool.getInstalledBusinessTemplate('erp5_base', + strict=True) + erp5_base._setRevision('') + + self.assertTrue("erp5_base" in (bt.getTitle() for bt in + self.templates_tool.getRepositoryBusinessTemplateList(update_only=True))) + erp5_base.replace() + 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 diff --git a/product/ERP5Type/tests/ERP5TypeTestCase.py b/product/ERP5Type/tests/ERP5TypeTestCase.py index f322751c55ccdc8410bc57dede959f14131e65bc..d4a0fb2f40ee1eb26907ed5d18641ed9b29b406b 100644 --- a/product/ERP5Type/tests/ERP5TypeTestCase.py +++ b/product/ERP5Type/tests/ERP5TypeTestCase.py @@ -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: diff --git a/product/ERP5VCS/WorkingCopy.py b/product/ERP5VCS/WorkingCopy.py index a49b7d90530a84c15d504a946c0d132c3fa8c579..0e73d38be658742e96f97273ec90edee2e8f765f 100644 --- a/product/ERP5VCS/WorkingCopy.py +++ b/product/ERP5VCS/WorkingCopy.py @@ -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()