diff --git a/product/ERP5/Tool/TemplateTool.py b/product/ERP5/Tool/TemplateTool.py index 538092a4a9c7b144052e5aed47b70aaf20ae10a4..f5fcc51bdf575253ad20096f54f1c141a09cafc9 100644 --- a/product/ERP5/Tool/TemplateTool.py +++ b/product/ERP5/Tool/TemplateTool.py @@ -38,13 +38,14 @@ from Globals import InitializeClass, DTMLFile, PersistentMapping from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type import Permissions from Products.ERP5.Document.BusinessTemplate import TemplateConditionError +from Products.ERP5.Document.BusinessTemplate import BusinessTemplateMissingDependency from tempfile import mkstemp, mkdtemp from Products.ERP5 import _dtmldir from OFS.Traversable import NotFound from difflib import unified_diff from cStringIO import StringIO from zLOG import LOG -from urllib import pathname2url, urlopen, splittype, urlretrieve +from urllib import pathname2url, urlopen, splittype, urlretrieve, quote import re from xml.dom.minidom import parse import struct @@ -56,6 +57,22 @@ except ImportError: from Products.ERP5Type.Message import Message N_ = lambda msgid, **kw: Message('ui', msgid, **kw) +class BusinessTemplateUnknownError(Exception): + """ Exception raised when the business template + is impossible to find in the repositories + """ + pass + +class UnsupportedComparingOperator(Exception): + """ Exception when the comparing string is unsupported + """ + pass + +class BusinessTemplateIsMeta(Exception): + """ Exception when the business template is provided by another one + """ + pass + class LocalConfiguration(Implicit): """ Contains local configuration information @@ -427,7 +444,7 @@ class TemplateTool (BaseTool): """ self.repository_dict = PersistentMapping() property_list = ('title', 'version', 'revision', 'description', 'license', - 'dependency', 'copyright') + 'dependency', 'provision', 'copyright') #LOG('updateRepositoryBusiessTemplateList', 0, # 'repository_list = %r' % (repository_list,)) for repository in repository_list: @@ -467,6 +484,8 @@ class TemplateTool (BaseTool): temp_property_dict.get('license', [''])[0] property_dict['dependency_list'] = \ temp_property_dict.get('dependency', ()) + property_dict['provision_list'] = \ + temp_property_dict.get('provision', ()) property_dict['copyright_list'] = \ temp_property_dict.get('copyright', ()) @@ -499,7 +518,198 @@ class TemplateTool (BaseTool): Return a repository and an id. """ return cPickle.loads(b64decode(uid)) - + + security.declarePublic( 'encodeRepositoryBusinessTemplateUid' ) + def encodeRepositoryBusinessTemplateUid(self, repository, id): + """ + encode the repository and the id of a business template. + Return an uid. + """ + return b64encode(cPickle.dumps((repository, id))) + + def compareVersionStrings(self, version, comparing_string): + """ + comparing_string is like "<= 0.2" | "operator version" + operators supported: '<=', '<' or '<<', '>' or '>>', '>=', '=' or '==' + """ + operator, comp_version = comparing_string.split(' ') + diff_version = self.compareVersions(version, comp_version) + if operator == '<' or operator == '<<': + if diff_version < 0: + return True; + return False; + if operator == '<=': + if diff_version <= 0: + return True; + return False; + if operator == '>' or operator == '>>': + if diff_version > 0: + return True; + return False; + if operator == '>=': + if diff_version >= 0: + return True; + return False; + if operator == '=' or operator == '==': + if diff_version == 0: + return True; + return False; + raise UnsupportedComparingOperator, 'Unsupported comparing operator: %s'%(operator,) + + security.declareProtected(Permissions.AccessContentsInformation, + 'IsOneProviderInstalled') + def IsOneProviderInstalled(self, title): + """ + return true if a business template that + provides the bt with the given title is + installed + """ + installed_bt_list = self.getInstalledBusinessTemplatesList() + for bt in installed_bt_list: + provision_list = bt.getProvisionList() + if title in provision_list: + return True + return False + + security.declareProtected(Permissions.AccessContentsInformation, + 'getLastestBTOnRepos') + def getLastestBTOnRepos(self, title, version_restriction=None): + """ + It's possible we have different versions of the same BT + available on various repositories or on the same repository. + This function returns the latest one that meet the version_restriction + (i.e "<= 0.2") in the following form : + tuple (repository, id) + """ + result = None + for repository, property_dict_list in self.repository_dict.items(): + for property_dict in property_dict_list: + provision_list = property_dict.get('provision_list', []) + if title in provision_list: + raise BusinessTemplateIsMeta, 'Business Template %s is provided by another one'%(title,) + if title == property_dict['title']: + if (version_restriction is None) or (self.compareVersionStrings(property_dict['version'], version_restriction)): + if (result is None) or (self.compareVersions(property_dict['version'], result[2]) > 0): + result = (repository, property_dict['id'], property_dict['version']) + if result is not None: + return (result[0], result[1]) + else: + raise BusinessTemplateUnknownError, 'Business Template %s (%s) could not be found in the repositories'%(title, version_restriction or '') + + security.declareProtected(Permissions.AccessContentsInformation, + 'getProviderList') + def getProviderList(self, title): + """ + return a list of business templates that provides + the given business template + """ + result_list = [] + for repository, property_dict_list in self.repository_dict.items(): + for property_dict in property_dict_list: + provision_list = property_dict['provision_list'] + if (title in provision_list) and (property_dict['title'] not in result_list): + result_list.append(property_dict['title']) + return result_list + + security.declareProtected(Permissions.AccessContentsInformation, + 'getDependencyList') + def getDependencyList(self, bt): + """ + Return the list of missing dependencies for a business + template, given a tuple : (repository, id) + """ + # We do not take into consideration the dependencies + # for meta business templates + if bt[0] == 'meta': + return [] + result_list = [] + for repository, property_dict_list in self.repository_dict.items(): + if repository == bt[0]: + for property_dict in property_dict_list: + if property_dict['id'] == bt[1]: + dependency_list = property_dict['dependency_list'] + for dependency_couple in dependency_list: + # dependency_couple is like "erp5_xhtml_style (>= 0.2)" + dependency_couple_list = dependency_couple.split(' ', 1) + dependency = dependency_couple_list[0] + version_restriction = None + if len(dependency_couple_list) > 1: + # remove parenthesis to get something like ">= O.2" + version_restriction = dependency_couple_list[1][1:-1] + require_update = False + installed_bt = self.portal_templates.getInstalledBusinessTemplate(dependency) + if version_restriction is not None: + if installed_bt is not None: + # Check if the installed version require an update + if not self.compareVersionStrings(installed_bt.getVersion(), version_restriction): + operator = version_restriction.split(' ')[0] + if operator in ('<', '<<', '<='): + raise BusinessTemplateMissingDependency, '%s (%s) is present but %s require: %s (%s)'%(dependency, installed_bt.getVersion(), property_dict['title'], dependency, version_restriction) + else: + require_update = True + if (require_update or installed_bt is None) \ + and dependency not in result_list: + # Get the lastest version of the dependency on the + # repository that meet the version restriction + provider_installed = False + try: + bt_dep = self.getLastestBTOnRepos(dependency, version_restriction) + except BusinessTemplateUnknownError: + raise BusinessTemplateMissingDependency, 'The following dependency could not be satisfied: %s (%s)\nReason: Business Template could not be found in the repositories'%(dependency, version_restriction or '') + except BusinessTemplateIsMeta: + provider_list = self.getProviderList(dependency) + for provider in provider_list: + if self.portal_templates.getInstalledBusinessTemplate(provider) is not None: + provider_installed = True + break + if not provider_installed: + bt_dep = ('meta', dependency) + if not provider_installed: + sub_dep_list = self.getDependencyList(bt_dep) + for sub_dep in sub_dep_list: + if sub_dep not in result_list: + result_list.append(sub_dep) + result_list.append(bt_dep) + return result_list + raise BusinessTemplateUnknownError, 'The Business Template %s could not be found on repository %s'%(bt[1], bt[0]) + + security.declareProtected(Permissions.AccessContentsInformation, + 'urlQuote') + def urlQuote(self, url): + """ wrapper for urllib.quote() + """ + return quote(url) + + def findProviderInBTList(self, provider_list, bt_list): + """ + Find one provider in provider_list which is present in + bt_list and returns the found tuple (repository, id) + in bt_list. + """ + for provider in provider_list: + for repository, id in bt_list: + if id.startswith(provider): + return (repository, id) + raise BusinessTemplateUnknownError, 'Provider not found in bt_list' + + security.declareProtected(Permissions.AccessContentsInformation, + 'sortBusinessTemplateList') + def sortBusinessTemplateList(self, bt_list): + """ + Sort a list of bt according to dependencies + """ + result_list = [] + for repository, id in bt_list: + dependency_list = self.getDependencyList((repository, id)) + dependency_list.append((repository, id)) + for dependency in dependency_list: + if dependency[0] == 'meta': + provider_list = self.getProviderList(dependency[1]) + dependency = self.findProviderInBTList(provider_list, bt_list) + if dependency not in result_list: + result_list.append(dependency) + return result_list + security.declareProtected( Permissions.AccessContentsInformation, 'getRepositoryBusinessTemplateList' ) def getRepositoryBusinessTemplateList(self, update_only=0, **kw): @@ -550,7 +760,6 @@ class TemplateTool (BaseTool): and property_dict['revision'] \ and installed_bt.getRevision() < property_dict['revision'] : template_item_list.append((repository, property_dict)) - # FIXME: resolve dependencies else: for repository, property_dict_list in self.repository_dict.items(): for property_dict in property_dict_list: