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 @@ ...@@ -50,66 +50,41 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string encoding="cdata"><![CDATA[ <value> <string>portal = context.getPortalObject()\n
if depth:\n
portal = context.getPortalObject()\n category_relative_url = parent.getMembershipCriterionCategory()\n
request = context.REQUEST\n else:\n
domain_list = []\n category_relative_url = portal.portal_preferences.getPreference(\n
\n \'preferred_\' + context.REQUEST[\'here\'].getPortalType().replace(\' Module\', \'\').lower().replace(\' \', \'_\') + \'_use\',\n
here = request[\'here\']\n )\n
portal_type = here.getPortalType().replace(\' Module\', \'\')\n if not category_relative_url:\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 return ()\n
\n child_list, resource_list = portal.portal_categories.use.restrictedTraverse(category_relative_url).Category_getUseCategoryListAndResourceList()\n
if sub_use_list:\n domain_list = []\n
if depth == 0:\n for child in child_list:\n
for sub_use in sub_use_list:\n domain = parent.generateTempDomain(id=child.getId())\n
domain = parent.generateTempDomain(id = \'new_%s\' % sub_use.getProperty(\'uid\'))\n domain.edit(\n
domain.edit(title=sub_use.getTranslatedTitle(),\n title=child.getTranslatedTitle(),\n
membership_criterion_category=(sub_use.getRelativeUrl(),),\n membership_criterion_category=(child.getRelativeUrl(), ),\n
domain_generator_method_id=script.id,\n domain_generator_method_id=script.id,\n
uid=use.getUid())\n )\n
domain.setCriterionPropertyList([\'related_resource_from_use_category_uid\'])\n domain.setCriterionPropertyList([\'related_resource_from_use_category_uid\'])\n
domain.setCriterion(\'related_resource_from_use_category_uid\', identity=sub_use.getUid())\n domain.setCriterion(\'related_resource_from_use_category_uid\', identity=child.getUid())\n
domain_list.append(domain)\n domain_list.append(domain)\n
return domain_list\n for resource in resource_list:\n
else:\n domain = parent.generateTempDomain(id=resource.getId())\n
use = portal.portal_categories.use.restrictedTraverse(parent.getMembershipCriterionCategory())\n domain.edit(\n
\n title=resource.getTranslatedTitle(),\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_base_category=(\'resource\', ),\n
membership_criterion_category=(\'resource/%s\' % service.getRelativeUrl(),),\n membership_criterion_category=(\'resource/\' + resource.getRelativeUrl(), ),\n
domain_generator_method_id=script.id,\n )\n
uid=service.getUid())\n
domain_list.append(domain)\n domain_list.append(domain)\n
\n return sorted(domain_list, key=lambda x: x.getTitle())\n
return domain_list\n </string> </value>
]]></string> </value>
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>depth, parent, **kw</string> </value> <value> <string>depth, parent</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <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 @@ ...@@ -51,52 +51,132 @@
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>"""\n <value> <string>"""\n
This script returns the list of items based on the preferred\n portal_type (None, string)\n
resources for tickets. It is intended to be used\n Used to determine "use" category to start recursing from, using preference\n
by ListField instances.\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 """\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 from Products.ERP5Type.Cache import CachingMethod\n
portal = context.getPortalObject()\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 \n
if not portal_type:\n accessor_id = \'getCompactTranslatedTitle\' if compact else \'getTranslatedTitle\'\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 \n
result_list = getResourceItemList(portal_type)[:]\n def getResourceItemList():\n
\n INDENT = \'\\xc2\\xa0\' * 2 # UTF-8 Non-breaking space\n
# BBB returns actual value in list field\n RESOURCE_INDENT = INDENT if indent_resource else \'\'\n
if include_context and context.getResource() and context.getResource() not in [result[1] for result in result_list]:\n getResourceTitle = lambda resource, category, depth: RESOURCE_INDENT * depth + getattr(resource, accessor_id)()\n
try:\n if indent_category:\n
resource_value = portal.portal_categories.getCategoryValue(context.getResource(), base_category=\'resource\')\n def getCategoryTitle(category, depth):\n
if resource_value is not None:\n return INDENT * depth + getattr(category, accessor_id)()\n
if resource_value.getPortalType() == \'Category\':\n else:\n
category_relative_url = resource_value.getCategoryRelativeUrl()\n def getCategoryTitle_(category, depth):\n
category_title = resource_value.getTranslatedTitle()\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 else:\n
category_relative_url = resource_value.getRelativeUrl()\n getCategoryTitle = lambda category, depth: None\n
category_title = resource_value.getTitle()\n def getResourceTitle(resource, category, depth):\n
result_list.append((category_title, category_relative_url))\n resource_title = getattr(resource, accessor_id)()\n
except Unauthorized:\n # depth - 1 because we are at category\'s child level\n
pass\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 \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> </string> </value>
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <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>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
668 669
\ No newline at end of file
...@@ -437,8 +437,12 @@ class TestCRM(BaseTestCRM): ...@@ -437,8 +437,12 @@ class TestCRM(BaseTestCRM):
# Then create One event and play with it # Then create One event and play with it
portal_type = 'Visit' portal_type = 'Visit'
module = self.portal.getDefaultModule(portal_type) module = self.portal.getDefaultModule(portal_type)
event = module.newContent(portal_type=portal_type, event = module.newContent(portal_type=portal_type)
resource='0') # 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.getResourceValue() is not None)
self.assertTrue(event.getResource() in\ self.assertTrue(event.getResource() in\
[item[1] for item in event.Event_getResourceItemList()]) [item[1] for item in event.Event_getResourceItemList()])
......
...@@ -235,7 +235,10 @@ class Predicate(XMLObject): ...@@ -235,7 +235,10 @@ class Predicate(XMLObject):
# Build the identity criterion # Build the identity criterion
catalog_kw = {} catalog_kw = {}
catalog_kw.update(kw) # query_table, REQUEST, ignore_empty_string, **kw catalog_kw.update(kw) # query_table, REQUEST, ignore_empty_string, **kw
for criterion in self.getCriterionList(): 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: if criterion.min and criterion.max:
catalog_kw[criterion.property] = { 'query' : (criterion.min, criterion.max), catalog_kw[criterion.property] = { 'query' : (criterion.min, criterion.max),
'range' : 'minmax' 'range' : 'minmax'
...@@ -263,6 +266,10 @@ class Predicate(XMLObject): ...@@ -263,6 +266,10 @@ class Predicate(XMLObject):
list(catalog_filter_set.intersection(parameter_filter_set)) list(catalog_filter_set.intersection(parameter_filter_set))
else: else:
catalog_kw[criterion.property] = criterion.identity 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') 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