Source code for pyams_utils.url

#
# 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.
#

"""PyAMS_utils.url module

This module provides several functions, adapters and TALES extensions which can be used to
generate object's URLs.

Three kinds of URLs can be used:
 - an absolute URL, which is the standard way to access an object via it's physical path
 - a canonical URL; this URL is the "preferred" one used to access an object, and is typically
   used by search engines to index contents
 - a relative URL; some contents can use this kind of URL to get access to an object from another
   context.
"""

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 import DISPLAY_CONTEXT_KEY_NAME
from pyams_utils.interfaces.tales import ITALESExtension
from pyams_utils.interfaces.url import ICanonicalURL, IRelativeURL
from pyams_utils.unicode import translate_string


__docformat__ = 'restructuredtext'


[docs]def generate_url(title, min_word_length=2): """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 (by default) are removed; terms are joined by hyphens. :param title: the input text :param min_word_length: minimum length of words to keep >>> from pyams_utils.url import generate_url >>> generate_url('This is my test') 'this-is-my-test' Single letters are removed from generated URLs: >>> generate_url('This word has a single a') 'this-word-has-single' But you can define the minimum length of word: >>> generate_url('This word has a single a', min_word_length=4) 'this-word-single' If input text contains slashes, they are replaced with hyphens: >>> generate_url('This string contains/slash') 'this-string-contains-slash' Punctation and special characters are completely removed: >>> generate_url('This is a string with a point. And why not?') 'this-is-string-with-point-and-why-not' """ return '-'.join(filter(lambda x: len(x) >= min_word_length, translate_string(title.replace('/', '-'), escape_slashes=False, force_lower=True, spaces='-', remove_punctuation=True, keep_chars='-').split('-')))
# # Request display context #
[docs]def get_display_context(request): """Get current display context The display context can be used when we generate a page to display an object in the context of another one; PyAMS_content package is using this feature to display "shared" contents as is they were located inside another site or folder... """ return request.annotations.get(DISPLAY_CONTEXT_KEY_NAME, request.context)
# # Absolute URLs management #
[docs]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: qstr = '' if isinstance(query, str): qstr = '?' + url_quote(query, QUERY_SAFE) elif query: qstr = '?' + urlencode(query, doseq=True) result += qstr return result
[docs]@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. """
[docs] def render(self, context=None, view_name=None): """Extension rendering; see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>` """ if context is None: context = self.context return absolute_url(context, self.request, view_name)
# # Canonical URLs management #
[docs]def canonical_url(context, request, view_name=None, query=None): """Get resource canonical URL We look for an :py:class:`ICanonicalURL <pyams_utils.interfaces.url.ICanonicalURL>` adapter; if none is found, we use the absolute_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) return absolute_url(context, request, view_name, query)
[docs]@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. """
[docs] def render(self, context=None, view_name=None): """Render TALES extension; see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>` """ if context is None: context = self.context return canonical_url(context, self.request, view_name)
# # Relative URLs management #
[docs]@adapter_config(context=(Interface, Interface), provides=IRelativeURL) class DefaultRelativeURLAdapter(ContextRequestAdapter): """Default relative URL adapter"""
[docs] def get_url(self, display_context=None, view_name=None, query=None): # pylint: disable=unused-argument """Default adapter returns absolute URL""" return absolute_url(self.context, self.request, view_name, query)
[docs]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_KEY_NAME, request.context) adapter = request.registry.getMultiAdapter((context, request), IRelativeURL) return adapter.get_url(display_context, view_name, query)
[docs]@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. """
[docs] def render(self, context=None, view_name=None, query=None): """Rander TALES extension; see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>` """ if context is None: context = self.context return relative_url(context, self.request, view_name=view_name, query=query)