parameters: (site, portal_type_name)
return_statement: return is_partially_generated, (tuple(base_class_list),
                                    portal_type_category_list,
                                    interface_class_list,
                                    attribute_dict)
docstring: Given a portal type, look up in Types Tool the corresponding
    Base Type object holding the definition of this portal type,
    and computes __bases__ and __dict__ for the class that will
    be created to represent this portal type
  
    Returns tuple with 4 items:
      - base_tuple: a tuple of classes to be used as __bases__
      - base_category_list: categories defined on the portal type
          (and portal type only: this excludes property sheets)
      - interface_list: list of zope interfaces the portal type implements
      - attribute dictionary: any additional attributes to put on the class
docstring_summary: Given a portal type, look up in Types Tool the corresponding
    Base Type object holding the definition of this portal type,
    and computes __bases__ and __dict__ for the class that will
    be created to represent this portal type
docstring_tokens: Given a portal type look up in Types Tool the corresponding Base Type object holding the definition of this portal type and computes __bases__ and __dict__ for the class that will be created to represent this portal type
function: def generatePortalTypeClass(site, portal_type_name):
    """
    Given a portal type, look up in Types Tool the corresponding
    Base Type object holding the definition of this portal type,
    and computes __bases__ and __dict__ for the class that will
    be created to represent this portal type
  
    Returns tuple with 4 items:
      - base_tuple: a tuple of classes to be used as __bases__
      - base_category_list: categories defined on the portal type
          (and portal type only: this excludes property sheets)
      - interface_list: list of zope interfaces the portal type implements
      - attribute dictionary: any additional attributes to put on the class
    """
    # LOG("ERP5Type.dynamic", INFO, "Loading portal type " + portal_type_name)
  
    global core_portal_type_class_dict
  
    portal_type_category_list = []
    attribute_dict = dict(portal_type=portal_type_name,
                          _categories=[],
                          constraints=[])
  
    # When only the inner portal type class is loaded (ERP5Site initial
    # installation, recursive loads, bootstrap)
    is_partially_generated = False
  
    if portal_type_name in core_portal_type_class_dict:
      if not core_portal_type_class_dict[portal_type_name]['generating']:
        # Loading the (full) outer portal type class
        core_portal_type_class_dict[portal_type_name]['generating'] = True
      else:
        # Loading the inner portal type class without any mixin,
        # interface or Property Sheet
        klass = _importFilesystemClass(document_class_registry.get(
          core_portal_type_class_dict[portal_type_name]['type_class']))
  
        # LOG("ERP5Type.dynamic", INFO,
        #     "Loaded portal type %s (INNER)" % portal_type_name)
  
        # Don't do anything else, just allow to load fully the outer
        # portal type class
        is_partially_generated = True
        return is_partially_generated, ((klass,), [], [], attribute_dict)
  
    # Ugly but done only once on Workflows (not data) so no migration code:
    # Configurator Workflow implementation (workflow_module) used to have a
    # 'Variable' Portal Type. Because the name was too generic this has been
    # renamed to 'Workflow Variable' in ERP5 Workflow (portal_workflow)
    from .. import WITH_LEGACY_WORKFLOW
    if WITH_LEGACY_WORKFLOW and portal_type_name == 'Variable':
      portal_type_name = 'Workflow Variable'
  
    # Do not use __getitem__ (or _getOb) because portal_type may exist in a
    # type provider other than Types Tool.
    portal_type = getattr(site.portal_types, portal_type_name, None)
  
    type_class = None
  
    if portal_type is not None:
      # type_class has a compatibility getter that should return
      # something even if the field is not set (i.e. Base Type object
      # was not migrated yet). It only works if factory_method_id is set.
      type_class = portal_type.getTypeClass()
  
      # The Tools used to have 'Folder' or None as type_class instead of
      # 'NAME Tool', so make sure the type_class is correct
      #
      # NOTE: under discussion so might be removed later on
      if portal_type_name.endswith('Tool') and type_class in ('Folder', None):
        type_class = portal_type_name.replace(' ', '')
  
      mixin_list = portal_type.getTypeMixinList()
      interface_list = portal_type.getTypeInterfaceList()
      portal_type_category_list = portal_type.getTypeBaseCategoryList()
      attribute_dict['_categories'] = portal_type_category_list[:]
      acquire_local_role = bool(portal_type.getTypeAcquireLocalRole())
    else:
      LOG("ERP5Type.dynamic", WARNING,
          "Cannot find a portal type definition for '%s', trying to guess..."
          % portal_type_name)
  
    # But if neither factory_init_method_id nor type_class are set on
    # the portal type, we have to try to guess, for compatibility.
    # Moreover, some tools, such as 'Activity Tool', don't have any
    # portal type
    if type_class is None:
      if portal_type_name in core_portal_type_class_dict:
        # Only happen when portal_types is empty (e.g. when creating a
        # new ERP5Site)
        type_class = core_portal_type_class_dict[portal_type_name]['type_class']
        is_partially_generated = True
      else:
        # Missing Portal Type. This should not happen (all ERP5 object *must*
        # have a Portal Type and newTempXXX being deprecated) but try anyway to
        # figure out a coresponding Document class.
        type_class = portal_type_name.replace(' ', '')
  
      mixin_list = []
      interface_list = []
      acquire_local_role = True
  
    # Ugly but done only once on Workflows (not data) so no migration code:
    # Configurator Workflow implementation (workflow_module) used to have a
    # dedicated Portal Type for Transition Variable but this is now the same as
    # any other Workflow Variable.
    if WITH_LEGACY_WORKFLOW:
      if portal_type_name == 'Transition Variable':
        type_class = 'WorkflowVariable'
      elif portal_type_name in ('State', 'Transition'):
        type_class = 'Workflow' + portal_type_name
  
    if type_class is None:
      raise AttributeError('Document class is not defined on Portal Type ' + \
                             portal_type_name)
  
    klass = None
    if '.' in type_class:
      type_class_path = type_class
    else:
      type_class_path = None
  
      # Skip any document within ERP5Type Product as it is needed for
      # bootstrapping anyway
      type_class_namespace = document_class_registry.get(type_class, '')
      if not (type_class_namespace.startswith('Products.ERP5Type') or
              portal_type_name in core_portal_type_class_dict):
        if portal_type_name.endswith('Tool'):
          import erp5.component.tool
          klass = _importComponentClass(erp5.component.tool, type_class)
  
        # Tool Component was introduced recently and some Tool have already been
        # migrated as Document Component
        if klass is None:
          import erp5.component.document
          klass = _importComponentClass(erp5.component.document, type_class)
  
      if klass is None:
        type_class_path = document_class_registry.get(type_class)
        if type_class_path is None:
          raise AttributeError('Document class %s has not been registered:'
                               ' cannot import it as base of Portal Type %s'
                               % (type_class, portal_type_name))
  
    if klass is None:
      try:
        klass = _importFilesystemClass(type_class_path)
      except ImportError:
        error_msg = 'Could not import %s of Portal Type %s' % (type_class,
                                                               portal_type_name)
  
        LOG("ERP5Type.Dynamic", WARNING, error_msg, error=True)
        raise AttributeError(error_msg)
  
    global property_sheet_generating_portal_type_set
  
    accessor_holder_list = []
  
    if portal_type_name not in property_sheet_generating_portal_type_set:
      # LOG("ERP5Type.dynamic", INFO,
      #     "Filling accessor holder list for portal_type " + portal_type_name)
  
      property_sheet_generating_portal_type_set.add(portal_type_name)
      try:
        # Initialize ZODB Property Sheets accessor holders
        accessor_holder_list = createAllAccessorHolderList(site,
                                                           portal_type_name,
                                                           portal_type,
                                                           klass)
  
        base_category_set = set(attribute_dict['_categories'])
        for accessor_holder in accessor_holder_list:
          base_category_set.update(accessor_holder._categories)
          attribute_dict['constraints'].extend(accessor_holder.constraints)
  
        attribute_dict['_categories'] = list(base_category_set)
      finally:
        property_sheet_generating_portal_type_set.remove(portal_type_name)
    else:
      is_partially_generated = True
  
    # LOG("ERP5Type.dynamic", INFO,
    #     "Filled accessor holder list for portal_type %s (%s)" % \
    #     (portal_type_name, accessor_holder_list))
  
    mixin_class_list = []
    if mixin_list:
      # Only one Mixin class per ZODB Component (!= FS) where module_name ==
      # class_name, name ending with 'Mixin'.
      #
      # Rationale: same as Document/Interface; consistent naming; avoid a
      # registry like there used to be with FS.
      import erp5.component.mixin
      for mixin in mixin_list:
        mixin_class = _importComponentClass(erp5.component.mixin, mixin)
        if mixin_class is None:
          mixin_class = _importFilesystemClass(mixin_class_registry[mixin])
  
        mixin_class_list.append(mixin_class)
  
    base_class_list = [klass] + accessor_holder_list + mixin_class_list + [
      # _getAcquireLocalRoles is accessed by security machinery, so it needs to
      # be fast: make it a ConstantGetter while we have access to portal_type
      # configuration.
      ACQUIRE_LOCAL_ROLE_GETTER_DICT[acquire_local_role],
    ]
  
    interface_class_list = []
    if interface_list:
      # Filesystem Interfaces may have defined several Interfaces in one file
      # but only *one* Interface per ZODB Component where module_name ==
      # class_name, name starting with 'I'.
      #
      # Rationale: same as Document/Mixin; consistent naming; avoid a registry
      # like there used to be for Mixin or importing all class in
      # Products.ERP5Type.interfaces.__init__.py.
      import erp5.component.interface
      from Products.ERP5Type import interfaces as filesystem_interfaces
      for interface in interface_list:
        interface_class = _importComponentClass(erp5.component.interface, interface)
        if interface_class is None:
          interface_class = getattr(filesystem_interfaces, interface)
  
        interface_class_list.append(interface_class)
  
    if portal_type_name in core_portal_type_class_dict:
      core_portal_type_class_dict[portal_type_name]['generating'] = False
  
    attribute_dict['_restricted_setter_set'] = {method
          for ancestor in base_class_list
          for permissions in getattr(ancestor, '__ac_permissions__', ())
          if permissions[0] not in ('Access contents information',
                                    'Modify portal content')
          for method in permissions[1]
          if method.startswith('set')}
  
    attribute_dict['_restricted_getter_set'] = {method
          for ancestor in base_class_list
          for permissions in getattr(ancestor, '__ac_permissions__', ())
          if permissions[0] not in ('Access contents information', )
          for method in permissions[1]
          if method.startswith('get')}
  
    #LOG("ERP5Type.dynamic", INFO,
    #    "Portal type %s loaded with bases %s" \
    #        % (portal_type_name, repr(base_class_list)))
  
    return is_partially_generated, (tuple(base_class_list),
                                    portal_type_category_list,
                                    interface_class_list,
                                    attribute_dict)
