如何在Django admin中实现Country、Province、District三级联动下拉框
Django Admin 三级联动下拉框实现方案
第一步:定义关联模型
首先需要完成三个层级数据模型的外键关联,示例代码如下:
# models.py from django.db import models class Country(models.Model): name = models.CharField(max_length=100, verbose_name="国家") def __str__(self): return self.name class Province(models.Model): name = models.CharField(max_length=100, verbose_name="省份") country = models.ForeignKey(Country, on_delete=models.CASCADE, verbose_name="所属国家") def __str__(self): return self.name class District(models.Model): name = models.CharField(max_length=100, verbose_name="区县") province = models.ForeignKey(Province, on_delete=models.CASCADE, verbose_name="所属省份") def __str__(self): return self.name # 引用这三个字段的业务模型示例 class Address(models.Model): country = models.ForeignKey(Country, on_delete=models.CASCADE, verbose_name="国家") province = models.ForeignKey(Province, on_delete=models.CASCADE, verbose_name="省份") district = models.ForeignKey(District, on_delete=models.CASCADE, verbose_name="区县") # 其余业务字段可自行补充
第二步:自定义Admin表单
在admin.py中重写业务模型的表单逻辑,控制初始下拉选项范围,同时注入联动脚本:
# admin.py from django.contrib import admin from django import forms from .models import Address, Country, Province, District class AddressForm(forms.ModelForm): class Meta: model = Address fields = "__all__" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 新增状态下初始省、区县下拉为空 if not self.instance.pk: self.fields["province"].queryset = Province.objects.none() self.fields["district"].queryset = District.objects.none() # 编辑状态下根据已保存值加载对应选项 else: self.fields["province"].queryset = Province.objects.filter(country=self.instance.country) self.fields["district"].queryset = District.objects.filter(province=self.instance.province) @admin.register(Address) class AddressAdmin(admin.ModelAdmin): form = AddressForm # 注入联动JS class Media: js = ("js/address_linkage.js",)
第三步:编写联动JS逻辑
在项目静态文件目录下新建js/address_linkage.js,监听下拉框变化并通过ajax动态加载选项:
django.jQuery(document).ready(function() { // 国家切换时加载对应省份 django.jQuery("#id_country").change(function() { const countryId = django.jQuery(this).val(); django.jQuery.ajax({ url: "/admin/load-provinces/", data: { country_id: countryId }, success: function(data) { django.jQuery("#id_province").empty().append('<option value="">---------</option>'); django.jQuery("#id_district").empty().append('<option value="">---------</option>'); data.forEach(province => { django.jQuery("#id_province").append(`<option value="${province.id}">${province.name}</option>`); }); } }); }); // 省份切换时加载对应区县 django.jQuery("#id_province").change(function() { const provinceId = django.jQuery(this).val(); django.jQuery.ajax({ url: "/admin/load-districts/", data: { province_id: provinceId }, success: function(data) { django.jQuery("#id_district").empty().append('<option value="">---------</option>'); data.forEach(district => { django.jQuery("#id_district").append(`<option value="${district.id}">${district.name}</option>`); }); } }); }); });
注意:如果你的字段ID不是Django默认生成的id_country、id_province、id_district,需要将JS中的选择器替换为实际字段ID,静态文件路径需和项目配置匹配。
第四步:添加数据查询接口
在项目路由中新增两个接口,用于返回对应层级的选项数据:
# urls.py from django.contrib import admin from django.urls import path from django.http import JsonResponse from .models import Province, District def load_provinces(request): country_id = request.GET.get("country_id") provinces = Province.objects.filter(country_id=country_id).values("id", "name") return JsonResponse(list(provinces), safe=False) def load_districts(request): province_id = request.GET.get("province_id") districts = District.objects.filter(province_id=province_id).values("id", "name") return JsonResponse(list(districts), safe=False) urlpatterns = [ path("admin/", admin.site.urls), # 新增联动查询接口 path("admin/load-provinces/", load_provinces), path("admin/load-districts/", load_districts), ]
内容的提问来源于stack exchange,提问作者Physics wala Boka
相关产品推荐
相关产品推荐

