src/pyams_utils/url.py
branchdev-tf
changeset 427 63284c98cdc1
parent 406 712193992d46
child 448 668147e09022
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 """PyAMS_utils.url module
       
    14 
       
    15 This module provides several functions, adapters and TALES extensions which can be used to
       
    16 generate object's URLs.
       
    17 
       
    18 Three kinds of URLs can be used:
       
    19  - an absolute URL, which is the standard way to access an object via it's physical path
       
    20  - a canonical URL; this URL is the "preferred" one used to access an object, and is typically
       
    21    used by search engines to index contents
       
    22  - a relative URL; some contents can use this kind of URL to get access to an object from another
       
    23    context.
       
    24 """
       
    25 
    13 from pyramid.encode import url_quote, urlencode
    26 from pyramid.encode import url_quote, urlencode
    14 from pyramid.url import QUERY_SAFE, resource_url
    27 from pyramid.url import QUERY_SAFE, resource_url
    15 from pyramid_zope_request import PyramidPublisherRequest
    28 from pyramid_zope_request import PyramidPublisherRequest
    16 from zope.interface import Interface
    29 from zope.interface import Interface
    17 
    30 
    36     >>> from pyams_utils.url import generate_url
    49     >>> from pyams_utils.url import generate_url
    37     >>> generate_url('This is my test')
    50     >>> generate_url('This is my test')
    38     'this-is-my-test'
    51     'this-is-my-test'
    39 
    52 
    40     Single letters are removed from generated URLs:
    53     Single letters are removed from generated URLs:
       
    54 
    41     >>> generate_url('This word has a single a')
    55     >>> generate_url('This word has a single a')
    42     'this-word-has-single'
    56     'this-word-has-single'
    43 
    57 
    44     But you can define the minimum length of word:
    58     But you can define the minimum length of word:
       
    59 
    45     >>> generate_url('This word has a single a', min_word_length=4)
    60     >>> generate_url('This word has a single a', min_word_length=4)
    46     'this-word-single'
    61     'this-word-single'
    47 
    62 
    48     If input text contains slashes, they are replaced with hyphens:
    63     If input text contains slashes, they are replaced with hyphens:
       
    64 
    49     >>> generate_url('This string contains/slash')
    65     >>> generate_url('This string contains/slash')
    50     'this-string-contains-slash'
    66     'this-string-contains-slash'
    51 
    67 
    52     Punctation and special characters are completely removed:
    68     Punctation and special characters are completely removed:
       
    69 
    53     >>> generate_url('This is a string with a point. And why not?')
    70     >>> generate_url('This is a string with a point. And why not?')
    54     'this-is-string-with-point-and-why-not'
    71     'this-is-string-with-point-and-why-not'
    55     """
    72     """
    56     return '-'.join(filter(lambda x: len(x) >= min_word_length,
    73     return '-'.join(filter(lambda x: len(x) >= min_word_length,
    57                            translate_string(title.replace('/', '-'), escape_slashes=False,
    74                            translate_string(title.replace('/', '-'), escape_slashes=False,
    62 #
    79 #
    63 # Request display context
    80 # Request display context
    64 #
    81 #
    65 
    82 
    66 def get_display_context(request):
    83 def get_display_context(request):
       
    84     """Get current display context
       
    85 
       
    86     The display context can be used when we generate a page to display an object in the context
       
    87     of another one; PyAMS_content package is using this feature to display "shared" contents as
       
    88     is they were located inside another site or folder...
       
    89     """
    67     return request.annotations.get(DISPLAY_CONTEXT, request.context)
    90     return request.annotations.get(DISPLAY_CONTEXT, request.context)
    68 
    91 
    69 
    92 
    70 #
    93 #
    71 # Absolute URLs management
    94 # Absolute URLs management
    97         if view_name.startswith('#'):
   120         if view_name.startswith('#'):
    98             result += view_name
   121             result += view_name
    99         else:
   122         else:
   100             result += '/' + view_name
   123             result += '/' + view_name
   101     if query:
   124     if query:
   102         qs = ''
   125         qstr = ''
   103         if isinstance(query, str):
   126         if isinstance(query, str):
   104             qs = '?' + url_quote(query, QUERY_SAFE)
   127             qstr = '?' + url_quote(query, QUERY_SAFE)
   105         elif query:
   128         elif query:
   106             qs = '?' + urlencode(query, doseq=True)
   129             qstr = '?' + urlencode(query, doseq=True)
   107         result += qs
   130         result += qstr
   108     return result
   131     return result
   109 
   132 
   110 
   133 
   111 @adapter_config(name='absolute_url', context=(Interface, Interface, Interface),
   134 @adapter_config(name='absolute_url', context=(Interface, Interface, Interface),
   112                 provides=ITALESExtension)
   135                 provides=ITALESExtension)
   115 
   138 
   116     A PyAMS TALES extension used to get access to an object URL from a page template.
   139     A PyAMS TALES extension used to get access to an object URL from a page template.
   117     """
   140     """
   118 
   141 
   119     def render(self, context=None, view_name=None):
   142     def render(self, context=None, view_name=None):
       
   143         """Extension rendering; see
       
   144         :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
       
   145         """
   120         if context is None:
   146         if context is None:
   121             context = self.context
   147             context = self.context
   122         return absolute_url(context, self.request, view_name)
   148         return absolute_url(context, self.request, view_name)
   123 
   149 
   124 
   150 
   125 #
   151 #
   126 # Canonical URLs management
   152 # Canonical URLs management
   127 #
   153 #
   128 
   154 
   129 def canonical_url(context, request, view_name=None, query=None):
   155 def canonical_url(context, request, view_name=None, query=None):
   130     """Get resource canonical URL"""
   156     """Get resource canonical URL
       
   157 
       
   158     We look for an :py:class:`ICanonicalURL <pyams_utils.interfaces.url.ICanonicalURL>` adapter;
       
   159     if none is found, we use the absolute_url.
       
   160     """
   131 
   161 
   132     # if we pass a string to canonical_url(), argument is returned as-is!
   162     # if we pass a string to canonical_url(), argument is returned as-is!
   133     if isinstance(context, str):
   163     if isinstance(context, str):
   134         return context
   164         return context
   135 
   165 
   137     if url_adapter is None:
   167     if url_adapter is None:
   138         url_adapter = request.registry.queryAdapter(context, ICanonicalURL)
   168         url_adapter = request.registry.queryAdapter(context, ICanonicalURL)
   139 
   169 
   140     if url_adapter is not None:
   170     if url_adapter is not None:
   141         return url_adapter.get_url(view_name, query)
   171         return url_adapter.get_url(view_name, query)
   142     else:
   172     return absolute_url(context, request, view_name, query)
   143         return absolute_url(context, request, view_name, query)
       
   144 
   173 
   145 
   174 
   146 @adapter_config(name='canonical_url', context=(Interface, Interface, Interface),
   175 @adapter_config(name='canonical_url', context=(Interface, Interface, Interface),
   147                 provides=ITALESExtension)
   176                 provides=ITALESExtension)
   148 class CanonicalUrlTalesExtension(ContextRequestViewAdapter):
   177 class CanonicalUrlTalesExtension(ContextRequestViewAdapter):
   150 
   179 
   151     A PyAMS TALES extension used to get access to an object's canonical URL from a page template.
   180     A PyAMS TALES extension used to get access to an object's canonical URL from a page template.
   152     """
   181     """
   153 
   182 
   154     def render(self, context=None, view_name=None):
   183     def render(self, context=None, view_name=None):
       
   184         """Render TALES extension; see
       
   185         :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
       
   186         """
   155         if context is None:
   187         if context is None:
   156             context = self.context
   188             context = self.context
   157         return canonical_url(context, self.request, view_name)
   189         return canonical_url(context, self.request, view_name)
   158 
   190 
   159 
   191 
   164 @adapter_config(context=(Interface, Interface), provides=IRelativeURL)
   196 @adapter_config(context=(Interface, Interface), provides=IRelativeURL)
   165 class DefaultRelativeURLAdapter(ContextRequestAdapter):
   197 class DefaultRelativeURLAdapter(ContextRequestAdapter):
   166     """Default relative URL adapter"""
   198     """Default relative URL adapter"""
   167 
   199 
   168     def get_url(self, display_context=None, view_name=None, query=None):
   200     def get_url(self, display_context=None, view_name=None, query=None):
       
   201         # pylint: disable=unused-argument
       
   202         """Default adapter returns absolute URL"""
   169         return absolute_url(self.context, self.request, view_name, query)
   203         return absolute_url(self.context, self.request, view_name, query)
   170 
   204 
   171 
   205 
   172 def relative_url(context, request, display_context=None, view_name=None, query=None):
   206 def relative_url(context, request, display_context=None, view_name=None, query=None):
   173     """Get resource URL relative to given context"""
   207     """Get resource URL relative to given context"""
   174     if isinstance(request, PyramidPublisherRequest):
   208     if isinstance(request, PyramidPublisherRequest):
   175         request = request._request
   209         request = request._request  # pylint: disable=protected-access
   176     if display_context is None:
   210     if display_context is None:
   177         display_context = request.annotations.get(DISPLAY_CONTEXT, request.context)
   211         display_context = request.annotations.get(DISPLAY_CONTEXT, request.context)
   178     adapter = request.registry.getMultiAdapter((context, request), IRelativeURL)
   212     adapter = request.registry.getMultiAdapter((context, request), IRelativeURL)
   179     return adapter.get_url(display_context, view_name, query)
   213     return adapter.get_url(display_context, view_name, query)
   180 
   214 
   187     A PyAMS TALES extension used to get an object's relative URL based on current request display
   221     A PyAMS TALES extension used to get an object's relative URL based on current request display
   188     context.
   222     context.
   189     """
   223     """
   190 
   224 
   191     def render(self, context=None, view_name=None, query=None):
   225     def render(self, context=None, view_name=None, query=None):
       
   226         """Rander TALES extension;
       
   227         see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
       
   228         """
   192         if context is None:
   229         if context is None:
   193             context = self.context
   230             context = self.context
   194         return relative_url(context, self.request, view_name=view_name, query=query)
   231         return relative_url(context, self.request, view_name=view_name, query=query)