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

如何让Django模型表单指定外键字段下拉框为空?

实现Django级联下拉:初始仅显示国家选项,动态加载州和城市

首先先看你提供的模型和表单代码:

模型代码

class hotel(models.Model):
    name = models.CharField(max_length=60)
    city = models.ForeignKey('city', on_delete=models.DO_NOTHING)
    state = models.ForeignKey('state', on_delete=models.DO_NOTHING)
    country = models.ForeignKey('country', on_delete=models.DO_NOTHING)
    
    def __str__(self):
        return self.name
    
    class Meta:
        verbose_name_plural = "hotels"

class city(models.Model):
    name = models.CharField(max_length=60)
    state = models.ForeignKey('state', on_delete=models.DO_NOTHING)
    
    def __str__(self):
        return self.name
    
    class Meta:
        verbose_name_plural = "cities"

class state(models.Model):
    name = models.CharField(max_length=60)
    country = models.ForeignKey('country', on_delete=models.DO_NOTHING)
    
    def __str__(self):
        return self.name
    
    class Meta:
        verbose_name_plural = "states"

class country(models.Model):
    name = models.CharField(max_length=60)
    code = models.CharField(max_length=160)
    
    def __str__(self):
        return self.name
    
    class Meta:
        verbose_name_plural = "countries"

初始ModelForm

class Hotel(ModelForm):
    class Meta:
        model = hotel
        fields = '__all__'

要实现你要的效果,咱们需要分两步:后端修改表单初始化逻辑,让state和city初始为空;前端添加AJAX交互,实现选择国家后加载对应州,选择州后加载对应城市。


第一步:修改ModelForm,初始清空State和City选项

修改你的Hotel表单,重写__init__方法,初始时把state和city的查询集设为空,同时处理编辑场景下的选项填充:

class Hotel(ModelForm):
    class Meta:
        model = hotel
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 初始加载时,让state和city下拉框只保留空选项
        self.fields['state'].queryset = state.objects.none()
        self.fields['city'].queryset = city.objects.none()

        # 如果是POST请求(比如表单提交后验证失败),根据提交的country值填充state
        if 'country' in self.data:
            try:
                country_id = int(self.data.get('country'))
                self.fields['state'].queryset = state.objects.filter(country_id=country_id).order_by('name')
            except (ValueError, TypeError):
                pass  # 忽略无效的country ID
        # 如果是编辑已有酒店的场景,根据当前酒店的country填充state
        elif self.instance.pk:
            self.fields['state'].queryset = self.instance.country.state_set.order_by('name')

        # 同理处理city的选项填充逻辑
        if 'state' in self.data:
            try:
                state_id = int(self.data.get('state'))
                self.fields['city'].queryset = city.objects.filter(state_id=state_id).order_by('name')
            except (ValueError, TypeError):
                pass
        elif self.instance.pk:
            self.fields['city'].queryset = self.instance.state.city_set.order_by('name')

这样当你初始化form = Hotel()时,state和city下拉框就只会显示空选项,只有country下拉框会加载所有国家数据。


第二步:添加前端AJAX交互,实现动态加载

接下来需要在模板里添加JavaScript,监听下拉框的变化事件,通过AJAX请求后端获取对应的州/城市数据,更新下拉选项。

1. 模板中的表单和JS代码

假设你的模板是hotel_form.html,可以这样写:

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">提交酒店信息</button>
</form>

<!-- 引入jQuery(如果项目里没引入的话) -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$(document).ready(function() {
    // 监听国家下拉框的变化
    $('#id_country').change(function() {
        const countryId = $(this).val();
        // 发送AJAX请求获取对应国家的州
        $.ajax({
            url: "{% url 'get_states' %}",
            data: { 'country_id': countryId },
            success: function(response) {
                // 清空州下拉框,添加新选项
                $('#id_state').empty();
                $.each(response, function(index, state) {
                    $('#id_state').append(`<option value="${state.id}">${state.name}</option>`);
                });
                // 州变化后,城市下拉框也要清空
                $('#id_city').empty();
            }
        });
    });

    // 监听州下拉框的变化
    $('#id_state').change(function() {
        const stateId = $(this).val();
        // 发送AJAX请求获取对应州的城市
        $.ajax({
            url: "{% url 'get_cities' %}",
            data: { 'state_id': stateId },
            success: function(response) {
                // 清空城市下拉框,添加新选项
                $('#id_city').empty();
                $.each(response, function(index, city) {
                    $('#id_city').append(`<option value="${city.id}">${city.name}</option>`);
                });
            }
        });
    });
});
</script>

2. 后端添加AJAX接口视图

在views.py里添加两个视图函数,处理州和城市的请求:

from django.http import JsonResponse
from .models import state, city

def get_states(request):
    # 获取前端传递的country_id
    country_id = request.GET.get('country_id')
    # 查询对应国家的所有州,按名称排序
    states_list = state.objects.filter(country_id=country_id).order_by('name').values('id', 'name')
    # 返回JSON格式的数据
    return JsonResponse(list(states_list), safe=False)

def get_cities(request):
    # 获取前端传递的state_id
    state_id = request.GET.get('state_id')
    # 查询对应州的所有城市,按名称排序
    cities_list = city.objects.filter(state_id=state_id).order_by('name').values('id', 'name')
    # 返回JSON格式的数据
    return JsonResponse(list(cities_list), safe=False)

3. 配置路由

在urls.py里添加这两个视图的路由:

from django.urls import path
from . import views

urlpatterns = [
    # 其他路由(比如表单页面的路由)
    path('hotel-form/', views.hotel_form_view, name='hotel_form'),
    # AJAX接口路由
    path('get-states/', views.get_states, name='get_states'),
    path('get-cities/', views.get_cities, name='get_cities'),
]

最后说明

这样设置后,用户打开表单时:

  1. 只有国家下拉框有所有选项,州和城市下拉框为空
  2. 选择国家后,州下拉框会自动加载对应国家的所有州
  3. 选择州后,城市下拉框会自动加载对应州的所有城市
  4. 如果是编辑已有酒店的场景,表单会自动加载对应国家、州、城市的选项,保持数据一致性

内容的提问来源于stack exchange,提问作者J J

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:10:30