Django Admin dependent DropDown HTML field

1.7k Views Asked by At

I have a Region and SubRegion ForeignKey in the Country Model. My SubRegion model also has a Region ForeignKey for the Region Model. I am having a hard time displaying the SubRegion dropdown in the Country Model on the basis of the Region selected.

What would be the best way to achieve it? It would be great if dropdowns were based on the value of their parent.

models.py

class Region(models.Model):
    name = models.CharField(max_length=20)
    is_active = models.BooleanField(verbose_name='is active?', default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

class SubRegion(models.Model):
    region = models.ForeignKey(Region, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)
    is_active = models.BooleanField(verbose_name='is active?', default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = 'Sub Regions'

class Country(models.Model):
    name = models.CharField(max_length=100)
    region = models.ForeignKey(Region, on_delete=models.CASCADE, verbose_name='Region')
    subregion = models.ForeignKey(SubRegion, on_delete=models.CASCADE, verbose_name='Sub Region')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

admin.py

class CountryAdmin(admin.ModelAdmin):
    list_display = ('name', 'is_active', 'is_default', 'created_at', 'updated_at', )
    list_filter = ('region', 'subregion', 'is_active', 'is_default', 'created_at', 'updated_at', )
    search_fields = ('name', 'official_name', )
    fieldsets = (
        ('Region Information', {'fields': ('region', 'subregion', )}),
        ('Basic Information', {'fields': ('name', 'official_name', )}),
        ('Codes Information', {'fields': ('cca2', 'ccn3', 'cca3', 'cioc','idd', 'status', )}),
        ('Coordinates', {'fields': ('latitude', 'longitude', )}),
        ('Membership & Statuses', {'fields': (('is_independent', 'is_un_member', 'is_default', 'is_active', ), )}),
        ('Country Flag', {'fields': ('png_flag_url', 'svg_flag_url', )}),
        ('Country Map', {'fields': ('google_map_url', 'openstreet_map_url', )}),
        ('Additional Information', {'fields': ('population', 'area', 'start_of_week', 'postal_code_format', 'postal_code_regex', )}),
    )
    inlines = [CountryTopLevelDomainInline, CountryCurrencyInline, CountryTimezoneInline]


admin.site.register(Country, CountryAdmin)

Currently, Whenever I am going to add/edit a country then the complete region and subregion are visible on the select dropdown. What I want like possibilities to load the subregion on the basis of region selection.

1

There are 1 best solutions below

0
On BEST ANSWER

After long research, I was able to solve this issue.

I have created the JS file inside my application.

static\geo_location\js\chained-region.js

var response_cache = {};
function get_subregion_for_region(region_id) {
    (function($){
        var selected_value = $("#id_subregion").val();
        if(response_cache[region_id]){
            $("#id_subregion").html(response_cache[region_id]);
            $("select#id_subregion option").each(function(){
                if ($(this).val() == selected_value)
                    $(this).attr("selected","selected");
            });
        } else {
            $.getJSON("/geo-location/get-subregions-for-region", {region_id:region_id}, function(res, textStatus){
                var options = '<option value="" selected="selected">---------</option>';
                $.each(res.data, function(i, item){
                    options += '<option value="' + item.id + '">' + item.name + '</option>';
                });
                response_cache[region_id] = options;
                $("#id_subregion").html(response_cache[region_id]);
                $("select#id_subregion option").each(function(){
                    if ($(this).val() == selected_value)
                        $(this).attr("selected","selected");
                });
            });
        }
    })(django.jQuery);
}

then, added the views function to handle AJAX requests.

views.py

from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from .models import SubRegion


@login_required
def subregion_for_region(request):
    if 'region_id' in request.GET and request.GET['region_id'] != '':
        subregions = SubRegion.objects.filter(region=request.GET['region_id'])
        return JsonResponse({'data': [{'id': subregion.id, 'name': subregion.name} for subregion in subregions]})
    else:
        return JsonResponse({'data': []})

Note:- I have added the @login_required decorator to prevent the unnecessary call.

And, in the admin.py I have created the form and added the "onchange" event on the region dropdown.

admin.py

from django import forms
class CountryForm(forms.ModelForm):
    class Meta:
        model = Country
        fields = '__all__'
        widgets = {
            'region': forms.Select(attrs={'onchange': 'get_subregion_for_region(this.value);'})
        }

class CountryAdmin(admin.ModelAdmin):
    ....

    form = CountryForm

    class Media:
        js = (
            'geo_location/js/chained-region.js',
        )

And last added the URL pattern in the application urls.py file. urls.py

from django.urls import path
from . import views

app_name = 'geo_location'
urlpatterns = [
    path('get-subregions-for-region/', views.subregion_for_region, name='subregions-for-region'),
]