views:

207

answers:

1

I'm using Django on Appengine. I'm using the django reverse() function everywhere, keeping everything as DRY as possible.

However, I'm having trouble applying this to my client-side javascript. There is a JS class that loads some data depending on a passed-in ID. Is there a standard way to not-hardcode the URL that this data should come from?

var rq = new Request.HTML({
    'update':this.element,
}).get('/template/'+template_id+'/preview'); //The part that bothers me.
+3  A: 

Yeah this always bugged me too.

There's the javascript-rendered-with-Django-template-language method similar to what you've displayed above, however, I found pretty quick that this can easily become too cumbersome when the javascript is stored in an external file.

There are probably many ways to attack this problem, but I created a simple helper app to allow url resolution via AJAX. There are two parts 2 parts to it -- the server side url/view which returns the value and the client side script that that provides access to it.

It seems easier than it actually is. You will frequently want to pass arguments into the resolve function and often the order of these arguments is very important. To maintain the order, when I pass the arguments back to Django, I orient them on the querystring with the key being the # of their order.

Once included (the javascript depends on jQuery), you can access it with...

// Equivalent to {% url blog_view_post post_id %}
var url = Django.reverse('blog_view_post', [post_id] );

On the server side...

javascript/views.py

from django.http import HttpResponse
from django.core.urlresolvers import reverse


def reverse_url(request, url_name):

    # Turn querystring into an array of couplets (2x list)
    # arg_couplets = [ ('key', 'value'), ('key', 'value') ]
    arg_couplets = request.REQUEST.items()

    # Sort by keys
    arg_couplets.sort(lambda x,y: cmp(x[0], y[0]))

    # Collapse list into just the values
    args = [c[1] for c in arg_couplets]

    try:
        if args:
            return HttpResponse(reverse(url_name, args=args))
        else:
            return HttpResponse(reverse(url_name))
    except:
        return HttpResponse()

On the client side... (make sure to include this in your template)

/media/django-javascript.js

(function($) {

    Django = function() {
        return {
            reverse: function(name, args) {
                var ret;
                var arguments = {};
                var c = 0;

                // Convert args to keyed dictionary
                for (i in args) {
                    arguments[c] = args[i];
                    c++;
                }

                $.ajax({
                    async: false,
                    url: '/javascript/reverse/' + name + '/',
                    data: arguments,
                    success: function(html) {
                        ret = html;
                    }
                });

                if (ret.length > 0) {
                    return ret;
                }
                else {
                    return null;
                }

            }
        }
    }();

})(jQuery);

On a side note, I've also done this for MEDIA_URL and some other often used settings.

T. Stone
Doing AJAX queries to resolve URLs seems very heavyweight. DRY is nice, but not at the cost of doing numerous superfluous requests for something that rarely changes. I would suggest simply having a separate, dynamically generated JS, that contains the information necessary to reconstruct the URLs, and include that everywhere you need it.
Nick Johnson
Is there any security objection to having the full URL structure exposed? Your solution could work without the requests if `reverse()` could somehow be ported to JavaScript, and the entire URL list would be included in a dynamic JS/JSON file. How complicated is `reverse()`?
Noio
Nick: it might be improved if instead of returning the url, it returned the result directly - after all, you're probably just going to send another request to url right after... cut out the middleman.
Koobz