如何在Django Admin面板实现国家与省份的联动下拉菜单
Django Admin实现国家-省份-城市联动下拉方案
我在Django Admin面板搭建了客户门户,全程使用Django默认模板,未自定义HTML。但遇到一个问题:无法实现国家与省份的联动下拉菜单。目前城市的关联下拉框中显示了所有城市,我希望用户先选择国家,省份下拉框自动显示对应选项,最后用户可手动输入所需城市名称。我了解到这种场景需要用到Ajax,但作为新手不擅长JavaScript,求实现方法或者其他可行方案。
现有代码
models.py
class Country(models.Model): id = models.AutoField(primary_key=True) parent_id = models.IntegerField(null=False) name = models.CharField(null=False, max_length=255) status = models.CharField(null= True, choices=Status_Choices, max_length=11, default="--Select Status--") added_by = models.IntegerField() updated_by = models.IntegerField(null=True) created_on = models.CharField(default=get_current_datetime_str , max_length=255) updated_on = models.CharField(default=get_current_datetime_str, max_length=255) def __str__(self): return self.name class State(models.Model): Country = models.ForeignKey(Country, on_delete=models.CASCADE) name = models.CharField(null=False, max_length=255) status = models.CharField(null= True, choices=Status_Choices, max_length=11, default="--Select Status--") added_by = models.IntegerField() updated_by = models.IntegerField(null=True) created_on = models.CharField(default=get_current_datetime_str , max_length=255) updated_on = models.CharField(default=get_current_datetime_str, max_length=255) def __str__(self): return self.name class City(models.Model): state = models.ForeignKey(State, on_delete=models.CASCADE) name = models.CharField(null=False, max_length=255) status = models.CharField(null= True, choices=Status_Choices, max_length=11, default="--Select Status--") added_by = models.IntegerField() updated_by = models.IntegerField(null=True) created_on = models.CharField(default=get_current_datetime_str , max_length=255) updated_on = models.CharField(default=get_current_datetime_str, max_length=255) def __str__(self): return self.name
forms.py
class CountryForm(forms.ModelForm): class Meta: model = Country fields = ['name'] def clean_name(self): name = self.cleaned_data['name'] if name == '--Select Country--': raise forms.ValidationError("Please select a valid country") return name class StateForm(forms.ModelForm): class Meta: model = State fields = ['name', 'Country'] class CityForm(forms.ModelForm): class Meta: model = City fields = ['name', 'state']
admin.py
class CountryAdmin(admin.ModelAdmin): exclude = ('parent_id', 'created_on', 'updated_on', 'added_by', 'updated_by') def save_model(self, request, obj, form, change): user = request.user if user.is_authenticated: obj.added_by = user.id else: ... if change: obj.updated_by = user.id super().save_model(request, obj, form, change) admin.site.register(Country, CountryAdmin) @admin.register(State) class StateAdmin(admin.ModelAdmin): exclude = ('parent_id', 'created_on', 'updated_on', 'added_by', 'updated_by') list_display = ('name', 'Country') def save_model(self, request, obj, form, change): user = request.user if user.is_authenticated: obj.added_by = user.id else: ... if change: obj.updated_by = user.id super().save_model(request, obj, form, change) @admin.register(City) class CityAdmin(admin.ModelAdmin): exclude = ('parent_id', 'created_on', 'updated_on', 'added_by', 'updated_by') list_display = ('name', 'state') verbose_name_plural = 'Locations' def save_model(self, request, obj, form, change): user = request.user if user.is_authenticated: obj.added_by = user.id else: ... if change: obj.updated_by = user.id super().save_model(request, obj, form, change)
实现步骤
1. 添加API视图获取对应国家的省份
在项目的views.py中新增视图,用于根据国家ID返回对应的省份列表:
from django.http import JsonResponse from .models import State def get_states_by_country(request): country_id = request.GET.get('country_id') states = State.objects.filter(Country_id=country_id).values('id', 'name') return JsonResponse(list(states), safe=False)
在urls.py中添加路由:
from django.urls import path from . import views urlpatterns = [ # 其他路由... path('get-states/', views.get_states_by_country, name='get_states'), ]
2. 自定义City表单,添加国家字段
修改forms.py中的CityForm,新增Country字段用于联动:
class CityForm(forms.ModelForm): # 新增国家字段,初始为空 country = forms.ModelChoiceField(queryset=Country.objects.all(), required=True, label="国家") class Meta: model = City fields = ['name', 'country', 'state'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 如果已有关联的state,默认选中对应的国家 if self.instance and self.instance.state: self.fields['country'].initial = self.instance.state.Country.id # 初始化省份下拉框为对应国家的省份 self.fields['state'].queryset = State.objects.filter(Country_id=self.instance.state.Country.id) else: # 默认省份下拉框为空 self.fields['state'].queryset = State.objects.none()
3. 给Admin页面注入JavaScript
创建一个admin/city_change_form.html模板文件(放在app的templates目录下),内容如下:
{% extends "admin/change_form.html" %} {% block extrahead %} {{ block.super }} <script type="text/javascript"> document.addEventListener('DOMContentLoaded', function() { // 获取国家和省份的下拉框元素 const countrySelect = document.getElementById('id_country'); const stateSelect = document.getElementById('id_state'); // 监听国家选择变化 countrySelect.addEventListener('change', function() { const countryId = this.value; // 清空省份下拉框 stateSelect.innerHTML = '<option value="">--- 选择省份 ---</option>'; if (countryId) { // 发送Ajax请求获取省份列表 fetch(`{% url 'get_states' %}?country_id=${countryId}`) .then(response => response.json()) .then(data => { // 添加省份选项 data.forEach(state => { const option = document.createElement('option'); option.value = state.id; option.textContent = state.name; stateSelect.appendChild(option); }); }); } }); }); </script> {% endblock %}
4. 修改Admin配置使用自定义表单和模板
更新admin.py中的CityAdmin类:
@admin.register(City) class CityAdmin(admin.ModelAdmin): exclude = ('parent_id', 'created_on', 'updated_on', 'added_by', 'updated_by') list_display = ('name', 'state') verbose_name_plural = 'Locations' # 使用自定义表单 form = CityForm # 指定自定义模板 change_form_template = 'admin/city_change_form.html' def save_model(self, request, obj, form, change): user = request.user if user.is_authenticated: obj.added_by = user.id else: ... if change: obj.updated_by = user.id super().save_model(request, obj, form, change)
额外说明
- 上述配置同时支持新增城市和编辑城市的场景
- 确保
get_current_datetime_str和Status_Choices等自定义变量在代码中已正确定义 - 测试前清空浏览器缓存,确保新的JavaScript代码生效
内容的提问来源于stack exchange,提问作者n0cuous
相关产品推荐
相关产品推荐

