An idea for improving @validate

3 views
Skip to first unread message

Steven Holmes

unread,
Jan 28, 2008, 8:33:33 PM1/28/08
to pylons-...@googlegroups.com
Hi,

@validate supports separating form rendering and form post processing
into distinct methods. This is convenient, but has a nasty flaw: It
requires two separate URLs, a form display URL and a URL to handle the
post. When a form post fails to validate, the form is re-displayed,
but at the post URL instead of the original form display URL. I find
this inconsistent and broken.

Here is a little example:

class AccountController(BaseController):
def create_form(self):
return render('create_account')

@validate(schema=CreateAccountSchema(), form='create_form')
def create(self):
return 'Account Created!'

To view the form we go to /account/create_form, then when we submit we
are taken to /account/create, where the form is re-displayed if there
were errors. Two different URLs for the same form. This is
unnecessarily exposing a (confusing) implementation detail to the user.

Changing @validate so that it will always internally redirect to
create_form if there is no form post would make things more
consistent. The form could then be viewed at /account/create and also
re-displayed with errors are /account/create, while the nice
separation between form display and form processing is maintained.

I don't know what effect this change would have on backwards
compatibility, but it can be made with a trivial (two lines or so)
change to @validate. (I can do a quick patch if anybody's interested).

Cheers,

Steven

Mike Orr

unread,
Jan 29, 2008, 1:33:58 AM1/29/08
to pylons-...@googlegroups.com
On Jan 28, 2008 5:33 PM, Steven Holmes <st...@linuxops.net> wrote:
> @validate supports separating form rendering and form post processing
> into distinct methods. This is convenient, but has a nasty flaw: It
> requires two separate URLs, a form display URL and a URL to handle the
> post. When a form post fails to validate, the form is re-displayed,
> but at the post URL instead of the original form display URL. I find
> this inconsistent and broken.

There seem to be two philosophies about that. I agree with you, that it's
cleaner to have everything in one method with:

if form_submitted:
if not form_has_errors:
do action
display form

Quixote's form handling is like this. But Pylons and TurboGears users
generally seem to prefer it the other way, so that each method just
does one thing.

--
Mike Orr <slugg...@gmail.com>

Alberto Valverde

unread,
Jan 29, 2008, 3:36:12 AM1/29/08
to pylons-...@googlegroups.com

With a little help of pylons.controllers.dispatch_on you can achieve what
you want:


@dispatch_on(POST="do_create")
def create(self):
render("my form")

@validate(schema="something", form="create")
def do_create(self):
process(self.form_seult)

the form shown at create() can "submit to itself" so the external API only
sees the /create url while you avoid branching inside the method to do two
different jobs which is cleaner, IMHO.

Alberto

Steven Holmes

unread,
Jan 29, 2008, 4:44:48 AM1/29/08
to pylons-...@googlegroups.com

Thanks, I didn't know about dispatch_on.

However, this isn't so much of a case of what I want as what I think
should happen by default. The pylons form tutorial teaches the two-URL
way and I think that having two different URLs for displaying the same
form is fundamentally broken (and unnecessary). Are there any
advantages to doing it this way that I've missed?

(My preferred method would actually involve saving the Invalid
exception somewhere and redirecting the user to referrer, but that's a
more fundamental change).

Cheers,

Steven

Qiangning Hong

unread,
Jan 29, 2008, 6:02:02 AM1/29/08
to pylons-...@googlegroups.com
On Tue, Jan 29, 2008 at 4:36 PM, Alberto Valverde <alb...@toscat.net> wrote:
> With a little help of pylons.controllers.dispatch_on you can achieve what
> you want:
>
> @dispatch_on(POST="do_create")
> def create(self):
> render("my form")
>
> @validate(schema="something", form="create")
> def do_create(self):
> process(self.form_seult)
>
> the form shown at create() can "submit to itself" so the external API only
> sees the /create url while you avoid branching inside the method to do two
> different jobs which is cleaner, IMHO.

i don't know dispatch_on before. can routes be configured as mapping
POST method of /create to do_create() and GET to create()? if it can,
it will have same effect as the code above.

--
Qiangning Hong
http://www.douban.com/people/hongqn/

Dmitry Lipovoi

unread,
Jan 29, 2008, 6:06:00 AM1/29/08
to pylons-discuss
also in routes it's possible to config one url to diffetent actions on
GET and POST requests.
something like this:

m.connect('myform', controller='form', action='handle_form',
conditions=dict(method=['POST']))
m.connect('myform', controller='form', action='show_form'))

Steven Holmes

unread,
Jan 29, 2008, 6:28:04 AM1/29/08
to pylons-...@googlegroups.com

On 29 Jan 2008, at 11:06, Dmitry Lipovoi wrote:

>
> also in routes it's possible to config one url to diffetent actions on
> GET and POST requests.
> something like this:
>
> m.connect('myform', controller='form', action='handle_form',
> conditions=dict(method=['POST']))
> m.connect('myform', controller='form', action='show_form'))
>

