diff --git a/product/ERP5Type/DocumentationHelper.py b/product/ERP5Type/DocumentationHelper.py new file mode 100644 index 0000000000000000000000000000000000000000..cc23038dbbee5f106a762f5373d56eff654363ae --- /dev/null +++ b/product/ERP5Type/DocumentationHelper.py @@ -0,0 +1,499 @@ +from Acquisition import Implicit +from Products.ERP5Type import Permissions +from AccessControl import ClassSecurityInfo +from Globals import InitializeClass + +from Products.ERP5Type.Accessor.Accessor import Accessor +from Products.ERP5Type.Base import WorkflowMethod + +class DocumentationHelper(Implicit): + """ + Example URIs + + person_module/23 + person_module/23#title + person_module/23#getTitle + portal_worklows/validation_workflow + portal_worklows/validation_workflow/states/draft + portal_worklows/validation_workflow/states/draft#title + Products.ERP5Type.Document.Person.notify + Products.ERP5Type.Document.Person.isRAD + portal_types/Person + portal_types/Person/actions#view + """ + # Methods to override + def __init__(self, uri): + raise NotImplemented + + def getTitle(self): + """ + Returns the title of the documentation helper + (ex. class name) + """ + raise NotImplemented + + def getType(self): + """ + Returns the type of the documentation helper + (ex. Class, float, string, Portal Type, etc.) + """ + raise NotImplemented + + def getSectionList(self): + """ + Returns a list of documentation sections + """ + raise NotImplemented + + def getURI(self): + """ + Returns a URI to later access this documentation + from portal_classes + """ + raise NotImplemented + + # Generic methods which all subclasses should inherit + def getClass(self): + """ + Returns our own class name + """ + return self.__class__ + + def view(self): + """ + Renders the documentation with a standard form + ex. PortalTypeInstanceDocumentationHelper_view + """ + return getattr(self, '%s_view' % self.__class__)() + + +class PortalTypeInstanceDocumentationHelper(DocumentationHelper): + """ + Provides access to all documentation information + of a portal type instance. + """ + + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + def __init__(self, uri): + self.instance = self.getPortalObject().restrictedTraverse(uri) + + # API Implementation + def getTitle(self): + """ + Returns the title of the documentation helper + """ + return instance.getTitleOrId() + + def getType(self): + """ + Returns the type of the documentation helper + """ + return instance.getPortalType() + + def getSectionList(self): + """ + Returns a list of documentation sections + """ + return [ + { + 'id' : 'instance_property', + 'title' : 'Instance Properties', + 'class' : 'InstancePropertyDocumentationHelper', + 'uri' : self.getClassPropertyURIList(), + } + { + 'id' : 'accessor', + 'title' : 'Accessors', + 'class' : 'AccessorDocumentationHelper', + 'uri' : self.getClassPropertyURIList(), + } + ] + + # Specific methods + def getPortalType(self): + """ + """ + return self.instance.getPortalType() + + def _getPropertyHolder(self): + from Products.ERP5Type.Base import Base + return Base.aq_portal_type[self.getPortalType()] + + security.declareProtected( Permissions.AccessContentsInformation, 'getAccessorMethodItemList' ) + def getAccessorMethodItemList(self): + """ + """ + return self._getPropertyHolder().getAccessorMethodItemList() + + security.declareProtected( Permissions.AccessContentsInformation, 'getAccessorMethodIdList' ) + def getAccessorMethodIdList(self): + """ + """ + return self._getPropertyHolder().getAccessorMethodIdList() + + security.declareProtected( Permissions.AccessContentsInformation, 'getWorkflowMethodItemList' ) + def getWorkflowMethodItemList(self): + """ + """ + return self._getPropertyHolder().getWorkflowMethodItemList() + + security.declareProtected( Permissions.AccessContentsInformation, 'getWorkflowMethodIdList' ) + def getWorkflowMethodIdList(self): + """ + """ + return self._getPropertyHolder().getWorkflowMethodIdList() + + security.declareProtected( Permissions.AccessContentsInformation, 'getActionMethodItemList' ) + def getActionMethodItemList(self): + """ + """ + return self._getPropertyHolder().getActionMethodItemList() + + security.declareProtected( Permissions.AccessContentsInformation, 'getActionMethodIdList' ) + def getActionMethodIdList(self): + """ + """ + return self._getPropertyHolder().getActionMethodIdList() + + security.declareProtected( Permissions.AccessContentsInformation, 'getClassMethodItemList' ) + def getClassMethodItemList(self, inherited=1, local=1): + """ + Return a list of tuple (id, method) for every class method + """ + klass = self.instance.__class__ + return self._getPropertyHolder().getClassMethodItemList(klass, inherited=inherited, local=local) + + security.declareProtected( Permissions.AccessContentsInformation, 'getClassMethodIdList' ) + def getClassMethodIdList(self, inherited=1, local=1): + """ + Return a list of tuple (id, method) for every class method + """ + klass = self.instance.__class__ + return self._getPropertyHolder().getClassMethodIdList(klass, inherited=inherited, local=local) + + security.declareProtected( Permissions.AccessContentsInformation, 'getClassPropertyItemList' ) + def getClassPropertyItemList(self, inherited=1, local=1): + """ + Return a list of tuple (id, method) for every class method + """ + klass = self.instance.__class__ + return self._getPropertyHolder().getClassPropertyItemList(klass, inherited=inherited, local=local) + + security.declareProtected( Permissions.AccessContentsInformation, 'getClassPropertyIdList' ) + def getClassPropertyIdList(self, inherited=1, local=1): + """ + Return a list of tuple (id, method) for every class method + """ + klass = self.instance.__class__ + return self._getPropertyHolder().getClassPropertyIdList(klass, inherited=inherited, local=local) + + def getGeneratedPropertyIdList(self): + """ + """ + + def getGeneratedBaseCategoryIdList(self): + """ + """ + +InitializeClass(PortalTypeInstanceDocumentationHelper) + +class PortalDocumentationHelper(DocumentationHelper): + """ + """ + +class PortalTypeDocumentationHelper(DocumentationHelper): + """ + """ + +class ClassDocumentationHelper(DocumentationHelper): + """ + """ + +class CallableDocumentationHelper(DocumentationHelper): + """ + """ + +class InstancePropertyDocumentationHelper(DocumentationHelper): + """ + """ + +class PropertyDocumentationHelper(DocumentationHelper): + """ + """ + +class WorkflowDocumentationHelper(DocumentationHelper): + """ + """ + + + +if 0: + if 0: + static_method_list = [] # Class methods + static_property_list = [] # Class attributes + dynamic_method_list = [] # Workflow methods + dynamic_property_list = [] # Document properties + dynamic_category_list = [] # Categories + dynamic_accessor_list = [] # Accessors + + + security.declareProtected( Permissions.ManagePortal, 'asDocumentationHelper' ) + def asDocumentationHelper(self, item_id=None): + """ + Fills and return a DocHelper object from context. + Overload this in any object the has to fill the DocHelper in its own way. + + item_id : If specified, the documented item will be + getattr(self, item_title) if it exists, otherwise None will + be returned. + + TODO: + - Check that filtering is correct : display only and every things + defined on the class itself. + - Add a list of all accessible things in the documented item context + (heritated methods & attributes) in a light way that still allows to + link directly to the corresponding documentation. + - Rewrite accessor generation system so that it can generate accessor + names when given a property/category. + + KEEPMEs: + There are pieces of code in this function that can have interesting + results, but who are also very verbose. They are disabled (commented + out) by default, but they should be kept. + Explanation of the interest : + The accessors are gathered from 2 sources : the ones defined on the + PortalType, and systematically generated names, and tested for + presence on the documented item. + There are 4 cases then : + -Accessor whose name was generated exists both on the PortalType and + on the documented item. That's normal. + -Accessor whose name was generated exists neither on the PortalType + nor on the documented item. That's normal, but could mean that the + accessor name generator isn't optimal. + -Accessor whose name was generated is found on the object but not on + the PortalType. This is a problem. + -Accessors gathered from PortalType aren't all found by guessing + systematically the names. That means that the accessor name + generation is not perfect and requires improvement. + + nb: the accessors are gathered from 2 sources, the first is somehow + accidental when searching for workflow methods defined on the + PortalType, and are browsed a second time to be able to group them + by property or category. + """ + if item_id is None: + documented_item = self + item_id = documented_item.getTitle() + elif getattr(self, item_id, None) is not None: + documented_item = getattr(self, item_id) + else: + return None + + # The documented object is an instance (or not) of this class. + item_class = getattr(documented_item, '__bases__', None) is None \ + and documented_item.__class__ \ + or documented_item + + static_method_list = [] # Class methods + static_property_list = [] # Class attributes + dynamic_method_list = [] # Workflow methods + dynamic_property_list = [] # Document properties + dynamic_category_list = [] # Categories + dynamic_accessor_list = [] # Accessors + found_accessors = {} # Accessor names : filled by PortalType-level + # scan, and used in PropertySheet-level scan. + dochelper = newTempDocumentationHelper(self.getParentValue(), self.getId(), + title=item_id, type=item_class.__name__, + description=inspect.getdoc(documented_item), + ) + dochelper.setInheritanceList([x.__name__ for x in item_class.__bases__]) + try: + dochelper.setSourcePath(inspect.getsourcefile(item_class)) + except (IOError, TypeError): + pass + # dochelper.setSecurity() # (maybe) TODO: Add class instance security gthering. + + # Class-level method & properties + for k, v in item_class.__dict__.items(): + subdochelper = newTempDocumentationHelper(dochelper, k, + title=k, description=inspect.getdoc(v), + security=repr(getattr(documented_item, '%s__roles__' % (k,),None))) + try: + subdochelper.setType(v.__class__.__name__) + except AttributeError: + pass + try: + subdochelper.setSourcePath(inspect.getsourcefile(v)) + except (IOError, TypeError), err: + pass + try: + subdochelper.setSourceCode(inspect.getsource(v)) + except (IOError, TypeError), err: + pass + try: + subdochelper.setArgumentList(inspect.getargspec(v)) + except (IOError, TypeError), err: + pass + if subdochelper.getType() in ('function',): # This is a method + static_method_list.append(subdochelper) + elif subdochelper.getType() in ('int', 'float', 'long', 'str', 'tuple', 'dict', 'list') \ + and not subdochelper.getTitle().startswith('__'): # This is a property + subdochelper.setContent(pformat(v)) + static_property_list.append(subdochelper) + # FIXME: Is there any other interesting type ? + + # PortalType-level methods + # XXX: accessing portal_type directly because accessors are not generated on instances + if getattr(documented_item, 'portal_type', None) is not None: + for k, v in Base.aq_portal_type[documented_item.portal_type].__dict__.items(): + if callable(v) and not (k.startswith('_base') or k.startswith('_category')): + subdochelper = newTempDocumentationHelper(dochelper, k, + title=k, description=inspect.getdoc(v), + security=repr(getattr(documented_item, '%s__roles__' % (k,),None))) + try: + my_type = v.__class__.__name__ + subdochelper.setType(my_type) + except AttributeError: + pass + if 'Setter' not in my_type and \ + 'Getter' not in my_type and \ + 'Tester' not in my_type: # Accessors are handled separatelly. + dynamic_method_list.append(subdochelper) +# KEEPME: usefull to track the differences between accessors defined on +# PortalType and the one detected on the documented item. +# else: +# found_accessors[k] = v + + def generatePropertyAccessorNameList(property): + """ + Generates the possible accessor names for given property. + + FIXME: Should not exist here, but as accessor generation system. + """ + from Products.ERP5Type.Utils import UpperCase + res=[] + cased_id = UpperCase(property['id']) + for hidden in ('', '_'): + for getset in ('get', 'set', 'has'): # 'is', + for default in ('', 'Default', 'Translated'): + for value in ('', 'Value', 'TranslationDomain'): + for multivalued in ('', 'List', 'Set'): + res.append('%s%s%s%s%s%s' % (hidden, getset, default, cased_id, value, multivalued)) + if property.has_key('acquired_property_id') and \ + property['type'] == 'content': + for aq_property_id in property['acquired_property_id']: + cased_id = UpperCase('%s_%s' % (property['id'], aq_property_id)) + for hidden in ('', '_'): + for getset in ('get', 'set'): + for default in ('', 'Default'): + for multivalued in ('', 'List'): + res.append('%s%s%s%s%s' % (hidden, getset, default, cased_id, multivalued)) + return res + + def generateCategoryAccessorNameList(category): + """ + Generates the possible accessor names for given category. + + FIXME: Should not exist here, but as accessor generation system. + """ + from Products.ERP5Type.Utils import UpperCase + cased_id=UpperCase(category) + res=['%s%sIds' % (cased_id[0].lower(), cased_id[1:]), + '%s%sValues' % (cased_id[0].lower(), cased_id[1:])] + for hidden in ('', '_'): + for default in ('', 'Default'): + for multivalued in ('', 'List', 'Set'): + for attribute in ('', 'TranslatedTitle', 'Uid', 'LogicalPath', 'Id', 'TitleOrId', 'Reference', 'Title'): + res.append('%sget%s%s%s%s' % (hidden, default, cased_id, attribute, multivalued)) + for attribute in ('', 'Value', 'Uid'): + res.append('%sset%s%s%s%s' % (hidden, default, cased_id, attribute, multivalued)) + return res + + def accessorAsDocumentationHelper(accessor): + """ + Generates a documentation helper about a given accessor. + """ + accessor_dochelper = newTempDocumentationHelper(subdochelper, accessor_name, + title=accessor_name, + description=inspect.getdoc(accessor)) + try: + accessor_dochelper.setSourcePath(inspect.getsourcefile(accessor)) + except (IOError, TypeError), err: + pass + try: + accessor_dochelper.setSourceCode(inspect.getsource(accessor)) + except (IOError, TypeError), err: + pass +# KEEPME: usefull to track the differences between accessors defined on +# PortalType and the one detected on the documented item. +# if found_accessors.has_key(accessor_name): +# del(found_accessors[accessor_name]) +# else: +# LOG('asDocumentationHelper', 0, +# 'Found but not in the accessor list : %s of type %s' % \ +# (accessor_name, accessor.__class__.__name__)) + return accessor_dochelper + + # PropertySheet-level properties & categories + # Also handles accessors. + seen_properties=[] + seen_categories=[] + if getattr(documented_item, 'property_sheets', None) is not None: + for property_sheet in documented_item.property_sheets: + if getattr(property_sheet, '_properties', None) is not None: + for property in property_sheet._properties: + if property in seen_properties: + continue + seen_properties.append(property) + subdochelper = newTempDocumentationHelper(dochelper, k, + title=property['id'], description=property['description'], + type=property['type'], security=property['mode'], + content=pformat(documented_item.getProperty(property['id']))) + subdochelper_dynamic_accessor_list = [] + for accessor_name in generatePropertyAccessorNameList(property): + accessor = getattr(item_class, accessor_name, getattr(documented_item, accessor_name, None)) + # First get it on the class, and if not on the instance, thereby among dynamic accessors. + if accessor is not None: + subdochelper_dynamic_accessor_list.append(accessorAsDocumentationHelper(accessor)) + subdochelper_dynamic_accessor_list.sort() + subdochelper.setDynamicAccessorList(subdochelper_dynamic_accessor_list) + dynamic_accessor_list.append(subdochelper) + if getattr(documented_item, property['id'], None) is not None: + dynamic_property_list.append(subdochelper) + if getattr(property_sheet, '_categories', None) is not None: + for category in property_sheet._categories: + if category in seen_categories: + continue + seen_categories.append(category) + subdochelper = newTempDocumentationHelper(dochelper, category, title=category, + content=pformat(documented_item.getCategoryMembershipList(category))) + subdochelper_dynamic_accessor_list = [] + for accessor_name in generateCategoryAccessorNameList(category): + accessor = getattr(item_class, accessor_name, getattr(documented_item, accessor_name, None)) + # First get it on the class, and if not on the instance, thereby among dynamic accessors. + if accessor is not None: + subdochelper_dynamic_accessor_list.append(accessorAsDocumentationHelper(accessor)) + subdochelper_dynamic_accessor_list.sort() + subdochelper.setDynamicAccessorList(subdochelper_dynamic_accessor_list) + dynamic_accessor_list.append(subdochelper) + dynamic_category_list.append(subdochelper) + +# KEEPME: usefull to track the differences between accessors defined on +# PortalType and the one detected on the documented item. +# LOG('asDocumentationHelper', 0, found_accessors) + static_method_list.sort() + dochelper.setStaticMethodList(static_method_list) + static_property_list.sort() + dochelper.setStaticPropertyList(static_property_list) + dynamic_method_list.sort() + dochelper.setDynamicMethodList(dynamic_method_list) + dynamic_accessor_list.sort() + dochelper.setDynamicAccessorList(dynamic_accessor_list) + dynamic_category_list.sort() + dochelper.setDynamicCategoryList(dynamic_category_list) + dynamic_property_list.sort() + dochelper.setDynamicPropertyList(dynamic_property_list) + return dochelper +