function_tokens: def generatePortalTypeClass ( site , portal_type_name ) : # LOG("ERP5Type.dynamic", INFO, "Loading portal type " + portal_type_name) global core_portal_type_class_dict portal_type_category_list = [ ] attribute_dict = dict ( portal_type = portal_type_name , _categories = [ ] , constraints = [ ] ) # When only the inner portal type class is loaded (ERP5Site initial # installation, recursive loads, bootstrap) is_partially_generated = False if portal_type_name in core_portal_type_class_dict : if not core_portal_type_class_dict [ portal_type_name ] [ 'generating' ] : # Loading the (full) outer portal type class core_portal_type_class_dict [ portal_type_name ] [ 'generating' ] = True else : # Loading the inner portal type class without any mixin, # interface or Property Sheet klass = _importFilesystemClass ( document_class_registry . get ( core_portal_type_class_dict [ portal_type_name ] [ 'type_class' ] ) ) # LOG("ERP5Type.dynamic", INFO, #     "Loaded portal type %s (INNER)" % portal_type_name) # Don't do anything else, just allow to load fully the outer # portal type class is_partially_generated = True return is_partially_generated , ( ( klass , ) , [ ] , [ ] , attribute_dict ) # Ugly but done only once on Workflows (not data) so no migration code: # Configurator Workflow implementation (workflow_module) used to have a # 'Variable' Portal Type. Because the name was too generic this has been # renamed to 'Workflow Variable' in ERP5 Workflow (portal_workflow) from . . import WITH_LEGACY_WORKFLOW if WITH_LEGACY_WORKFLOW and portal_type_name == 'Variable' : portal_type_name = 'Workflow Variable' # Do not use __getitem__ (or _getOb) because portal_type may exist in a # type provider other than Types Tool. portal_type = getattr ( site . portal_types , portal_type_name , None ) type_class = None if portal_type is not None : # type_class has a compatibility getter that should return # something even if the field is not set (i.e. Base Type object # was not migrated yet). It only works if factory_method_id is set. type_class = portal_type . getTypeClass ( ) # The Tools used to have 'Folder' or None as type_class instead of # 'NAME Tool', so make sure the type_class is correct # # NOTE: under discussion so might be removed later on if portal_type_name . endswith ( 'Tool' ) and type_class in ( 'Folder' , None ) : type_class = portal_type_name . replace ( ' ' , '' ) mixin_list = portal_type . getTypeMixinList ( ) interface_list = portal_type . getTypeInterfaceList ( ) portal_type_category_list = portal_type . getTypeBaseCategoryList ( ) attribute_dict [ '_categories' ] = portal_type_category_list [ : ] acquire_local_role = bool ( portal_type . getTypeAcquireLocalRole ( ) ) else : LOG ( "ERP5Type.dynamic" , WARNING , "Cannot find a portal type definition for '%s', trying to guess..." % portal_type_name ) # But if neither factory_init_method_id nor type_class are set on # the portal type, we have to try to guess, for compatibility. # Moreover, some tools, such as 'Activity Tool', don't have any # portal type if type_class is None : if portal_type_name in core_portal_type_class_dict : # Only happen when portal_types is empty (e.g. when creating a # new ERP5Site) type_class = core_portal_type_class_dict [ portal_type_name ] [ 'type_class' ] is_partially_generated = True else : # Missing Portal Type. This should not happen (all ERP5 object *must* # have a Portal Type and newTempXXX being deprecated) but try anyway to # figure out a coresponding Document class. type_class = portal_type_name . replace ( ' ' , '' ) mixin_list = [ ] interface_list = [ ] acquire_local_role = True # Ugly but done only once on Workflows (not data) so no migration code: # Configurator Workflow implementation (workflow_module) used to have a # dedicated Portal Type for Transition Variable but this is now the same as # any other Workflow Variable. if WITH_LEGACY_WORKFLOW : if portal_type_name == 'Transition Variable' : type_class = 'WorkflowVariable' elif portal_type_name in ( 'State' , 'Transition' ) : type_class = 'Workflow' + portal_type_name if type_class is None : raise AttributeError ( 'Document class is not defined on Portal Type ' + portal_type_name ) klass = None if '.' in type_class : type_class_path = type_class else : type_class_path = None # Skip any document within ERP5Type Product as it is needed for # bootstrapping anyway type_class_namespace = document_class_registry . get ( type_class , '' ) if not ( type_class_namespace . startswith ( 'Products.ERP5Type' ) or portal_type_name in core_portal_type_class_dict ) : if portal_type_name . endswith ( 'Tool' ) : import erp5 . component . tool klass = _importComponentClass ( erp5 . component . tool , type_class ) # Tool Component was introduced recently and some Tool have already been # migrated as Document Component if klass is None : import erp5 . component . document klass = _importComponentClass ( erp5 . component . document , type_class ) if klass is None : type_class_path = document_class_registry . get ( type_class ) if type_class_path is None : raise AttributeError ( 'Document class %s has not been registered:' ' cannot import it as base of Portal Type %s' % ( type_class , portal_type_name ) ) if klass is None : try : klass = _importFilesystemClass ( type_class_path ) except ImportError : error_msg = 'Could not import %s of Portal Type %s' % ( type_class , portal_type_name ) LOG ( "ERP5Type.Dynamic" , WARNING , error_msg , error = True ) raise AttributeError ( error_msg ) global property_sheet_generating_portal_type_set accessor_holder_list = [ ] if portal_type_name not in property_sheet_generating_portal_type_set : # LOG("ERP5Type.dynamic", INFO, #     "Filling accessor holder list for portal_type " + portal_type_name) property_sheet_generating_portal_type_set . add ( portal_type_name ) try : # Initialize ZODB Property Sheets accessor holders accessor_holder_list = createAllAccessorHolderList ( site , portal_type_name , portal_type , klass ) base_category_set = set ( attribute_dict [ '_categories' ] ) for accessor_holder in accessor_holder_list : base_category_set . update ( accessor_holder . _categories ) attribute_dict [ 'constraints' ] . extend ( accessor_holder . constraints ) attribute_dict [ '_categories' ] = list ( base_category_set ) finally : property_sheet_generating_portal_type_set . remove ( portal_type_name ) else : is_partially_generated = True # LOG("ERP5Type.dynamic", INFO, #     "Filled accessor holder list for portal_type %s (%s)" % \ #     (portal_type_name, accessor_holder_list)) mixin_class_list = [ ] if mixin_list : # Only one Mixin class per ZODB Component (!= FS) where module_name == # class_name, name ending with 'Mixin'. # # Rationale: same as Document/Interface; consistent naming; avoid a # registry like there used to be with FS. import erp5 . component . mixin for mixin in mixin_list : mixin_class = _importComponentClass ( erp5 . component . mixin , mixin ) if mixin_class is None : mixin_class = _importFilesystemClass ( mixin_class_registry [ mixin ] ) mixin_class_list . append ( mixin_class ) base_class_list = [ klass ] + accessor_holder_list + mixin_class_list + [ # _getAcquireLocalRoles is accessed by security machinery, so it needs to # be fast: make it a ConstantGetter while we have access to portal_type # configuration. ACQUIRE_LOCAL_ROLE_GETTER_DICT [ acquire_local_role ] , ] interface_class_list = [ ] if interface_list : # Filesystem Interfaces may have defined several Interfaces in one file # but only *one* Interface per ZODB Component where module_name == # class_name, name starting with 'I'. # # Rationale: same as Document/Mixin; consistent naming; avoid a registry # like there used to be for Mixin or importing all class in # Products.ERP5Type.interfaces.__init__.py. import erp5 . component . interface from Products . ERP5Type import interfaces as filesystem_interfaces for interface in interface_list : interface_class = _importComponentClass ( erp5 . component . interface , interface ) if interface_class is None : interface_class = getattr ( filesystem_interfaces , interface ) interface_class_list . append ( interface_class ) if portal_type_name in core_portal_type_class_dict : core_portal_type_class_dict [ portal_type_name ] [ 'generating' ] = False attribute_dict [ '_restricted_setter_set' ] = { method for ancestor in base_class_list for permissions in getattr ( ancestor , '__ac_permissions__' , ( ) ) if permissions [ 0 ] not in ( 'Access contents information' , 'Modify portal content' ) for method in permissions [ 1 ] if method . startswith ( 'set' ) } attribute_dict [ '_restricted_getter_set' ] = { method for ancestor in base_class_list for permissions in getattr ( ancestor , '__ac_permissions__' , ( ) ) if permissions [ 0 ] not in ( 'Access contents information' , ) for method in permissions [ 1 ] if method . startswith ( 'get' ) } #LOG("ERP5Type.dynamic", INFO, #    "Portal type %s loaded with bases %s" \ #        % (portal_type_name, repr(base_class_list))) return is_partially_generated , ( tuple ( base_class_list ) , portal_type_category_list , interface_class_list , attribute_dict )