Commit 6bf9e694 by Julien Muchembled

Merge support for ZODB indexing of category related documents

2 parents a7780bc6 7865c58d
......@@ -26,6 +26,7 @@
#
##############################################################################
from collections import deque
import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
......@@ -42,6 +43,30 @@ class TestCMFCategory(ERP5TypeTestCase):
region2 = 'europe/west/germany'
region_list = [region1, region2]
category_dict = dict(
region = dict(
acquisition_base_category_list=('subordination','parent'),
acquisition_portal_type_list="python: ['Address', 'Organisation', 'Person']",
acquisition_mask_value=1,
acquisition_object_id_list=['default_address'],
contents=('europe', ('west', ('france', 'germany'))),
),
subordination = dict(
acquisition_portal_type_list="python: ['Career', 'Organisation']",
acquisition_object_id_list=['default_career'],
),
gender = dict(
fallback_base_category_list=['subordination'],
),
resource = dict(
),
test0 = dict(
),
test1 = dict(
contents=('a', ('ab', 'ac', ('acd',))),
),
)
def getTitle(self):
return "CMFCategory"
......@@ -74,16 +99,44 @@ class TestCMFCategory(ERP5TypeTestCase):
portal = self.portal
self.validateRules()
portal_categories = self.getCategoriesTool()
for name, kw in self.category_dict.iteritems():
try:
bc = portal_categories[name]
except KeyError:
bc = portal_categories.newContent(name)
edit_kw = dict(
acquisition_copy_value=0,
acquisition_append_value=0,
acquisition_mask_value=0,
acquisition_portal_type_list="python: []",
related_locally_indexed=0)
edit_kw.update(kw)
queue = deque(((bc, edit_kw.pop('contents', ())),))
bc.edit(**edit_kw)
while queue:
parent, contents = queue.popleft()
for x in contents:
if type(x) is str:
try:
category = parent[x]
except KeyError:
category = parent.newContent(x)
else:
queue.append((category, x))
# This test creates Person inside Person and Organisation inside
# Organisation, so we modifiy type informations to allow anything inside
# Person and Organisation (we'll cleanup on teardown)
self.getTypesTool().getTypeInfo('Person').filter_content_types = 0
organisation_ti = self.getTypesTool().getTypeInfo('Organisation')
organisation_ti.filter_content_types = 0
# we also enable 'destination' category on organisations
self._organisation_categories = organisation_ti.getTypeBaseCategoryList()
organisation_ti._setTypeBaseCategoryList(self._organisation_categories
+ ['destination', 'resource'])
# Organisation, so we modify type informations to allow anything inside
# Person and Organisation (we'll cleanup on teardown).
self._original_categories = {}
for portal_type, categories in (
('Person', []),
('Organisation', ['destination', 'resource'])):
ti = self.getTypesTool().getTypeInfo(portal_type)
ti.filter_content_types = 0
self._original_categories[portal_type] = x = ti.getTypeBaseCategoryList()
x += 'test0', 'test1'
ti._setTypeBaseCategoryList(x + categories)
# Make persons.
person_module = self.getPersonModule()
......@@ -109,62 +162,14 @@ class TestCMFCategory(ERP5TypeTestCase):
sale_packing_list_module = portal.sale_packing_list_module
if self.id1 not in sale_packing_list_module.objectIds():
sale_packing_list_module.newContent(id=self.id1)
# This set the acquisition for region
for bc in ('region', ):
if not hasattr(portal_categories, bc):
portal_categories.newContent(portal_type='Base Category',id=bc)
portal_categories[bc].setAcquisitionBaseCategoryList(('subordination','parent'))
portal_categories[bc].setAcquisitionPortalTypeList("python: ['Address', 'Organisation', 'Person']")
portal_categories[bc].setAcquisitionMaskValue(1)
portal_categories[bc].setAcquisitionCopyValue(0)
portal_categories[bc].setAcquisitionAppendValue(0)
portal_categories[bc].setAcquisitionObjectIdList(['default_address'])
if not 'europe' in portal_categories[bc].objectIds():
portal_categories[bc].newContent(id='europe',portal_type='Category')
big_region = portal_categories[bc]['europe']
# Now we have to include by hand no categories
if not 'west' in big_region.objectIds():
big_region.newContent(id='west',portal_type='Category')
region = big_region['west']
if not 'france' in region.objectIds():
region.newContent(id='france',portal_type='Category')
if not 'germany' in region.objectIds():
region.newContent(id='germany',portal_type='Category')
for bc in ('subordination', ):
if not hasattr(portal_categories, bc):
portal_categories.newContent(portal_type='Base Category',id=bc)
portal_categories[bc].setAcquisitionPortalTypeList("python: ['Career', 'Organisation']")
portal_categories[bc].setAcquisitionMaskValue(0)
portal_categories[bc].setAcquisitionCopyValue(0)
portal_categories[bc].setAcquisitionAppendValue(0)
portal_categories[bc].setAcquisitionObjectIdList(['default_career'])
for bc in ('gender', ):
if not hasattr(portal_categories, bc):
portal_categories.newContent(portal_type='Base Category',id=bc)
portal_categories[bc].setAcquisitionPortalTypeList("python: []")
portal_categories[bc].setAcquisitionMaskValue(0)
portal_categories[bc].setAcquisitionCopyValue(0)
portal_categories[bc].setAcquisitionAppendValue(0)
portal_categories[bc].setFallbackBaseCategoryList(['subordination'])
for bc in ('resource', ):
if not hasattr(portal_categories, bc):
portal_categories.newContent(portal_type='Base Category',id=bc)
portal_categories[bc].setAcquisitionPortalTypeList("python: []")
portal_categories[bc].setAcquisitionMaskValue(0)
portal_categories[bc].setAcquisitionCopyValue(0)
portal_categories[bc].setAcquisitionAppendValue(0)
def beforeTearDown(self):
"""Clean up."""
# categories
for bc in ('region', 'subordination', 'gender', 'resource'):
bc_obj = self.getPortal().portal_categories[bc]
bc_obj.manage_delObjects()
# type informations
self.getTypesTool().getTypeInfo('Person').filter_content_types = 1
organisation_ti = self.getTypesTool().getTypeInfo('Organisation')
organisation_ti.filter_content_types = 1
organisation_ti = self._organisation_categories
for portal_type, categories in self._original_categories.iteritems():
ti = self.getTypesTool().getTypeInfo(portal_type)
ti.filter_content_types = 1
ti._setTypeBaseCategoryList(categories)
def login(self):
uf = self.portal.acl_users
......@@ -1075,6 +1080,76 @@ class TestCMFCategory(ERP5TypeTestCase):
# Check indexation
self.tic()
def test_setCategoryMemberShip(self):
person = self.getPersonModule().newContent(portal_type='Person')
category_tool = self.getCategoriesTool()
bc = category_tool.newContent()
bc.newContent('a')
bc.newContent('b')
base = (bc.id + '/').__add__
def get(*args, **kw):
return category_tool.getCategoryMembershipList(person, *args, **kw)
def _set(*args, **kw):
return category_tool._setCategoryMembership(person, *args, **kw)
_set(bc.id, list('aa'))
self.assertEqual(get(bc.id), list('aa'))
_set(bc.id, list('baa'))
self.assertEqual(get(bc.id), list('aba'))
_set(bc.id, map(base, 'bb'), 1)
self.assertEqual(get(bc.id), list('bb'))
_set(bc.id, map(base, 'abb'), 1)
self.assertEqual(get(bc.id), list('bab'))
_set(bc.id, ())
def test_relatedIndex(self):
category_tool = self.getCategoriesTool()
newOrganisation = self.getOrganisationModule().newContent
organisation = newOrganisation()
other_organisation = newOrganisation(destination_value=organisation)
person = self.getPersonModule().newContent(test0_value=organisation,
test1='a/ac/acd')
self.tic()
get = organisation.getTest0RelatedValueList
a = category_tool.test1.a
def check():
self.assertEqual([person, other_organisation],
category_tool.getRelatedValueList(organisation))
self.assertEqual([person], get())
self.assertEqual([person], get(portal_type='Person'))
self.assertEqual([], get(portal_type='Organisation'))
self.assertEqual([person], a.getTest1RelatedValueList(
portal_type='Person'))
self.assertEqual([a], a.getTest1RelatedValueList(
strict_membership=True))
self.assertEqual([person], a.ac.acd.getTest1RelatedValueList(
portal_type='Person', strict_membership=True))
category_tool.test0._setRelatedLocallyIndexed(True)
category_tool.test1._setRelatedLocallyIndexed(True)
check()
related_list = sorted(a.getTest1RelatedList())
self.assertTrue(person.getRelativeUrl() in related_list)
self.assertEqual(related_list, sorted(x.getRelativeUrl()
for x in self.portal.portal_catalog(test1_uid=a.getUid())))
related = organisation._related_index
self.assertTrue(related)
self.assertEqual([person.getRelativeUrl()], list(related.test0))
person.unindexObject()
self.tic()
category_tool.test0._setRelatedLocallyIndexed(False)
self.assertEqual([], get())
category_tool.test0._setRelatedLocallyIndexed(True)
check()
person.categories = tuple(x for x in person.categories
if not x.startswith('test0/'))
self.assertEqual([], get())
self.assertFalse(related)
self.assertEqual([], list(related.test0))
related = a.ac.acd._related_index.test1
self.assertEqual(list(related), [person.getRelativeUrl()])
person._setTest1Value(a)
self.assertEqual(list(related), [])
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestCMFCategory))
......
......@@ -544,7 +544,8 @@ class BaseTemplateItem(Implicit, Persistent):
klass = obj.__class__
classname = klass.__name__
attr_set = set(('_dav_writelocks', '_filepath', '_owner', 'last_id', 'uid',
attr_set = set(('_dav_writelocks', '_filepath', '_owner', '_related_index',
'last_id', 'uid',
'__ac_local_roles__', '__ac_local_roles_group_id_dict__'))
if export:
if not keep_workflow_history:
......
......@@ -40,6 +40,7 @@ from Products.ERP5Type.Core.Folder import OFS_HANDLER
from Products.ERP5Type.CopySupport import CopyContainer
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type.Cache import caching_instance_method
from Products.ERP5Type.dynamic import portal_type_class
from zLOG import LOG
......@@ -84,54 +85,10 @@ class CategoryTool(CopyContainer, CMFCategoryTool, BaseTool):
def hasContent(self,id):
return id in self.objectIds()
security.declareProtected(Permissions.AccessContentsInformation, 'getCategoryParentUidList')
def getCategoryParentUidList(self, relative_url, base_category = None, strict=0):
"""
Returns the uids of all categories provided in categorie. This
method can support relative_url such as site/group/a/b/c which
base category is site yet use categories defined in group.
It is also able to use acquisition to create complex categories
such as site/group/a/b/c/b1/c1 where b and b1 are both children
categories of a.
relative_url -- a single relative url of a list of
relative urls
strict -- if set to 1, only return uids of parents, not
relative_url
"""
uid_dict = {}
if type(relative_url) is type('a'): relative_url = (relative_url,)
for path in relative_url:
try:
o = self.getCategoryValue(path, base_category=base_category)
if o is not None:
if base_category is None:
my_base_category = self.getBaseCategoryId(path)
else:
my_base_category = base_category
bo = getattr(self, my_base_category, None)
if bo is not None:
bo_uid = bo.getUid()
uid_dict[(o.getUid(), bo_uid, 1)] = 1 # Strict membership
if o.meta_type == 'ERP5 Category' or o.meta_type == 'ERP5 Base Category' or \
o.meta_type == 'CMF Category' or o.meta_type == 'CMF Base Category':
# This goes up in the category tree
# XXX we should also go up in some other cases....
# ie. when some documents act as categories
if not strict:
while o.meta_type == 'ERP5 Category' or o.meta_type == 'CMF Category':
o = o.aq_parent # We want acquisition here without aq_inner
uid_dict[(o.getUid(), bo_uid, 0)] = 1 # Non strict
except (TypeError, KeyError):
LOG('WARNING: CategoriesTool',0, 'Unable to find uid for %s' % path)
return uid_dict.keys()
security.declareProtected(Permissions.AccessContentsInformation, 'getUids')
getUids = getCategoryParentUidList
@caching_instance_method(id='portal_categories.getBaseCategoryDict', cache_factory='erp5_content_long', cache_id_generator=lambda m, *a, **k:m)
@caching_instance_method(
id='portal_categories.getBaseCategoryDict',
cache_factory='erp5_content_long',
cache_id_generator=lambda *a: portal_type_class.last_sync)
def getBaseCategoryDict(self):
"""
Cached method to which resturns a dict with category names as keys, and None as values.
......
<?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>return not (value and (\n
request.other[\'field_my_acquisition_object_id_list\'] or\n
request.other[\'field_my_acquisition_base_category_list\']))\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>value, request</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>BaseCategory_validateRelatedLocallyIndexed</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -105,6 +105,7 @@
<key> <string>right</string> </key>
<value>
<list>
<string>my_related_locally_indexed</string>
<string>my_acquisition_copy_value</string>
<string>my_acquisition_mask_value</string>
<string>my_acquisition_append_value</string>
......
<?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>external_validator</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>delegated_message_list</string> </key>
<value>
<list>
<string>external_validator_failed</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_related_locally_indexed</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>Local index is incompatible with category acquision.</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>external_validator</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_checkbox</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>Index Related Documents Locally</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Method" module="Products.Formulator.MethodField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>method_name</string> </key>
<value> <string>BaseCategory_validateRelatedLocallyIndexed</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -58,15 +58,11 @@ order_by_expression</string> </value>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
SELECT DISTINCT catalog.uid, catalog.path, portal_type\n
SELECT DISTINCT catalog.uid, path, relative_url, portal_type\n
FROM catalog, category\n
WHERE catalog.uid = category.uid\n
<dtml-if portal_type>\n
AND\n
(<dtml-in portal_type>\n
<dtml-unless sequence-start> OR </dtml-unless>\n
catalog.portal_type=\'<dtml-var sequence-item>\'\n
</dtml-in>)\n
AND <dtml-sqltest portal_type type="string" multiple>\n
</dtml-if>\n
AND (<dtml-var "portal_categories.buildSQLSelector(category_list)">)\n
<dtml-if strict_membership>\n
......
41060
\ No newline at end of file
41061
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_local_properties</string> </key>
<value>
<tuple>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>mode</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>string</string> </value>
</item>
</dictionary>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/boolean</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Determines if related values should be indexed on target documents (i.e. in ZODB) in addition to catalog.\n
This is incompatible with category acquisition.</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>related_locally_indexed_property</string> </value>
</item>
<item>
<key> <string>mode</string> </key>
<value> <string>w</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: 0</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
56
\ No newline at end of file
57
\ No newline at end of file
......@@ -862,20 +862,19 @@ class TestResource(ERP5TypeTestCase):
self.tic()
# Test the cases
for product, variation, node, base_price in test_case_list:
categories = []
if node is not None:
self.logMessage("Check product %s with destination section %s" % \
(product.getTitle(), node.getTitle()),
tab=1)
self.assertEquals(base_price,
product.getPrice(
categories=['destination_section/%s' % node.getRelativeUrl(),
variation]))
categories.append('destination_section/' + node.getRelativeUrl())
else:
self.logMessage("Check product %s without destination section" % \
product.getTitle(),
tab=1)
self.assertEquals(base_price,
product.getPrice(categories=[variation]))
if variation:
categories.append(variation)
self.assertEqual(base_price, product.getPrice(categories=categories))
# The following test tests Movement.getPrice, which is based on the movement
......
......@@ -48,8 +48,7 @@ class SetSetter(BaseSetter):
def __call__(self, instance, *args, **kw):
if self._warning:
LOG("ERP5Type Deprecated Setter Id:",0, self._id)
value = set(args[0])
instance._setValue(self._key, value,
instance._setValue(self._key, set(args[0]),
spec=kw.get('spec',()),
filter=kw.get('filter', None),
portal_type=kw.get('portal_type',()),
......@@ -451,7 +450,7 @@ class UidSetSetter(BaseSetter):
def __call__(self, instance, *args, **kw):
if self._warning:
LOG("ERP5Type Deprecated Getter Id:",0, self._id)
instance._setValueUidList(self._key, args[0],
instance._setValueUidList(self._key, set(args[0]),
spec=kw.get('spec',()),
filter=kw.get('filter', None),
portal_type=kw.get('portal_type',()),
......
......@@ -2013,11 +2013,8 @@ class Base( CopyContainer,
checked_permission=None):
# We must do an ordered list so we can not use the previous method
# self._setValue(id, self.portal_catalog.getObjectList(uids), spec=spec)
references = []
if type(uids) not in (type(()), type([])):
uids = [uids]
for uid in uids:
references.append(self.portal_catalog.getObject(uid))
references = map(self.getPortalObject().portal_catalog.getObject,
(uids,) if isinstance(uids, (int, long)) else uids)
self._setValue(id, references, spec=spec, filter=filter, portal_type=portal_type,
keep_default=keep_default, checked_permission=checked_permission)
......@@ -2178,9 +2175,6 @@ class Base( CopyContainer,
"""
return self._getCategoryTool().getAcquiredCategoryList(self)
def _getAcquiredCategoryList(self):
return self._getCategoryTool()._getAcquiredCategoryList(self)
security.declareProtected( Permissions.ModifyPortalContent, 'setCategoryList' )
def setCategoryList(self, path_list):
self.portal_categories.setCategoryList(self, path_list)
......
......@@ -722,27 +722,21 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
self.assertEquals(person.getDefaultRegion(), 'alpha')
person.setRegionUid(alpha.getUid())
self.assertEquals(person.getRegion(), 'alpha')
person.setRegionUidList([alpha.getUid(), alpha.getUid()])
self.assertEquals(person.getRegionList(), ['alpha', 'alpha'])
person.setRegionUidList([beta.getUid(), beta.getUid()])
self.assertEquals(person.getRegionList(), ['beta', 'beta'])
person.setRegionUidSet([alpha.getUid(), alpha.getUid()])
self.assertEquals(person.getRegionSet(), ['alpha'])
self.assertEquals(person.getRegionList(), ['alpha'])
person.setRegionUidList([alpha.getUid(), beta.getUid(), alpha.getUid()])
self.assertEquals(person.getRegionList(), ['alpha', 'beta', 'alpha'])
person.setRegionUidSet([alpha.getUid(), beta.getUid(), alpha.getUid()])
result = person.getRegionSet()
result.sort()
self.assertEquals(result, ['alpha', 'beta'])
self.assertEquals(sorted(person.getRegionSet()), ['alpha', 'beta'])
person.setDefaultRegionUid(beta.getUid())
self.assertEquals(person.getDefaultRegion(), 'beta')
result = person.getRegionSet()
result.sort()
self.assertEquals(result, ['alpha', 'beta'])
self.assertEquals(sorted(person.getRegionSet()), ['alpha', 'beta'])
self.assertEquals(person.getRegionList(), ['beta', 'alpha'])
person.setDefaultRegionUid(alpha.getUid())
self.assertEquals(person.getDefaultRegion(), 'alpha')
result = person.getRegionSet()
result.sort()
self.assertEquals(result, ['alpha', 'beta'])
self.assertEquals(sorted(person.getRegionSet()), ['alpha', 'beta'])
self.assertEquals(person.getRegionList(), ['alpha', 'beta'])
# Test accessor on documents rather than on categories
person.setDefaultRegionUid(person.getUid())
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!