Tornado + Backbone (REST)

486 views
Skip to first unread message

Emilio Daniel González

unread,
Jun 28, 2012, 1:39:40 AM6/28/12
to python-...@googlegroups.com
Hi folks, I just learn that there is a pretty big issue trying to use a Tornado application along with a Backbone based front-end.

It's about the way of Backbone Models does the requests to the server, you can read about it in this post by Joshua Herring.

I was able to workaround this problem by adding the following code to my application base handler:

from tornado.escape import json_decode

class AppBaseHandler(web.BaseHandler):

    def initialize(self):
        if (self.request.body):
            arguments = json_decode(self.request.body)
            for key, value in arguments.iteritems():
                if not isinstance(value, list):
                    arguments[key] = [value]
            self.request.arguments = arguments

This seems to work properly, but it's pretty annoying having to rewrite the self.request.arguments dictionary, so along with sharing this I'm looking forward to someone come out with a better solution.

Cheers!

Alek Storm

unread,
Jun 28, 2012, 2:53:25 AM6/28/12
to python-...@googlegroups.com
Maybe we should make argument decoding customizable - either through an overridable subclass hook on HTTPServer, or a callable argument passed to its constructor.

Why the `if not isinstance(value, list)` check?

Alek

Ben Darnell

unread,
Jun 28, 2012, 3:30:33 AM6/28/12
to python-...@googlegroups.com
I should probably start a FAQ for questions like this that keep coming
up (and I'd welcome suggestions on how and where to explain things
like this).

request.arguments and the methods that work with it (get_argument,
get_arguments, decode_argument) are designed around the limitations
and peculiarities of the x-www-form-encoded format (technically a
multimap but typically used as a map, underspecified character
encodings, etc). If you weren't saddled with that format to begin
with, why try to fit your relatively expressive json data into its
constraints?

Just do something like this, and then use self.json_args.get("foo")
instead of self.get_argument("foo"):

def prepare(self):
if self.request.headers.get("Content-Type") == "application/json":
self.json_args = json_decode(self.request.body)

Note that when initialize() runs it's too early to send a proper error
page, so it should only be used for very simple code that can't really
fail. prepare() is a better place to do things like this.

-Ben

Andrew Grigorev

unread,
Jun 28, 2012, 4:24:57 AM6/28/12
to python-...@googlegroups.com
28.06.2012 11:30, Ben Darnell пишет:
> I should probably start a FAQ for questions like this that keep coming
> up (and I'd welcome suggestions on how and where to explain things
> like this).

https://github.com/facebook/tornado/wiki/faq is the best place, imho
--
Andrew

Emilio Daniel González

unread,
Jun 28, 2012, 11:08:42 AM6/28/12
to python-...@googlegroups.com
Alek, the "if" is to avoid making a list within a list (coming from the request).

Ben, your suggestion looks quite elegant in comparison, the think is I already have the whole backend developed using get_argument, so it will be annoying to re-implement all my handlers.

I'll change from initialize to prepare (as I did in the first time, but I changed to initialize because it felt more natural to do that kind of stuff in an early stage).

Thanks for the help!

- Emilio

Gordon Radlein

unread,
Jun 29, 2012, 5:41:21 PM6/29/12
to python-...@googlegroups.com
This may be a bit late, but if you want to skip messing with Tornado at all you can simply change the Backbone.sync method to something like the following, which would submit params as part of the query string instead of as a JSON object:

var _backboneSync = Backbone.sync; 
Backbone.sync = function(method, model, options) {
        options || (options = {});
        options.data = options.data || {};
        _.extend(options.data, {
                '_xsrf' : $.getCookie('_xsrf')
        });
        // if it's a PUT or DELETE request we need to add 
        if(method == 'create' || method == 'update' || method == 'delete') {
            _.extend(options.data, model.toJSON());
            if (!options.url) {
                options.url = (_.isFunction(model['url']) ? model['url']() : model['url']) || urlError();
            }
            options.url += '?' + $.param(options.data);
            options.data = {};
        }
        return _backboneSync(method, model, options);
}

Emilio Daniel González

unread,
Jun 29, 2012, 11:34:39 PM6/29/12
to python-...@googlegroups.com
Thanks Gordon, it's an elegant workaround. I'm just getting involve with Backbone and I think it will be a really good friend =P

Cheers!
--
- Emilio

Aleksandar Radulovic

unread,
Jul 6, 2012, 3:38:28 AM7/6/12
to python-...@googlegroups.com
Hi,

The "pretty big issue" that blog post's mentioning is that people
don't read documentation. Instead, they rather think things work as
they expect will do. The author of the blog post points that out
himself several times.

Don't see any reason to modify Backbone.sync as it's *very* easy to
map the Backbone routing to Tornado. All is needed is reading a bit of
documentation :)

Plus, as Ben said, you can use prepare() to do sanity checks on the
incoming data (if its JSON or not).

Cheers,
alex
--
a lex 13 x
http://a13x.net | @a13xnet

Emilio Daniel González

unread,
Jul 6, 2012, 10:13:01 AM7/6/12
to python-...@googlegroups.com
I did use "prepare", Gordon just pointed out an alternative workaround to my issue. The thing was that I had all my handlers already implemented using "self.get_argument" and I wanted to port the frontend to Backbone.

Maybe you should try to read the threads more carefully before bring out your RTFM flag, I know my english isn't good, but I think you can understand:

"...the think* is I already have the whole backend developed using get_argument, so it will be annoying to re-implement all my handlers."

*thing

Sincerely,

Emilio
--
- Emilio

Aleksandar Radulovic

unread,
Jul 6, 2012, 4:38:19 PM7/6/12
to python-...@googlegroups.com
Hehe, dont worry, I read your post very well :), my RTM rant was targeted at the afore-mentioned blog..

-alex

Sent from my iPhone
Reply all
Reply to author
Forward
0 new messages