Dependent or chained dropdown list is a special field that relies on a previously selected field so to display a list of filtered options. A common use case is on the selection of state/province and cities, where you first pick the state, and then based on the state, the application displays a list of cities located in the state.
Example Scenario
Take the application below as an example:
models.py
In the application we are going to create a simple form processing to create and update person objects. The dependent
dropdown list will be used on the country
and city
fields of the person model.
urls.py
Finally, the three basic views:
views.py
The example is already working, except it may allow inconsistent data to be saved in the database. For example, someone could pick Brazil from the country dropdown and then New York from the city dropdown. Also, so far, that’s not what we want. We want the city dropdown to be filtered based on the country selection.
The HTML is just a simple form rendering:
Dependent Dropdown Form
The best way to implement it is by creating a model form. This way we are going to have great flexibility to work on the features.
forms.py
The example above is a simple form definition with an important detail: right now we are overriding the default init method, and setting the queryset of the city field to an empty list of cities:
PS: Don’t forget to change the view definition to use our new form class instead:
views.py
Now we need to create a view to return a list of cities for a given country. This view will be used via AJAX requests.
views.py
Simple function based view is great for this kind of implementation. Below, what our HTML template looks like:
templates/hr/city_dropdown_list_options.html
See what we are doing here? This template will be used to compose just this tiny piece of HTML. Then the challenge now is to load only this part without having to reload the entire HTML page.
Before we proceed, let’s create an URL route for this view:
urls.py
Now it’s time to create an AJAX request. In the example below I’m using jQuery, but you can use any JavaScript framework (or just plain JavaScript) to create the asynchronous request:
templates/person_form.html
First thing, I added an ID for the form (personForm
) so we can access it more easily. After that, I added a data
attribute to the form data-cities-url
. That’s a good strategy for cases where you are going to implement the
JavaScript in a separate file, so you can access the URL rendered by Django.
Then, after that we have a listener on the country dropdown, identified by id_country
. This ID is automatically
generated by Django. Our listener is waiting for this value to change. When it changes, it will fire an AJAX request
to the server, passing the selected country ID to our view.
Upon success of the request, our tiny script will add the HTML rendered by the load_cities
view inside the cities
dropdown list, which is identified by the HTML ID id_city
.
Right now the front end is already good, but the back-end is not quite working as expected. If we submit the form as it is now, we are going to see the following error message:
That’s because of our empty list of cities in the form definition. I wanted to show you this error message, because it’s actually very useful. It will help us to keep the consistency of our form. Meaning the Django form check if the provided value exists in the queryset.
Below, the fix:
forms.py
This form will provide a very nice behavior. If there is form POST data (data is not None
), it will load the list of
cities using the country ID the form received. If it’s an invalid input, just discard it and the form will display a
nice error for the user. If there is no POST data but there is an instance in the form (meaning the form is being used t
o updated an existing person), use the list of cities from the selected country. If not, just return an empty list of
cities, as it’s a brand new form (self.fields['city']
was already set to an empty queryset, remember?).
Alternatively you could completely remove the country field from the form definition, as it is related to the city anyway. But I preferred to keep it there because there are cases where you need to save both values, and in the example above you can have an idea of how to implement it without having to interfere with the rendering process of the form (which is a great thing).
Conclusions
Best way to learn is by trying it yourself. Check the code on GitHub and try it locally. Modify this example, make it yours. If you have any questions, please leave in the comments below!
You can see this example live at dependent-dropdown-example.herokuapp.com;
For the source code, go to github.com/sibtc/dependent-dropdown-example/.