19

I'm trying to have my form submit to a route which will validate the data then redirect back to the original route.

For example:

  • User loads the page website.com/post
  • Form POSTs the data to website.com/post-save
  • User gets redirected back to website.com/post

Pyramid is giving me some troubles doing this.

Here's my slimmed down views.py

def _get_link_form(post_data):
    """ Returns the initialised form object """

    return LinkForm(post_data)

def home_page(request):

    form = _get_link_form(request.POST)
    return {'form' : form}

def save_post(request):
    """ form data is submitted here """"

    form = _get_link_form(request.POST)

    if not form.validate():
        return home_page(request, form)

This is the code I've been playing around with. Not only does it not work, it also feels messy and hacked up. Surely there's a simpler way to 'redirect after-POST' in Pyramid?

dave
  • 7,717
  • 19
  • 68
  • 100

6 Answers6

27

Your problem is most easily solved by simply POSTing to the same URL that your form is shown at, and simply redirecting the user away from the page when the POST is successful. That way until the form is successfully submitted you do not change URLs.

If you're just dying to POST to a different URL, then you need to save the data using sessions, since you're obviously handling the form data between requests.

Typically if you want to be able to handle errors in your forms you would use a session and flash messages. To do this you simply add a location for flash messages to appear in your base template and setup session support using something like pyramid_beaker.

Assuming your home page is setup at the 'home' named-route:

from pyramid.httpexceptions import HTTPFound

def myview(request):
    user = '<default user field value>'
    if 'submit' in request.POST:
        user = request.POST.get('user')
        # validate your form data
        if <form validates successfully>:
            request.session.flash('Form was submitted successfully.')

            url = request.route_url('home') 
            return HTTPFound(location=url)
    return {
        # globals for rendering your form
        'user': user,
    }

Notice how if the form fails to validate you use the same code you did to render the form originally, and only if it is successful do you redirect. This format can also handle populating the form with the values used in the submission, and default values.

You can loop through the flash messages in your template of choice using request.session.peek_flash() and request.session.pop_flash().

route_url supports mutating the query string on the generated url as well, if you want to flag your home page view to check the session data.

You can obviously just pass everything in the query string back to the home page, but that's a pretty big security vulnerability that sessions can help protect against.

Michael Merickel
  • 23,153
  • 3
  • 54
  • 70
8

The Pyramid documentation has a particularly on-point section with the following example:

from pyramid.httpexceptions import HTTPFound

def myview(request):
    return HTTPFound(location='http://example.com')
Greg Sadetsky
  • 4,863
  • 1
  • 38
  • 48
John Flatness
  • 32,469
  • 5
  • 79
  • 81
  • Redirecting is easy. The problem is, how do I redirect back to `home_page` and send along the form errors found in `save_post` ? – dave Apr 10 '11 at 06:43
  • One way you could deal with this is to sort of reverse the way you're doing things now: Have `home_page` also be the view that the form submits to, and then do a redirect only if the submission was successful. So, if you need to display errors, `home_page` already has the form object it needs. – John Flatness Apr 10 '11 at 06:59
  • I was considering that but I have two different view callables that will render the form. I could have them both submit to the one, main, view callable (home_page, in this example) but it'd be nice if each view callable could redirect back to itself after submitting - if that makes sense... – dave Apr 10 '11 at 07:14
  • I think the current URL for that section is [this one](http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/views.html#using-a-view-callable-to-do-an-http-redirect). – Carlos Agarie Oct 03 '14 at 21:11
1

The Pyramid documentation has content about Redirect, you can see more information in below link :

Pyramid documentation

import pyramid.httpexceptions as exc
raise exc.HTTPFound(request.route_url("section1"))   # Redirect

Edited: Actually you can do that on client side with Javascript, first you should send particular response to client side(either with flashes some data or return Response object):

window.location = '{{ request.route_path("route_name") }}';
Javad Asoodeh
  • 299
  • 3
  • 12
1

I do this like so:

from pyramid.httpexceptions import HTTPCreated

response = HTTPCreated()
response.location = self.request.resource_url( newResource )
return response

This sends the HTTP Created code , 201

Jasper van den Bosch
  • 3,169
  • 4
  • 32
  • 55
0

A clean way is using the "overload" provided by pyramid for different request types, por example, you can decorate your methods this way:

@action(request_method='GET',
        renderer='mypackage:/templates/save.mako',
        name='save')
def save(request):
    ''' Fill the template with default values or leave it blank'''
     return {}


@action(request_method='POST',
        renderer='mypackage:/templates/save.mako',
        name='save')
def save_post(request):
    """ form data is submitted here """"
    # process form

In the HTML, you must call the action form, like

<form method="POST" id="tform" action="${request.route_url('home', action='save')}">

This way, one method is processed when the method POST is used, and the other when the GET is used. The same name, but two implementations.

Antonio Beamud
  • 2,281
  • 1
  • 15
  • 26
  • Sure, but on the POST branch, when validation fails, you should generally do, what you do in GET... – pihentagy Feb 04 '13 at 15:46
  • There's no `action` decorator in Pyramid. I guess you're using [`action`](http://pyramid-handlers.readthedocs.org/en/latest/api.html#pyramid_handlers.action) decorator from *pyramid_handlers* package. – Piotr Dobrogost Feb 16 '16 at 20:05
  • I think you missed the point of the question which was to submit a form using POST to a **different** url then the one used to GET the form. Both of your handlers are registered under the same name (`save`) thus they are accessible under the same url. – Piotr Dobrogost Feb 16 '16 at 20:56
  • I'm proposing a cleaner way of acheiving their requeriments, using the same url but managing the code between 2 methods. I'm using the same approach Michael Merickel is using in other response. – Antonio Beamud Feb 17 '16 at 10:04
0

Assuming your homepage is the default view of your pyramid web app, you can do:

def _get_link_form(post_data):
    """ Returns the initialised form object """

    return LinkForm(post_data)

def home_page(request):

    form = _get_link_form(request.POST)
    return {'form' : form}

def save_post(request):   
    form = _get_link_form(request.POST)

    if not form.validate():
        from pyramid.httpexceptions import HTTPFound
        return HTTPFound(location=request.application_url)

Basically you need to know how the home_page view was "added" to your Configurator. If your homepage actually lives at /few/levels/deep/homepage then a redirect might look like this:

        return HTTPFound(location=request.application_url + '/few/levels/deep/homepage')
Rocky Burt
  • 401
  • 3
  • 7
  • 1
    Using `HTTPFound` results in HTTP response code 302 thus the next request will be GET instead of the original POST. As a consequence you won't get form's data in the `home_page` view because you are looking in `request.POST`. – Piotr Dobrogost Feb 16 '16 at 21:12