Commit 3caddcdf authored by Julien Muchembled's avatar Julien Muchembled

Allow portal types to customize dynamically initialization of property holders

* Replace direct access to some ERP5TypeInformation attributes by use of
  accessors:
  - base_category_list -> getTypeBaseCategoryList
  - property_sheet_list -> getTypePropertySheetList
* Split Base.initializePortalTypeDynamicProperties to delegate some work to
  portal types (new 'updatePropertySheetDefinitionDict' method).

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@30159 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 9fd4bc75
...@@ -98,8 +98,9 @@ class TestCMFCategory(ERP5TypeTestCase): ...@@ -98,8 +98,9 @@ class TestCMFCategory(ERP5TypeTestCase):
organisation_ti = self.getTypesTool().getTypeInfo('Organisation') organisation_ti = self.getTypesTool().getTypeInfo('Organisation')
organisation_ti.filter_content_types = 0 organisation_ti.filter_content_types = 0
# we also enable 'destination' category on organisations # we also enable 'destination' category on organisations
self._organisation_categories = cat = organisation_ti.base_category_list self._organisation_categories = organisation_ti.getTypeBaseCategoryList()
organisation_ti.base_category_list = tuple(list(cat) + ['destination']) organisation_ti._setTypeBaseCategoryList(self._organisation_categories
+ ['destination'])
# Make persons. # Make persons.
person_module = self.getPersonModule() person_module = self.getPersonModule()
......
...@@ -89,7 +89,7 @@ class TestERP5Category(ERP5TypeTestCase): ...@@ -89,7 +89,7 @@ class TestERP5Category(ERP5TypeTestCase):
# associate base categories on Organisation portal type # associate base categories on Organisation portal type
portal_type = self.getTypeTool()[self.portal_type] portal_type = self.getTypeTool()[self.portal_type]
portal_type.base_category_list = [self.base_cat, self.base_cat2] portal_type._setTypeBaseCategoryList([self.base_cat, self.base_cat2])
# Reset aq dynamic # Reset aq dynamic
_aq_reset() _aq_reset()
...@@ -322,8 +322,8 @@ class TestERP5Category(ERP5TypeTestCase): ...@@ -322,8 +322,8 @@ class TestERP5Category(ERP5TypeTestCase):
# associate the base category with our portal types # associate the base category with our portal types
ttool = self.getTypesTool() ttool = self.getTypesTool()
ttool['Organisation'].base_category_list = ['test_aq_category'] ttool['Organisation']._setTypeBaseCategoryList(['test_aq_category'])
ttool['Telephone'].base_category_list = ['test_aq_category'] ttool['Telephone']._setTypeBaseCategoryList(['test_aq_category'])
doc = self.organisation doc = self.organisation
subdoc = doc['1'] subdoc = doc['1']
......
...@@ -145,8 +145,8 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional): ...@@ -145,8 +145,8 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
self.portal.unittest_module.getShortTitleTranslationDomain()) self.portal.unittest_module.getShortTitleTranslationDomain())
type_information = self.portal.portal_types[module_portal_type] type_information = self.portal.portal_types[module_portal_type]
self.assertTrue('business_application' in type_information.base_category_list) self.assertTrue('business_application'
in type_information.getTypeBaseCategoryList())
def test_02_FavouritesMenu(self, quiet=quiet, run=run_all_test): def test_02_FavouritesMenu(self, quiet=quiet, run=run_all_test):
""" """
......
...@@ -75,7 +75,7 @@ class TestInteractionWorkflow(ERP5TypeTestCase): ...@@ -75,7 +75,7 @@ class TestInteractionWorkflow(ERP5TypeTestCase):
Organisation = Products.ERP5Type.Document.Organisation.Organisation Organisation = Products.ERP5Type.Document.Organisation.Organisation
Organisation.doSomethingStupid = doSomethingStupid Organisation.doSomethingStupid = doSomethingStupid
portal_type = self.getTypeTool()['Organisation'] portal_type = self.getTypeTool()['Organisation']
portal_type.base_category_list = ['size'] portal_type._setTypeBaseCategoryList(['size'])
organisation_module = self.getOrganisationModule() organisation_module = self.getOrganisationModule()
self.organisation = organisation_module.newContent( self.organisation = organisation_module.newContent(
portal_type = self.portal_type) portal_type = self.portal_type)
......
...@@ -2161,16 +2161,7 @@ class TestInventoryDocument(InventoryAPITestCase): ...@@ -2161,16 +2161,7 @@ class TestInventoryDocument(InventoryAPITestCase):
does not allow such things does not allow such things
""" """
portal = self.getPortal() portal = self.getPortal()
self._addPropertySheet('Inventory', 'InventoryConstraint')
portal_type_name = 'Inventory'
property_sheet_name = 'InventoryConstraint'
# We set the property sheet on the portal type
ti = self.getTypesTool().getTypeInfo(portal_type_name)
ti.property_sheet_list = list(ti.property_sheet_list) +\
[property_sheet_name]
# reset aq_dynamic cache
_aq_reset()
try: try:
inventory_module = portal.getDefaultModule(portal_type='Inventory') inventory_module = portal.getDefaultModule(portal_type='Inventory')
inventory = inventory_module.newContent(portal_type='Inventory') inventory = inventory_module.newContent(portal_type='Inventory')
...@@ -2222,17 +2213,16 @@ class TestInventoryDocument(InventoryAPITestCase): ...@@ -2222,17 +2213,16 @@ class TestInventoryDocument(InventoryAPITestCase):
self.assertTrue(len([x for x in workflow_error_message \ self.assertTrue(len([x for x in workflow_error_message \
if x.find('There is already an inventory')])) if x.find('There is already an inventory')]))
finally: finally:
# remove all property sheet we added to type informations # remove all property sheets we added to type informations
ttool = self.getTypesTool() ttool = self.getTypesTool()
ti = ttool.getTypeInfo(portal_type_name) for ti_name, psheet_list in self._added_property_sheets.iteritems():
ps_list = ti.property_sheet_list ti = ttool.getTypeInfo(ti_name)
psheet_list = [property_sheet_name] property_sheet_set = set(ti.getTypePropertySheetList())
for psheet in psheet_list: property_sheet_set.difference_update(psheet_list)
if psheet in ps_list: ti._setTypePropertySheetList(list(property_sheet_set))
ps_list.remove(psheet)
ti.property_sheet_list = ps_list
transaction.commit() transaction.commit()
_aq_reset() _aq_reset()
def test_15_InventoryAfterModificationInFuture(self): def test_15_InventoryAfterModificationInFuture(self):
""" """
Test inventory after adding a new movement in future Test inventory after adding a new movement in future
......
...@@ -197,19 +197,18 @@ class ResourceVariationTestCase(ERP5TypeTestCase): ...@@ -197,19 +197,18 @@ class ResourceVariationTestCase(ERP5TypeTestCase):
#adding to base categories of resources #adding to base categories of resources
#for use setRequiredSizeList and setOptionColourList methods #for use setRequiredSizeList and setOptionColourList methods
self.portal.portal_types['Product'].base_category_list = [ self.portal.portal_types['Product']._setTypeBaseCategoryList([
'required_size', 'required_size',
'option_colour', 'option_colour',
'individual_aspect'] 'individual_aspect'])
self.portal.portal_types['Service'].base_category_list = [ self.portal.portal_types['Service']._setTypeBaseCategoryList([
'required_size', 'required_size',
'option_colour', 'option_colour',
'individual_aspect'] 'individual_aspect'])
self.portal.portal_types['Component'].base_category_list = [ self.portal.portal_types['Component']._setTypeBaseCategoryList([
'required_size', 'required_size',
'option_colour', 'option_colour',
'individual_aspect'] 'individual_aspect'])
transaction.commit() transaction.commit()
self.tic() self.tic()
......
...@@ -150,12 +150,7 @@ class TestWorklist(ERP5TypeTestCase): ...@@ -150,12 +150,7 @@ class TestWorklist(ERP5TypeTestCase):
return int(action_name[left_parenthesis_offset + 1:-1]) return int(action_name[left_parenthesis_offset + 1:-1])
def associatePropertySheet(self): def associatePropertySheet(self):
from Products.ERP5Type.Base import _aq_reset self._addPropertySheet(self.checked_portal_type, 'SortIndex')
ti = self.getTypesTool().getTypeInfo(self.checked_portal_type)
ti.property_sheet_list = list(ti.property_sheet_list) + \
['SortIndex']
# reset aq_dynamic cache
_aq_reset()
def addWorkflowCataloguedVariable(self, workflow_id, variable_id): def addWorkflowCataloguedVariable(self, workflow_id, variable_id):
variables = self.getWorkflowTool()[workflow_id].variables variables = self.getWorkflowTool()[workflow_id].variables
......
...@@ -68,37 +68,28 @@ def createPreferenceToolAccessorList(portal) : ...@@ -68,37 +68,28 @@ def createPreferenceToolAccessorList(portal) :
property sheets by looking at all registered property sheets property sheets by looking at all registered property sheets
and considering those which name ends with 'Preference' and considering those which name ends with 'Preference'
""" """
attr_list = [] property_list = []
typestool = getToolByName(portal, 'portal_types')
pref_portal_type = typestool.getTypeInfo('Preference')
# 'Dynamic' property sheets added through ZMI # 'Dynamic' property sheets added by portal_type
zmi_property_sheet_list = [] pref_portal_type = portal.portal_types.getTypeInfo('Preference')
if pref_portal_type is None: if pref_portal_type is None:
LOG('ERP5Form.PreferenceTool', PROBLEM, LOG('ERP5Form.PreferenceTool', PROBLEM,
'Preference type information is not installed.') 'Preference type information is not installed.')
else: else:
for property_sheet in pref_portal_type.property_sheet_list : pref_portal_type.updatePropertySheetDefinitionDict(
try: {'_properties': property_list})
zmi_property_sheet_list.append(
getattr(__import__(property_sheet), property_sheet))
except ImportError, e :
LOG('ERP5Form.PreferenceTool', PROBLEM,
'unable to import Property Sheet %s' % property_sheet, e)
# 'Static' property sheets defined on the class # 'Static' property sheets defined on the class
# The Preference class should be imported from the common location # The Preference class should be imported from the common location
# in ERP5Type since it could be overloaded in another product # in ERP5Type since it could be overloaded in another product
from Products.ERP5Type.Document.Preference import Preference from Products.ERP5Type.Document.Preference import Preference
class_property_sheet_list = Preference.property_sheets for property_sheet in Preference.property_sheets:
# We can now merge property_list += property_sheet._properties
for property_sheet in ( tuple(zmi_property_sheet_list) +
class_property_sheet_list ) : # Generate common method names
# then generate common method names for prop in property_list:
for prop in property_sheet._properties : if prop.get('preference'):
if not prop.get('preference', 0) :
# only properties marked as preference are used # only properties marked as preference are used
continue
attribute = prop['id'] attribute = prop['id']
attr_list = [ 'get%s' % convertToUpperCase(attribute)] attr_list = [ 'get%s' % convertToUpperCase(attribute)]
if prop['type'] in list_types : if prop['type'] in list_types :
......
...@@ -549,41 +549,29 @@ def initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, portal): ...@@ -549,41 +549,29 @@ def initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, portal):
initializePortalTypeDynamicProperties(parent_object, parent_klass, initializePortalTypeDynamicProperties(parent_object, parent_klass,
parent_type, parent_type,
parent_object._aq_key(), portal) parent_object._aq_key(), portal)
# Initiatise portal_type properties (XXX)
ptype_object = getattr(aq_base(portal.portal_types), ptype, None)
prop_list = list(getattr(klass, '_properties', [])) prop_list = list(getattr(klass, '_properties', []))
cat_list = list(getattr(klass, '_categories', [])) cat_list = list(getattr(klass, '_categories', []))
constraint_list = list(getattr(klass, '_constraints', [])) constraint_list = list(getattr(klass, '_constraints', []))
if ptype_object is not None: ps_definition_dict = {'_properties': prop_list,
# Make sure this is an ERP5Type object
ps_list = [getattr(PropertySheet, p, None) for p in
ptype_object.property_sheet_list]
ps_list = [p for p in ps_list if p is not None]
# Always append the klass.property_sheets to this list (for compatibility)
# Because of the order we generate accessors, it is still possible
# to overload data access for some accessors
ps_list = tuple(ps_list) + getClassPropertyList(klass)
#LOG('ps_list',0, str(ps_list))
else:
ps_list = getClassPropertyList(klass)
for base in ps_list:
property_sheet_definition_dict = {
'_properties': prop_list,
'_categories': cat_list, '_categories': cat_list,
'_constraints': constraint_list '_constraints': constraint_list}
}
for ps_property_name, current_list in \
property_sheet_definition_dict.items():
if hasattr(base, ps_property_name):
ps_property = getattr(base, ps_property_name)
if isinstance(ps_property, (tuple, list)):
current_list += ps_property
else :
raise ValueError, "%s is not a list for %s" % (ps_property_name,
base)
# Initialize portal_type properties (XXX)
# Always do it before processing klass.property_sheets (for compatibility).
# Because of the order we generate accessors, it is still possible
# to overload data access for some accessors.
ptype_object = portal.portal_types._getOb(ptype, None)
if ptype_object is not None: if ptype_object is not None:
cat_list += ptype_object.base_category_list ptype_object.updatePropertySheetDefinitionDict(ps_definition_dict)
for base in getClassPropertyList(klass):
for list_name, current_list in ps_definition_dict.items():
try:
current_list += getattr(base, list_name, ())
except TypeError:
raise ValueError("%s is not a list for %s" % (list_name, base))
prop_holder._portal_type = ptype prop_holder._portal_type = ptype
prop_holder._properties = prop_list prop_holder._properties = prop_list
prop_holder._categories = cat_list prop_holder._categories = cat_list
......
...@@ -420,6 +420,33 @@ class ERP5TypeInformation(XMLObject, ...@@ -420,6 +420,33 @@ class ERP5TypeInformation(XMLObject,
return ob return ob
security.declarePrivate('updatePropertySheetDefinitionDict')
def updatePropertySheetDefinitionDict(self, definition_dict):
for property_sheet_name in self.getTypePropertySheetList():
base = getattr(PropertySheet, property_sheet_name, None)
if base is not None:
for list_name, property_list in definition_dict.items():
try:
property_list += getattr(base, list_name, ())
except TypeError:
raise ValueError("%s is not a list for %s" % (list_name, base))
if '_categories' in definition_dict:
definition_dict['_categories'] += self.getTypeBaseCategoryList()
# The following 2 methods are needed before there are generated.
security.declareProtected(Permissions.AccessContentsInformation,
'getTypePropertySheetList')
def getTypePropertySheetList(self):
"""Getter for 'type_property_sheet' property"""
return list(self.property_sheet_list)
security.declareProtected(Permissions.AccessContentsInformation,
'getTypeBaseCategoryList')
def getTypeBaseCategoryList(self):
"""Getter for 'type_base_category' property"""
return list(self.base_category_list)
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getInstanceBaseCategoryList') 'getInstanceBaseCategoryList')
def getInstanceBaseCategoryList(self): def getInstanceBaseCategoryList(self):
...@@ -534,8 +561,8 @@ class ERP5TypeInformation(XMLObject, ...@@ -534,8 +561,8 @@ class ERP5TypeInformation(XMLObject,
self.getTypeFactoryMethodId(), self.getTypeFactoryMethodId(),
self.getTypeAddPermission(), self.getTypeAddPermission(),
self.getTypeInitScriptId()] self.getTypeInitScriptId()]
search_source_list += self.getTypePropertySheetList(()) search_source_list += self.getTypePropertySheetList()
search_source_list += self.getTypeBaseCategoryList(()) search_source_list += self.getTypeBaseCategoryList()
return ' '.join(filter(None, search_source_list)) return ' '.join(filter(None, search_source_list))
security.declarePrivate('getDefaultViewFor') security.declarePrivate('getDefaultViewFor')
......
...@@ -57,40 +57,24 @@ class TranslationProviderBase(object): ...@@ -57,40 +57,24 @@ class TranslationProviderBase(object):
Create the initial list of association between property and domain name Create the initial list of association between property and domain name
""" """
property_domain_dict = {} property_domain_dict = {}
ptype_object = self
# get the klass of the object based on the constructor document
m = Products.ERP5Type._m
ptype_name = ''.join(ptype_object.id.split(' '))
constructor = self.factory # This is safer than: 'add%s' %(ptype_name)
klass = None
for method, doc in m.items():
if method == constructor:
klass = doc.klass
break
# get the property sheet list for the portal type # get the property sheet list for the portal type
# from the list of property sheet defined on the portal type ps_list = [getattr(PropertySheet, p, None)
ps_list = map(lambda p: getattr(PropertySheet, p, None), for p in self.getTypePropertySheetList()]
ptype_object.property_sheet_list) m = Products.ERP5Type._m
ps_list = filter(lambda p: p is not None, ps_list) if m.has_key(self.factory):
# from the property sheets defined on the class klass = m[self.factory].klass
if klass is not None: if klass is not None:
from Products.ERP5Type.Base import getClassPropertyList from Products.ERP5Type.Base import getClassPropertyList
ps_list = tuple(ps_list) + getClassPropertyList(klass) ps_list += getClassPropertyList(klass)
# get all properties from the property sheet list
current_list = []
for base in ps_list:
if hasattr(base, '_properties'):
# XXX must check that property is translatable
ps_property = getattr(base, '_properties')
if type(ps_property) in (type(()), type([])):
current_list += ps_property
# create TranslationInformation object for each property # create TranslationInformation object for each property
for prop in current_list: for base in ps_list:
if prop.get('translatable', 0): for prop in getattr(base, '_properties', ()):
prop_id = prop['id'] prop_id = prop['id']
if not property_domain_dict.has_key(prop_id): if prop.get('translatable') and prop_id not in property_domain_dict:
domain_name = prop.get('translation_domain', None) domain_name = prop.get('translation_domain')
property_domain_dict[prop_id] = TranslationInformation(prop_id, domain_name) property_domain_dict[prop_id] = TranslationInformation(prop_id,
domain_name)
original_property_domain_dict = getattr(aq_base(self), original_property_domain_dict = getattr(aq_base(self),
'_property_domain_dict', _MARKER) '_property_domain_dict', _MARKER)
......
...@@ -422,6 +422,9 @@ class ERP5TypeTestCase(backportUnittest.TestCase, PortalTestCase): ...@@ -422,6 +422,9 @@ class ERP5TypeTestCase(backportUnittest.TestCase, PortalTestCase):
global current_app global current_app
current_app = self.app current_app = self.app
self._updateConnectionStrings() self._updateConnectionStrings()
# keep a mapping type info name -> property sheet list, to remove them in
# tear down.
self._added_property_sheets = {}
def afterSetUp(self): def afterSetUp(self):
'''Called after setUp() has completed. This is '''Called after setUp() has completed. This is
...@@ -566,12 +569,11 @@ class ERP5TypeTestCase(backportUnittest.TestCase, PortalTestCase): ...@@ -566,12 +569,11 @@ class ERP5TypeTestCase(backportUnittest.TestCase, PortalTestCase):
# We set the property sheet on the portal type # We set the property sheet on the portal type
ti = self.getTypesTool().getTypeInfo(portal_type_name) ti = self.getTypesTool().getTypeInfo(portal_type_name)
if property_sheet_name not in ti.property_sheet_list: property_sheet_set = set(ti.getTypePropertySheetList())
ti.property_sheet_list = list(ti.property_sheet_list) +\ property_sheet_set.add(property_sheet_name)
[property_sheet_name] ti._setTypePropertySheetList(list(property_sheet_set))
# remember that we added a property sheet for tear down # remember that we added a property sheet for tear down
if getattr(self, '_added_property_sheets', None) is not None:
self._added_property_sheets.setdefault( self._added_property_sheets.setdefault(
portal_type_name, []).append(property_sheet_name) portal_type_name, []).append(property_sheet_name)
# reset aq_dynamic cache # reset aq_dynamic cache
......
...@@ -63,9 +63,6 @@ class PropertySheetTestCase(ERP5TypeTestCase): ...@@ -63,9 +63,6 @@ class PropertySheetTestCase(ERP5TypeTestCase):
"""Set up the fixture. """ """Set up the fixture. """
ERP5TypeTestCase.setUp(self) ERP5TypeTestCase.setUp(self)
installRealClassTool(self.getPortal()) installRealClassTool(self.getPortal())
# keep a mapping type info name -> property sheet list, to remove them in
# tear down.
self._added_property_sheets = {}
def tearDown(self): def tearDown(self):
"""Clean up """ """Clean up """
...@@ -75,14 +72,14 @@ class PropertySheetTestCase(ERP5TypeTestCase): ...@@ -75,14 +72,14 @@ class PropertySheetTestCase(ERP5TypeTestCase):
# remove all property sheet we added to type informations # remove all property sheet we added to type informations
for ti_name, psheet_list in self._added_property_sheets.items(): for ti_name, psheet_list in self._added_property_sheets.items():
ti = ttool.getTypeInfo(ti_name) ti = ttool.getTypeInfo(ti_name)
ps_list = ti.property_sheet_list property_sheet_set = set(ti.getTypePropertySheetList())
for psheet in psheet_list: for psheet in psheet_list:
if psheet in ps_list: if psheet in property_sheet_set:
ps_list.remove(psheet) property_sheet_set.remove(psheet)
# physically remove property sheet, otherwise invalid property sheet # physically remove property sheet, otherwise invalid property sheet
# could break next tests. # could break next tests.
removeLocalPropertySheet(psheet) removeLocalPropertySheet(psheet)
ti.property_sheet_list = ps_list ti._setTypePropertySheetList(list(property_sheet_set))
transaction.commit() transaction.commit()
_aq_reset() _aq_reset()
ERP5TypeTestCase.tearDown(self) ERP5TypeTestCase.tearDown(self)
...@@ -1232,10 +1229,9 @@ class TestPropertySheet: ...@@ -1232,10 +1229,9 @@ class TestPropertySheet:
self._addProperty('Person', self.DEFAULT_ORGANISATION_TITLE_ACQUIRED_PROP) self._addProperty('Person', self.DEFAULT_ORGANISATION_TITLE_ACQUIRED_PROP)
# add destination base category to Person TI # add destination base category to Person TI
person_ti = self.getTypesTool().getTypeInfo('Person') person_ti = self.getTypesTool().getTypeInfo('Person')
if 'destination' not in person_ti.base_category_list: base_category_list = person_ti.getTypeBaseCategoryList()
person_ti.base_category_list = tuple(list( if 'destination' not in base_category_list:
self.getTypesTool().getTypeInfo('Person').base_category_list) + person_ti._setTypeBaseCategoryList(base_category_list + ['destination'])
['destination', ])
_aq_reset() _aq_reset()
person = self.getPersonModule().newContent(id='1', portal_type='Person') person = self.getPersonModule().newContent(id='1', portal_type='Person')
...@@ -1265,10 +1261,10 @@ class TestPropertySheet: ...@@ -1265,10 +1261,10 @@ class TestPropertySheet:
self._addProperty('Person', self.DEFAULT_ORGANISATION_TITLE_ACQUIRED_PROP) self._addProperty('Person', self.DEFAULT_ORGANISATION_TITLE_ACQUIRED_PROP)
# add destination base category to Person TI # add destination base category to Person TI
person_ti = self.getTypesTool().getTypeInfo('Person') person_ti = self.getTypesTool().getTypeInfo('Person')
if 'destination' not in person_ti.base_category_list: base_category_list = person_ti.getTypeBaseCategoryList()
person_ti.base_category_list = tuple(list( if 'destination' not in base_category_list:
self.getTypesTool().getTypeInfo('Person').base_category_list) + person_ti._setTypeBaseCategoryList(base_category_list + ['destination'])
['destination', ]) _aq_reset()
person = self.getPersonModule().newContent(id='1', portal_type='Person') person = self.getPersonModule().newContent(id='1', portal_type='Person')
another_person = self.getPersonModule().newContent( another_person = self.getPersonModule().newContent(
...@@ -2179,7 +2175,7 @@ class TestPropertySheet: ...@@ -2179,7 +2175,7 @@ class TestPropertySheet:
ti = self.getTypesTool()['Person'] ti = self.getTypesTool()['Person']
self.assertFalse(hasattr(doc, 'getDestination')) self.assertFalse(hasattr(doc, 'getDestination'))
ti.edit(type_base_category_list= ti.edit(type_base_category_list=
ti.getTypeBaseCategoryList(()) + ['destination']) ti.getTypeBaseCategoryList() + ['destination'])
self.assertTrue(hasattr(doc, 'getDestination')) self.assertTrue(hasattr(doc, 'getDestination'))
def test_aq_reset_on_workflow_chain_change(self): def test_aq_reset_on_workflow_chain_change(self):
......
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