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

Django Admin添加页实现按Plan选择过滤Test下拉选项(多外键模型)

在Django Admin中实现Plan选择过滤Test下拉列表

嘿,我来给你分享个超实用的简便方案,不用额外依赖第三方库,用原生Django+JavaScript就能搞定这个需求!

步骤1:自定义Result的Admin表单和配置

首先在你的admin.py里,给Result模型创建自定义表单,初始化时根据已选的Plan过滤Test选项,同时指定加载自定义JS:

from django.contrib import admin
from .models import TestPlan, Test, Result
from django import forms

class ResultAdminForm(forms.ModelForm):
    class Meta:
        model = Result
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 编辑已有Result时,根据关联的Plan过滤Test选项
        if self.instance.pk and self.instance.plan:
            self.fields['test'].queryset = Test.objects.filter(test_plan=self.instance.plan)
        else:
            # 新建时默认清空Test选项,等选完Plan再加载
            self.fields['test'].queryset = Test.objects.none()

class ResultAdmin(admin.ModelAdmin):
    form = ResultAdminForm
    list_display = ('plan', 'test', 'status', 'version')  # 可根据需求调整显示字段

    # 引入自定义JS文件
    class Media:
        js = ('admin/js/filter_test_by_plan.js',)

# 注册所有模型
admin.site.register(TestPlan)
admin.site.register(Test)
admin.site.register(Result, ResultAdmin)

步骤2:创建动态过滤的JavaScript文件

在你的项目static/admin/js/目录下(如果没有就新建),创建filter_test_by_plan.js,用来监听Plan选择的变化,动态请求对应Test列表:

document.addEventListener('DOMContentLoaded', function() {
    // 获取Admin表单里的Plan和Test下拉框(Django默认生成的id是id_字段名)
    const planSelect = document.getElementById('id_plan');
    const testSelect = document.getElementById('id_test');

    // 监听Plan选择变化
    planSelect.addEventListener('change', function() {
        const planId = this.value;
        
        // 如果没选Plan,清空Test选项
        if (!planId) {
            testSelect.innerHTML = '<option value="">---------</option>';
            return;
        }

        // 请求后端获取对应Plan的Test列表
        fetch(`/admin/get_tests_by_plan/?plan_id=${planId}`)
            .then(response => response.json())
            .then(tests => {
                // 清空现有Test选项,添加默认空选项
                testSelect.innerHTML = '<option value="">---------</option>';
                // 遍历返回的Test数据,添加到下拉框
                tests.forEach(test => {
                    const option = document.createElement('option');
                    option.value = test.id;
                    option.textContent = test.name;
                    testSelect.appendChild(option);
                });
            })
            .catch(err => console.error('加载Test列表失败:', err));
    });
});

步骤3:添加后端接口返回Test数据

在你的views.py里,写一个视图函数,用来根据Plan ID返回对应的Test列表,同时限制只有管理员能访问:

from django.http import JsonResponse
from django.contrib.admin.views.decorators import staff_member_required
from .models import Test, TestPlan

@staff_member_required
def get_tests_by_plan(request):
    plan_id = request.GET.get('plan_id')
    if not plan_id:
        return JsonResponse([], safe=False)
    
    try:
        # 获取指定Plan的所有Test,返回id和名称
        plan = TestPlan.objects.get(id=plan_id)
        tests = Test.objects.filter(test_plan=plan).values('id', 'test_name')
        # 整理成JS需要的格式
        test_list = [{'id': t['id'], 'name': t['test_name']} for t in tests]
        return JsonResponse(test_list, safe=False)
    except TestPlan.DoesNotExist:
        return JsonResponse([], safe=False)

然后在urls.py里添加这个接口的路由:

from django.contrib import admin
from django.urls import path
from .views import get_tests_by_plan

urlpatterns = [
    path('admin/', admin.site.urls),
    # 添加Test列表接口
    path('admin/get_tests_by_plan/', get_tests_by_plan, name='get_tests_by_plan'),
]

最后检查配置

  1. 确保你的settings.py里正确配置了静态文件路径:
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'  # 可选,部署时用
STATICFILES_DIRS = [BASE_DIR / 'static']  # 如果你的静态文件放在项目根目录的static下
  1. 运行python manage.py collectstatic(生产环境需要,开发环境如果DEBUG=True的话会自动加载)

这样就能实现你要的效果了:新建或编辑Result时,选完Plan之后,Test下拉框会自动只显示属于该Plan的Test选项~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:37:10