16

My goal: In Pyramid, to call another view-callable, and to get a Response object back without knowing any details about that view-callable.

In my Pyramid application, say I have a view "foo" which is defined using a view_config decorator:

@view_config(route_name="foo",
             renderer="foo.jinja2")
def foo_view(request):
    return {"whereami" : "foo!"}

Now say that I want to route "bar" to a view that does the same thing for the time being, so it internally calls foo_view and returns its Response:

@view_config(route_name="bar")
def bar_view(request):
   return foo_view(request)

...but wait! That doesn't work, since foo_view doesn't return a Response, its renderer does.

So, this will work:

@view_config(route_name="bar",
             renderer="foo.jinja2")
def bar_view(request):
    return foo_view(request)

as it will apply the same renderer as foo_view did. But this is bad, as I now must repeat myself by copying the renderer value AND having to know the renderer of the view being called.

So, I am going to hope that there is some function available in Pyramid that allows calling another view-callable and getting a Response object back without knowing or caring how it was rendered:

@view_config(route_name="bar")
def bar_view(request):
    response = some_function_that_renders_a_view_callable(foo_view, request)
    return response

What would some_function_that_renders_a_view_callable be?

pyramid.views.render_view appears to search for a view by name; I don't want to give my views names.

(Note: Returning HTTPFound to cause the client to redirect to the target route is what I am trying avoid. I want to "internally" redirect).

aculich
  • 14,545
  • 9
  • 64
  • 71
kes
  • 5,983
  • 8
  • 41
  • 69

6 Answers6

10

Yep. There is some concerns

  • doesn't return a Response
  • predicates/renderer
  • permissions
  • request properties associated to old request

Thats why you should not call view from view as function, unless you know what you doing

Pyramid creators did awesome tool for server side redirect - http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/subrequest.html

enomad
  • 1,053
  • 9
  • 16
5

You can invoking a view with using request.invoke_subrequest:

from wsgiref.simple_server import make_server

from pyramid.config import Configurator

from pyramid.request import Request


def view_one(request):

    subreq = Request.blank('/view_two')
    response = request.invoke_subrequest(subreq)
    return response

def view_two(request):

    request.response.body = 'This came from view_two'
    return request.response

if __name__ == '__main__':

    config = Configurator()
    config.add_route('one', '/view_one')
    config.add_route('two', '/view_two')
    config.add_view(view_one, route_name='one')
    config.add_view(view_two, route_name='two')
    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()`

When /view_one is visted in a browser, the text printed in the browser pane will be "This came from view_two". The view_one view used the pyramid.request.Request.invoke_subrequest() API to obtain a response from another view (view_two) within the same application when it executed. It did so by constructing a new request that had a URL that it knew would match the view_two view registration, and passed that new request along to pyramid.request.Request.invoke_subrequest(). The view_two view callable was invoked, and it returned a response. The view_one view callable then simply returned the response it obtained from the view_two view callable.

Tim Tisdall
  • 9,914
  • 3
  • 52
  • 82
Anouar Mokhtari
  • 2,084
  • 4
  • 23
  • 23
4

I was struggling with this as well. I have a solution using the render_to_response method, though I'm sure there's a "more correct" way to do it. Until someone posts it, however, here is how I handled this:

from pyramid.renderers import render_to_response

@view_config(route_name="foo", renderer="foo.mak")
def foo_view(request):
    return {'stuff':'things', '_renderer':'foo.mak')

def bar_view(request):
    values = foo_view(request)
    renderer = values['_renderer']
    return render_to_response(renderer,values)

(Pyramid 1.3)

This requires a renderer to be used, but by declaring that renderer in the original view's return values, you can retrieve it in another view without knowing what it is. I'm suspecting the need to do this isn't easily findable because there's other, better methods for accomplishing tasks solved by this solution.

Another shortcoming is that it relies on direct import of the view callable. It would be nice if it could be looked up directly by route.

tilgovi
  • 306
  • 2
  • 11
chris
  • 2,404
  • 3
  • 27
  • 33
3

The Pyramid documentation here indicates that leaving the name key word argument out of view_config will cause the view to be registered by the function itself (rather than a string):

Such a registration... implies that the view name will be *my_view*

So, in your case you should be able to use pyramid.view.render_view or pyramid.view.render_view_to_response referencing foo_view directly:

@view_config(route_name="bar")
def bar_view(request):
    return pyramid.views.render_view_to_response(None, request, name=foo_view)

Update:

Yep, your right, passing the view function does not work.

It's interesting, but taking your example code and applying the route_name to the config did not work for me. However, the following example, just giving the view a name sets the route url and gives the view a name. In this fashion render_view_to_response works as advertised. Naming, your views may not be what you want, but this configuration accomplishes the same thing as your example code without added configuration.

@view_config(name="foo")
def foo_view(request):
    # returning a response here, in lieu of having
    # declared a renderer to delegate to...
    return Response('Where am i? `{0[whereami]}'.format({"whereami" : "foo!"}))

