Django Admin链式下拉修改后字段置空问题求助
Django Admin链式下拉选择字段置空问题解决
问题描述
在Django Admin中为SchoolProfileForm重写change_form,实现省(Province)、区(District)、市(City)、街道(Ward)的链式下拉选择功能。首次设置区及对应市并保存时,数据可正常存入数据库;但返回编辑页面修改已保存的市后,数据库中市和区字段都会被置空,修改街道时也会出现市字段被置空的情况。
现有JavaScript代码
function resetDistrict() { let $ = django.jQuery; let district_list = '<option value="" selected="">---------</option>' $('#select2-id_district-container').html(district_list); console.log(district_list) } function getDistrict(province_id) { resetDistrict() let $ = django.jQuery; $.get('/school/district/' + province_id, function (resp){ let district_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ district_list += '<option value="'+ item.id +'">'+ item.name +'</option>' }); $('#id_district').html(district_list); }); } function getDistrictfromDb(province_id) { let $ = django.jQuery; $.get('/school/district/' + province_id, function (resp){ let district_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ district_list += '<option value="'+ item.id +'">'+ item.name +'</option>' }); $('#id_district').html(district_list); }); } function resetCity() { let $ = django.jQuery; let city_list = '<option value="" selected="">---------</option>' $('#select2-id_city-container').html(city_list); console.log(city_list) } function getCity(district_id) { resetCity(); let $ = django.jQuery; $.get('/school/city/' + district_id, function (resp){ let city_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ city_list += '<option value="'+ item.id +'">'+ item.name +'</option>' }); $('#id_city').html(city_list); }); } function getCityfromDb(district_id) { let $ = django.jQuery; $.get('/school/city/' + district_id, function (resp){ let city_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ city_list += '<option value="'+ item.id +'">'+ item.name +'</option>' }); $('#id_city').html(city_list); }); } function resetWard() { let $ = django.jQuery; let ward_list = '<option value="" selected="">---------</option>' $('#select2-id_ward-container').html(ward_list); console.log(ward_list) } function getWard(city_id) { resetWard() let $ = django.jQuery; $.get('/school/ward/' + city_id, function (resp){ let ward_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ ward_list += '<option value="'+ item.id +'">'+ item.name +'</option>' }); $('#id_ward').html(ward_list); }); } function getWardfromDb(city_id) { let $ = django.jQuery; $.get('/school/ward/' + city_id, function (resp){ let ward_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ ward_list += '<option value="'+ item.id +'">'+ item.name +'</option>' }); $('#id_ward').html(ward_list); }); }
现有forms.py代码
class SchoolProfileForm(forms.ModelForm): class Meta: model = SchoolProfile fields = '__all__' def __init__(self, *args, **kwargs): super(SchoolProfileForm, self).__init__(*args, **kwargs) # 编辑时设置默认值,省作为一级选项默认加载 try: self.initial['province'] = kwargs['instance'].province.id except: pass province_list = [('', '---------')] + [(i.id, i.name) for i in Province.objects.all()] # 区、市、街道为子级选项,点击父级时加载 try: self.initial['district'] = kwargs['instance'].district.id district_init_form = [(i.id, i.name) for i in District.objects.filter( province=kwargs['instance'].province )] except: district_init_form = [('', '---------')] try: self.initial['city'] = kwargs['instance'].city.id city_init_form = [(i.id, i.name) for i in City.objects.filter( district=kwargs['instance'].district )] except: city_init_form = [('', '---------')] try: self.initial['ward'] = kwargs['instance'].ward.id ward_init_form = [(i.id, i.name) for i in Ward.objects.filter( city=kwargs['instance'].city )] except: ward_init_form = [('', '---------')] # 重写表单控件,添加onchange事件调用AJAX函数 self.fields['province'].widget = forms.Select( attrs={ 'id': 'id_province', 'class': 'location-select', 'onchange': 'getDistrict(this.value);', }, choices=province_list, ) self.fields['district'].widget = forms.Select( attrs={ 'id': 'id_district', 'class': 'location-select', 'onchange': 'getCity(this.value);', }, choices=district_init_form ) self.fields['city'].widget = forms.Select( attrs={ 'id': 'id_city', 'class': 'location-select', 'onchange': 'getWard(this.value);', }, choices=city_init_form ) self.fields['ward'].widget = forms.Select( attrs={ 'id': 'id_ward', 'class': 'location-select', }, choices=ward_init_form ) self.fields['locale'].widget = forms.TextInput( attrs={ 'id': 'id_locale', 'width': '200px', }, )
现有views.py代码
def province_list(request): province = Province.objects.all() return JsonResponse({'data':[{'id': p.id, 'name': p.name} for p in province ]}) def district_list(request, province_id): district = District.objects.filter(province_id=province_id) return JsonResponse({'data': [{'id': d.id, 'name': d.name} for d in district ]}) def city_list(request, district_id): city = City.objects.filter(district_id=district_id) return JsonResponse({'data': [{'id': c.id, 'name': c.name} for c in city]}) def ward_list(request, city_id): ward = Ward.objects.filter(city_id=city_id) return JsonResponse({'data': [{'id': w.id, 'name': w.name} for w in ward ]})
问题原因及解决方案
核心问题分析
- Select2控件同步问题:直接操作原生
<select>的HTML内容,但未同步更新Select2组件状态,导致表单提交时Select2实际提交空值。 - 初始化选中状态缺失:编辑页面加载时仅设置
self.initial,未确保下拉选项包含已保存值并选中,Select2无法识别初始选中状态。 - 重置函数逻辑错误:
resetDistrict等函数仅修改Select2显示容器,未同步清空原生<select>选中状态,导致数据不一致。
具体修复步骤
1. 修复JavaScript代码:同步Select2与原生控件
修改所有下拉框操作函数,确保更新原生控件后触发Select2更新,新增编辑页面初始化逻辑:
function resetDistrict() { let $ = django.jQuery; // 清空原生select并添加默认选项 $('#id_district').html('<option value="" selected="">---------</option>'); // 同步更新Select2 $('#id_district').select2('destroy').select2(); } function getDistrict(province_id) { resetDistrict() let $ = django.jQuery; $.get('/school/district/' + province_id, function (resp){ let district_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ district_list += '<option value="'+ item.id +'">'+ item.name +'</option>' }); $('#id_district').html(district_list); // 同步Select2 $('#id_district').select2('destroy').select2(); }); } // 编辑页面初始化区选项并设置选中值 function initDistrict(province_id, selectedDistrictId) { let $ = django.jQuery; $.get('/school/district/' + province_id, function (resp){ let district_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ // 为已保存选项添加selected属性 let selected = item.id == selectedDistrictId ? 'selected' : ''; district_list += `<option value="${item.id}" ${selected}>${item.name}</option>`; }); $('#id_district').html(district_list); $('#id_district').select2('destroy').select2(); // 加载对应市选项 if(selectedDistrictId) { initCity(selectedDistrictId, $('#id_city').data('selected')); } }); } function resetCity() { let $ = django.jQuery; $('#id_city').html('<option value="" selected="">---------</option>'); $('#id_city').select2('destroy').select2(); } function getCity(district_id) { resetCity(); let $ = django.jQuery; $.get('/school/city/' + district_id, function (resp){ let city_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ city_list += '<option value="'+ item.id +'">'+ item.name +'</option>' }); $('#id_city').html(city_list); $('#id_city').select2('destroy').select2(); }); } // 编辑页面初始化市选项并设置选中值 function initCity(district_id, selectedCityId) { let $ = django.jQuery; $.get('/school/city/' + district_id, function (resp){ let city_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ let selected = item.id == selectedCityId ? 'selected' : ''; city_list += `<option value="${item.id}" ${selected}>${item.name}</option>`; }); $('#id_city').html(city_list); $('#id_city').select2('destroy').select2(); // 加载对应街道选项 if(selectedCityId) { initWard(selectedCityId, $('#id_ward').data('selected')); } }); } function resetWard() { let $ = django.jQuery; $('#id_ward').html('<option value="" selected="">---------</option>'); $('#id_ward').select2('destroy').select2(); } function getWard(city_id) { resetWard() let $ = django.jQuery; $.get('/school/ward/' + city_id, function (resp){ let ward_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ ward_list += '<option value="'+ item.id +'">'+ item.name +'</option>' }); $('#id_ward').html(ward_list); $('#id_ward').select2('destroy').select2(); }); } // 编辑页面初始化街道选项并设置选中值 function initWard(city_id, selectedWardId) { let $ = django.jQuery; $.get('/school/ward/' + city_id, function (resp){ let ward_list = '<option value="" selected="">---------</option>' $.each(resp.data, function(i, item){ let selected = item.id == selectedWardId ? 'selected' : ''; ward_list += `<option value="${item.id}" ${selected}>${item.name}</option>`; }); $('#id_ward').html(ward_list); $('#id_ward').select2('destroy').select2(); }); } // 页面加载完成后初始化编辑状态 django.jQuery(document).ready(function() { let $ = django.jQuery; let provinceId = $('#id_province').val(); let selectedDistrictId = $('#id_district').data('selected'); let selectedCityId = $('#id_city').data('selected'); let selectedWardId = $('#id_ward').data('selected'); if(provinceId && selectedDistrictId) { initDistrict(provinceId, selectedDistrictId); } else if(provinceId) { getDistrict(provinceId); } });
2. 修改forms.py:传递选中值并确保选项完整性
为子级下拉框添加data-selected属性传递已保存ID,确保初始选项列表包含已保存值:
class SchoolProfileForm(forms.ModelForm): class Meta: model = SchoolProfile fields = '__all__' def __init__(self, *args, **kwargs): super(SchoolProfileForm, self).__init__(*args, **kwargs) instance = kwargs.get('instance') # 省选项初始化 province_list = [('', '---------')] + [(i.id, i.name) for i in Province.objects.all()] self.fields['province'].widget = forms.Select( attrs={ 'id': 'id_province', 'class': 'location-select', 'onchange': 'getDistrict(this.value);', }, choices=province_list, ) # 区选项初始化 district_init_form = [('', '---------')] selected_district_id = None if instance and instance.district: selected_district_id = instance.district.id # 先添加已选中的区,再补充同省其他区 district_init_form.append((selected_district_id, instance.district.name)) district_init_form.extend([ (d.id, d.name) for d in District.objects.filter(province=instance.province) if d.id != selected_district_id ]) else: district_init_form.extend([(d.id, d.name) for d in District.objects.all()]) self.fields['district'].widget = forms.Select( attrs={ 'id': 'id_district', 'class': 'location-select', 'onchange': 'getCity(this.value);', 'data-selected': selected_district_id or '', }, choices=district_init_form ) # 市选项初始化 city_init_form = [('', '---------')] selected_city_id = None if instance and instance.city: selected_city_id = instance.city.id city_init_form.append((selected_city_id, instance.city.name)) city_init_form.extend([ (c.id, c.name) for c in City.objects.filter(district=instance.district) if c.id != selected_city_id ]) else: city_init_form.extend([(c.id, c.name) for c in City.objects.all()]) self.fields['city'].widget = forms.Select( attrs={ 'id': 'id_city', 'class': 'location-select', 'onchange': 'getWard(this.value);', 'data-selected': selected_city_id or '', }, choices=city_init_form ) # 街道选项初始化 ward_init_form = [('', '---------')] selected_ward_id = None if instance and instance.ward: selected_ward_id = instance.ward.id ward_init_form.append((selected_ward_id, instance.ward.name)) ward_init_form.extend([ (w.id, w.name) for w in Ward.objects.filter(city=instance.city) if w.id != selected_ward_id ]) else: ward_init_form.extend([(w.id, w.name) for w in Ward.objects.all()]) self.fields['ward'].widget = forms.Select( attrs={ 'id': 'id_ward', 'class': 'location-select', 'data-selected': selected_ward_id or '', }, choices=ward_init_form ) self.fields['locale'].widget = forms.TextInput( attrs={
相关产品推荐
相关产品推荐

