Search code examples
djangodjango-admindjango-autocomplete-light

creating linked lists in django admin for foreign keys


I have some chained foreign key relationships like this:

class Continent(models.Model):
    continent = models.CharField(max_length=30)

class Country(models.Model):
    country = models.CharField(max_length=30)
    continent = models.ForeignKey(Continent)

class City(models.Model):
    city = models.CharField(max_length=30)
    country = models.ForeignKey(Country)

class Person(models.Model):
    name = models.CharField(max_length=30)
    continent = models.ForeignKey(Continent)
    country = models.ForeignKey(Country)
    city = models.ForeignKey(City)

and in the person admin create new item view, I want the lists of countries and cities to be changed based on what continent is selected, etc. I tried LinkedSelect of django suit but I think that is not meant for this. I read a bit about django select2 but I don't see any support for that. Any ideas whether there is a package that can help?


update: I came across this

which suggests django smart selects. I tried it. There are 2 problems: - it requires you to modify the model so that's a red sign. - it shows the list in a form of categories, but it still allows you to select the wrong item which is not desirable. (show_all does not work for the GroupedForeignKey)

I have a great idea. Since I want to use autocompletion using django-autocomplete-light, if I can add an event handler that says when you select the first list, then modify the autocomplete url of the second list to pass in an additional parameter, then the whole chain will work. The thing I'm stuck at is that when I change the url (data-autocomplete-light-url), it is not taking effect. I don't know how to trigger it to reload.


Solution

  • Fortunately, this is actually part of django-autocomplete-light.

    You will have to create your own form (if not already done):

    class PersonForm(forms.ModelForm):
    
        class Meta:
            model = Person
            fields = ('__all__')
            widgets = {
                'country': autocomplete.ModelSelect2(url='country-autocomplete'
                                                     forward=['continent']),
                'city': autocomplete.ModelSelect2(url='city-autocomplete'
                                                  forward=['country']),
            }
    

    Update your autocompletes:

    class CountryAutocomplete(autocomplete.Select2QuerySetView):
        def get_queryset(self):
            if not self.request.is_authenticated():
                return Country.objects.none()
    
            qs = Country.objects.all()
    
            continent = self.forwarded.get('continent', None)
    
            if continent:
                qs = qs.filter(continent=continent)
    
            if self.q:
                qs = qs.filter(country__istartswith=self.q)
    
            return qs
    
    class CityAutocomplete(autocomplete.Select2QuerySetView):
        def get_queryset(self):
            if not self.request.is_authenticated():
                return City.objects.none()
    
            qs = City.objects.all()
    
            country = self.forwarded.get('country', None)
    
            if country:
                qs = qs.filter(country=country)
    
            if self.q:
                qs = qs.filter(city__istartswith=self.q)
    
            return qs
    

    And use the new form in your ModelAdmin:

    class PersonAdmin(admin.ModelAdmin):
        form = PersonForm