Sure, it's possible, but what's really bugging me is that this has to
be worked around in the first place ;)

In my opinion, it should be *default* do the right thing, which is to
have one exposed url for a form. Or at least, the documentation should
be altered or extended so that the one-url way of doing things is
prominent.

Basically, what I'm saying is I think @validate is broken and should
be fixed, irrespective of any work-arounds that exist.

Cheers,

Steven

Mike Orr

unread,
Jan 29, 2008, 11:21:41 AM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008 3:28 AM, Steven Holmes <st...@linuxops.net> wrote:
> In my opinion, it should be *default* do the right thing, which is to
> have one exposed url for a form. Or at least, the documentation should
> be altered or extended so that the one-url way of doing things is
> prominent.

Yes, we're disagreeing on what the default should be. Well, you'll
have to convince Ben. I think he likes it the other way.

--
Mike Orr <slugg...@gmail.com>

Steven Holmes

unread,
Jan 29, 2008, 11:44:05 AM1/29/08
to pylons-...@googlegroups.com

I will use all my powers of persuasion ;)

Besides, I'M RIGHT :P

Matt Feifarek

unread,
Jan 29, 2008, 11:53:05 AM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008 11:21 AM, Mike Orr <slugg...@gmail.com> wrote:

Yes, we're disagreeing on what the default should be.  Well, you'll
have to convince Ben.  I think he likes it the other way.

I don't have this particular religion, but what would a REST zealot say? Seems like two different URI's for the same thing is bad.

I've hacked up my base controller class to make it work the way that Steven wants. I suppose to me, clean URIs are better than clean controllers. And in my case, the "mess" isn't in my controllers, it's in my base controller class.

Plus, I'm not sure that decorators make things cleaner; it just puts some mysterious magic in my code. @validate is pretty easy to understand magic, but it's still one little line that completely changes the expected dispatch behavior of pylons (one url = one request = one route = one action = one method = one response). I don't like 'em.


Steven Holmes

unread,
Jan 29, 2008, 12:05:35 PM1/29/08
to pylons-...@googlegroups.com

I'm not a fan of @validate either, as a general rule. I like being
able to select schemas at request time (for things like a
CRUDController it lets me have subclasses with things like
create_schema = <foo>). However, I can see that @validate would be
very useful for a significant number of common cases.

The ideal way to handle forms, in my opinion, is to always redirect
after a POST. If there are errors, save the Invalid exception and
redirect to the referrer. This allows form actions to be targeted by
multiple forms in different places. (The usual example I give of this
is adding products to a shopping cart--I want to be able to have the
add form both on the product summary and on the full product page.
Redirecting to referer automatically does the right thing).

Ian Bicking

unread,
Jan 29, 2008, 12:40:44 PM1/29/08
to pylons-...@googlegroups.com
Matt Feifarek wrote:
> Yes, we're disagreeing on what the default should be. Well, you'll
> have to convince Ben. I think he likes it the other way.
>
>
> I don't have this particular religion, but what would a REST zealot say?
> Seems like two different URI's for the same thing is bad.

REST zealots say that you should have:

GET /resource -> rendered thing
GET /resource/edit -> form
POST /resource -> make the change

So there is generally another URL involved. But REST zealots don't deal
with HTML forms.

Ian

Mike Orr

unread,
Jan 29, 2008, 1:51:18 PM1/29/08
to pylons-...@googlegroups.com

This gets into how Pylons should support REST, and what this implies
for the changes I'm making in Routes 2. Ben and I discussed
map.resource a week ago, and he said it follows the Atom Publishing
Protocol, a widely-respected REST spec. I haven't studied it yet but
here are some links on it.

http://tools.ietf.org/html/rfc5023
http://www.ibm.com/developerworks/library/x-atompp1/

As a recap, map.resource believes that:

GET /messages => display index page
POST /messages => create new message
GET /messages/new => display add form (action = POST /messages)
GET /messages/1 => display message 1
GET /messages/1/edit => display edit form (action = PUT /messages/1)
PUT /messages/1 => modify message 1
DELETE /messages/ 1 => delete message 1

(Routes 1 has ";edit" instead of "/edit", but Ben said that's an
invalid URL syntax borrowed from Rails, which Rails has since
corrected.)

PUT and DELETE are implemented by an "_action" query param to a POST
form, which Routes notices and changes the request method. Again, Ben
says this is widely done in other programming languages.

Atom specifies the URLs, not the controller methods. So we could
rename the actions or merge the form-submission ones. But I'm wary of
making such a big change for such a small benefit.

One problem I've come across is the lack of a delete confirmation
form. Ben says you can do it in Javascript before the DELETE
executed, but I need something that degrades if users don't have
Javascript. It looks like " GET /messages/1/delete" would nicely fit
into that scheme.

@validate is another issue. It's a nice convenience but it does limit
what you can do in your code. Going to a one-method scheme would
require it to be bypassed or rewritten in the application. I've also
found another problem in that the forms I'm porting to Pylons have a
Cancel button, which is supposed to bypass the validators and do a
redirect. I'd either have to have a big OR validator around all the
others, or wrap or rewrite @validate to process "cancel" before
executing the validators.

So, I think the Pylons docs need to mention @rewrite's limitions and
show how to work around them (calling the validators manually in the
action), and perhaps also show the one-method technique. However, I'm
not sure the one-method technique will be supported by map.resource,
though you can use arguments to get something approaching that.

--
Mike Orr <slugg...@gmail.com>

Ian Bicking

unread,
Jan 29, 2008, 2:15:07 PM1/29/08
to pylons-...@googlegroups.com

It's kind of wonky, though, because web applications don't expose
"resources". When you get /messages you get an HTML page. You don't
get a well-formed container of resources like in atompub. When you get
/messages/1 you get HTML again. When you edit you send
application/x-form-www-urlencoded (or whatever it is). That's not the
same content type as what you were given (HTML). It's not even that
close to the same content type. (Though the intention of a FormEncode
Schema is actually to take a resource and turn it into something
isomorphic to an HTML form, and then convert it back again... but
expressing form submissions as a transformation of data is tricky.)

As long as you are doing this, you might want to consider some kind of
validation submission as well, that can be used by Javascript to submit
and get errors. Also a preview submission.

