Tutorial

 
Return to main page

model_url()s for objects outside a virtual root

Author: junkafarian
Date: 09/14/10 at 13:42:57

If part of your application is served behind a virtual root, repoze.bfg.url.model_url will return any urls for objects outside of the virtual root using the same url as is configured in the request object passed to the utility.

I found a need to serve the root of the traversal graph on one domain and a child object on another which meant any references to objects external to the vroot resulted in 404's.

This amendment to the default TraversalContextURL implementation allows you to specify an alternative domain to serve requests outside of the virtual root alongside configuring the virtual root using environ variables.
  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
from zope.interface import implements
from repoze.bfg.interfaces import IContextURL
from repoze.bfg.interfaces import VH_ROOT_KEY

from repoze.bfg.traversal import find_model
from repoze.bfg.traversal import find_root
from repoze.bfg.traversal import model_path

NON_VH_URL_KEY = 'HTTP_X_NON_VHM_URL'

class TraversalContextURL(object):
    """ The IContextURL adapter used to generate URLs for a context
    object obtained via graph traversal"""
    implements(IContextURL)

    vroot_varname = VH_ROOT_KEY
    non_vroot_varname = NON_VH_URL_KEY

    def __init__(self, context, request):
        self.context = context
        self.request = request

    def virtual_root(self):
        environ = self.request.environ
        vroot_varname = self.vroot_varname
        if vroot_varname in environ:
            return find_model(self.context, environ[vroot_varname])
        # shortcut instead of using find_root; we probably already
        # have it on the request
        try:
            return self.request.root
        except AttributeError:
            return find_root(self.context)
        
    def __call__(self):
        """ Generate a URL based on the :term:`lineage` of a
        :term:`model` object obtained via :term:`traversal`.  If any
        model in the context lineage has a Unicode name, it will be
        converted to a UTF-8 string before being attached to the URL.
        If a ``HTTP_X_VHM_ROOT`` key is present in the WSGI
        environment, its value will be treated as a 'virtual root
        path': the path of the URL generated by this will be
        left-stripped of this virtual root path value.
        """
        path = model_path(self.context)
        if path != '/':
            path = path + '/'
        request = self.request
        environ = request.environ
        vroot_varname = self.vroot_varname
        non_vroot_varname = self.non_vroot_varname

        app_url = request.application_url # never ends in a slash
        
        # if the path starts with the virtual root path, trim it out
        if vroot_varname in environ:
            vroot_path = environ[vroot_varname]
            if path.startswith(vroot_path):
                path = path[len(vroot_path):]
            elif non_vroot_varname in environ:
                # If an alternative domain is configured, use that
                app_url = environ[non_vroot_varname].rstrip('/') # never ends in a slash

        return app_url + path



#### tests ####

from repoze.bfg.tests.test_traversal import TraversalContextURLTests as BFGTraversalContextURLTests
from repoze.bfg.tests.test_traversal import DummyContext, DummyRequest

class TraversalContextURLTests(BFGTraversalContextURLTests):
    def _getTargetClass(self):
        return TraversalContextURL

    def test_call_outside_vroot_no_non_vhm_url(self):
        """ If the ``NON_VH_URL_KEY`` var is not set, the original
            behaviour should remain.
        """
        from repoze.bfg.interfaces import VH_ROOT_KEY
        root = DummyContext()
        root.__parent__ = None
        root.__name__ = None
        one = DummyContext()
        one.__parent__ = root
        one.__name__ = 'one'
        two = DummyContext()
        two.__parent__ = one
        two.__name__ = 'two'
        three = DummyContext()
        three.__parent__ = root
        three.__name__ = 'three'
        request = DummyRequest({VH_ROOT_KEY:'/one'})
        context_url = self._makeOne(three, request)
        result = context_url()
        self.assertEqual(result, 'http://example.com:5432/three/')
        
        request = DummyRequest({VH_ROOT_KEY:'/one/two'})
        context_url = self._makeOne(three, request)
        result = context_url()
        self.assertEqual(result, 'http://example.com:5432/three/')

    def test_call_outside_vroot_with_non_vhm_url(self):
        """ If the ``NON_VH_URL_KEY`` var is set, any model_url()
            calls for objects located outside the vroot should use
            the configured url.
        """
        from repoze.bfg.interfaces import VH_ROOT_KEY
        root = DummyContext()
        root.__parent__ = None
        root.__name__ = None
        one = DummyContext()
        one.__parent__ = root
        one.__name__ = 'one'
        two = DummyContext()
        two.__parent__ = one
        two.__name__ = 'two'
        three = DummyContext()
        three.__parent__ = root
        three.__name__ = 'three'
        request = DummyRequest({VH_ROOT_KEY:'/one',
                                NON_VH_URL_KEY:'http://example.com:4321'})
        context_url = self._makeOne(three, request)
        result = context_url()
        self.assertEqual(result, 'http://example.com:4321/three/')
        
        context_url = self._makeOne(two, request)
        result = context_url()
        self.assertEqual(result, 'http://example.com:5432/two/')
        
        request = DummyRequest({VH_ROOT_KEY:'/one/two',
                                NON_VH_URL_KEY:'http://example.com:4321'})
        context_url = self._makeOne(three, request)
        result = context_url()
        self.assertEqual(result, 'http://example.com:4321/three/')
        
        context_url = self._makeOne(two, request)
        result = context_url()
        self.assertEqual(result, 'http://example.com:5432/')