如何在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
相关产品推荐
相关产品推荐

