简体   繁体   中英

How to Implement 3-way Dependent/Chained Dropdown List with Django?

I am using django and postgresql. I'm dealing with a 3-way dependent drop-down list. After adding the country selection, the province area is automatically updated depending on the selected country. After selecting a province, the county field opens only when the page is refreshed. I would be glad if you help me in this regard.

models.py

from django.db import models

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

    def __str__(self):
        return self.name

class City(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    name = models.CharField(max_length=30)

    def __str__(self):
        return self.name

class District(models.Model):
    city = models.ForeignKey(City, on_delete=models.CASCADE)
    name = models.CharField(max_length=30)

    def __str__(self):
        return self.name

class Person(models.Model):
    name = models.CharField(max_length=100)
    birthdate = models.DateField(null=True, blank=True)
    country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True)
    city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True)
    district = models.ForeignKey(District, on_delete=models.SET_NULL, null=True)

    def __str__(self):
        return self.name

forms.py

from django import forms
from .models import Person, Country, City, District

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person
        fields = ('name', 'birthdate', 'country', 'city', 'district')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['city'].queryset = City.objects.none()

        if 'country' in self.data:
            try:
                country_id = int(self.data.get('country'))
                self.fields['city'].queryset = City.objects.filter(country_id=country_id).order_by('name')
            except (ValueError, TypeError):
                pass  # invalid input from the client; ignore and fallback to empty City queryset
        elif self.instance.pk:
            self.fields['city'].queryset = self.instance.country.city_set.order_by('name')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['district'].queryset = District.objects.none()

        if 'city' in self.data:
            try:
                city_id = int(self.data.get('city'))
                self.fields['district'].queryset = District.objects.filter(city_id=city_id).order_by('name')
            except (ValueError, TypeError):
                pass  # invalid input from the client; ignore and fallback to empty District queryset
        elif self.instance.pk:
            self.fields['district'].queryset = self.instance.city.district_set.order_by('name')

views.py

def load_cities(request):
    country_id = request.GET.get('country')
    cities = City.objects.filter(country_id=country_id).order_by('name')
    return render(request, 'neighbor/city_dropdown_list_options.html', {'cities': cities})

def load_districts(request):
    city_id = request.GET.get('city')
    districts = District.objects.filter(city_id=city_id).order_by('name')
    return render(request, 'neighbor/district_dropdown_list_options.html', {'districts': districts})

templates.html

<h2>Person Form</h2>
  <form method="post" id="personForm" data-cities-url="{% url 'ajax_load_cities' %}" novalidate>
    {% csrf_token %}
    <table>
      {{ form.as_table }}
    </table>
    <button type="submit">Save</button>
    <a href="{% url 'person_changelist' %}">Nevermind</a>
  </form>
  <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  <script>
    $("#id_country").change(function () {
      var url = $("#personForm").attr("data-cities-url");
      var countryId = $(this).val();
      $.ajax({
        url: url,
        data: {
          'country': countryId
        },
        success: function (data) {
          $("#id_city").html(data);
        }
      });
    });
    $("#id_city").change(function () {
      var url = $("#personForm").attr("data-districts-url");
      var cityId = $(this).val();
      $.ajax({
        url: url,
        data: {
          'city': cityId
        },
        success: function (data) {
          $("#id_district").html(data);
        }
      });
    });
  </script>

I'm referring to the example here.

https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html

  1. In your templates.html file, you are calling the same URL hence the same view for country and city fields change. you need to make a new URL to handle the request in load_district view.

    make the following changes

    $("#id_city").change(function () { var url = $("#personForm").attr("data-districts-url"); var cityId = $(this).val(); $.ajax({ url: '{% url 'ajax_load_districts' %}', data: { 'city': cityId }, success: function (data) { $("#id_district").html(data); } }); });

  2. you don't need to modify init() method twice, just remove these 2 lines from froms.py. (line number 22 and 23)

     def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
  3. add a new URL for handling ajax request for district

    path('ajax/load-district/', views.load_districts, name='ajax_load_districts'),

you can clone this example, i have used Country, City, Vanue... https://github.com/masbhanoman/django_3way_chained_dropdown_list

Thank you very much for your answer and the link to GitHub. It works perfectly with 3 dependent dropdown lists. You can still slightly improve your JS code:

var url = $("#personForm").attr("data-districts-url");

can be deleted as you don't use the variable and directly set an url, which, by the way, makes your code quite readable.

One addition point here is regarding the id formatting, so in my case was necessary to use {{ value|safe }} on dropdown, so:

<option value="">---------</option>
{% for city in cities %}
<option value="{{ city.pk|safe }}">{{ city.name }}</option>
{% endfor %}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM