Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an idiomatic approach in Django for writing unobtrusive JavaScript and/or making AJAX form submissions?

I'm a Ruby/Rails developer now working at a Python/Django shop. I've started to warm up to Python, however, I'm still struggling to find Django comparable to Rails in certain aspects I find important. A lot of my current and future work will focus on making AJAX requests to our API. As a Rails developer, I'd have used unobtrusive javascript and in particular on form submissions added a data-remote tag, as shown below.

I'd then write a method in the controller to handle the request and would have written a JavaScript/jQuery function using event delegation in a JS file located in the /assets/js directory to handle the response on the client-side. I assumed coming over to Django there would be a similar way of implementing this sort of functionality.

What I guess I'm really trying to say is I assumed Django would offer similar "magic" to Rails in terms of not having to write out jQuery AJAX functions every time I wanted to make an AJAX request. I wrote a rough comparison (very rough) of how I'd write both of these out. I'm looking to learn if this is an incorrect approach to what I would do in Rails in Django. I know StackOverflow isn't meant for opinions, but I think breaking principles that apply no matter what language/framework you're using, i.e. DRYing up code by not writing out AJAX functions over and over, isn't really going against an opinion, its more like breaking an accepted rule.

My current approach to working with AJAX requests in Django feels wrong, or maybe I'm just used to the "magic" Rails offers via the data-remote="true" attribute. Would love some guidance on the topic to help me determine a solid approach, thanks.

RAILS

views/some_controller/form.html.erb

<form action="<%= endpoint %>" method="post" data-remote="true" id="form">
  FORM FIELDS HERE
</form>

assets/javascripts/some_model.js

$('body').on('ajax:success', '#form', function(event, data) {
  DO SOME STUFF HERE
});

controllers/some_controller.rb

def some_ajax_action
  if request.xhr?
    THIS IS AN AJAX REQUEST RENDER A VIEW PARTIAL & 
    MANIPULATE THE DOM WITH JS OR RESPOND WITH JSON
  else
    THIS ISNT AN AJAX REQUEST
  end
end

DJANGO

some_app/templates/form.html

<form action="{% url 'app:view' %}" method="post" id="form">
   FORM FIELDS HERE OR {{ BUILD_FORM_FROM_CONTEXT }} 
</form>

some_app/static/assets/js/some_app.js

$("#form").on("submit", function(event) {
  $.ajax({
    type: "POST",
    beforeSend: function (request) {
      request.setRequestHeader("X-CSRFToken", csrftoken);
    },
    data: data,
    url: "endpoint",
    dataType: 'json',
    contentType: 'application/json',
    }).done(function(data) {
      cb(null, data)
    }).fail(function(data) {
      cb(data)
    }).always(function(data) {
      cb(data)
    })
  });
});
like image 587
evkline Avatar asked Apr 03 '14 01:04

evkline


People also ask

Can you use Ajax with Django?

Using Ajax in Django can be done by directly using an Ajax library like JQuery or others. Let's say you want to use JQuery, then you need to download and serve the library on your server through Apache or others. Then use it in your template, just like you might do while developing any Ajax-based application.

How send data from Ajax to Django?

To send and receive data to and from a web server, AJAX uses the following steps: Create an XMLHttpRequest object. Use the XMLHttpRequest object to exchange data asynchronously between the client and the server. Use JavaScript and the DOM to process the data.

How do you make a dynamic dropdown list with Ajax in Django?

To make an Ajax request, we first need to include the jquery library to our page. We define id = "question-subject" to our subject dropdown list. We select this list with the jquery selector and detect a change in this list with the change event. We assign the ID of the selected subject to a variable.


2 Answers

The answer to your question is no. There is no idiomatic approach to AJAX with Django. It has no opinions on AJAX, especially on the frontend.

The backend tends to follow more similar patterns due to the way class based views (CBV) are structured; it's common to see a simple AJAXResponseMixin mixed into CBVs which leverages the fact all generic views reliably generate their context via a single method get_context_data. The mixins can just take said context and convert it to JSON.

There are no AJAX patterns forced upon django; you merely have to build your own the way you like.

If you like the way your rails example does it, why not?

The purpose of this exercise is to show you how little framework is really involved in this idea. It's a handful of lines of code.

