src/pyams_utils/url.py
changeset 289 c8e21d7dd685
child 290 1c40b34d2646
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/url.py	Wed Dec 05 12:45:56 2018 +0100
@@ -0,0 +1,165 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+from pyramid.encode import url_quote, urlencode
+from pyramid.url import QUERY_SAFE, resource_url
+from zope.interface import Interface
+
+from pyams_utils.adapter import ContextRequestAdapter, ContextRequestViewAdapter, adapter_config
+from pyams_utils.interfaces.tales import ITALESExtension
+from pyams_utils.interfaces.url import DISPLAY_CONTEXT, ICanonicalURL, IRelativeURL
+from pyams_utils.unicode import translate_string
+
+
+def generate_url(title):
+    """Generate an SEO-friendly content URL from it's title
+
+    The original title is translated to remove accents, converted to lowercase, and words
+    shorter than three characters are removed; terms are joined by hyphens.
+    """
+    return '-'.join(filter(lambda x: len(x) > 2,
+                           translate_string(title, escape_slashes=True, force_lower=True, spaces='-',
+                                            remove_punctuation=True, keep_chars='-').split('-')))
+
+
+#
+# Request display context
+#
+
+def get_display_context(request):
+    return request.annotations.get(DISPLAY_CONTEXT, request.context)
+
+
+#
+# Absolute URLs management
+#
+
+def absolute_url(context, request, view_name=None, query=None):
+    """Get resource absolute_url
+
+    :param object context: the persistent object for which absolute URL is required
+    :param request: the request on which URL is based
+    :param str view_name: an optional view name to add to URL
+    :param str/dict query: an optional URL arguments string or mapping
+
+    This absolute URL function is based on default Pyramid's :py:func:`resource_url` function, but
+    add checks to remove some double slashes, and add control on view name when it begins with a '#'
+    character which is used by MyAMS.js framework.
+    """
+
+    # if we pass a string to absolute_url(), argument is returned as-is!
+    if isinstance(context, str):
+        return context
+
+    # if we have several parents without name in the lineage, the resource URL contains a double slash
+    # which generates "NotFound" exceptions; so we replace it with a single slash...
+    result = resource_url(context, request).replace('//', '/').replace(':/', '://')
+    if result.endswith('/'):
+        result = result[:-1]
+    if view_name:
+        if view_name.startswith('#'):
+            result += view_name
+        else:
+            result += '/' + view_name
+    if query:
+        qs = ''
+        if isinstance(query, str):
+            qs = '?' + url_quote(query, QUERY_SAFE)
+        elif query:
+            qs = '?' + urlencode(query, doseq=True)
+        result += qs
+    return result
+
+
+@adapter_config(name='absolute_url', context=(Interface, Interface, Interface), provides=ITALESExtension)
+class AbsoluteUrlTalesExtension(ContextRequestViewAdapter):
+    """extension:absolute_url(context, view_name) TALES extension
+
+    A PyAMS TALES extension used to get access to an object URL from a page template.
+    """
+
+    def render(self, context=None, view_name=None):
+        if context is None:
+            context = self.context
+        return absolute_url(context, self.request, view_name)
+
+
+#
+# Canonical URLs management
+#
+
+def canonical_url(context, request, view_name=None, query=None):
+    """Get resource canonical URL"""
+
+    # if we pass a string to canonical_url(), argument is returned as-is!
+    if isinstance(context, str):
+        return context
+
+    url_adapter = request.registry.queryMultiAdapter((context, request), ICanonicalURL)
+    if url_adapter is None:
+        url_adapter = request.registry.queryAdapter(context, ICanonicalURL)
+
+    if url_adapter is not None:
+        return url_adapter.get_url(view_name, query)
+    else:
+        return absolute_url(context, request, view_name, query)
+
+
+@adapter_config(name='canonical_url', context=(Interface, Interface, Interface), provides=ITALESExtension)
+class CanonicalUrlTalesExtension(ContextRequestViewAdapter):
+    """extension:canonical_url(context, view_name) TALES extension
+
+    A PyAMS TALES extension used to get access to an object's canonical URL from a page template.
+    """
+
+    def render(self, context=None, view_name=None):
+        if context is None:
+            context = self.context
+        return canonical_url(context, self.request, view_name)
+
+
+#
+# Relative URLs management
+#
+
+@adapter_config(context=(Interface, Interface), provides=IRelativeURL)
+class DefaultRelativeURLAdapter(ContextRequestAdapter):
+    """Default relative URL adapter"""
+
+    def get_url(self, display_context=None, view_name=None, query=None):
+        return absolute_url(self.context, self.request, view_name, query)
+
+
+def relative_url(context, request, display_context=None, view_name=None, query=None):
+    """Get resource URL relative to given context"""
+    if display_context is None:
+        display_context = request.annotations.get(DISPLAY_CONTEXT, request.context)
+    adapter = request.registry.getMultiAdapter((context, request), IRelativeURL)
+    return adapter.get_url(display_context, view_name, query)
+
+
+@adapter_config(name='relative_url', context=(Interface, Interface, Interface), provides=ITALESExtension)
+class RelativeUrlTalesExtension(ContextRequestViewAdapter):
+    """extension:relative_url(context, view_name, query) TALES extension
+
+    A PyAMS TALES extension used to get an object's relative URL based on current request display context.
+    """
+
+    def render(self, context=None, view_name=None, query=None):
+        if context is None:
+            context = self.context
+        return relative_url(context, self.request, view_name=view_name, query=query)