Search code examples
djangodjango-formsmanytomanyfield

Django chain filtering results based on selection in manytomany fields


I have the following models:

class Country(models.Model):
    name = models.CharField(max_length=50)

class State(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    name =  models.CharField(max_length=50, null=False, blank=False)

class Species(models.Model):
    name = models.CharField(max_length=50, null=False, blank=False)
    country =models.ManyToManyField(Country)
    state =models.ManyToManyField(State)

Let say I added multiple countries into the a form or admin field. Is there a way to filter the results from the state field based on what I have selected in the country field?

An example of this would be:

Countries: States

USA: CA, VA, NV, OR

Mexico: PUB, JAL, SIN

Canada: NU, ON, QC

If I select USA and Canada the state field on the form will yield:

NU, ON, QC, 
CA, VA, NV, OR

The closest thing I can find that does something similar is django-smart-select but it does not work for my case.


Solution

  • I would use Ajax+JS to achieve this. I would do something along these lines (I have not tested this code):

    HTML :

    <select name="country">
      <option value="">Choose a country</option>
      <option value="USA">USA</option>
      <option value="Mexico">Mexico</option>
      <option value="Canada">Canada</option>
    </select>
    
    <select name="state" disabled>
      <option value="">Choose a state</option>
    </select>
    

    JS :

    $('select[name="country"]').on('change', function() {
      var country = $(this).val();
      var states = [];
      $('select[name="state"] option:gt(0)').remove(); // cleaning: removes all options except the first
    
      if (!(country)) {
        $('select[name="state"]').prop("disabled", true);
      } else {
        $.ajax({
          url: "{% url 'get_states' %}",
          dataType: "json",
          data: {
            country: country
          },
          success: function(data) {
            data = JSON.parse(data);
            for (var key in data) {
              $('select[name="state"]').append(
                $('<option>', {
                  value: key,
                  text: data[key]
                })
              );
            }
          }
        });
        $('select[name="state"]').prop("disabled", false);
      }
    });
    

    In urls.py :

    url(r'^getstates$', 'myapp.views.get_states', name='get_states'),
    

    In views.py :

    from django.shortcuts import HttpResponse
    import json
    from myapp.models import Country, State
    
    
    def get_states(request):
        if request.is_ajax():
            country = request.GET.get('country', '')
            states = State.objects.filter(country__name=country)
            state_dict = {}
            for state in states:
                state_dict[state.pk] = state.name
            return HttpResponse(json.dumps(state_dict))
    

    Hope this helps!