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

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 ]})

问题原因及解决方案

核心问题分析

  1. Select2控件同步问题:直接操作原生<select>的HTML内容,但未同步更新Select2组件状态,导致表单提交时Select2实际提交空值。
  2. 初始化选中状态缺失:编辑页面加载时仅设置self.initial,未确保下拉选项包含已保存值并选中,Select2无法识别初始选中状态。
  3. 重置函数逻辑错误: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={
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 12:57:05