Dependencies between autocompletes

This means that the selected value in an autocomplete widget is used to filter choices from another autocomplete widget.

This page drives through the example in autocomplete_light/example_apps/dependant_autocomplete/.

Specifications

Consider such a model:

from django.db import models


class Dummy(models.Model):
    parent = models.ForeignKey('self', null=True, blank=True)
    country = models.ForeignKey('cities_light.country')
    region = models.ForeignKey('cities_light.region')

    def __unicode__(self):
        return '%s %s' % (self.country, self.region)

And we want two autocompletes in the form, and make the “region” autocomplete to be filtered using the value of the “country” autocomplete.

Autocompletes

Register an Autocomplete for Region that is able to use ‘country_id’ GET parameter to filter choices:

import autocomplete_light.shortcuts as autocomplete_light
from cities_light.models import Country, Region

autocomplete_light.register(Country, search_fields=('name', 'name_ascii',),
    autocomplete_js_attributes={'placeholder': 'country name ..'})


class AutocompleteRegion(autocomplete_light.AutocompleteModelBase):
    autocomplete_js_attributes={'placeholder': 'region name ..'}

    def choices_for_request(self):
        q = self.request.GET.get('q', '')
        country_id = self.request.GET.get('country_id', None)

        choices = self.choices.all()
        if q:
            choices = choices.filter(name_ascii__icontains=q)
        if country_id:
            choices = choices.filter(country_id=country_id)

        return self.order_choices(choices)[0:self.limit_choices]

autocomplete_light.register(Region, AutocompleteRegion)

Javascript

Actually, a normal modelform is sufficient. But it was decided to use Form.Media to load the extra javascript:

import autocomplete_light.shortcuts as autocomplete_light
from django import forms

from .models import Dummy


class DummyForm(autocomplete_light.ModelForm):
    class Media:
        """
        We're currently using Media here, but that forced to move the
        javascript from the footer to the extrahead block ...

        So that example might change when this situation annoys someone a lot.
        """
        js = ('dependant_autocomplete.js',)

    class Meta:
        model = Dummy
        exclude = []

That’s the piece of javascript that ties the two autocompletes:

$(document).ready(function() {
    $('body').on('change', '.autocomplete-light-widget select[name$=country]', function() {
        var countrySelectElement = $(this);
        var regionSelectElement = $('#' + $(this).attr('id').replace('country', 'region'));
        var regionWidgetElement = regionSelectElement.parents('.autocomplete-light-widget');

        // When the country select changes
        value = $(this).val();

        if (value) {
            // If value is contains something, add it to autocomplete.data
            regionWidgetElement.yourlabsWidget().autocomplete.data = {
                'country_id': value[0],
            };
        } else {
            // If value is empty, empty autocomplete.data
            regionWidgetElement.yourlabsWidget().autocomplete.data = {}
        }

        // example debug statements, that does not replace using breakbpoints and a proper debugger but can hel
        // console.log($(this), 'changed to', value);
        // console.log(regionWidgetElement, 'data is', regionWidgetElement.yourlabsWidget().autocomplete.data)
    })
});

Conclusion

Again, there are many ways to acheive this. It’s just a working example you can test in the demo, you may copy it and adapt it to your needs.