Commit 96bbfad2 authored by Arnaud Fontaine's avatar Arnaud Fontaine

ZODB Components: More Documents to migrate from Products.ERP5.Document.

parent 1266c706
Pipeline #10223 failed with stage
......@@ -30,7 +30,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from erp5.component.document.DeliveryLine import DeliveryLine
from Products.ERP5.Document.Amount import Amount
from erp5.component.document.Amount import Amount
class AccountingTransactionLine(DeliveryLine):
......
......@@ -30,7 +30,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.Path import Path
from erp5.component.document.Path import Path
class Assignment(Path):
......
......@@ -31,7 +31,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.Path import Path
from erp5.component.document.Path import Path
class Career(Path):
"""
......
......@@ -30,7 +30,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5.Document.Path import Path
from erp5.component.document.Path import Path
class Supply(Path, XMLObject):
"""A Supply defines precise pricing and shipping conditions between
......
......@@ -30,7 +30,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.Path import Path
from erp5.component.document.Path import Path
class SupplyCell(Path):
"""A Supply Cell is used for different variations in a supply line.
......
......@@ -32,8 +32,8 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.XMLMatrix import XMLMatrix
from Products.ERP5.Document.Amount import Amount
from Products.ERP5.Document.Path import Path
from erp5.component.document.Amount import Amount
from erp5.component.document.Path import Path
from Products.ERP5Type.Utils import convertToUpperCase
......
......@@ -32,7 +32,7 @@ from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type.Core.Predicate import Predicate
from Products.ERP5.Document.Amount import Amount
from erp5.component.document.Amount import Amount
from erp5.component.module.MovementGroup import MovementGroupNode
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
......
......@@ -29,7 +29,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.Path import Path
from erp5.component.document.Path import Path
class ConfigurationSave(Path):
""" This class is the base class for all template items. """
......
......@@ -27,7 +27,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from erp5.component.document.Item import Item
from Products.ERP5.mixin.mail_message import MailMessageMixin
from erp5.component.mixin.MailMessageMixin import MailMessageMixin
import email
......
......@@ -33,7 +33,7 @@ from erp5.component.mixin.RuleMixin import RuleMixin
from erp5.component.mixin.MovementGeneratorMixin import MovementGeneratorMixin
from erp5.component.mixin.MovementCollectionUpdaterMixin import \
MovementCollectionUpdaterMixin
from Products.ERP5.Document.PredicateMatrix import PredicateMatrix
from erp5.component.document.PredicateMatrix import PredicateMatrix
from erp5.component.interface.IRule import IRule
from erp5.component.interface.IDivergenceController import IDivergenceController
from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
......
......@@ -28,7 +28,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.Path import Path
from erp5.component.document.Path import Path
from Products.ERP5.mixin.periodicity import PeriodicityMixin
class PeriodicityLineMixin(PeriodicityMixin):
......
......@@ -35,8 +35,8 @@ from AccessControl import ClassSecurityInfo
from Products.CMFCategory.Renderer import Renderer
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.Amount import Amount
from Products.ERP5.Document.MappedValue import MappedValue
from erp5.component.document.Amount import Amount
from erp5.component.document.MappedValue import MappedValue
from erp5.component.mixin.AmountGeneratorMixin import AmountGeneratorMixin
from Products.ERP5.mixin.variated import VariatedMixin
......
......@@ -30,7 +30,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from erp5.component.document.Item import Item
from Products.ERP5.Document.TextDocument import TextDocument
from erp5.component.document.TextDocument import TextDocument
class SoftwareLicence(TextDocument, Item):
"""
......
......@@ -32,7 +32,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5.Document.Path import Path
from erp5.component.document.Path import Path
from Products.ERP5Type.Core.Predicate import Predicate
from Products.ERP5.ExplanationCache import _getExplanationCache
from erp5.component.interface.IBusinessLink import IBusinessLink
......
......@@ -33,7 +33,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5.Document.Path import Path
from erp5.component.document.Path import Path
from Products.ERP5.ExplanationCache import _getExplanationCache, _getBusinessLinkClosure
from erp5.component.module.MovementCollectionDiff import _getPropertyAndCategoryList
from erp5.component.interface.IBusinessProcess import IBusinessProcess
......
......@@ -37,7 +37,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.mixin.composition import _getEffectiveModel
from Products.ERP5.Document.MappedValue import MappedValue
from erp5.component.document.MappedValue import MappedValue
from erp5.component.mixin.AmountGeneratorMixin import AmountGeneratorMixin
from Products.ERP5.mixin.variated import VariatedMixin
from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
......
......@@ -32,7 +32,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5.Document.Path import Path
from erp5.component.document.Path import Path
from Products.ERP5.ExplanationCache import _getExplanationCache
from erp5.component.interface.ITradeModelPath import ITradeModelPath
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from AccessControl.ZopeGuards import guarded_getattr
from AccessControl import ClassSecurityInfo
from zLOG import LOG, WARNING
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.Document import Document, ConversionError, _MARKER, DEFAULT_CONTENT_TYPE
from Products.ERP5.Document.File import File
from Products.ERP5Type.WebDAVSupport import TextContent
import re
from Products.ERP5.Document.Document import VALID_IMAGE_FORMAT_LIST, VALID_TEXT_FORMAT_LIST
import cStringIO
from string import Template
# Mixin Import
from Products.ERP5.mixin.cached_convertable import CachedConvertableMixin
from Products.ERP5.mixin.base_convertable import BaseConvertableFileMixin
from Products.ERP5Type.mixin.text_content_history import TextContentHistoryMixin
from Products.ERP5Type.Utils import guessEncodingFromText
from lxml import html as etree_html
from lxml import etree
from Products.ERP5Type.ImageUtil import transformUrlToDataURI
class TextDocument(CachedConvertableMixin, BaseConvertableFileMixin, TextContentHistoryMixin,
TextContent, File):
"""A TextDocument impletents IDocument, IFile, IBaseConvertable, ICachedconvertable
and ITextConvertable
"""
meta_type = 'ERP5 Text Document'
portal_type = 'Text Document'
add_permission = Permissions.AddPortalContent
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.DublinCore
, PropertySheet.Version
, PropertySheet.Document
, PropertySheet.ExternalDocument
, PropertySheet.Url
, PropertySheet.TextDocument
, PropertySheet.Data
, PropertySheet.Reference
)
def _substituteTextContent(self, text, safe_substitute=True, **kw):
# If a method for string substitutions of the text content, perform it.
# Decode everything into unicode before the substitutions, in order to
# avoid encoding errors.
method_id = self.getTextContentSubstitutionMappingMethodId()
if method_id:
try:
method = guarded_getattr(self, method_id)
except AttributeError:
LOG('TextDocument', WARNING, 'could not get the substitution'
' mapping method %s from %r, so the content will not be'
' substituted.' % (method_id, self.getRelativeUrl()))
return text
mapping = method(**kw)
is_str = isinstance(text, str)
if is_str:
text = text.decode('utf-8')
class UnicodeMapping:
def __getitem__(self, item):
v = mapping[item]
if isinstance(v, str):
v = v.decode('utf-8')
elif not isinstance(v, unicode):
v = str(v).decode('utf-8')
return v
unicode_mapping = UnicodeMapping()
if safe_substitute:
text = Template(text).safe_substitute(unicode_mapping)
else:
text = Template(text).substitute(unicode_mapping)
# If the original was a str, convert it back to str.
if is_str:
text = text.encode('utf-8')
return text
security.declareProtected(Permissions.AccessContentsInformation, 'asSubjectText')
def asSubjectText(self, substitution_method_parameter_dict=None, safe_substitute=True, **kw):
"""
Converts the subject of the document to a textual representation.
"""
subject = TextDocument.inheritedAttribute('asSubjectText')(self, **kw)
if substitution_method_parameter_dict is None:
substitution_method_parameter_dict = {}
return self._substituteTextContent(subject, safe_substitute=safe_substitute,
**substitution_method_parameter_dict)
def _convert(self, format, substitution_method_parameter_dict=None,
safe_substitute=True, charset=None, text_content=None, substitute=True, **kw):
"""
Convert text using portal_transforms or oood
"""
# XXX 'or DEFAULT_CONTENT_TYPE' is compaptibility code used for old
# web_page that have neither content_type nor text_format. Migration
# should be done to make all web page having content_type property
src_mimetype = self.getContentType() or DEFAULT_CONTENT_TYPE
if not format and src_mimetype == 'text/html':
format = 'html' # Force safe_html
if not format:
# can return document without conversion
return src_mimetype, self.getTextContent()
portal = self.getPortalObject()
mime_type = portal.mimetypes_registry.lookupExtension('name.%s' % format)
original_mime_type = mime_type = str(mime_type)
if text_content is None:
# check if document has set text_content and convert if necessary
text_content = self.getTextContent()
if text_content:
kw['format'] = format
convert_kw = {}
# PortalTransforms does not accept empty values for 'encoding' parameter
if charset:
kw['charset'] = convert_kw['encoding'] = charset
if not self.hasConversion(**kw):
portal_transforms = portal.portal_transforms
filename = self.getFilename()
if mime_type == 'text/html':
mime_type = 'text/x-html-safe'
if src_mimetype != "image/svg+xml":
result = portal_transforms.convertToData(mime_type, text_content,
object=self, context=self,
filename=filename,
mimetype=src_mimetype,
**convert_kw)
if result is None:
raise ConversionError('TextDocument conversion error. '
'portal_transforms failed to convert '
'from %r to %s: %r' %
(src_mimetype, mime_type, self))
else:
result = text_content
if format in VALID_IMAGE_FORMAT_LIST:
# Include extra parameter for image conversions
temp_image = self.portal_contributions.newContent(
portal_type='Image',
file=cStringIO.StringIO(),
filename=self.getId(),
temp_object=1)
temp_image._setData(result)
mime, result = temp_image.convert(**kw)
self.setConversion(result, original_mime_type, **kw)
else:
mime_type, result = self.getConversion(**kw)
if substitute and format in VALID_TEXT_FORMAT_LIST:
# only textual content can be sustituted
if substitution_method_parameter_dict is None:
substitution_method_parameter_dict = {}
result = self._substituteTextContent(result, safe_substitute=safe_substitute,
**substitution_method_parameter_dict)
return original_mime_type, result
else:
# text_content is not set, return empty string instead of None
return original_mime_type, ''
security.declareProtected(Permissions.AccessContentsInformation, 'getContentBaseURL')
def getContentBaseURL(self):
"""
Returns the content base URL based on the actual content
(in HTML)
"""
if self.hasTextContent():
html = self._asHTML()
# a document can be entirely stripped by safe_html
# so its html conversion can be empty
if html.strip():
html_tree = etree_html.fromstring(html)
base_list = [href for href in html_tree.xpath('//base/@href') if href]
if base_list:
return str(base_list[0])
return Document.getContentBaseURL(self)
security.declareProtected(Permissions.ModifyPortalContent, 'setBaseData')
def setBaseData(self, value):
"""Store base_data into text_content
"""
self._setTextContent(value)
security.declareProtected(Permissions.ModifyPortalContent, '_setBaseData')
_setBaseData = setBaseData
security.declareProtected(Permissions.ModifyPortalContent,
'_baseSetBaseData')
_baseSetBaseData = _setBaseData
security.declareProtected(Permissions.ModifyPortalContent,
'setBaseContentType')
def setBaseContentType(self, value):
"""store value into content_type
"""
self._setContentType(value)
security.declareProtected(Permissions.ModifyPortalContent,
'_setBaseContentType')
_setBaseContentType = setBaseContentType
security.declareProtected(Permissions.ModifyPortalContent,
'_baseSetBaseContentType')
_baseSetBaseContentType = _setBaseContentType
security.declareProtected(Permissions.AccessContentsInformation,
'getBaseData')
def getBaseData(self, default=_MARKER):
"""
"""
self._checkConversionFormatPermission(None)
if default is _MARKER:
return self.getTextContent()
else:
return self.getTextContent(default=default)
security.declareProtected(Permissions.AccessContentsInformation,
'hasBaseData')
def hasBaseData(self):
"""
"""
return self.hasTextContent()
security.declareProtected(Permissions.AccessContentsInformation,
'getContentType')
def getContentType(self, default=_MARKER):
"""Backward compatibility, read content_type
from text_format property
"""
if not self.hasContentType():
# getProperty can not be used
# because text_format is not registered in local_properties
if default is _MARKER:
return getattr(self, 'text_format', None)
else:
return getattr(self, 'text_format', default)
else:
if default is _MARKER:
return self._baseGetContentType()
else:
return self._baseGetContentType(default)
# base_convertable support
security.declareProtected(Permissions.AccessContentsInformation,
'isSupportBaseDataConversion')
def isSupportBaseDataConversion(self):
"""
"""
return True
def _convertToBaseFormat(self):
"""Conversion to base format for TextDocument consist
to convert file content into utf-8
"""
def guessCharsetAndConvert(document, text_content, content_type):
"""
return encoded content_type and message if encoding
is not utf-8
"""
codec = guessEncodingFromText(text_content, content_type)
if codec is not None:
try:
text_content = text_content.decode(codec).encode('utf-8')
except (UnicodeDecodeError, LookupError):
message = 'Conversion to base format with codec %r fails' % codec
# try again with another guesser based on file command
codec = guessEncodingFromText(text_content, 'text/plain')
if codec is not None:
try:
text_content = text_content.decode(codec).encode('utf-8')
except (UnicodeDecodeError, LookupError):
message = 'Conversion to base format with codec %r fails'\
% codec
else:
message = 'Conversion to base format with codec %r succeeds'\
% codec
else:
message = 'Conversion to base format with codec %r succeeds'\
% codec
else:
message = 'Conversion to base format without codec fails'
return text_content, message
content_type = self.getContentType() or DEFAULT_CONTENT_TYPE
text_content = self.getData()
if content_type.endswith('xml'):
try:
tree = etree.fromstring(text_content)
text_content = etree.tostring(tree, encoding='utf-8', xml_declaration=True)
content_type = 'application/xml'
message = 'Conversion to base format succeeds'
except etree.XMLSyntaxError:
message = 'Conversion to base format without codec fails'
elif content_type == 'text/html':
re_match = self.charset_parser.search(text_content)
message = 'Conversion to base format succeeds'
if re_match is not None:
charset = re_match.group('charset')
try:
# Use encoding in html document
text_content = text_content.decode(charset).encode('utf-8')
except (UnicodeDecodeError, LookupError):
# Encoding read from document is wrong
text_content, message = guessCharsetAndConvert(self,
text_content, content_type)
else:
message = 'Conversion to base format with charset %r succeeds'\
% charset
if charset.lower() != 'utf-8':
charset = 'utf-8' # Override charset if convertion succeeds
# change charset value in html_document as well
def subCharset(matchobj):
keyword = matchobj.group('keyword')
charset = matchobj.group('charset')
if not (keyword or charset):
# no match, return same string
return matchobj.group(0)
elif keyword:
# if keyword is present, replace charset just after
return keyword + 'utf-8'
text_content = self.charset_parser.sub(subCharset, text_content)
else:
text_content, message = guessCharsetAndConvert(self,
text_content, content_type)
else:
# generaly text/plain
try:
# if succeeds, not need to change encoding
# it's already utf-8
text_content.decode('utf-8')
except (UnicodeDecodeError, LookupError), error_message:
text_content, message = guessCharsetAndConvert(self,
text_content, content_type)
else:
message = 'Conversion to base format succeeds'
self._setBaseData(text_content)
self._setBaseContentType(content_type)
return message
security.declareProtected(Permissions.AccessContentsInformation,
'getTextContent')
def getTextContent(self, default=_MARKER):
"""Overriden method to check
permission to access content in raw format
"""
self._checkConversionFormatPermission(None)
if default is _MARKER:
return self._baseGetTextContent()
else:
return self._baseGetTextContent(default)
# Backward compatibility for replacement of text_format by content_type
security.declareProtected(Permissions.AccessContentsInformation,
'getTextFormat')
def getTextFormat(self, default=_MARKER):
"""
"""
LOG('TextDocument', WARNING,
'Usage of text_format is deprecated, use content_type instead')
return self.getContentType(default)
security.declareProtected(Permissions.ModifyPortalContent, 'setTextFormat')
def setTextFormat(self, value):
"""
"""
LOG('TextDocument', WARNING,
'Usage of text_format is deprecated, use content_type instead')
return self.setContentType(value)
security.declareProtected(Permissions.ModifyPortalContent,
'_setTextFormat')
def _setTextFormat(self, value):
"""
"""
LOG('TextDocument', WARNING,
'Usage of text_format is deprecated, use content_type instead')
return self._setContentType(value)
def getData(self, default=_MARKER):
"""getData must returns original content but TextDocument accepts
data or text_content to store original content.
Fallback on text_content property if data is not defined
"""
if not self.hasData():
if default is _MARKER:
return self.getTextContent()
else:
return self.getTextContent(default)
else:
if default is _MARKER:
return File.getData(self)
else:
return File.getData(self, default)
......@@ -70,7 +70,7 @@ class Amount(Base, VariatedMixin):
# THIS MUST BE UPDATE WITH CATEGORY ACQUISITION
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationCategoryList')
def getVariationCategoryList(self, default=[], base_category_list=(),
def getVariationCategoryList(self, default=None, base_category_list=(), # pylint: disable=arguments-differ
omit_optional_variation=0, omit_option_base_category=None):
"""
Returns the possible discrete variations
......@@ -130,7 +130,7 @@ class Amount(Base, VariatedMixin):
**kw).render(object_list)
return variation_category_item_list
def _setVariationCategoryList(self, value):
def _setVariationCategoryList(self, value): # pylint: disable=arguments-differ
resource = self.getDefaultResourceValue()
if resource is not None:
variation_list = resource.getVariationBaseCategoryList()
......@@ -139,13 +139,13 @@ class Amount(Base, VariatedMixin):
security.declareProtected(Permissions.ModifyPortalContent,
'setVariationCategoryList')
def setVariationCategoryList(self, value):
def setVariationCategoryList(self, value): # pylint: disable=arguments-differ
self._setVariationCategoryList(value)
self.reindexObject()
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationBaseCategoryList')
def getVariationBaseCategoryList(self, default=[],
def getVariationBaseCategoryList(self, default=None,
omit_optional_variation=0, omit_option_base_category=None):
"""
Return the list of base_category from all variation related to
......@@ -203,7 +203,7 @@ class Amount(Base, VariatedMixin):
security.declareProtected(Permissions.AccessContentsInformation, \
'getVariationRangeCategoryList')
def getVariationRangeCategoryList(self, default=[], base_category_list=(),
def getVariationRangeCategoryList(self, default=None, base_category_list=(),
base=1, **kw):
"""
Returns possible variation category values for the
......@@ -215,7 +215,7 @@ class Amount(Base, VariatedMixin):
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationRangeBaseCategoryList')
def getVariationRangeBaseCategoryList(self, default=[],
def getVariationRangeBaseCategoryList(self, default=None, # pylint: disable=arguments-differ
omit_optional_variation=0, omit_option_base_category=None):
"""
Returns possible variations base categories for this amount ie.
......@@ -243,7 +243,7 @@ class Amount(Base, VariatedMixin):
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationRangeBaseCategoryItemList')
def getVariationRangeBaseCategoryItemList(self, omit_optional_variation=0,
def getVariationRangeBaseCategoryItemList(self, omit_optional_variation=0, # pylint: disable=arguments-differ
omit_option_base_category=None, display_id="title",
display_none_category=0):
"""
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>Amount</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.Document.Amount</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.Amount</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -30,8 +30,8 @@ import zope.interface
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.XMLMatrix import XMLMatrix
from Products.ERP5.Document.Amount import Amount
from Products.ERP5.Document.MappedValue import MappedValue
from erp5.component.document.Amount import Amount
from erp5.component.document.MappedValue import MappedValue
from erp5.component.mixin.AmountGeneratorMixin import AmountGeneratorMixin
from erp5.component.interface.IAmountGeneratorLine import IAmountGeneratorLine
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>BaseDomain</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.Document.BaseDomain</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.BaseDomain</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -73,14 +73,14 @@ class ContributionPredicate(Predicate, XMLObject):
if getattr(aq_base(self), '_identity_criterion', None) is None:
self._identity_criterion = {}
self._range_criterion = {}
for property, value in self._identity_criterion.iteritems():
result = result and (context.getProperty(property) in value)
for property, (min, max) in self._range_criterion.iteritems():
value = context.getProperty(property)
if min is not None:
result = result and (value >= min)
if max is not None:
result = result and (value < max)
for property_, value in self._identity_criterion.iteritems():
result = result and (context.getProperty(property_) in value)
for property_, (min_, max_) in self._range_criterion.iteritems():
value = context.getProperty(property_)
if min_ is not None:
result = result and (value >= min_)
if max_ is not None:
result = result and (value < max_)
multimembership_criterion_base_category_list = \
self.getMultimembershipCriterionBaseCategoryList()
membership_criterion_base_category_list = \
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>ContributionPredicate</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.Document.ContributionPredicate</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.ContributionPredicate</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -36,7 +36,7 @@ from AccessControl.PermissionRole import PermissionRole
from Products.ERP5Type import Permissions, PropertySheet
from erp5.component.document.Movement import Movement
from Products.ERP5.Document.MappedValue import MappedValue
from erp5.component.document.MappedValue import MappedValue
from erp5.component.document.ImmobilisationMovement import ImmobilisationMovement
from erp5.component.interface.IDivergenceController import IDivergenceController
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>DomainGenerator</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.Document.DomainGenerator</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.DomainGenerator</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -32,9 +32,9 @@ from DateTime import DateTime
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.TextDocument import TextDocument
from erp5.component.document.TextDocument import TextDocument
from Products.ERP5.Document.File import File
from Products.ERP5.mixin.mail_message import MailMessageMixin, testCharsetAndConvert
from erp5.component.mixin.MailMessageMixin import MailMessageMixin, testCharsetAndConvert
from Products.ERP5.mixin.document_proxy import DocumentProxyMixin, DocumentProxyError
from MethodObject import Method
......
......@@ -37,7 +37,7 @@ from erp5.component.interface.IImmobilisationItem import IImmobilisationItem
from Products.ERP5Type.DateUtils import addToDate, getClosestDate, roundDate
from Products.ERP5Type.DateUtils import getRoundedMonthBetween, millis
from Products.ERP5Type.DateUtils import getAccountableYearFraction
from Products.ERP5.Document.Amount import Amount
from erp5.component.document.Amount import Amount
from erp5.component.document.Item import Item
from Products.CMFCore.utils import getToolByName
from erp5.component.document.ImmobilisationMovement import (
......
......@@ -31,7 +31,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5.Document.Amount import Amount
from erp5.component.document.Amount import Amount
class Item(XMLObject, Amount):
......
......@@ -29,12 +29,11 @@
import zope.interface
from AccessControl import ClassSecurityInfo
from Acquisition import aq_base
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.Core.Predicate import Predicate
TRANSFORMATION_FIX = True
_MARKER = []
_MARKER = object()
class MappedValue(Predicate):
"""
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>MappedValue</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.Document.MappedValue</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.MappedValue</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -41,7 +41,7 @@ from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type.UnrestrictedMethod import unrestricted_apply
from erp5.component.mixin.AmountGeneratorMixin import AmountGeneratorMixin
from Products.ERP5.mixin.composition import CompositionMixin
from Products.ERP5.Document.Amount import Amount
from erp5.component.document.Amount import Amount
from Products.ERP5Type.Cache import transactional_cached
from erp5.component.interface.IAmountGenerator import IAmountGenerator
from erp5.component.interface.IMovement import IMovement
......
......@@ -30,8 +30,8 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.MappedValue import MappedValue
from Products.ERP5.Document.Amount import Amount
from erp5.component.document.MappedValue import MappedValue
from erp5.component.document.Amount import Amount
class Path(MappedValue, Amount):
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>Path</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.Document.Path</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.Path</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>PredicateGroup</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.Document.PredicateGroup</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.PredicateGroup</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -85,8 +85,8 @@ class PredicateMatrix(XMLMatrix):
c.edit( mapped_value_property_list = ( 'title',),
predicate_category_list = [self._getOb(k_item).getRelativeUrl()
for k_item in k],
title = " * ".join(map(lambda k_item : \
self.unrestrictedTraverse(k_item).getTitle(), k)),
title = " * ".join([
self.unrestrictedTraverse(k_item).getTitle() for k_item in k ]),
force_update = 1
)
else :
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>PredicateMatrix</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.Document.PredicateMatrix</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.PredicateMatrix</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from AccessControl.ZopeGuards import guarded_getattr
from AccessControl import ClassSecurityInfo
from zLOG import LOG, WARNING
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.Document import Document, ConversionError, _MARKER, DEFAULT_CONTENT_TYPE
from Products.ERP5.Document.File import File
from Products.ERP5Type.WebDAVSupport import TextContent
from Products.ERP5.Document.Document import VALID_IMAGE_FORMAT_LIST, VALID_TEXT_FORMAT_LIST
import cStringIO
from string import Template
# Mixin Import
from Products.ERP5.mixin.cached_convertable import CachedConvertableMixin
from Products.ERP5.mixin.base_convertable import BaseConvertableFileMixin
from Products.ERP5Type.mixin.text_content_history import TextContentHistoryMixin
from Products.ERP5Type.Utils import guessEncodingFromText
from lxml import html as etree_html
from lxml import etree
class TextDocument(CachedConvertableMixin, BaseConvertableFileMixin, TextContentHistoryMixin,
TextContent, File):
"""A TextDocument impletents IDocument, IFile, IBaseConvertable, ICachedconvertable
and ITextConvertable
"""
meta_type = 'ERP5 Text Document'
portal_type = 'Text Document'
add_permission = Permissions.AddPortalContent
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.DublinCore
, PropertySheet.Version
, PropertySheet.Document
, PropertySheet.ExternalDocument
, PropertySheet.Url
, PropertySheet.TextDocument
, PropertySheet.Data
, PropertySheet.Reference
)
def _substituteTextContent(self, text, safe_substitute=True, **kw):
# If a method for string substitutions of the text content, perform it.
# Decode everything into unicode before the substitutions, in order to
# avoid encoding errors.
method_id = self.getTextContentSubstitutionMappingMethodId()
if method_id:
try:
method = guarded_getattr(self, method_id)
except AttributeError:
LOG('TextDocument', WARNING, 'could not get the substitution'
' mapping method %s from %r, so the content will not be'
' substituted.' % (method_id, self.getRelativeUrl()))
return text
mapping = method(**kw)
is_str = isinstance(text, str)
if is_str:
text = text.decode('utf-8')
class UnicodeMapping:
def __getitem__(self, item):
v = mapping[item]
if isinstance(v, str):
v = v.decode('utf-8')
elif not isinstance(v, unicode):
v = str(v).decode('utf-8')
return v
unicode_mapping = UnicodeMapping()
if safe_substitute:
text = Template(text).safe_substitute(unicode_mapping)
else:
text = Template(text).substitute(unicode_mapping)
# If the original was a str, convert it back to str.
if is_str:
text = text.encode('utf-8')
return text
security.declareProtected(Permissions.AccessContentsInformation, 'asSubjectText')
def asSubjectText(self, substitution_method_parameter_dict=None, safe_substitute=True, **kw):
"""
Converts the subject of the document to a textual representation.
"""
subject = TextDocument.inheritedAttribute('asSubjectText')(self, **kw)
if substitution_method_parameter_dict is None:
substitution_method_parameter_dict = {}
return self._substituteTextContent(subject, safe_substitute=safe_substitute,
**substitution_method_parameter_dict)
def _convert(self, format, substitution_method_parameter_dict=None, # pylint: disable=redefined-builtin
safe_substitute=True, charset=None, text_content=None, substitute=True, **kw):
"""
Convert text using portal_transforms or oood
"""
# XXX 'or DEFAULT_CONTENT_TYPE' is compaptibility code used for old
# web_page that have neither content_type nor text_format. Migration
# should be done to make all web page having content_type property
src_mimetype = self.getContentType() or DEFAULT_CONTENT_TYPE
if not format and src_mimetype == 'text/html':
format = 'html' # Force safe_html
if not format:
# can return document without conversion
return src_mimetype, self.getTextContent()
portal = self.getPortalObject()
mime_type = portal.mimetypes_registry.lookupExtension('name.%s' % format)
original_mime_type = mime_type = str(mime_type)
if text_content is None:
# check if document has set text_content and convert if necessary
text_content = self.getTextContent()
if text_content:
kw['format'] = format
convert_kw = {}
# PortalTransforms does not accept empty values for 'encoding' parameter
if charset:
kw['charset'] = convert_kw['encoding'] = charset
if not self.hasConversion(**kw):
portal_transforms = portal.portal_transforms
filename = self.getFilename()
if mime_type == 'text/html':
mime_type = 'text/x-html-safe'
if src_mimetype != "image/svg+xml":
result = portal_transforms.convertToData(mime_type, text_content,
object=self, context=self,
filename=filename,
mimetype=src_mimetype,
**convert_kw)
if result is None:
raise ConversionError('TextDocument conversion error. '
'portal_transforms failed to convert '
'from %r to %s: %r' %
(src_mimetype, mime_type, self))
else:
result = text_content
if format in VALID_IMAGE_FORMAT_LIST:
# Include extra parameter for image conversions
temp_image = self.portal_contributions.newContent(
portal_type='Image',
file=cStringIO.StringIO(),
filename=self.getId(),
temp_object=1)
temp_image._setData(result)
_, result = temp_image.convert(**kw)
self.setConversion(result, original_mime_type, **kw)
else:
mime_type, result = self.getConversion(**kw)
if substitute and format in VALID_TEXT_FORMAT_LIST:
# only textual content can be sustituted
if substitution_method_parameter_dict is None:
substitution_method_parameter_dict = {}
result = self._substituteTextContent(result, safe_substitute=safe_substitute,
**substitution_method_parameter_dict)
return original_mime_type, result
else:
# text_content is not set, return empty string instead of None
return original_mime_type, ''
security.declareProtected(Permissions.AccessContentsInformation, 'getContentBaseURL')
def getContentBaseURL(self):
"""
Returns the content base URL based on the actual content
(in HTML)
"""
if self.hasTextContent():
html = self._asHTML()
# a document can be entirely stripped by safe_html
# so its html conversion can be empty
if html.strip():
html_tree = etree_html.fromstring(html)
base_list = [href for href in html_tree.xpath('//base/@href') if href]
if base_list:
return str(base_list[0])
return Document.getContentBaseURL(self)
security.declareProtected(Permissions.ModifyPortalContent, 'setBaseData')
def setBaseData(self, value):
"""Store base_data into text_content
"""
self._setTextContent(value)
security.declareProtected(Permissions.ModifyPortalContent, '_setBaseData')
_setBaseData = setBaseData
security.declareProtected(Permissions.ModifyPortalContent, '_baseSetBaseData')
_baseSetBaseData = _setBaseData
security.declareProtected(Permissions.ModifyPortalContent, 'setBaseContentType')
def setBaseContentType(self, value):
"""store value into content_type
"""
self._setContentType(value)
security.declareProtected(Permissions.ModifyPortalContent, '_setBaseContentType')
_setBaseContentType = setBaseContentType
security.declareProtected(Permissions.ModifyPortalContent, '_baseSetBaseContentType')
_baseSetBaseContentType = _setBaseContentType
security.declareProtected(Permissions.AccessContentsInformation, 'getBaseData')
def getBaseData(self, default=_MARKER):
"""
"""
self._checkConversionFormatPermission(None)
if default is _MARKER:
return self.getTextContent()
else:
return self.getTextContent(default=default)
security.declareProtected(Permissions.AccessContentsInformation, 'hasBaseData')
def hasBaseData(self):
"""
"""
return self.hasTextContent()
security.declareProtected(Permissions.AccessContentsInformation, 'getContentType')
def getContentType(self, default=_MARKER): # pylint: disable=arguments-differ
"""Backward compatibility, read content_type
from text_format property
"""
if not self.hasContentType():
# getProperty can not be used
# because text_format is not registered in local_properties
if default is _MARKER:
return getattr(self, 'text_format', None)
else:
return getattr(self, 'text_format', default)
else:
if default is _MARKER:
return self._baseGetContentType()
else:
return self._baseGetContentType(default)
# base_convertable support
security.declareProtected(Permissions.AccessContentsInformation, 'isSupportBaseDataConversion')
def isSupportBaseDataConversion(self):
"""
"""
return True
def _convertToBaseFormat(self):
"""Conversion to base format for TextDocument consist
to convert file content into utf-8
"""
def guessCharsetAndConvert(document, text_content, content_type):
"""
return encoded content_type and message if encoding
is not utf-8
"""
codec = guessEncodingFromText(text_content, content_type)
if codec is not None:
try:
text_content = text_content.decode(codec).encode('utf-8')
except (UnicodeDecodeError, LookupError):
message = 'Conversion to base format with codec %r fails' % codec
# try again with another guesser based on file command
codec = guessEncodingFromText(text_content, 'text/plain')
if codec is not None:
try:
text_content = text_content.decode(codec).encode('utf-8')
except (UnicodeDecodeError, LookupError):
message = 'Conversion to base format with codec %r fails'\
% codec
else:
message = 'Conversion to base format with codec %r succeeds'\
% codec
else:
message = 'Conversion to base format with codec %r succeeds'\
% codec
else:
message = 'Conversion to base format without codec fails'
return text_content, message
content_type = self.getContentType() or DEFAULT_CONTENT_TYPE
text_content = self.getData()
if content_type.endswith('xml'):
try:
tree = etree.fromstring(text_content)
text_content = etree.tostring(tree, encoding='utf-8', xml_declaration=True)
content_type = 'application/xml'
message = 'Conversion to base format succeeds'
except etree.XMLSyntaxError: # pylint: disable=catching-non-exception
message = 'Conversion to base format without codec fails'
elif content_type == 'text/html':
re_match = self.charset_parser.search(text_content)
message = 'Conversion to base format succeeds'
if re_match is not None:
charset = re_match.group('charset')
try:
# Use encoding in html document
text_content = text_content.decode(charset).encode('utf-8')
except (UnicodeDecodeError, LookupError):
# Encoding read from document is wrong
text_content, message = guessCharsetAndConvert(self,
text_content, content_type)
else:
message = 'Conversion to base format with charset %r succeeds'\
% charset
if charset.lower() != 'utf-8':
charset = 'utf-8' # Override charset if convertion succeeds
# change charset value in html_document as well
def subCharset(matchobj):
keyword = matchobj.group('keyword')
charset = matchobj.group('charset')
if not (keyword or charset):
# no match, return same string
return matchobj.group(0)
elif keyword:
# if keyword is present, replace charset just after
return keyword + 'utf-8'
text_content = self.charset_parser.sub(subCharset, text_content)
else:
text_content, message = guessCharsetAndConvert(self,
text_content, content_type)
else:
# generaly text/plain
try:
# if succeeds, not need to change encoding
# it's already utf-8
text_content.decode('utf-8')
except (UnicodeDecodeError, LookupError):
text_content, message = guessCharsetAndConvert(self,
text_content, content_type)
else:
message = 'Conversion to base format succeeds'
self._setBaseData(text_content)
self._setBaseContentType(content_type)
return message
security.declareProtected(Permissions.AccessContentsInformation, 'getTextContent')
def getTextContent(self, default=_MARKER):
"""Overriden method to check
permission to access content in raw format
"""
self._checkConversionFormatPermission(None)
if default is _MARKER:
return self._baseGetTextContent()
else:
return self._baseGetTextContent(default)
# Backward compatibility for replacement of text_format by content_type
security.declareProtected(Permissions.AccessContentsInformation, 'getTextFormat')
def getTextFormat(self, default=_MARKER):
"""
"""
LOG('TextDocument', WARNING,
'Usage of text_format is deprecated, use content_type instead')
return self.getContentType(default)
security.declareProtected(Permissions.ModifyPortalContent, 'setTextFormat')
def setTextFormat(self, value):
"""
"""
LOG('TextDocument', WARNING,
'Usage of text_format is deprecated, use content_type instead')
return self.setContentType(value)
security.declareProtected(Permissions.ModifyPortalContent, '_setTextFormat')
def _setTextFormat(self, value):
"""
"""
LOG('TextDocument', WARNING,
'Usage of text_format is deprecated, use content_type instead')
return self._setContentType(value)
def getData(self, default=_MARKER):
"""getData must returns original content but TextDocument accepts
data or text_content to store original content.
Fallback on text_content property if data is not defined
"""
if not self.hasData():
if default is _MARKER:
return self.getTextContent()
else:
return self.getTextContent(default)
else:
if default is _MARKER:
return File.getData(self)
else:
return File.getData(self, default)
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>TextDocument</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.Document.TextDocument</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.TextDocument</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -76,7 +76,7 @@ class Transition(XMLObject):
status_dict[state_bc_id] = document.getCategoryMembershipList(state_bc_id)[0]
state_object = document.unrestrictedTraverse(status_dict[state_bc_id])
object = workflow.getStateChangeInformation(document, state_object, transition=self)
object_ = workflow.getStateChangeInformation(document, state_object, transition=self)
# Update all variables
for variable in workflow.contentValues(portal_type='Variable'):
......@@ -87,13 +87,13 @@ class Transition(XMLObject):
if variable_title in form_kw:
status_dict[variable_title] = form_kw[variable_title]
else:
status_dict[variable_title] = variable.getInitialValue(object=object)
status_dict[variable_title] = variable.getInitialValue(object=object_)
# Update all transition variables
if form_kw is not None:
object.REQUEST.other.update(form_kw)
object_.REQUEST.other.update(form_kw)
for variable in self.contentValues(portal_type='Transition Variable'):
status_dict[variable.getCausalityTitle()] = variable.getInitialValue(object=object)
status_dict[variable.getCausalityTitle()] = variable.getInitialValue(object=object_)
workflow._updateWorkflowHistory(document, status_dict)
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>Transition</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5Workflow.Document.Transition</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.Transition</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -35,7 +35,7 @@ from Acquisition import aq_base, Implicit
from Products.ERP5.GeneratedAmountList import GeneratedAmountList
from Products.ERP5Type import Permissions
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5.Document.MappedValue import MappedValue
from erp5.component.document.MappedValue import MappedValue
from erp5.component.interface.IAmountGenerator import IAmountGenerator
# XXX What should be done when there is no base_application ?
......
......@@ -30,7 +30,6 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type import Permissions
from Products.ERP5Type.Utils import guessEncodingFromText
from Products.ERP5.Document.TextDocument import TextDocument
from zLOG import LOG, INFO
from email.header import decode_header, HeaderParseError
......@@ -45,7 +44,7 @@ def testCharsetAndConvert(text_content, content_type, encoding):
text_content = text_content.decode(encoding).encode('utf-8')
else:
text_content = text_content.decode().encode('utf-8')
except (UnicodeDecodeError, LookupError), error_message:
except (UnicodeDecodeError, LookupError):
encoding = guessEncodingFromText(text_content, content_type)
if encoding is not None:
try:
......@@ -96,7 +95,6 @@ class MailMessageMixin:
# Try to get the favourite text format defined on preference
preferred_content_type = self.getPortalObject().portal_preferences.\
getPreferredTextFormat('text/html')
favourite_part = None
for subpart in part.get_payload():
if subpart.get_content_type() == preferred_content_type:
part_list.insert(0, subpart)
......@@ -210,11 +208,11 @@ class MailMessageMixin:
if 'text/html' in content_type:
part_encoding = part.get_content_charset()
message_text = part.get_payload(decode=1)
text_result, encoding = testCharsetAndConvert(message_text,
text_result, _ = testCharsetAndConvert(message_text,
content_type,
part_encoding)
# Strip out html content in safe mode.
mime, content = self.convert(format='html',
_, content = self.convert(format='html',
text_content=text_result,
encoding=part_encoding,
index=index) # add index to generate
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Mixin Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>MailMessageMixin</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5.mixin.mail_message</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>mixin.erp5.MailMessageMixin</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Mixin Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
document.erp5.Amount
document.erp5.AmountGeneratorLine
document.erp5.AppliedRule
document.erp5.BaseDomain
document.erp5.ContributionPredicate
document.erp5.Delivery
document.erp5.DeliveryCell
document.erp5.DeliveryLine
document.erp5.DomainGenerator
document.erp5.EmailDocument
document.erp5.Event
document.erp5.ImmobilisableItem
......@@ -15,9 +19,15 @@ document.erp5.InvoiceCell
document.erp5.InvoiceLine
document.erp5.Item
document.erp5.MailMessage
document.erp5.MappedValue
document.erp5.Movement
document.erp5.Order
document.erp5.PackingList
document.erp5.Path
document.erp5.PredicateGroup
document.erp5.PredicateMatrix
document.erp5.Project
document.erp5.ScriptConstraint
document.erp5.SimulationMovement
document.erp5.TextDocument
document.erp5.Transition
\ No newline at end of file
mixin.erp5.AmountGeneratorMixin
mixin.erp5.ExplainableMixin
mixin.erp5.MailMessageMixin
mixin.erp5.MovementCollectionUpdaterMixin
mixin.erp5.MovementGeneratorMixin
mixin.erp5.RuleMixin
......
......@@ -53,7 +53,7 @@ implements_tuple_list = [
(('erp5.component.document.Image', 'Image'), 'IDocument'),
(('Products.ERP5.Document.File', 'File'), 'IDocument'),
(('erp5.component.document.OOoDocument', 'OOoDocument'), 'IDocument'),
(('Products.ERP5.Document.TextDocument', 'TextDocument'), 'IDocument'),
(('erp5.component.document.TextDocument', 'TextDocument'), 'IDocument'),
(('erp5.component.document.EmailDocument', 'EmailDocument'), 'IDocument'),
(('erp5.component.document.Event', 'Event'), 'IDocument'),
# IAmountList
......
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