@view_config(name="bar")
def bar_view(request):
    # handles the response if bar_view has a renderer 
    return render_view_to_response(None, request, name='foo')

@view_config(name="baz")
def baz_view(request):
    # presumably this would not work if foo_view was
    # not returning a Response object directly, as it
    # skips over the rendering part. I think you would
    # have to declare a renderer on this view in that case.
    return foo_view(request)

if __name__ == '__main__':
    config = Configurator()
    config.scan()
    app = config.make_wsgi_app()
    serve(app, host='127.0.0.1', port='5000')
Mark Gemmill
  • 5,889
  • 2
  • 27
  • 22
  • 1
    This results in "ValueError: Could not convert view return value "None" into a response object." Is there something else I need to do to the target view to make this work? – kes Aug 13 '11 at 16:24
2

Not the precise solution you asked for, but a solution to the problem you describe:

Create a view class, of which both foo and bar are methods. Then bar can call self.foo()

Common view_configuration, such as the template name can be applied to the class, and then you can decorate each method with just the view name.

In short, the following should meet your needs, if I understand the problem correctly.

@view_defaults(renderer="foo.jinja2")
class WhereaboutsAreFoo(object):

    @view_config(route-name="foo")
    def foo_view(self):
        return {"whereami" : "foo!"}

    @view_config(route-name="bar")
    def bar_view(self):
        return self.foo_view()
mac01021
  • 745
  • 4
  • 13
  • granted, this may not have been an option when the question was posed. I'm not sure when pyramid 1.3 came out. – mac01021 Feb 07 '13 at 20:13
  • *My goal: In Pyramid, to call another view-callable, and to get a Response object back without knowing any details about that view-callable.* Certainly having to place *another* view-callable in specific class is in direct opposition to not *knowing any details about that view-callable.* – Piotr Dobrogost Oct 23 '15 at 08:16
  • Pretty sure this doesn't work. Here's what happened when I tried it: ValueError: Could not convert return value of the view callable method foo_view of class example.views.ExampleViews into a response object. The value returned was {'whereami': 'foo!'}. You may have forgotten to define a renderer in the view configuration. – jmercouris Oct 25 '15 at 17:30
1

can't you do something like that:

@view_config(name="baz")
def baz_view(request):
    return HTTPFound(location=self.request.route_path('foo'))
Jens
  • 8,423
  • 9
  • 58
  • 78
  • 1
    Yes and no -- HTTP redirect is twice the round trip, and it could be confusing to the user if she notices that the page returned is at another URL than what she requested. The later becomes more of a concern when the original URL we are talking about is the home '/' page. – Jerry Mar 19 '12 at 09:43
  • That's a great way to make a redirect to a view without using a URL and, IMO, preferable to internally calling other views. – Javier May 15 '14 at 15:19