src/pyams_utils/traversing.py
branchdev-tf
changeset 427 63284c98cdc1
parent 329 1482a4b86075
equal deleted inserted replaced
426:2022e4da3ad9 427:63284c98cdc1
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    10 # FOR A PARTICULAR PURPOSE.
    10 # FOR A PARTICULAR PURPOSE.
    11 #
    11 #
    12 
    12 
    13 __docformat__ = 'restructuredtext'
    13 """PyAMS_utils.traversing module
       
    14 
       
    15 This module provides a custom Pyramid "namespace" traverser: using "++name++" URLs allows
       
    16 to traverse URLs based on custom traversing adapters.
       
    17 
       
    18 It also provides a "get_parent" function, which returns a parent object of given object providing
       
    19 a given interface.
       
    20 """
    14 
    21 
    15 from pyramid.compat import decode_path_info, is_nonstr_iter
    22 from pyramid.compat import decode_path_info, is_nonstr_iter
    16 from pyramid.exceptions import NotFound, URLDecodeError
    23 from pyramid.exceptions import NotFound, URLDecodeError
    17 from pyramid.interfaces import VH_ROOT_KEY
    24 from pyramid.interfaces import VH_ROOT_KEY
    18 from pyramid.location import lineage
    25 from pyramid.location import lineage
    26 from pyams_utils.adapter import ContextAdapter, adapter_config
    33 from pyams_utils.adapter import ContextAdapter, adapter_config
    27 from pyams_utils.interfaces.traversing import IPathElements
    34 from pyams_utils.interfaces.traversing import IPathElements
    28 from pyams_utils.registry import query_utility
    35 from pyams_utils.registry import query_utility
    29 
    36 
    30 
    37 
       
    38 __docformat__ = 'restructuredtext'
       
    39 
       
    40 
    31 class NamespaceTraverser(ResourceTreeTraverser):
    41 class NamespaceTraverser(ResourceTreeTraverser):
    32     """Custom traverser handling views and namespaces
    42     """Custom traverser handling views and namespaces
    33 
    43 
    34     This is an upgraded version of native Pyramid traverser.
    44     This is an upgraded version of native Pyramid traverser.
    35     It adds:
    45     It adds:
    39 
    49 
    40     PLUS_SELECTOR = '+'
    50     PLUS_SELECTOR = '+'
    41     NAMESPACE_SELECTOR = PLUS_SELECTOR * 2
    51     NAMESPACE_SELECTOR = PLUS_SELECTOR * 2
    42 
    52 
    43     def __call__(self, request):
    53     def __call__(self, request):
    44 
    54         # pylint: disable=too-many-locals,too-many-branches,too-many-statements
    45         environ = request.environ
    55         environ = request.environ
    46         matchdict = request.matchdict
    56         matchdict = request.matchdict
    47 
    57 
    48         if matchdict is not None:
    58         if matchdict is not None:
    49             path = matchdict.get('traverse', slash) or slash
    59             path = matchdict.get('traverse', slash) or slash
    66                 # empty if mounted under a path in mod_wsgi, for example
    76                 # empty if mounted under a path in mod_wsgi, for example
    67                 path = request.path_info or slash
    77                 path = request.path_info or slash
    68             except KeyError:
    78             except KeyError:
    69                 # if environ['PATH_INFO'] is just not there
    79                 # if environ['PATH_INFO'] is just not there
    70                 path = slash
    80                 path = slash
    71             except UnicodeDecodeError as e:
    81             except UnicodeDecodeError as exc:
    72                 raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason)
    82                 raise URLDecodeError(exc.encoding, exc.object, exc.start, exc.end, exc.reason)
    73 
    83 
    74         if VH_ROOT_KEY in environ:
    84         if VH_ROOT_KEY in environ:
    75             # HTTP_X_VHM_ROOT
    85             # HTTP_X_VHM_ROOT
    76             vroot_path = decode_path_info(environ[VH_ROOT_KEY])
    86             vroot_path = decode_path_info(environ[VH_ROOT_KEY])
    77             vroot_tuple = split_path_info(vroot_path)
    87             vroot_tuple = split_path_info(vroot_path)
    81             vroot_tuple = ()
    91             vroot_tuple = ()
    82             vpath = path
    92             vpath = path
    83             vroot_idx = -1
    93             vroot_idx = -1
    84 
    94 
    85         root = self.root
    95         root = self.root
    86         ob = vroot = root
    96         obj = vroot = root
    87 
    97 
    88         request.registry.notify(BeforeTraverseEvent(root, request))
    98         request.registry.notify(BeforeTraverseEvent(root, request))
    89 
    99 
    90         if vpath == slash:
   100         if vpath == slash:
    91             # invariant: vpath must not be empty
   101             # invariant: vpath must not be empty
   102             ns_selector = self.NAMESPACE_SELECTOR
   112             ns_selector = self.NAMESPACE_SELECTOR
   103             view_selector = self.VIEW_SELECTOR
   113             view_selector = self.VIEW_SELECTOR
   104             vpath_tuple = split_path_info(vpath)
   114             vpath_tuple = split_path_info(vpath)
   105 
   115 
   106             for segment in vpath_tuple:
   116             for segment in vpath_tuple:
   107                 if ob is not root:
   117                 if obj is not root:
   108                     request.registry.notify(BeforeTraverseEvent(ob, request))
   118                     request.registry.notify(BeforeTraverseEvent(obj, request))
   109 
   119 
   110                 if segment == plus_selector:
   120                 if segment == plus_selector:
   111                     # check for custom namespace called '+'
   121                     # check for custom namespace called '+'
   112                     # currently this namespace is used in PyAMS_default_theme package to get direct access to a given
   122                     # currently this namespace is used in PyAMS_default_theme package to get
   113                     # content
   123                     # direct access to a given content
   114                     registry = get_current_registry()
   124                     registry = get_current_registry()
   115                     traverser = registry.queryMultiAdapter((ob, request), ITraversable, '+')
   125                     traverser = registry.queryMultiAdapter((obj, request), ITraversable, '+')
   116                     if traverser is None:
   126                     if traverser is None:
   117                         raise NotFound()
   127                         raise NotFound()
   118                     try:
   128                     try:
   119                         ob = traverser.traverse(vpath_tuple[vroot_idx + i + 2], vpath_tuple[vroot_idx + i + 3:])
   129                         obj = traverser.traverse(vpath_tuple[vroot_idx + i + 2],
       
   130                                                  vpath_tuple[vroot_idx + i + 3:])
   120                     except IndexError:
   131                     except IndexError:
   121                         # the "+" namespace traverser is waiting for additional elements from input URL
   132                         # the "+" namespace traverser is waiting for additional elements from
   122                         # so a "+" URL not followed by something else is just an error!
   133                         # input URL so a "+" URL not followed by something else is just an error!
   123                         raise NotFound()
   134                         raise NotFound()
   124                     else:
   135                     else:
   125                         i += 1
   136                         i += 1
   126                         return {
   137                         return {
   127                             'context': ob,
   138                             'context': obj,
   128                             'view_name': ''.join(vpath_tuple[vroot_idx + i + 2:]),
   139                             'view_name': ''.join(vpath_tuple[vroot_idx + i + 2:]),
   129                             'subpath': vpath_tuple[i + 2:],
   140                             'subpath': vpath_tuple[i + 2:],
   130                             'traversed': vpath_tuple[:vroot_idx + i + 2],
   141                             'traversed': vpath_tuple[:vroot_idx + i + 2],
   131                             'virtual_root': vroot,
   142                             'virtual_root': vroot,
   132                             'virtual_root_path': vroot_tuple,
   143                             'virtual_root_path': vroot_tuple,
   133                             'root': root
   144                             'root': root
   134                         }
   145                         }
   135 
   146 
   136                 elif segment[:2] == ns_selector:
   147                 elif segment[:2] == ns_selector:
   137                     # check for namespace prefixed by '++'
   148                     # check for namespace prefixed by '++'
   138                     # when a namespace is detected, named "ITraversable" multi-adapters are searched for
   149                     # when a namespace is detected, named "ITraversable" multi-adapters are
   139                     # context and request, or for context, sequentially; a NotFound exception is raised if traverser
   150                     # searched for context and request, or for context, sequentially; a NotFound
   140                     # can't be found, otherwise it's "traverse" method is called to get new context
   151                     # exception is raised if traverser can't be found, otherwise it's "traverse"
   141                     ns, name = segment[2:].split(ns_selector, 1)
   152                     # method is called to get new context
       
   153                     nss, name = segment[2:].split(ns_selector, 1)
   142                     registry = get_current_registry()
   154                     registry = get_current_registry()
   143                     traverser = registry.queryMultiAdapter((ob, request), ITraversable, ns)
   155                     traverser = registry.queryMultiAdapter((obj, request), ITraversable, nss)
   144                     if traverser is None:
   156                     if traverser is None:
   145                         traverser = registry.queryAdapter(ob, ITraversable, ns)
   157                         traverser = registry.queryAdapter(obj, ITraversable, nss)
   146                     if traverser is None:
   158                     if traverser is None:
   147                         raise NotFound()
   159                         raise NotFound()
   148                     ob = traverser.traverse(name, vpath_tuple[vroot_idx + i + 1:])
   160                     obj = traverser.traverse(name, vpath_tuple[vroot_idx + i + 1:])
   149                     i += 1
   161                     i += 1
   150                     continue
   162                     continue
   151 
   163 
   152                 elif segment[:2] == view_selector:
   164                 elif segment[:2] == view_selector:
   153                     # check for view name prefixed by '@@'
   165                     # check for view name prefixed by '@@'
   154                     return {
   166                     return {
   155                         'context': ob,
   167                         'context': obj,
   156                         'view_name': segment[2:],
   168                         'view_name': segment[2:],
   157                         'subpath': vpath_tuple[i + 1:],
   169                         'subpath': vpath_tuple[i + 1:],
   158                         'traversed': vpath_tuple[:vroot_idx + i + 1],
   170                         'traversed': vpath_tuple[:vroot_idx + i + 1],
   159                         'virtual_root': vroot,
   171                         'virtual_root': vroot,
   160                         'virtual_root_path': vroot_tuple,
   172                         'virtual_root_path': vroot_tuple,
   161                         'root': root
   173                         'root': root
   162                     }
   174                     }
   163 
   175 
   164                 try:
   176                 try:
   165                     getitem = ob.__getitem__
   177                     getitem = obj.__getitem__
   166                 except AttributeError:
   178                 except AttributeError:
   167                     return {
   179                     return {
   168                         'context': ob,
   180                         'context': obj,
   169                         'view_name': segment,
   181                         'view_name': segment,
   170                         'subpath': vpath_tuple[i + 1:],
   182                         'subpath': vpath_tuple[i + 1:],
   171                         'traversed': vpath_tuple[:vroot_idx + i + 1],
   183                         'traversed': vpath_tuple[:vroot_idx + i + 1],
   172                         'virtual_root': vroot,
   184                         'virtual_root': vroot,
   173                         'virtual_root_path': vroot_tuple,
   185                         'virtual_root_path': vroot_tuple,
   174                         'root': root
   186                         'root': root
   175                     }
   187                     }
   176 
   188 
   177                 try:
   189                 try:
   178                     next = getitem(segment)
   190                     next_item = getitem(segment)
   179                 except KeyError:
   191                 except KeyError:
   180                     return {
   192                     return {
   181                         'context': ob,
   193                         'context': obj,
   182                         'view_name': segment,
   194                         'view_name': segment,
   183                         'subpath': vpath_tuple[i + 1:],
   195                         'subpath': vpath_tuple[i + 1:],
   184                         'traversed': vpath_tuple[:vroot_idx + i + 1],
   196                         'traversed': vpath_tuple[:vroot_idx + i + 1],
   185                         'virtual_root': vroot,
   197                         'virtual_root': vroot,
   186                         'virtual_root_path': vroot_tuple,
   198                         'virtual_root_path': vroot_tuple,
   187                         'root': root
   199                         'root': root
   188                     }
   200                     }
   189                 if i == vroot_idx:
   201                 if i == vroot_idx:
   190                     vroot = next
   202                     vroot = next_item
   191                 ob = next
   203                 obj = next_item
   192                 i += 1
   204                 i += 1
   193 
   205 
   194         if ob is not root:
   206         if obj is not root:
   195             request.registry.notify(BeforeTraverseEvent(ob, request))
   207             request.registry.notify(BeforeTraverseEvent(obj, request))
   196 
   208 
   197         return {
   209         return {
   198             'context': ob,
   210             'context': obj,
   199             'view_name': empty,
   211             'view_name': empty,
   200             'subpath': subpath,
   212             'subpath': subpath,
   201             'traversed': vpath_tuple,
   213             'traversed': vpath_tuple,
   202             'virtual_root': vroot,
   214             'virtual_root': vroot,
   203             'virtual_root_path': vroot_tuple,
   215             'virtual_root_path': vroot_tuple,
   208 def get_parent(context, interface=Interface, allow_context=True, condition=None):
   220 def get_parent(context, interface=Interface, allow_context=True, condition=None):
   209     """Get first parent of the context that implements given interface
   221     """Get first parent of the context that implements given interface
   210 
   222 
   211     :param object context: base element
   223     :param object context: base element
   212     :param Interface interface: the interface that parend should implement
   224     :param Interface interface: the interface that parend should implement
   213     :param boolean allow_context: if 'True' (the default), traversing is done starting with context; otherwise,
   225     :param boolean allow_context: if 'True' (the default), traversing is done starting with
   214         traversing is done starting from context's parent
   226         context; otherwise, traversing is done starting from context's parent
   215     :param callable condition: an optional function that should return a 'True' result when called with parent
   227     :param callable condition: an optional function that should return a 'True' result when
   216         as first argument
   228         called with parent as first argument
   217     """
   229     """
   218     if allow_context:
   230     if allow_context:
   219         parent = context
   231         parent = context
   220     else:
   232     else:
   221         parent = getattr(context, '__parent__', None)
   233         parent = getattr(context, '__parent__', None)
   236     be able to search object based on a given parent
   248     be able to search object based on a given parent
   237     """
   249     """
   238 
   250 
   239     @property
   251     @property
   240     def parents(self):
   252     def parents(self):
       
   253         """Get list of parents OIDs"""
   241         intids = query_utility(IIntIds)
   254         intids = query_utility(IIntIds)
   242         if intids is None:
   255         if intids is None:
   243             return []
   256             return []
   244         return [intids.register(parent) for parent in lineage(self.context)]
   257         return [intids.register(parent) for parent in lineage(self.context)]