Commit 3f396373 authored by Ayush Tiwari's avatar Ayush Tiwari

bt5_config: Add BusinessPropertyItem for saving values of properties

parent 2216aa39
...@@ -56,7 +56,6 @@ from Acquisition import Implicit, aq_base, aq_inner, aq_parent ...@@ -56,7 +56,6 @@ from Acquisition import Implicit, aq_base, aq_inner, aq_parent
from zLOG import LOG, INFO, WARNING from zLOG import LOG, INFO, WARNING
from Products.ERP5Type.XMLObject import XMLObject from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.Core.Folder import Folder from Products.ERP5Type.Core.Folder import Folder
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.PythonScripts.PythonScript import PythonScript from Products.PythonScripts.PythonScript import PythonScript
...@@ -149,7 +148,7 @@ class BusinessManager(Folder): ...@@ -149,7 +148,7 @@ class BusinessManager(Folder):
meta_type = 'ERP5 Business Manager' meta_type = 'ERP5 Business Manager'
portal_type = 'Business Manager' portal_type = 'Business Manager'
add_permission = Permissions.AddPortalContent add_permission = Permissions.AddPortalContent
allowed_types = ('Business Item',) allowed_types = ('Business Item', 'Business Property Item',)
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -340,7 +339,8 @@ class BusinessManager(Folder): ...@@ -340,7 +339,8 @@ class BusinessManager(Folder):
for path_item in path_item_list: for path_item in path_item_list:
if '#' in str(path_item[0]): if '#' in str(path_item[0]):
PathItem = self.newContent(portal_type='Business Item') import pdb; pdb.set_trace()
PathItem = self.newContent(portal_type='Business Property Item')
# If its a property, no need to resolve the path # If its a property, no need to resolve the path
PathItem.edit( PathItem.edit(
item_path=path_item[0], item_path=path_item[0],
...@@ -584,16 +584,15 @@ class BusinessItem(XMLObject): ...@@ -584,16 +584,15 @@ class BusinessItem(XMLObject):
"""Saves the path and values for objects, properties, etc, the """Saves the path and values for objects, properties, etc, the
attributes for a path configuration being: attributes for a path configuration being:
- path (similar to an xpath expression) - item_path (similar to an xpath expression)
Examples of path : Examples of path :
portal_type/Person portal_type/Person
portal_type/Person#title portal_type/Person#title
portal_type/Person#property_sheet?ancestor=DublinCore portal_type/Person#property_sheet?ancestor=DublinCore
portal_type/Person#property_sheet?position=2 portal_type/Person#property_sheet?position=2
- sign (+1/-1) - item_sign (+1/-1)
- layer (0, 1, 2, 3, etc.) - item_layer (0, 1, 2, 3, etc.)
- value (a set of pickable value in python) - item_value (a set of pickable value in python)"""
- hash of the value"""
add_permission = Permissions.AddPortalContent add_permission = Permissions.AddPortalContent
# Declarative security # Declarative security
...@@ -614,30 +613,6 @@ class BusinessItem(XMLObject): ...@@ -614,30 +613,6 @@ class BusinessItem(XMLObject):
item_layer=item_layer, item_layer=item_layer,
**kw) **kw)
def _generateHash(self, item_value=None):
"""
Generate hash based on value for the object.
Initially, for simplicity, we go on with SHA256 values only
"""
LOG('Business Manager', INFO, 'Genrating hash')
if not item_value:
# Raise in case there is no value for the BusinessItem object
raise ValueError, "Value not defined for the BusinessItem"
elif self.isProperty:
# In case of property, the value is a PersisitentMapping object, so it
# can be easily hashed after formatting
sha256 = hash(pprint.pformat(item_value))
else:
# Expects to raise error on case the value for the object
# is not picklable
try:
sha256 = hashlib.sha256(item_value).hexdigest()
except TypeError:
obj_dict = item_value.__dict__.copy()
del obj_dict['uid']
sha256 = hash(pprint.pformat(obj_dict))
return sha256
def build(self, context, **kw): def build(self, context, **kw):
""" """
Extract value for the given path from the OFS Extract value for the given path from the OFS
...@@ -651,40 +626,25 @@ class BusinessItem(XMLObject): ...@@ -651,40 +626,25 @@ class BusinessItem(XMLObject):
LOG('Business Manager', INFO, 'Building Business Item') LOG('Business Manager', INFO, 'Building Business Item')
p = context.getPortalObject() p = context.getPortalObject()
path = self.getProperty('item_path') path = self.getProperty('item_path')
if '#' in str(path): try:
self.isProperty = True # XXX: After we apply _resolve path list while storing Data for the
relative_url, property_id = path.split('#') # Business Manager, this should be of no use as there will be no path
obj = p.unrestrictedTraverse(relative_url) # where we are going to achieve something different for relative_path
property_value = obj.getProperty(property_id) # from the result of _resolvePath on a given path.
property_type = obj.getPropertyType(property_id) # TODO: Remove this after checking successfull implementation of
# Create a persistent object which can be saved inside ZODB for the value # _resolve path in Business Manager in storeTemplateData
value = PersistentMapping() for relative_url in self._resolvePath(p, [], path.split('/')):
value['name'] = property_id obj = p.unrestrictedTraverse(relative_url)
value['type'] = property_type obj = obj._getCopy(context)
value['value'] = property_value obj = obj.__of__(context)
self.setProperty('item_value', value) # XXX: '_recursiveRemoveUid' is not working as expected
# Add the value object in the database _recursiveRemoveUid(obj)
obj._p_jar.add(value) obj = aq_base(obj)
else: obj.isIndexable = ConstantGetter('isIndexable', value=False)
try: self._setObject(obj.getId(), obj, suppress_events=True)
# XXX: After we apply _resolve path list while storing Data for the except AttributeError:
# Business Manager, this should be of no use as there will be no path # In case the object doesn't exist, just pass without raising error
# where we are going to achieve something different for relative_path pass
# from the result of _resolvePath on a given path.
# TODO: Remove this after checking successfull implementation of
# _resolve path in Business Manager in storeTemplateData
for relative_url in self._resolvePath(p, [], path.split('/')):
obj = p.unrestrictedTraverse(relative_url)
obj = obj._getCopy(context)
obj = obj.__of__(context)
# XXX: '_recursiveRemoveUid' is not working as expected
_recursiveRemoveUid(obj)
obj = aq_base(obj)
obj.isIndexable = ConstantGetter('isIndexable', value=False)
self._setObject(obj.getId(), obj, suppress_events=True)
except AttributeError:
# In case the object doesn't exist, just pass without raising error
pass
def applyValueToPath(self): def applyValueToPath(self):
""" """
...@@ -734,25 +694,6 @@ class BusinessItem(XMLObject): ...@@ -734,25 +694,6 @@ class BusinessItem(XMLObject):
relative_url_list + [object_id], id_list[1:])) relative_url_list + [object_id], id_list[1:]))
return path_list return path_list
def setPropertyToPath(self, path, property_name, value):
"""
Set property for the object at given path
"""
portal = self.getPortalObject()
obj = portal.unrestrictedTraverse(path)
obj.setProperty(property_name, value)
def generateXML(self):
"""
Generate XML for different objects/type/properties differently.
1. Objects: Use XMLImportExport from ERP5Type
2. For properties, first get the property type, then create XML object
for the different property differenty(Use ObjectPropertyItem from BT5)
3. For attributes, we can export part of the object, rather than exporting
whole of the object
"""
pass
def install(self, context): def install(self, context):
""" """
Set the value to the defined path. Set the value to the defined path.
...@@ -761,47 +702,30 @@ class BusinessItem(XMLObject): ...@@ -761,47 +702,30 @@ class BusinessItem(XMLObject):
# ObjectTemplateItem and handle the installation there. # ObjectTemplateItem and handle the installation there.
portal = context.getPortalObject() portal = context.getPortalObject()
path = self.getProperty('item_path') path = self.getProperty('item_path')
if '#' in str(path): path_list = path.split('/')
self.isProperty = True container_path = path_list[:-1]
relative_url, property_id = path.split('#') object_id = path_list[-1]
obj = portal.unrestrictedTraverse(relative_url) try:
prop = self.getProperty('value') container = self.unrestrictedResolveValue(portal, container_path)
# First remove the property from the existing path and keep the default except KeyError:
# empty, and update only if the sign is +1 # parent object can be set to nothing, in this case just go on
obj._delPropValue(prop['name']) container_url = '/'.join(container_path)
if self.getProperty('item_sign') == 1: old_obj = container._getOb(object_id, None)
obj.setProperty(prop['name'], prop['value'], prop['type']) # delete the old object before installing a new object
else: if old_obj:
path_list = path.split('/') container._delObject(object_id)
container_path = path_list[:-1] # Create a new object only if sign is +1
object_id = path_list[-1] # If sign is +1, set the new object on the container
try: if int(self.getProperty('item_sign')) == 1:
container = self.unrestrictedResolveValue(portal, container_path) # install object
except KeyError: obj = self.objectValues()[0]
# parent object can be set to nothing, in this case just go on obj = obj._getCopy(container)
container_url = '/'.join(container_path) # Before making `obj` a sub-object of `container`, we should the acquired
old_obj = container._getOb(object_id, None) # roles on obj
# delete the old object before installing a new object obj.isIndexable = ConstantGetter('isIndexable', value=False)
if old_obj: delattr(obj, '__ac_local_roles__')
container._delObject(object_id) container._setObject(object_id, obj, suppress_events=True)
# Create a new object only if sign is +1 obj = container._getOb(object_id)
# If sign is +1, set the new object on the container
if int(self.getProperty('item_sign')) == 1:
# install object
obj = self.objectValues()[0]
obj = obj._getCopy(container)
# Before making `obj` a sub-object of `container`, we should the acquired
# roles on obj
obj.isIndexable = ConstantGetter('isIndexable', value=False)
delattr(obj, '__ac_local_roles__')
container._setObject(object_id, obj, suppress_events=True)
obj = container._getOb(object_id)
"""
aq_base(obj).uid = portal.portal_catalog.newUid()
del obj.isIndexable
if getattr(aq_base(obj), 'reindexObject', None) is not None:
obj.reindexObject()
"""
def unrestrictedResolveValue(self, context=None, path='', default=_MARKER, def unrestrictedResolveValue(self, context=None, path='', default=_MARKER,
restricted=0): restricted=0):
...@@ -904,87 +828,6 @@ class BusinessItem(XMLObject): ...@@ -904,87 +828,6 @@ class BusinessItem(XMLObject):
return merged_value return merged_value
def _guessFilename(self, document, key, data):
"""
Try to guess the extension based on the id of the document
"""
yield key
document_base = aq_base(document)
# Try to guess the extension based on the reference of the document
if hasattr(document_base, 'getReference'):
yield document.getReference()
elif isinstance(document_base, ERP5BaseBroken):
yield getattr(document_base, "reference", None)
# Try to guess the extension based on the title of the document
yield getattr(document_base, "title", None)
# Try to guess from content
if data:
for test in imghdr.tests:
extension = test(data, None)
if extension:
yield 'x.' + extension
def guessExtensionOfDocument(self, document, key, data=None):
"""
Guesses and returns the extension of an ERP5 document.
The process followed is:
1. Try to guess extension by the id of the document
2. Try to guess extension by the title of the document
3. Try to guess extension by the reference of the document
4. Try to guess from content (only image data is tested)
If there's a content type, we only return an extension that matches.
In case everything fails then:
- '.bin' is returned for binary files
- '.txt' is returned for text
"""
document_base = aq_base(document)
# XXX Zope items like DTMLMethod would not implement getContentType method
mime = None
if hasattr(document_base, 'getContentType'):
content_type = document.getContentType()
elif isinstance(document_base, ERP5BaseBroken):
content_type = getattr(document_base, "content_type", None)
else:
content_type = None
# For stable export, people must have a MimeTypes Registry, so do not
# fallback on mimetypes. We prefer the mimetypes_registry because there
# are more extensions and we can have preferred extensions.
# See also https://bugs.python.org/issue1043134
mimetypes_registry = self.getPortalObject()['mimetypes_registry']
if content_type:
try:
mime = mimetypes_registry.lookup(content_type)[0]
except (IndexError, MimeTypeException):
pass
for key in self._guessFilename(document, key, data):
if key:
ext = os.path.splitext(key)[1][1:].lower()
if ext and (mimetypes_registry.lookupExtension(ext) is mime if mime
else mimetypes_registry.lookupExtension(ext)):
return ext
if mime:
# return first registered extension (if any)
if mime.extensions:
return mime.extensions[0]
for ext in mime.globs:
if ext[0] == "*" and ext.count(".") == 1:
return ext[2:].encode("utf-8")
# in case we could not read binary flag from mimetypes_registry then return
# '.bin' for all the Portal Types where exported_property_type is data
# (File, Image, Spreadsheet). Otherwise, return .bin if binary was returned
# as 1.
binary = getattr(mime, 'binary', None)
if binary or binary is None is not data:
return 'bin'
# in all other cases return .txt
return 'txt'
def removeProperties(self, def removeProperties(self,
obj, obj,
export, export,
...@@ -1047,3 +890,52 @@ class BusinessItem(XMLObject): ...@@ -1047,3 +890,52 @@ class BusinessItem(XMLObject):
def getParentBusinessManager(self): def getParentBusinessManager(self):
return self.aq_parent return self.aq_parent
class BusinessPropertyItem(XMLObject):
"""Class to deal with path(s) which refer to property of an ERP5 object.
Used to store property name, type and value for a given object and property"""
add_permission = Permissions.AddPortalContent
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
portal_type = 'Business Property Item'
meta_type = 'Business Property Item'
isIndexable = False
isProperty = True
def _edit(self, item_path='', item_sign=1, item_layer=0, *args, **kw):
"""
Overriden function so that we can update attributes for BusinessItem objects
"""
return super(BusinessPropertyItem, self)._edit(item_path=item_path,
item_sign=item_sign,
item_layer=item_layer,
**kw)
def build(self, context, **kw):
p = context.getPortalObject()
path = self.getProperty('item_path')
relative_url, property_id = path.split('#')
obj = p.unrestrictedTraverse(relative_url)
property_value = obj.getProperty(property_id)
property_type = obj.getPropertyType(property_id)
self.setProperty('name', property_id)
self.setProperty('type', property_type)
self.setProperty('value', property_value)
def install(self, context):
portal = context.getPortalObject()
path = self.getProperty('item_path')
relative_url, property_id = path.split('#')
obj = portal.unrestrictedTraverse(relative_url)
property_name = self.getProperty('name')
property_type = self.getProperty('type')
property_value = self.getProperty('value')
# First remove the property from the existing path and keep the default
# empty, and update only if the sign is +1
obj._delPropValue(property_name)
if int(self.getProperty('item_sign')) == 1:
obj.setProperty(property_name, property_value, property_type)
...@@ -1772,9 +1772,15 @@ class TemplateTool (BaseTool): ...@@ -1772,9 +1772,15 @@ class TemplateTool (BaseTool):
# thus all the BusinessItem sub-objects should have single value # thus all the BusinessItem sub-objects should have single value
# Update hashes of item in old state before installation # Update hashes of item in old state before installation
for item in old_installation_state.objectValues(): for item in old_installation_state.objectValues():
value_list = item.objectValues() if item.isProperty:
value_list = item.getProperty('value')
else:
value_list = item.objectValues()
if value_list: if value_list:
item.setProperty('item_sha', self.calculateComparableHash(value_list[0])) item.setProperty('item_sha', self.calculateComparableHash(
value_list[0],
item.isProperty,
))
# Path Item List for installation_process should be the difference between # Path Item List for installation_process should be the difference between
# old and new installation state # old and new installation state
...@@ -1782,7 +1788,15 @@ class TemplateTool (BaseTool): ...@@ -1782,7 +1788,15 @@ class TemplateTool (BaseTool):
# If the path has been removed, then add it with sign = -1 # If the path has been removed, then add it with sign = -1
old_item = old_installation_state.getBusinessItemByPath(item.getProperty('item_path')) old_item = old_installation_state.getBusinessItemByPath(item.getProperty('item_path'))
# Calculate sha for the items in new_insatallation_state # Calculate sha for the items in new_insatallation_state
item.setProperty('item_sha', self.calculateComparableHash(item.objectValues()[0])) import pdb; pdb.set_trace()
if item.isProperty:
value = item.getProperty('value')
else:
value = item.objectValues()[0]
item.setProperty('item_sha', self.calculateComparableHash(
value,
item.isProperty,
))
if old_item: if old_item:
# If the old_item exists, we match the hashes and if it differs, then # If the old_item exists, we match the hashes and if it differs, then
# add the new item # add the new item
...@@ -1807,7 +1821,7 @@ class TemplateTool (BaseTool): ...@@ -1807,7 +1821,7 @@ class TemplateTool (BaseTool):
installMultipleBusinessManager = updateInstallationState installMultipleBusinessManager = updateInstallationState
def calculateComparableHash(self, object): def calculateComparableHash(self, object, isProperty=False):
""" """
Remove some attributes before comparing hashses Remove some attributes before comparing hashses
and return hash of the comparable object dict, in case the object is and return hash of the comparable object dict, in case the object is
...@@ -1817,7 +1831,7 @@ class TemplateTool (BaseTool): ...@@ -1817,7 +1831,7 @@ class TemplateTool (BaseTool):
attributes which changes at small updation, like workflow_history, attributes which changes at small updation, like workflow_history,
uid, volatile attributes(which starts with _v) uid, volatile attributes(which starts with _v)
""" """
if object.__class__.__name__ == 'PersistentMapping': if isProperty:
obj_dict = object obj_dict = object
else: else:
obj_dict = object.__dict__.copy() obj_dict = object.__dict__.copy()
...@@ -1827,6 +1841,7 @@ class TemplateTool (BaseTool): ...@@ -1827,6 +1841,7 @@ class TemplateTool (BaseTool):
removable_attributes.append('uid') removable_attributes.append('uid')
removable_attributes.append('_owner') removable_attributes.append('_owner')
removable_attributes.append('isIndexable')
for attr in removable_attributes: for attr in removable_attributes:
try: try:
del obj_dict[attr] del obj_dict[attr]
...@@ -1848,6 +1863,7 @@ class TemplateTool (BaseTool): ...@@ -1848,6 +1863,7 @@ class TemplateTool (BaseTool):
try: try:
if '#' in str(path): if '#' in str(path):
isProperty = True
relative_url, property_id = path.split('#') relative_url, property_id = path.split('#')
obj = portal.restrictedTraverse(relative_url) obj = portal.restrictedTraverse(relative_url)
property_value = obj.getProperty(property_id) property_value = obj.getProperty(property_id)
...@@ -1860,16 +1876,12 @@ class TemplateTool (BaseTool): ...@@ -1860,16 +1876,12 @@ class TemplateTool (BaseTool):
if not property_value: if not property_value:
raise KeyError raise KeyError
property_type = obj.getPropertyType(property_id) property_type = obj.getPropertyType(property_id)
# Create a persistent object to compare the hash obj = property_value
value = PersistentMapping()
value['name'] = property_id
value['type'] = property_type
value['value'] = property_value
obj = value
else: else:
isProperty = False
obj = portal.restrictedTraverse(path) obj = portal.restrictedTraverse(path)
obj_sha = self.calculateComparableHash(obj) obj_sha = self.calculateComparableHash(obj, isProperty)
# Get item at old state # Get item at old state
old_item = old_state.getBusinessItemByPath(path) old_item = old_state.getBusinessItemByPath(path)
......
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