I think using POST is okay; if you are using _action=PUT it isn't a PUT
anyway, and it feels like it's not much more than HTTP verb push-ups.
POST over a resource updates the resource in some fashion; updating a
resource in-place via POST is okay (PUT might be better, but
not-really-a-PUT *isn't* better).

Ian

Mike Orr

unread,
Jan 29, 2008, 2:26:22 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008 11:15 AM, Ian Bicking <ia...@colorstudy.com> wrote:
> I think using POST is okay; if you are using _action=PUT it isn't a PUT
> anyway, and it feels like it's not much more than HTTP verb push-ups.
> POST over a resource updates the resource in some fashion; updating a
> resource in-place via POST is okay (PUT might be better, but
> not-really-a-PUT *isn't* better).

I generally agree with you, and I keep going back and forth whether to
use map.resource in my own applications. Because what does it really
matter if you use POST or PUT? Users can't effectively predict URLs
across applications anyway, and there's no practical reason to; e.g.,
no Google GWebsite that's a common front end for interacting with a
variety of sites. "/resource/" vs "/resource/1" is a good idea, but
I'm not sure the rest of it matters.

Are there any other practical arguments for a REST-like
add/edit/delete, or is it all just academic ideology?

But still, something needs to be done for Pylons, and map.resource
exists, so I guess a fair number of people are using it.

--
Mike Orr <slugg...@gmail.com>

Matt Feifarek

unread,
Jan 29, 2008, 2:35:35 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008 2:26 PM, Mike Orr <slugg...@gmail.com> wrote:
Are there any other practical arguments for a REST-like
add/edit/delete, or is it all just academic ideology?

IANARZ (I am not a REST Zealot) but...

I noticed when I moved to Pylons (from Webware, fwiw) that one of the first things I did before writing any code was work out the URI-space. Then went to routes. I somehow stumbled through my own preferences on the same pattern that Ian mentions:

/accounts --> list current accounts
/account/8733 == /account/8733/view
/account/8733/edit
/account/new --> empty form
/account/8733/view/attachments/2 (etc.)


This is different from stock Pylons, which recommends:
/account/view/8733
To me, viewing is a property of 8733, not vice versa. So, I switched 'em. That was easy in routes.

It felt to me like this step made me write a better application; things landed in better places. I don't expect to ever have any kind of robot use these applications, and I bet never once will a user look at the location bar and think "gee, those are nice urls" but it helped me write better software, I think.

That's worth something.




Ian Bicking

unread,
Jan 29, 2008, 2:45:42 PM1/29/08
to pylons-...@googlegroups.com
Mike Orr wrote:
> On Jan 29, 2008 11:15 AM, Ian Bicking <ia...@colorstudy.com> wrote:
>> I think using POST is okay; if you are using _action=PUT it isn't a PUT
>> anyway, and it feels like it's not much more than HTTP verb push-ups.
>> POST over a resource updates the resource in some fashion; updating a
>> resource in-place via POST is okay (PUT might be better, but
>> not-really-a-PUT *isn't* better).
>
> I generally agree with you, and I keep going back and forth whether to
> use map.resource in my own applications. Because what does it really
> matter if you use POST or PUT? Users can't effectively predict URLs
> across applications anyway, and there's no practical reason to; e.g.,
> no Google GWebsite that's a common front end for interacting with a
> variety of sites. "/resource/" vs "/resource/1" is a good idea, but
> I'm not sure the rest of it matters.
>
> Are there any other practical arguments for a REST-like
> add/edit/delete, or is it all just academic ideology?

POST or PUT over a resource automatically invalidates all caches (at
least all that see the request), so that is of some benefit. I like PUT
insofar as it is clearly symmetric with GET. If there is not a strong
symmetry then I don't see any purpose to it.

POST /resource/1?_action=PUT doesn't invalidate any caches, and so it's
not just rather useless, it's actually counterproductive.

Technically you could use Accept headers to handle a resource with
multiple representations. But the Accept header totally blows; browsers
lie about it (they claim they'd prefer XML over HTML?!?) and it totally
messes up caching and the fixes are hard to handle.

> But still, something needs to be done for Pylons, and map.resource
> exists, so I guess a fair number of people are using it.

Well, for services I think the atompub layout is nice. For rendered
HTML and HTML forms it doesn't work that well IMHO.

Ian

Mike Orr

unread,
Jan 29, 2008, 3:33:19 PM1/29/08
to pylons-...@googlegroups.com

I googled around to see how other people are combining Atom and HTML.
It looks like Atom is an advantage only if you want to syndicate the
resource. And even with that, if you only want to syndicate it
read-only but require all changes to be done at the original website,
it looks like only the "index" and "show" URLs need to be conformat
(/article/ and /article/1), and the modify URLs can be anything. And
the index URL would have to provide a bona fide XML atom feed, not an
HTML page with hyperlinks.

So, I think this means map.resource is good at what it does -- Atom
serialization -- but it's not a complete answer for how to organize a
set of index/show/add/modify/delete operations in a website. I've
thought about keeping map.resource as-is and adding another more
flexible method for HTML resources that want to be standardized and
somewhat REST-like but not necessarily completely REST, and using GET
for forms and POST for execution. I haven't yet gotten into the
design, though one-method "foo/add", "foo/1/edit" and "foo/1/delete"
are a possibility, optionally expandable to the two-method style
(ask_delete, confirm_delete, something like that). Are there any good
standards or precendants to guide this?

--
Mike Orr <slugg...@gmail.com>

Ian Bicking

unread,
Jan 29, 2008, 3:46:26 PM1/29/08
to pylons-...@googlegroups.com
Mike Orr wrote:
> I googled around to see how other people are combining Atom and HTML.
> It looks like Atom is an advantage only if you want to syndicate the
> resource. And even with that, if you only want to syndicate it
> read-only but require all changes to be done at the original website,
> it looks like only the "index" and "show" URLs need to be conformat
> (/article/ and /article/1), and the modify URLs can be anything. And
> the index URL would have to provide a bona fide XML atom feed, not an
> HTML page with hyperlinks.

Remembering atompub again, it's really pretty loose on URLs. There is a
service document which is kind of the frontend. That points to a
collection, which is what everyone thinks of. The collection and
service document point to each other, they don't have to have any
particular URL layout.

The collection should produce the entries as a feed. That feed can be
paged (and probably should be) so while the collection has a head, to
get the complete collection there's multiple URLs, and those URLs are
again pointers. Each entry has a pointer to its URL, which can again be
anywhere.

The collection's main URL must accept POST. The entry URLs must accept
DELETE and PUT. You can lay out those URLs however you want. It could be:

/atom.xml - head
/atom-pg2.xml - for paging
/entries/2008/01/29/asdlkfjkj.xml - for the entry
/entries?id=3493 - another entry option

Really atompub doesn't care at all, and though the examples tend to
suggest a certain logical structure, it just a very loose suggestion.
You can cross domains or use query strings or whatever. Clients can
suggest URL slugs, but it's just a suggestion, and there's no indication
about where in the URL the slug may appear.

> So, I think this means map.resource is good at what it does -- Atom
> serialization -- but it's not a complete answer for how to organize a
> set of index/show/add/modify/delete operations in a website. I've
> thought about keeping map.resource as-is and adding another more
> flexible method for HTML resources that want to be standardized and
> somewhat REST-like but not necessarily completely REST, and using GET
> for forms and POST for execution. I haven't yet gotten into the
> design, though one-method "foo/add", "foo/1/edit" and "foo/1/delete"
> are a possibility, optionally expandable to the two-method style
> (ask_delete, confirm_delete, something like that). Are there any good
> standards or precendants to guide this?

Note there's a possible namespace collision between /collection/add and
/collection/item-id... what if the item-id is "add"? I'd be more
inclined to use a query string, like /collection?form=add. Maybe "form"
is a better indication than the common "action", as the point is that
these forms (in proper REST style) aren't actually doing anything.

Ian

Ben Bangert

unread,
Jan 29, 2008, 3:57:00 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008, at 11:45 AM, Ian Bicking wrote:

> POST or PUT over a resource automatically invalidates all caches (at

Ok... so POST and PUT invalidate the cache...

> POST /resource/1?_action=PUT doesn't invalidate any caches, and so
> it's
> not just rather useless, it's actually counterproductive.

Err, wait, didn't you just say that POST invalidates the cache? I
should note that the _method=PUT is in the body, not the URL, as its a
POST.

Cheers,
Ben

Ben Bangert

unread,
Jan 29, 2008, 3:58:01 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008, at 11:35 AM, Matt Feifarek wrote:

> /accounts --> list current accounts
> /account/8733 == /account/8733/view
> /account/8733/edit
> /account/new --> empty form
> /account/8733/view/attachments/2 (etc.)

That's pretty much exactly how Rotues map.resource sets things up as
well.

Cheers,
Ben

Ian Bicking

unread,
Jan 29, 2008, 4:05:46 PM1/29/08
to pylons-...@googlegroups.com

I didn't realize it was in the body, not a query string. In that case
it's just absurd. You can't add stuff to the body of a PUT. The body
*is* the PUT. You could add it to a header. But you can only do that
in an XHR request.

Ian

Ben Bangert

unread,
Jan 29, 2008, 4:05:45 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008, at 11:15 AM, Ian Bicking wrote:

> It's kind of wonky, though, because web applications don't expose
> "resources". When you get /messages you get an HTML page. You don't
> get a well-formed container of resources like in atompub. When you
> get
> /messages/1 you get HTML again. When you edit you send
> application/x-form-www-urlencoded (or whatever it is). That's not the
> same content type as what you were given (HTML). It's not even that
> close to the same content type. (Though the intention of a FormEncode
> Schema is actually to take a resource and turn it into something
> isomorphic to an HTML form, and then convert it back again... but
> expressing form submissions as a transformation of data is tricky.)

This is where content type switching comes in, which should be in
Pylons 0.9.7. If you want the resource represented as HTML, you either
specify it in the HTTP headers with the Accept header, or you
explicity ask by extension:
GET /messages/1.html

Or if you want an XML representation of that object, and the site
provides it:
GET /messages/1.xml OR, you ask for GET /messages/1 with the HTTP
Accept header set *only* to application/xml.

When you send the POST, you specify the content-type desired in the
response with the Accept header, again, this requires some more
elegant content type switching, which is going into Pylons 0.9.7. This
means that in your view action on a controller, you might have
something like:

if mimetype('html'):
return render('some/html/template')
elif mimetype('xml'):
# prepare subset of data for XML viewing
return render('genshi', 'some/xml/template')
etc.

Since browsers generally flaunt rather obscene HTTP Accept parameters,
you attempt to return HTML first if the browser can handle it, and
only after that you fall-back to the other format desired. The
mimetype function above will also first return content as html if its
explicit in the URL (/messages/1.html), this way clients that can't
customize HTTP Accept headers can also still properly ask for the
format desired (maybe some JS library doing a GET on /messages/1.json).

> As long as you are doing this, you might want to consider some kind of
> validation submission as well, that can be used by Javascript to
> submit
> and get errors. Also a preview submission.
>
> I think using POST is okay; if you are using _action=PUT it isn't a
> PUT
> anyway, and it feels like it's not much more than HTTP verb push-ups.
> POST over a resource updates the resource in some fashion; updating a
> resource in-place via POST is okay (PUT might be better, but
> not-really-a-PUT *isn't* better).

It's really just to have a consistent API for parts of a webapp that
you'd like to either be all REST on, or easily expose as a web service.

Cheers,
Ben

Philip Jenvey

unread,
Jan 29, 2008, 4:10:57 PM1/29/08
to pylons-...@googlegroups.com

On Jan 29, 2008, at 11:45 AM, Ian Bicking wrote:

>
> POST or PUT over a resource automatically invalidates all caches (at
> least all that see the request), so that is of some benefit. I
> like PUT
> insofar as it is clearly symmetric with GET. If there is not a strong
> symmetry then I don't see any purpose to it.
>
> POST /resource/1?_action=PUT doesn't invalidate any caches, and so
> it's
> not just rather useless, it's actually counterproductive.


That POST would invalidate caches for /resource/1, I thought you just
pointed that out in the last paragraph? =]

--
Philip Jenvey


Philip Jenvey

unread,
Jan 29, 2008, 4:11:08 PM1/29/08
to pylons-...@googlegroups.com

On Jan 29, 2008, at 10:51 AM, Mike Orr wrote:

> One problem I've come across is the lack of a delete confirmation
> form. Ben says you can do it in Javascript before the DELETE
> executed, but I need something that degrades if users don't have
> Javascript. It looks like " GET /messages/1/delete" would nicely fit
> into that scheme.


GETs should not have any side effects. So modifying/deleting a
resource should be done via a POST.

--
Philip Jenvey


Matt Feifarek

unread,
Jan 29, 2008, 4:12:17 PM1/29/08
to pylons-...@googlegroups.com
I didn't know that there was such a thing. The default routing.py doesn't mention it, so I guess I never found it.

make_map() stock certainly doesn't work that way.

Ben Bangert

unread,
Jan 29, 2008, 4:19:40 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008, at 1:05 PM, Ian Bicking wrote:
> I didn't realize it was in the body, not a query string. In that case
> it's just absurd. You can't add stuff to the body of a PUT. The body
> *is* the PUT. You could add it to a header. But you can only do that
> in an XHR request.

Huh? I'm saying, that the way the form_tag handler in WebHelpers
works, is if you do:
h.form_tag('/some/url/to/send', method='put')

It makes a form tag with the normal POST method, but adds in a hidden
field called _method='PUT', that when Routes sees it in the Routes
Middleware, modifies the REQUEST_METHOD so that it appears as if it
was a PUT, and everything you had that assumed it would be a PUT works
just fine even though browers can't PUT. If you're doing a real PUT,
there's no need to add _method='PUT'... its already a PUT.

What are you talking about?

Cheers,
Ben

Ian Bicking

unread,
Jan 29, 2008, 4:26:45 PM1/29/08
to pylons-...@googlegroups.com

I still don't get what you are doing. I'm talking about HTTP. Here's
what a sensible RESTful interaction looks like:

GET /some/resource -> response:
200 OK
Content-Type: application/xml

<xml>some doc</xml>


Given that, to update with PUT you do:

PUT /some/resource
Content-Type: application/xml

<xml>updated version of doc</xml>


What kinds of HTTP requests are you talking about?

Ian

Ben Bangert

unread,
Jan 29, 2008, 4:39:15 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008, at 1:26 PM, Ian Bicking wrote:

> I still don't get what you are doing. I'm talking about HTTP. Here's
> what a sensible RESTful interaction looks like:

Browsers can't do sensible RESTful interactions, as they have no PUT
or DELETE. What I'm referring to is how that's 'faked' for browsers by
including a _method='PUT' or _method='DELETE' in a POST body (for use
*only* with browsers).

> GET /some/resource -> response:
> 200 OK
> Content-Type: application/xml
>
> <xml>some doc</xml>
>
> Given that, to update with PUT you do:
>
> PUT /some/resource
> Content-Type: application/xml
>
> <xml>updated version of doc</xml>
>
> What kinds of HTTP requests are you talking about?

Yup, and the map.resource, with the mimetype stuff in Pylons 0.9.7
will do exactly what you illustrate there. Again, the talk of the
_method stuff included in the POST is purely to deal with web browsers
since they can't PUT/DELETE. Clients using a XML RESTful API to the
resource will not include such stuff of course, and merely do the PUT/
DELETE exactly as you indicated.

This is how map.resource works, and is intended to work with both
browsers, and service consumers.

- Ben

Mike Orr

unread,
Jan 29, 2008, 5:51:50 PM1/29/08
to pylons-...@googlegroups.com

What I meant was, "GET /messages/1/delete displays the confirmation
form ("Do you really want to delete 'My Favorite Pony'?"). Then
either "DELETE /messages/1" or "POST /messages/1/delete" could be
implemented to delete it. The latter is what I've used in the past.

Though I like Ian's idea of using a query param to choose a form... maybe.

--
Mike Orr <slugg...@gmail.com>

Steven Holmes

unread,
Jan 29, 2008, 5:55:00 PM1/29/08
to pylons-...@googlegroups.com

On 29 Jan 2008, at 18:51, Mike Orr wrote:
>
> @validate is another issue. It's a nice convenience but it does limit
> what you can do in your code. Going to a one-method scheme would
> require it to be bypassed or rewritten in the application. I've also
> found another problem in that the forms I'm porting to Pylons have a
> Cancel button, which is supposed to bypass the validators and do a
> redirect. I'd either have to have a big OR validator around all the
> others, or wrap or rewrite @validate to process "cancel" before
> executing the validators.
>
> So, I think the Pylons docs need to mention @rewrite's limitions and
> show how to work around them (calling the validators manually in the
> action), and perhaps also show the one-method technique. However, I'm
> not sure the one-method technique will be supported by map.resource,
> though you can use arguments to get something approaching that.

I'm not sure what you mean by one-method scheme.

What I was suggesting for @validate would still have two methods on
the controller, one for form processing and one for displaying the
form. It's just these would be accessed at the *same* URL, being
differentiated by whether it was POST or a GET.

This is exactly what happens now when re-submitting a form that didn't
validate... the form is redisplayed at, say, /account/create and also
POSTed to /account/create. The difference is that there is an extra /
account/create_form URL through which the form is accessed initially.
What good does this extra URL do? As far as I can tell, none. So what
I'm proposing isn't fundamentally different from what happens already
with @validate, and it's not using any techniques that are not already
used in the current @validate.

Regarding URLs, I only have the very vaguest idea of what REST is.
Much like Matt, I structure my URLs like this:

/account/create <-- Create an account (GET displays the form, POST
creates the account and redirects to /account/id)
/account/id <-- Displays the account details
/account/id/edit <-- Edit account details (similar to /account/create)
/account/delete <-- Deletes the account (GET gives a form asking
confirmation, POST does the deletion).

This has worked very well for me so far.

Cheers,

Steven

Mike Orr

unread,
Jan 29, 2008, 6:01:05 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008 1:12 PM, Matt Feifarek <matt.f...@gmail.com> wrote:

> make_map() stock certainly doesn't work that way.

":controller/:action/:id" does go the other way, if your actions are
things like "view", "add", "delete". I use them differently, with the
controller being a section of the site and the action being a
subsection, as in /faq vs /faq/privacy_policy. Until recently all my
pages have been read-only so this has worked well. But for modifiable
things I'll need something else.

Routes 2 will replace ":controller/:action/:id" with a single default
route, which is non-minimizable. So if it's ":controller/:action",
you'd have to specify both or use another route. If it's
":controller", you could only specify the controller (useful for TG2
and WSGI controllers; not useful for Pylons controllers). That might
make it a good time to transition away from /articles/edit/12 type
URLs.

--
Mike Orr <slugg...@gmail.com>

Mike Orr

unread,
Jan 29, 2008, 6:17:59 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008 2:55 PM, Steven Holmes <st...@linuxops.net> wrote:
>
>
> On 29 Jan 2008, at 18:51, Mike Orr wrote:
> >
> > @validate is another issue. It's a nice convenience but it does limit
> > what you can do in your code. Going to a one-method scheme would
> > require it to be bypassed or rewritten in the application. I've also
> > found another problem in that the forms I'm porting to Pylons have a
> > Cancel button, which is supposed to bypass the validators and do a
> > redirect. I'd either have to have a big OR validator around all the
> > others, or wrap or rewrite @validate to process "cancel" before
> > executing the validators.
> >
> > So, I think the Pylons docs need to mention @rewrite's limitions and
> > show how to work around them (calling the validators manually in the
> > action), and perhaps also show the one-method technique. However, I'm
> > not sure the one-method technique will be supported by map.resource,
> > though you can use arguments to get something approaching that.
>
> I'm not sure what you mean by one-method scheme.

I meant having the same controller method both display the form and
process it. This is how some other frameworks do it, and what I
thought you wanted. The method would need an "if" to distinguish
whether there's form input, and another to distinguish whether there
were errors.

I forgot that @validate without the form= argument would call the
original method, so it should work in a one-method scenario after all.

> What I was suggesting for @validate would still have two methods on
> the controller, one for form processing and one for displaying the
> form. It's just these would be accessed at the *same* URL, being
> differentiated by whether it was POST or a GET.
>
> This is exactly what happens now when re-submitting a form that didn't
> validate... the form is redisplayed at, say, /account/create and also
> POSTed to /account/create. The difference is that there is an extra /
> account/create_form URL through which the form is accessed initially.
> What good does this extra URL do? As far as I can tell, none. So what
> I'm proposing isn't fundamentally different from what happens already
> with @validate, and it's not using any techniques that are not already
> used in the current @validate.

I don't see why it matters that an error form is displayed at a
different URL than the original form, especially since it's a
temporary transition step (not something to bookmark). But point
taken that the same URL could route to two different methods depending
on the HTTP action.

> Regarding URLs, I only have the very vaguest idea of what REST is.
> Much like Matt, I structure my URLs like this:
>
> /account/create <-- Create an account (GET displays the form, POST
> creates the account and redirects to /account/id)
> /account/id <-- Displays the account details
> /account/id/edit <-- Edit account details (similar to /account/create)
> /account/delete <-- Deletes the account (GET gives a form asking
> confirmation, POST does the deletion).

I assume you mean "/account/id/delete".

That's what I've been doing too. I've been trying to port my pages
to map.resource to be REST-ly correct, but I'm starting to think this
is not necessary for things like accounts that will never be
syndicated.

--
Mike Orr <slugg...@gmail.com>

Ian Bicking

unread,
Jan 29, 2008, 6:27:44 PM1/29/08
to pylons-...@googlegroups.com
Ben Bangert wrote:
> On Jan 29, 2008, at 1:26 PM, Ian Bicking wrote:
>
>> I still don't get what you are doing. I'm talking about HTTP. Here's
>> what a sensible RESTful interaction looks like:
>
> Browsers can't do sensible RESTful interactions, as they have no PUT or
> DELETE. What I'm referring to is how that's 'faked' for browsers by
> including a _method='PUT' or _method='DELETE' in a POST body (for use
> *only* with browsers).

While _method=DELETE mostly makes sense to me (you aren't effecting the
natural body, since DELETE doesn't have a body), I still don't get how
this works with PUT. It's not a PUT. Why pretend it's a PUT? It feels
like an abuse of the term.

Ian

Ben Bangert

unread,
Jan 29, 2008, 6:59:32 PM1/29/08
to pylons-...@googlegroups.com
On Jan 29, 2008, at 3:27 PM, Ian Bicking wrote:

> While _method=DELETE mostly makes sense to me (you aren't effecting
> the
> natural body, since DELETE doesn't have a body), I still don't get how
> this works with PUT. It's not a PUT. Why pretend it's a PUT? It
> feels
> like an abuse of the term.

So that you have a API that works with browsers and real service
consumers (that do know PUT). Again, its purely for browsers since
they can't do PUT/DELETE. In the case of faking a DELETE from a web
browser, you will not actually be sending a DELETE (browser's can't),
so you do a POST and in the body you have the _method=DELETE. Same
thing for a PUT.

You pretend its a PUT so that you can code up a single controller that
is compatible with both browsers *and* service oriented clients.

- Ben

Ian Bicking

unread,
Jan 29, 2008, 7:06:03 PM1/29/08
to pylons-...@googlegroups.com
Ben Bangert wrote:
> On Jan 29, 2008, at 3:27 PM, Ian Bicking wrote:
>
>> While _method=DELETE mostly makes sense to me (you aren't effecting the
>> natural body, since DELETE doesn't have a body), I still don't get how
>> this works with PUT. It's not a PUT. Why pretend it's a PUT? It feels
>> like an abuse of the term.
>
> So that you have a API that works with browsers and real service
> consumers (that do know PUT). Again, its purely for browsers since they
> can't do PUT/DELETE. In the case of faking a DELETE from a web browser,
> you will not actually be sending a DELETE (browser's can't), so you do a
> POST and in the body you have the _method=DELETE. Same thing for a PUT.

Why don't you just match method=('PUT', 'POST') so the same route
accepts both verbs? Calling a POST a PUT is just silly.

> You pretend its a PUT so that you can code up a single controller that
> is compatible with both browsers *and* service oriented clients.

Yes, but service based clients and browsers send totally different
request bodies. Making them look like they were submitted with same
verb doesnt change that.

Mike Orr

unread,
Jan 29, 2008, 7:09:54 PM1/29/08
to pylons-...@googlegroups.com
It sounds like we want a new map method that does what Steven is
describing, for sites that want to add/modify/delete something but
don't need the limitations/opportunities of map.resources.

Anybody have an idea for a method name? map.resource2,
map.nonresource, map.entity, map.object, ...?

--
Mike Orr <slugg...@gmail.com>

Steven Holmes

unread,
Jan 29, 2008, 8:07:31 PM1/29/08
to pylons-...@googlegroups.com
Just realised I accidentally sent this to Mike directly instead of the
list.

On 29 Jan 2008, at 23:17, Mike Orr wrote:

>
> On Jan 29, 2008 2:55 PM, Steven Holmes <st...@linuxops.net> wrote:
>
>> What I was suggesting for @validate would still have two methods on
>> the controller, one for form processing and one for displaying the
>> form. It's just these would be accessed at the *same* URL, being
>> differentiated by whether it was POST or a GET.
>>
>> This is exactly what happens now when re-submitting a form that
>> didn't
>> validate... the form is redisplayed at, say, /account/create and also
>> POSTed to /account/create. The difference is that there is an extra /
>> account/create_form URL through which the form is accessed initially.
>> What good does this extra URL do? As far as I can tell, none. So what
>> I'm proposing isn't fundamentally different from what happens already
>> with @validate, and it's not using any techniques that are not
>> already
>> used in the current @validate.
>
> I don't see why it matters that an error form is displayed at a
> different URL than the original form, especially since it's a
> temporary transition step (not something to bookmark). But point
> taken that the same URL could route to two different methods depending
> on the HTTP action.

It matters for two reasons: It's exposing an implementation detail to
the user (they don't give a damn that internally the form is using two
methods). It's also inconsistent. Why *shouldn't* a user bookmark a
URL to edit their account? And why would the user expect two pages
that display the same form to act so differently--one being
bookmarkable, one not being bookmarkable. It's just unnecessarily
confusing. They don't care if the step is a transition step or not,
and they certainly shouldn't have to care. And what will happen if the
user clicks return in the address bar and reloads the page with a GET?
It breaks. It's just a Bad Way Of Doing Things. There are no
advantages to it that I can see.

>> Regarding URLs, I only have the very vaguest idea of what REST is.
>> Much like Matt, I structure my URLs like this:
>>
>> /account/create <-- Create an account (GET displays the form, POST
>> creates the account and redirects to /account/id)
>> /account/id <-- Displays the account details
>> /account/id/edit <-- Edit account details (similar to /account/
>> create)
>> /account/delete <-- Deletes the account (GET gives a form asking
>> confirmation, POST does the deletion).
>
> I assume you mean "/account/id/delete".
>
> That's what I've been doing too. I've been trying to port my pages
> to map.resource to be REST-ly correct, but I'm starting to think this
> is not necessary for things like accounts that will never be
> syndicated.

Yeah, I meant /account/id/delete

Cheers,

Steven

Reply all
Reply to author
Forward
0 new messages