summaryrefslogtreecommitdiff
path: root/repoze/bfg/url.py
blob: 528d83ae9ac80aae944214e0275da6f255f2f104 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
""" Utility functions for dealing with URLs in repoze.bfg """

import urllib

from zope.component import queryMultiAdapter
from repoze.bfg.interfaces import IContextURL

from repoze.bfg.traversal import TraversalContextURL
from repoze.bfg.traversal import quote_path_segment

def model_url(model, request, *elements, **kw):
    """
    Generate a string representing the absolute URL of the model (or
    context) object based on the ``wsgi.url_scheme``, ``HTTP_HOST`` or
    ``SERVER_NAME`` in the request, plus any ``SCRIPT_NAME``.  If a
    'virtual root path' is present in the request environment (the
    value of the WSGI environ key ``HTTP_X_VHM_ROOT``), and the
    ``model`` was obtained via traversal, the URL path will not
    include the virtual root prefix (it will be stripped out of the
    generated URL).  If a ``query`` keyword argument is provided, a
    query string based on its value will be composed and appended to
    the generated URL string (see details below).  The overall result
    of this function is always a UTF-8 encoded string (never unicode).

    .. note:: If the ``model`` used is the result of a traversal, it
       must be :term:`location`-aware.  The 'model' can also be the
       context of a URL dispatch; contexts found this way do not need
       to be location-aware.

    Any positional arguments passed in as ``elements`` must be strings
    or unicode objects.  These will be joined by slashes and appended
    to the generated model URL.  Each of the elements passed in is
    URL-quoted before being appended; if any element is unicode, it
    will converted to a UTF-8 bytestring before being URL-quoted.

    .. warning:: if no ``elements`` arguments are specified, the model
                 URL will end with a trailing slash.  If any
                 ``elements`` are used, the generated URL will *not*
                 end in trailing a slash.

    If a keyword argument ``query`` is present, it will used to
    compose a query string that will be tacked on to the end of the
    URL.  The value of ``query`` must be a sequence of two-tuples *or*
    a data structure with an ``.items()`` method that returns a
    sequence of two-tuples (presumably a dictionary).  This data
    structure will be turned into a query string per the documentation
    of ``repoze.url.urlencode`` function.  After the query data is
    turned into a query string, a leading ``?`` is prepended, and the
    the resulting string is appended to the generated URL.

    .. note:: Python data structures that are passed as ``query``
              which are sequences or dictionaries are turned into a
              string under the same rules as when run through
              urllib.urlencode with the ``doseq`` argument equal to
              ``True``.  This means that sequences can be passed as
              values, and a k=v pair will be placed into the query
              string for each value.

    If a keyword argument ``anchor`` is present, its string
    representation will be used as a named anchor in the generated URL
    (e.g. if ``anchor`` is passed as ``foo`` and the model URL is
    ``http://example.com/model/url``, the resulting generated URL will
    be ``http://example.com/model/url#foo``).

    .. note:: If ``anchor`` is passed as a string, it should be UTF-8
              encoded. If ``anchor`` is passed as a Unicode object, it
              will be converted to UTF-8 before being appended to the
              URL.  The anchor value is not quoted in any way before
              being appended to the generated URL.

    If both ``anchor`` and ``query`` are specified, the anchor element
    will always follow the query element,
    e.g. ``http://example.com?foo=1#bar``.
    """
    
    context_url = queryMultiAdapter((model, request), IContextURL)
    if context_url is None:
        # b/w compat for unit tests
        context_url = TraversalContextURL(model, request)
    model_url = context_url()

    qs = ''
    anchor = ''

    if 'query' in kw:
        qs = '?' + urlencode(kw['query'], doseq=True)

    if 'anchor' in kw:
        anchor = kw['anchor']
        if isinstance(anchor, unicode):
            anchor = anchor.encode('utf-8')
        anchor = '#' + anchor

    if elements:
        suffix = '/'.join([quote_path_segment(s) for s in elements])
    else:
        suffix = ''

    return model_url + suffix + qs + anchor

def urlencode(query, doseq=False):
    """
    A wrapper around Python's stdlib `urllib.urlencode function
    <http://docs.python.org/library/urllib.html>`_ which accepts
    unicode keys and values within the ``query`` dict/sequence; all
    Unicode keys and values are first converted to UTF-8 before being
    used to compose the query string.  The behavior of the function is
    otherwise the same as the stdlib version.

    The value of ``query`` must be a sequence of two-tuples
    representing key/value pairs *or* an object (often a dictionary)
    with an ``.items()`` method that returns a sequence of two-tuples
    representing key/value pairs.  ``doseq`` controls what happens
    when a sequence is presented as one of the values.  See the Python
    stdlib documentation for ``urllib.urlencode`` for more
    information.
    """
    if hasattr(query, 'items'):
        # presumed to be a dictionary
        query = query.items()

    newquery = []
    for k, v in query:

        if k.__class__ is unicode:
            k = k.encode('utf-8')

        try:
            v.__iter__
        except AttributeError:
            if v.__class__ is unicode:
                v = v.encode('utf-8')
        else:
            L = []
            for x in v:
                if x.__class__ is unicode:
                    x = x.encode('utf-8')
                L.append(x)
            v = L

        newquery.append((k, v))

    return urllib.urlencode(newquery, doseq=doseq)