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

如何在Django模型表单中实现关联国家的城市选择?

实现步骤提示

1. 修正模型结构(核心前提)

你的City模型目前没有与Country关联,这是无法实现“城市绑定对应国家”的根本问题,先修改模型:

class City(models.Model):
    name_city = models.CharField(max_length=150, db_index=True)
    # 新增外键关联国家,related_name方便通过国家查询下属城市
    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name="cities")  

    def __str__(self):
        return self.name_city

同时建议优化Citizen_2的字段命名,避免与关联模型字段混淆:

class Citizen_2(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    city = models.ForeignKey(City, on_delete=models.CASCADE)
    birthday = models.DateField()
    profession = models.CharField(max_length=150)
    hobby = models.CharField(max_length=150)

修改后执行python manage.py makemigrations和python manage.py migrate更新数据库。

2. 实现国家、城市的添加表单

国家添加表单

直接用ModelForm快速实现:

# forms.py
from django import forms
from .models import Country, City

class CountryForm(forms.ModelForm):
    class Meta:
        model = Country
        fields = ['name_country']

视图中处理表单提交逻辑,直接保存即可。

城市添加表单

让用户先选择所属国家,再填写城市名称,确保城市与国家绑定:

class CityForm(forms.ModelForm):
    class Meta:
        model = City
        fields = ['country', 'name_city']
        labels = {
            'country': '所属国家',
            'name_city': '城市名称'
        }

表单提交时,会自动将城市与选中的国家关联保存,无需额外逻辑。

3. 实现Citizen_2表单的动态关联(选国家后只显示对应城市)

后端表单定义

先编写基础的ModelForm:

class CitizenForm(forms.ModelForm):
    class Meta:
        model = Citizen_2
        fields = ['country', 'city', 'birthday', 'profession', 'hobby']

新增AJAX接口获取对应城市

添加视图返回指定国家的城市数据:

# views.py
from django.http import JsonResponse

def get_cities_by_country(request):
    country_id = request.GET.get('country_id')
    cities = City.objects.filter(country_id=country_id).values('id', 'name_city')
    return JsonResponse(list(cities), safe=False)

在urls.py配置路由:

path('get-cities/', views.get_cities_by_country, name='get_cities'),

前端JS实现动态更新下拉框

在Citizen表单模板中添加JS,监听国家选择事件,更新城市选项:

<script>
document.getElementById('id_country').addEventListener('change', function() {
    const countryId = this.value;
    const citySelect = document.getElementById('id_city');
    
    // 清空现有城市选项
    citySelect.innerHTML = '<option value="">请选择城市</option>';
    
    if (countryId) {
        fetch(`/get-cities/?country_id=${countryId}`)
            .then(response => response.json())
            .then(cities => {
                cities.forEach(city => {
                    const option = document.createElement('option');
                    option.value = city.id;
                    option.textContent = city.name_city;
                    citySelect.appendChild(option);
                });
            });
    }
});
</script>

后端表单验证(防止前端篡改)

重写表单的clean方法,确保提交的城市确实属于选中的国家:

class CitizenForm(forms.ModelForm):
    class Meta:
        model = Citizen_2
        fields = ['country', 'city', 'birthday', 'profession', 'hobby']
    
    def clean(self):
        cleaned_data = super().clean()
        country = cleaned_data.get('country')
        city = cleaned_data.get('city')
        
        if country and city and city.country != country:
            raise forms.ValidationError("所选城市不属于该国家,请重新选择")
        
        return cleaned_data

4. 额外优化建议

  • 给Country和City的名称字段添加unique约束,避免重复数据:
    name_country = models.CharField(max_length=150, db_index=True, unique=True)
    name_city = models.CharField(max_length=150, db_index=True, unique=True)
    
  • 模板中添加表单样式,提升用户体验;若使用Admin后台,可自定义Admin类适配动态关联逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 06:56:54