First, let's reproduce your data-remote=True tag and event listener system using the syntax you've outlined. Note, this is all pseudo code.

$(function() {
    $("[data-remote=true]").each(function(index, el) {
        $(el).submit(function(ev) {
            ev.preventDefault(); // disable default form submit. We are using ajax.
            $.ajax({
                url: $(this).attr('action') || '',
                data: $(this).serialize(),
                method: $(this).attr('method') || 'get',
                success: function(response) {
                    // on success, trigger an event with the response data
                    $(el).trigger('ajax:success', [response]);
                },
                error: function(xhr) {
                    $(el).trigger('ajax:error', [xhr]);
                }
            })
            return false;
        })
    })
})

Done. Now let's use it in a template:

<form id="my-django-form" method="POST" data-remote="true" action="some-controller">
    {{ form.as_p }}
    <input type="submit" />
</form>

Cool, now some JS to respond to the submission of this form:

$(function() {
    $('body').on('ajax:success', '#my-django-form', function(event, data) {
        alert("My barebones ajax framework was successful!", data);
    })
    $('body').on('error', '#my-django-form', function(event, data) {
        alert("Whoops, error!");
    })
})

The controller / django view:

def some_ajax_action(request):
    """ Some basic AJAX Handler.
    """
    if request.is_ajax():
        return http.HttpResponse(json.dumps({
            'this-is': 'some-json',
        }))
    else:
        print "This isn't an ajax request"

Ideas on how to apply as a framework and reusable pattern, including partial rendering common to many frameworks.

class AJAXFrameworkMixin(object):
    """ A more advanced, reusable pattern for class based views.
    Perhaps also allowing partial rendering via custom header `RENDER_PARTIAL` used by the jQuery request.
    """
    def dispatch(self, request, *args, **kwargs):
        if request.is_ajax():
            partial = request.META.get('X_RENDER_PARTIAL')
            if partial:
                return self.handle_ajax_partial(request, partial)
            return self.handle_ajax(request)

        return super(AJAXFrameworkMixin, self).dispatch(request, *args, **kwargs)

    def handle_ajax(self, request):
        """ Ajax specific handler.
        Convert this view context into JSON.
        """
        ctx = self.get_context_data()
        return http.HttpResponse(json.dumps(ctx))

    def handle_ajax_partial(self, request, partial):
        """ Given a render partial header from the barebones AJAX framework, render said partial with context.
        """
        t = template.loader.get_template(partial)
        return t.render(template.RequestContext(request, self.get_context_data()))

To finish this example, we'd modify the initial jQuery script to set a header based on a new data attribute, such as data-partial-name.

Now our simple framework can call a specific method on a class based view via HTML data attribute. i.e. setting data-partial="some-template-name.html" will trigger YourView.handle_ajax_partial which will return rendered HTML.

You could then automate the rendering / updating of said call via adding a default handler to your data-remote function, if data-partial has been set.

like image 102
Yuji 'Tomita' Tomita Avatar answered Oct 22 '22 09:10

Yuji 'Tomita' Tomita


This isn't a way to easily do it within Django, but with a few extra libraries, you can make handling ajax form posts easier.

CsrfProtection.js - This will append the csrf token header to your ajax requests.

(function() {
  var csrfSafeMethod, sameOrigin;

  csrfSafeMethod = function(method) {
    return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method);
  };

  sameOrigin = function(url) {
    var host, origin, protocol, sr_origin;
    host = document.location.host;
    protocol = document.location.protocol;
    sr_origin = '#' + host;
    origin = protocol + sr_origin;
    return (url === origin || url.slice(0, origin.length + 1) === origin + '/') || (url === sr_origin || url.slice(0, sr_origin.length + 1) === sr_origin + '/') || !(/^(\/\/|http:|https:).*/.test(url));
  };

  $.ajaxSetup({
    beforeSend: function(xhr, settings) {
      if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
        return xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
      }
    }
  });

}).call(this);

Then include the javascript library jquery.form.js. Using this library will allow you to do something like the following:

$('#form_id').ajaxSubmit({
    success: function(response) {console.log(response);},
    error: function(response) {console.log(response);}
});

It may not make it as easy as what you've listed out with Ruby, but it does reduce the duplication of the csrf token and the form serialization.

like image 25
schillingt Avatar answered Oct 22 '22 10:10

schillingt