Leveraging jQuery and Django
July 3, 2018
There are many powerful frameworks to build robust, dynamic, and feature rich websites. Anyone reading this post most likely understands that browsers handle client-side presentation responsibilities while data management, complex logic, and other responsibilities are handled server side. The term "full stack" developer is a term used to describe developers who write both front-end and backend code.
Client Side (front-end)
It is necessary for anyone serious about client side programming to master HTML5, CSS, and JavaScript. JavaScript itself has a large number of frameworks (e.g. Angular, React, etc.) and libraries which are beyond the scope of this blog, but Asynchronous JavaScript and XML (AJAX) and jQuery are relevant. AJAX is a process/technique for to send/receive data via HTTP requests/responses. jQuery is a JavaScript library that makes working with the Document Object Model (DOM) much easier to include interacting with AJAX. These are the two client-side technologies we will work with in this blog.
Server Side (backend)
There are several popular and widely used frameworks for developing server side components includeing Node.js
and Django. I like Node and use it on occasion, but I prefer Django due to its tremendous community support,
versatility, ease of use, and avaialabiilty of plug-in applications including authentication mechanisms,
filters, and much more. A very common design pattern is known as Model-View-Controller (MVC) and it is worth
understanding. The model
is a representation of data which is often saved in databases. Django has a
powerful mechanism for modeling data objects designed as classes. Most django model classes inherit from models.Model.
Django's object relational mapping (ORM) techniques allow very complicated relationships between model classes
to be easily created (beyond the scope of this blog). The view
is what users see and it is the presentation
layer for models displayed in an appropriate manner. The controller
controls the flow of information between
the model and view and it is responsible for controlling requests and returning responses. That's really
the essence of Django; it receives a request and returns a response.
Use Case
I recently wrote an application that required a signficant amount of interaction between the browser client and the backend database. A user is expected to select a customer from a drop-down list. In the example I typed in "storm" to narrow down the result set (shout out to Daniel Farrell and his bootstrap-combobox.js which is fantastic!). I needed a way to efficiently retrieve information from the server based on actions performed by a user. In this case the user is only required to select a customer and then a piece of equipment owned by that customer (filtered by the server) and the client and server side code handles taking care of customer address and equipment specific informaiton. This is done using jQuery/AJAX client-side and Django server-side.
After selecting the "Stormy Brewing Company" you can see that the address information has been filled in. Also note that all equipment owned by the Stormy Brewing Company has been returned and populated in the drop down box which previously stated "Select Customer."
Once a piece of equipment is selected you can see below that the model and serial number have been returned and populated as well.
Client -- jQuery and AJAX
JavaScript's event loop is synchronous and it relies on callbacks to handle events triggered via user interaction.
In this case our company drop-down has an id of company
and the equipment drop-down has an id of make
. The
JavaScript code below shows the jQuery actions triggered when a change event happens on either drop-down. Notice
the code for these two events is almost identical with the only difference being the parameter passed into
DjangoInfo.django...if it's a company then "NA" is passed; otherwise, the value of the currenly selected equipment
is sent (we will see why later). The change events for both company and equipment creates a variable called
companyPromise
and when it's ready it's callback is invoked which in turn calls a method named setCompanyData passing
the data returned by the server.
$('#company').change(function() { if ($('#company').val() != null) { // Request a Promise object using DjangoInfo var companyPromise = DjangoInfo.django("NA"); companyPromise.done(function (data) { setCompanyData(data) }).fail(function (error) { console.log(error); }) // We can "chain" the fail callback to done; won't be called if there is no error } }); $('#make').change(function() { var companyPromise = DjangoInfo.django($('#make').val()); companyPromise.done(function(data) { setCompanyData(data) }).fail(function(error) { console.log(error); }); });
Below is a variable named DjangoInfo which returns a JavaScript Promise object. A deferred object is an object than can create a promise and change its state to resolved or rejected which is what is implemented below. Deferreds are typically used if you write your own function and want to provide a promise to the calling code. You are the producer of the value.
A promise is, as the name says, a promise about a future value. You can attach callbacks to it to get that value. The promise is "given" to the invoking function and it is the receiver of the future value. Keep in mind the invoking function is the change event triggered when a user selects a company or equipment. The change event cannot modify the state of the promise. Only the code that created the promise can change its state. In this case, the callbacks are registered to the change events for the company and equipment drop down lists. Below is the code:
var DjangoInfo = { django: function(equip_pk) { var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); $.ajaxSetup({ headers: { "X-CSRFToken": csrftoken } }); var url = "/company_info/"; var promise = $.Deferred(); // jQuery method for creating a Promise object $.ajax({ type: "POST", url: url, contentType: "application/json", data: { company_pk: $('#company').val(), equip_pk: equip_pk }, dataType: "json", success: function(data) { promise.resolve(data); // Calls the done callback }, error: function() { promise.reject("Oops!") // Calls the fail callback } }); return promise; } };
DRY Principle: We need to set the address and equipment attributes whenever company or equipment is selected
function setCompanyData(data) { $('#id_equip').text(data["equip_model"]+ " - " + data["equip_serial_num"]) $('#id_address').text(data["address"]); $('#make').html(data["cust_equip"]); $('#model').val(data["equip_model"]); $('#serial_number').val(data["equip_serial_num"]); $('#id_street_address').val(data["street_address"]); $('#id_city').val(data["city"]); $('#id_region_code').val(data["region_code"]); }
Server (Django)
You can see we are using an AJAX call to make an HttpRequest using the post method.
The server expects two variables to be passed. We always send the selected company and the selected equipment if the change event was caused
by the equipment drop-down. If not, "NA" is passed as the equipment attribute. Note the url variable set above (/company_info/). If you are thinking this must have
something to do with controller
in MVC you are correct. Django maps the requested URL within its URL configuration file and routes it to the appropriate
Django view. Below is our URL mapping in urls.py:
from django.conf.urls import url from django.contrib import admin from collopack.auth.views import * from django.conf.urls.static import static urlpatterns = [ ... url(r'company_info/$', company_info, name='company_info'), ... ]
This will result in Django invoking the company_info method:
@csrf_exempt def company_info(request): try: content = {} company_data = request.body.decode('utf-8').split('&')[0] comp_pk = company_data.split('=')[1] company = Company.objects.get(pk=comp_pk) equip_data = request.body.decode('utf-8').split('&')[1] equip_pk = equip_data.split('=')[1] if equip_pk != "NA": sel_equip = Equipment.objects.get(pk=equip_pk) content["equip_model"] = sel_equip.make content["equip_serial_num"] = sel_equip.serial_num add_equip = "" equip_list = Equipment.objects.all().filter(customer_id=company.pk) for equip in equip_list: if equip_list.count() == 1: # If customer only has 1 piece of equipment then send the model and serial number content["equip_model"] = equip.make content["equip_serial_num"] = equip.serial_num if equip_pk == str(equip.pk): # Set equipment to selected value in drop down add_equip += "<option value = " + str(equip.pk) + " selected>" + Equipment.objects.get(pk=equip.pk).equip_name + "</option>" else: add_equip += "<option value = " + str(equip.pk) + ">" + Equipment.objects.get(pk=equip.pk).equip_name + "</option>" if add_equip == "": add_equip = "<option value = 1>No Equipment</option>" content["equip_model"] = "Model" content["equip_serial_num"] = "Serial Number" content["street_address"] = company.address1 content["city"] = company.city content["region_code"] = company.state content["cust_equip"] = add_equip content["address"] = "{0} {1} {2}".format(company.address1, company.city, company.state) return HttpResponse(json.dumps(content)) except: pass
Django doesn't automatically handle JSON encoded data. When form-encoded data is received in a POST request, django
will automatically populate its HttpRequest.POST dictionary object (and also HttpRequest.REQUEST). This doesn't happen
with json data. The query dictionary in HttpRequest.POST will remain empty unless the the
contentType and dataType parameters in the ajax call are removed or commented out. Since we set the contentType and dataType
parameters as json (JavaScript Object Notation) we need to access the passed attributes by parsing the request body:
company_data = request.body.decode('utf-8').split('&')[0]
splits out the company and equipment attributes respectively with the
key value pair separated by an =
sign. That explains the code lines comp_pk = company_data.split('=')[1]
and
company = Company.objects.get(pk=comp_pk)
. I point this out because it's a good illustration of the model
at work here; Company is
the Django model and we retrieve the correct company using the primary key which was passed in as a value. A similar action is performed
to retrieve equipment that belongs to a customer. All Equipment model objects are searched and filtered using a foreign key relationship
defined between the Customer and Equipment models (a piece of equipment has a foreign key relationship to a company). We iterate
through the equipment list to dynamically build the equipment drop-down list and add other attributes such as address, city, equipment
model, etc. The response is prepared using a Python dictionary named content
which is then converted to a string by json.dumps(content)
and returned as the HttpResponse object. Hopefully, it's obvious the HttpResponse is the data returned in our promise object which can then be used
by our view
to display the appropriate data.
This post might be confusing to some, but we covered a lot of ground. One of my objectives was to demonstrate the power of JavaScript's Promise
objects. We were able to re-use the JavaScript DjangoInfo variable and Django company-info method to process requests for both companies and
equipment. This is consistent with Django's Don't Repeat Yourself (DRY)
mantra.