Commit 123e7c69 authored by Vincent Pelletier's avatar Vincent Pelletier

erp5_crm: Add support for deeper "use" resource hierachy.

Remove depth limitations on domain generator.
Make ListField item list generator present resource hierarchically (either
through indentation or through relative title-based paths, depending on
preference).
Also, share a bit of code between both.
parent 148b1a57
......@@ -50,66 +50,41 @@
</item>
<item>
<key> <string>_body</string> </key>
<value> <string encoding="cdata"><![CDATA[
portal = context.getPortalObject()\n
request = context.REQUEST\n
<value> <string>portal = context.getPortalObject()\n
if depth:\n
category_relative_url = parent.getMembershipCriterionCategory()\n
else:\n
category_relative_url = portal.portal_preferences.getPreference(\n
\'preferred_\' + context.REQUEST[\'here\'].getPortalType().replace(\' Module\', \'\').lower().replace(\' \', \'_\') + \'_use\',\n
)\n
if not category_relative_url:\n
return ()\n
child_list, resource_list = portal.portal_categories.use.restrictedTraverse(category_relative_url).Category_getUseCategoryListAndResourceList()\n
domain_list = []\n
\n
here = request[\'here\']\n
portal_type = here.getPortalType().replace(\' Module\', \'\')\n
\n
preference_id = \'preferred_%s_use\' % \'_\'.join(token.lower() for token in portal_type.split(\' \'))\n
preferred_use = portal.portal_preferences.getPreference(preference_id)\n
if not preferred_use:\n
return ()\n
\n
use = portal.portal_categories.use.restrictedTraverse(preferred_use)\n
sub_use_list = use.contentValues()\n
\n
max_depth = 0\n
if sub_use_list:\n
max_depth = 1\n
if depth > max_depth:\n
return ()\n
\n
if sub_use_list:\n
if depth == 0:\n
for sub_use in sub_use_list:\n
domain = parent.generateTempDomain(id = \'new_%s\' % sub_use.getProperty(\'uid\'))\n
domain.edit(title=sub_use.getTranslatedTitle(),\n
membership_criterion_category=(sub_use.getRelativeUrl(),),\n
domain_generator_method_id=script.id,\n
uid=use.getUid())\n
domain.setCriterionPropertyList([\'related_resource_from_use_category_uid\'])\n
domain.setCriterion(\'related_resource_from_use_category_uid\', identity=sub_use.getUid())\n
domain_list.append(domain)\n
return domain_list\n
else:\n
use = portal.portal_categories.use.restrictedTraverse(parent.getMembershipCriterionCategory())\n
\n
sql_kw = { \'portal_type\': portal.getPortalResourceTypeList(),\n
\'use_uid\': use.getUid(),\n
\'validation_state\': \'validated\',\n
\'sort_on\': \'title\'}\n
\n
for service in portal.portal_catalog(**sql_kw):\n
domain = parent.generateTempDomain(id = \'new_%s\' % service.getProperty(\'uid\'))\n
domain.edit(title=service.getTranslatedTitle(),\n
membership_criterion_base_category=(\'resource\', ),\n
membership_criterion_category=(\'resource/%s\' % service.getRelativeUrl(),),\n
domain_generator_method_id=script.id,\n
uid=service.getUid())\n
for child in child_list:\n
domain = parent.generateTempDomain(id=child.getId())\n
domain.edit(\n
title=child.getTranslatedTitle(),\n
membership_criterion_category=(child.getRelativeUrl(), ),\n
domain_generator_method_id=script.id,\n
)\n
domain.setCriterionPropertyList([\'related_resource_from_use_category_uid\'])\n
domain.setCriterion(\'related_resource_from_use_category_uid\', identity=child.getUid())\n
domain_list.append(domain)\n
\n
return domain_list\n
]]></string> </value>
for resource in resource_list:\n
domain = parent.generateTempDomain(id=resource.getId())\n
domain.edit(\n
title=resource.getTranslatedTitle(),\n
membership_criterion_base_category=(\'resource\', ),\n
membership_criterion_category=(\'resource/\' + resource.getRelativeUrl(), ),\n
)\n
domain_list.append(domain)\n
return sorted(domain_list, key=lambda x: x.getTitle())\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>depth, parent, **kw</string> </value>
<value> <string>depth, parent</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>portal = context.getPortalObject()\n
return context.objectValues(), portal.portal_catalog(\n
portal_type=portal.getPortalResourceTypeList(),\n
strict_use_uid=context.getUid(),\n
validation_state=\'validated\',\n
)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Category_getUseCategoryListAndResourceList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -51,52 +51,132 @@
<item>
<key> <string>_body</string> </key>
<value> <string>"""\n
This script returns the list of items based on the preferred\n
resources for tickets. It is intended to be used\n
by ListField instances.\n
portal_type (None, string)\n
Used to determine "use" category to start recursing from, using preference\n
settings for this portal type.\n
When None, context\'s portal_type is used.\n
empty_item (bool)\n
Controls presence of [\'\', \'\'] element in result.\n
indent_category (bool)\n
When true, category captions are indented.\n
When false, categories captions are paths, relative to topmost category (not\n
necessarily a Base Category !).\n
indent_resource (bool)\n
When true, resource captions are indented.\n
When false, resource captions are not indented.\n
compact (bool)\n
When true, getCompactTranslatedTitle is used to generate captions.\n
When false, getTranslatedTitle is used to generate captions.\n
empty_category (bool)\n
When true, categories with no resource children (at any depth) are present\n
in result.\n
When false, categories with no resource children (at any depth) are pruned\n
from result.\n
\n
When indent_category, indent_resource and compact are simultaneously not\n
provided (or None), a default is built from\n
getPreferredCategoryChildItemListMethodId.\n
"""\n
from zExceptions import Unauthorized\n
# Note: a possible improvement would be to merge consecutive disabled entries.\n
# This is difficult though, because it requires splitting work a lot,\n
# increasing complexity significantly for such little improvement:\n
# - non-child categories must not be concatenated (empty /1/12/ must not be\n
# merged with a following /2/)\n
# - all resource child must be properly indented\n
# It is much simpler if only "empty_category=False" case is handled.\n
from Products.ERP5Type.Cache import CachingMethod\n
portal = context.getPortalObject()\n
portal_preferences = portal.portal_preferences\n
category_id = portal_preferences.getPreference(\n
\'preferred_\' + (portal_type or context.getPortalType()).lower().replace(\' \', \'_\') + \'_use\',\n
)\n
if indent_category == indent_resource == compact == None:\n
indent_category, indent_resource, compact = {\n
\'getCategoryChildTranslatedCompactLogicalPathItemList\': (False, False, True),\n
\'getCategoryChildTranslatedLogicalPathItemList\': (False, True, False),\n
\'getCategoryChildTranslatedIndentedCompactTitleItemList\': (True, False, True),\n
\'getCategoryChildTranslatedIndentedTitleItemList\': (True, True, False),\n
}.get(portal_preferences.getPreferredCategoryChildItemListMethodId(), (True, True, False))\n
\n
if not portal_type:\n
portal_type = context.getPortalType()\n
\n
def getResourceItemList(portal_type):\n
preference_id = \'preferred_%s_use\' % \'_\'.join(token.lower() for token in portal_type.split(\' \'))\n
sql_kw = {\'portal_type\': portal.getPortalResourceTypeList(),\n
\'use_uid\': portal.portal_categories.getCategoryUid(portal.portal_preferences.getPreference(preference_id), base_category=\'use\'),\n
\'validation_state\': \'validated\',\n
\'sort_on\': \'title\'}\n
return [(\'\', \'\')] + [(result.getTranslatedTitle(), result.getRelativeUrl()) for result in portal.portal_catalog(**sql_kw)]\n
\n
getResourceItemList = CachingMethod(getResourceItemList, \n
id=(script.id, portal_type, context.Localizer.get_selected_language()), \n
cache_factory=\'erp5_ui_long\')\n
\n
result_list = getResourceItemList(portal_type)[:]\n
accessor_id = \'getCompactTranslatedTitle\' if compact else \'getTranslatedTitle\'\n
\n
# BBB returns actual value in list field\n
if include_context and context.getResource() and context.getResource() not in [result[1] for result in result_list]:\n
try:\n
resource_value = portal.portal_categories.getCategoryValue(context.getResource(), base_category=\'resource\')\n
if resource_value is not None:\n
if resource_value.getPortalType() == \'Category\':\n
category_relative_url = resource_value.getCategoryRelativeUrl()\n
category_title = resource_value.getTranslatedTitle()\n
else:\n
category_relative_url = resource_value.getRelativeUrl()\n
category_title = resource_value.getTitle()\n
result_list.append((category_title, category_relative_url))\n
except Unauthorized:\n
pass\n
def getResourceItemList():\n
INDENT = \'\\xc2\\xa0\' * 2 # UTF-8 Non-breaking space\n
RESOURCE_INDENT = INDENT if indent_resource else \'\'\n
getResourceTitle = lambda resource, category, depth: RESOURCE_INDENT * depth + getattr(resource, accessor_id)()\n
if indent_category:\n
def getCategoryTitle(category, depth):\n
return INDENT * depth + getattr(category, accessor_id)()\n
else:\n
def getCategoryTitle_(category, depth):\n
result = []\n
append = result.append\n
for _ in xrange(depth + 1):\n
append(getattr(category, accessor_id)())\n
category = category.getParentValue()\n
return \'/\'.join(result[::-1])\n
if indent_resource:\n
getCategoryTitle = getCategoryTitle_\n
else:\n
getCategoryTitle = lambda category, depth: None\n
def getResourceTitle(resource, category, depth):\n
resource_title = getattr(resource, accessor_id)()\n
# depth - 1 because we are at category\'s child level\n
category_path = getCategoryTitle_(category, depth - 1)\n
if category_path:\n
return category_path + \'/\' + resource_title\n
return resource_title\n
def recurse(category, depth):\n
child_list, resource_list = category.Category_getUseCategoryListAndResourceList()\n
# Resources before child categories, to avoid ambiguity when resources are not indented\n
result = sorted(\n
[(getResourceTitle(x, category, depth), x.getRelativeUrl()) for x in resource_list],\n
key=lambda x: x[0],\n
)\n
append = result.append\n
extend = result.extend\n
for _, caption, grand_child_list in sorted(\n
[(x.getIntIndex(), getCategoryTitle(x, depth), recurse(x, depth + 1)) for x in child_list],\n
key=lambda x: x[:2],\n
):\n
if grand_child_list or empty_category:\n
if caption is not None:\n
append((caption, None))\n
extend(grand_child_list)\n
return result\n
category = portal.portal_categories.getCategoryValue(category_id, base_category=\'use\')\n
if category is None:\n
return []\n
return recurse(category, 0)\n
\n
return result_list\n
result = CachingMethod(\n
getResourceItemList,\n
id=(\n
script.id,\n
context.Localizer.get_selected_language(),\n
bool(indent_resource),\n
bool(indent_category),\n
accessor_id,\n
bool(empty_category),\n
category_id,\n
),\n
cache_factory=\'erp5_ui_long\',\n
)()\n
if empty_item:\n
prefix = [(\'\', \'\')]\n
else:\n
prefix = []\n
if include_context:\n
context_resource_value = context.getResourceValue()\n
context_resource = context.getResource()\n
if context_resource_value is not None and context_resource not in [x for _, x in result]:\n
prefix.append((getattr(context_resource_value, accessor_id)(), context_resource))\n
return prefix + result\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>portal_type=None, include_context=True</string> </value>
<value> <string>portal_type=None, include_context=True, empty_item=True, indent_category=None, indent_resource=None, compact=None, empty_category=False</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
668
\ No newline at end of file
669
......@@ -437,8 +437,12 @@ class TestCRM(BaseTestCRM):
# Then create One event and play with it
portal_type = 'Visit'
module = self.portal.getDefaultModule(portal_type)
event = module.newContent(portal_type=portal_type,
resource='0')
event = module.newContent(portal_type=portal_type)
# Check that existing valid resource relations which should not be normaly
# found by Event_getResourceItemList are present.
self.assertTrue(event.getResource() not in\
[item[1] for item in event.Event_getResourceItemList()])
event.setResource('0')
self.assertTrue(event.getResourceValue() is not None)
self.assertTrue(event.getResource() in\
[item[1] for item in event.Event_getResourceItemList()])
......
......@@ -235,34 +235,41 @@ class Predicate(XMLObject):
# Build the identity criterion
catalog_kw = {}
catalog_kw.update(kw) # query_table, REQUEST, ignore_empty_string, **kw
for criterion in self.getCriterionList():
if criterion.min and criterion.max:
catalog_kw[criterion.property] = { 'query' : (criterion.min, criterion.max),
'range' : 'minmax'
}
elif criterion.min:
catalog_kw[criterion.property] = { 'query' : criterion.min,
'range' : 'min'
}
elif criterion.max:
catalog_kw[criterion.property] = { 'query' : criterion.max,
'range' : 'max'
}
else:
# if a filter was passed as argument
if catalog_kw.has_key(criterion.property):
if isinstance(catalog_kw[criterion.property], (tuple, list)):
catalog_filter_set = set(catalog_kw[criterion.property])
else:
catalog_filter_set = set([catalog_kw[criterion.property]])
if isinstance(criterion.identity, (tuple, list)):
parameter_filter_set = set(criterion.identity)
else:
parameter_filter_set = set([criterion.identity])
catalog_kw[criterion.property] = \
list(catalog_filter_set.intersection(parameter_filter_set))
criterion_list = self.getCriterionList()
# BBB: accessor is not present on old Predicate property sheet.
if criterion_list or getattr(self, 'isEmptyPredicateValid', lambda: True)():
for criterion in criterion_list:
if criterion.min and criterion.max:
catalog_kw[criterion.property] = { 'query' : (criterion.min, criterion.max),
'range' : 'minmax'
}
elif criterion.min:
catalog_kw[criterion.property] = { 'query' : criterion.min,
'range' : 'min'
}
elif criterion.max:
catalog_kw[criterion.property] = { 'query' : criterion.max,
'range' : 'max'
}
else:
catalog_kw[criterion.property] = criterion.identity
# if a filter was passed as argument
if catalog_kw.has_key(criterion.property):
if isinstance(catalog_kw[criterion.property], (tuple, list)):
catalog_filter_set = set(catalog_kw[criterion.property])
else:
catalog_filter_set = set([catalog_kw[criterion.property]])
if isinstance(criterion.identity, (tuple, list)):
parameter_filter_set = set(criterion.identity)
else:
parameter_filter_set = set([criterion.identity])
catalog_kw[criterion.property] = \
list(catalog_filter_set.intersection(parameter_filter_set))
else:
catalog_kw[criterion.property] = criterion.identity
else:
# By catalog definition, no object has uid 0, so this condition forces an
# empty result.
catalog_kw['uid'] = 0
portal_catalog = getToolByName(self, 'portal_catalog')
......
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