You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 19:55:34