ajax-with-django

Dependent Dropdown list in Django with Ajax in plain JS

A tutorial on how to use ajax with django in plain javascript to create dependent drop downs

Introduction

In this tutorial we will learn how to use ajax with Django. We will create a dependent dropdown with ajax. We will do it step by step i.e we will execute and check that each step is working fine before moving on.

Scenario:

Lets say, we want to have 2 drop downs for country and cities. When a user selects a country, he only gets options of corresponding cities in the other drop down.
We will have a 1 to many relationship from Country to City table. I will create some data for models with 3 countries and 3 cities in each country. If you want to load data from a csv in to your models, i will write about it soon. For now, our 3 countries and 9 cities will do the job.

Github link for final code (Check it AFTER reading the tutorial):

https://github.com/mazharrazmian/webwithdjango/tree/master/dropdownProject

Lets Start:

Now, as i said, we will be doing this step by step. Since we are working with javascript, the first thing is to make sure your javascript (static files) work. Just do a simple console.log("hello world") in index.js, run the server, go to your terminal and check your console if its outputting 'hello world', if its not working, you need to fix your static files (or js file) before going further.

Now that we have our javascript working, we will create a view in our views.py file.
This is how my views.py looks like:

 
from django.shortcuts import render
from .models import Country
# Create your views here.
def index(request):
 countries = Country.objects.all()
 context = {
 'countries' : countries,
 }
 return render(request,'index.html',context=context)


  

We are getting all the Country objects from our database and passing them to our index.html where they will be used in select box.

index.html:

 
{% load static %}
<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <title>Index</title>
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="stylesheet" type="text/css" media="screen" href="{% static 'css/index.css' %}">
 <script src='{% static "js/index.js" %}'></script>
</head>
<body>
 <h1>Just a basic template </h1>

 <form>
 <select name="country" class="country" id='country'>

 {% for country in countries %}

 <option value="{{country.pk}}">{{country.name}}</option>

 {% endfor %}

 </select>

<select name="city" id="city">

 </select>

 </form>
</body>
</html>


  

This will create 2 select boxes in our index.html with all the countries as 'options' in first, and empty cities list in second.

Now we need to send a request to our server when someone selects a country or when the option changes. We will use javascript to handle the onchange event on Country selector.

This is our index.js file as of now

 
window.onload = function(){

 let selector = document.querySelector("#country");
 selector.addEventListener('change',function(){

 let country_id = selector.value;

 ajax_request(country_id);
 });


function ajax_request(id){
 var xhttp = new XMLHttpRequest();
 xhttp.onreadystatechange = function() {
 if (this.readyState == 4 && this.status == 200) {
 console.log("GOT RESPONSE");
 }
 };
 xhttp.open("GET", `/ajax_handler/${id}`, true);
 xhttp.send();
}


}


  

You should add ajax_handler/<int:id> route in your urls.py and name it 'ajax_handler'.

Notice that we are only logging the output to console, you should always follow this approach when working with javascript or jquery with django or other languages, you first need to make sure that your request is being sent, then make sure that it is coming back, and AFTER that, implement your logic.

Now we need to make sure that our requests are being recieved on the server correctly, we will use a function based view for that in our views.py.

add this to your views.py

def ajax_handler(request,id):
print(id)

Again, you can see we are only printing the id recieved in the url, run this code, you will get a 500 server error, but don't worry, it is because we are not sending any response from our view, but if you see the 'id' of country being printed on your console, you are good to go.

Now that we know that our server is recieving ajax requests, we need to implement the logic and send data back to our index.html to add options to our 'City selector'.

 
from .models import City
import json
from django.http import JsonResponse
...
...
def ajax_handler(request,id):
 cities = City.objects.filter(country__id=id).values_list('id','name')
 cities = dict(cities)
 return JsonResponse({
 'cities' : cities,
 })


  

The first line in ajax_handler is filtering cities based on the id we recieved in url through ajax, then it is only getting 'id' and 'name' attribute from the object and returning a queryset, since querysets are not JSON Serializable, we convert the queryset to a dict and return it as JsonResponse to our index.html.
When you run this code, you should see list of cities of each country in your console when you select a country. Now we just need to do some javascript stuff to add these cities to our <option> tags in the 'City Selector'.

Here's how our new index.js looks like:

 
let country_id = selector.value;
 //sending ajax request to the view with id of selected country. Function is defined below.
 ajax_request(country_id);
 });


function ajax_request(id){
//creating new xhttp request.
 var xhttp = new XMLHttpRequest();
 xhttp.onreadystatechange = function() {
 if (this.readyState == 4 && this.status == 200) {
 //converting the response text to a javascript object.
 res = JSON.parse(this.responseText)
 cities = res.cities;
//Deleting all children of the City Selector, so that we only get the cities of selected country.
 removeChilds(document.getElementById('city'));
 for(const prop in cities){
 add_option(prop,cities[prop]);
 }
 }
 };
//sending ajax GET request to server with id of selected city.
 xhttp.open("GET", `/ajax_handler/${id}`, true);
 xhttp.send();
}


function add_option(val,text){
 var sel = document.getElementById('city');

 // create new option element
var opt = document.createElement('option');

// create text node to add to option element (opt)
opt.appendChild( document.createTextNode(text) );

// set value property of opt
opt.value = val;

 // add opt to end of select box (sel)
sel.appendChild(opt);
 }

}

var removeChilds = function (node) {
 var last;
//Delete every lastChild of the City Selector------- Delete all children
 while (last = node.lastChild) node.removeChild(last);
};


  

Now when you refresh your page and click select a country, you will see its corresponding cities in the other select box. But there's a small problem, when you first visit the page, a country is selected but there are no values in the City selector. A simple fix for that is to just add an option tag with a random 'value' as the first child of your Country selector, and edit your index.js to check for that value when the 'onchange' event is fired, your <select> element should look like this:

 
<select name="country" class="country" id='country'>
 <option value="no_country">Select a country</option>
 {% for country in countries %}

 <option value="{{country.pk}}">{{country.name}}</option>

 {% endfor %}

 </select>


  

Your window.onload in index.js should look like this:

 
let selector = document.querySelector("#country");
 selector.addEventListener('change',function(){

 let country_id = selector.value;
 console.log(country_id)
 if(country_id == "no_country"){
 removeChilds(document.getElementById('city'));
 }
 else{
 ajax_request(country_id);
 }

 });


  

Now we are only sending the request when a country is selected, else our Cities Select box will be empty.

Learn Django with us

Visit Youtube Channel
Latest articles