在Django Admin表单中通过外键过滤关联模型的纯Django实现
纯Django实现Admin中关联字段过滤(无JavaScript)
首先需要修正你代码中的拼写错误(这些错误会导致功能异常):
- 模型类
subject改为Subject(符合Python类命名规范) - 模型
Subject中的外键字段discipline关联的Discipline改为Disciplina(对应你的模型类名) - 模型
Question中的suject字段改为subject,关联Subject类 - Admin代码中的
Discipline改为Disciplina,Assunto改为Subject,Questao改为Question QuestionAdmin中search_fields里的gabarito改为answer(对应模型字段名)Subject模型的related_name改为subjects(拼写正确)
实现步骤
1. 修改QuestionAdmin,重写字段过滤逻辑
通过重写formfield_for_foreignkey方法,根据当前请求中已选择的Disciplina过滤Subject选项:
from django.contrib import admin from django.http import HttpResponseRedirect from django.urls import reverse from .models import Disciplina, Subject, Question @admin.register(Disciplina) class DisciplinaAdmin(admin.ModelAdmin): list_display = ('name', 'origem') search_fields = ('name',) ordering = ('name',) @admin.register(Subject) class SubjectAdmin(admin.ModelAdmin): list_display = ('name', 'discipline_name') search_fields = ('name',) ordering = ('name',) def discipline_name(self, obj): return obj.discipline.name discipline_name.short_description = 'Disciplina' @admin.register(Question) class QuestionAdmin(admin.ModelAdmin): list_display = ('discipline_name', 'subject_name', 'latex_text', 'answer', 'dificulty', 'image', 'link_image') search_fields = ('latex_text', 'answer') ordering = ('discipline', 'subject') fields = ('discipline', 'subject', 'latex_text', 'math_jax', 'answer', 'dificulty', 'image', 'link_image', 'link_bucket') def discipline_name(self, obj): return obj.discipline.name discipline_name.short_description = 'Disciplina' def subject_name(self, obj): return obj.subject.name subject_name.short_description = 'Assunto' def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'subject': # 从URL参数中获取已选择的discipline ID discipline_id = request.GET.get('discipline') if discipline_id: kwargs['queryset'] = Subject.objects.filter(discipline_id=discipline_id) else: # 未选择discipline时,默认隐藏所有subject选项 kwargs['queryset'] = Subject.objects.none() return super().formfield_for_foreignkey(db_field, request, **kwargs) def changeform_view(self, request, object_id=None, form_url='', extra_context=None): extra_context = extra_context or {} # 传递已选择的discipline到模板 if not object_id and 'discipline' in request.GET: extra_context['selected_discipline'] = Disciplina.objects.get(id=request.GET['discipline']) return super().changeform_view(request, object_id, form_url, extra_context) def response_add(self, request, obj, post_url_continue=None): # 处理「过滤科目」按钮的提交请求,不保存对象仅刷新页面 if '_filter-subjects' in request.POST: discipline_id = request.POST.get('discipline') if discipline_id: return self.redirect_to_add_form(request, {'discipline': discipline_id}) return super().response_add(request, obj, post_url_continue) def redirect_to_add_form(self, request, params): # 构造带过滤参数的添加页面URL url = reverse('admin:your_app_name_question_add') # 替换your_app_name为实际应用名 query_string = '&'.join([f"{k}={v}" for k, v in params.items()]) return HttpResponseRedirect(f"{url}?{query_string}")
2. 自定义Admin添加页面模板,添加过滤触发按钮
在你的应用目录下创建templates/admin/your_app_name/question/change_form.html(替换your_app_name为实际应用名),写入以下内容:
{% extends "admin/change_form.html" %} {% block form_top %} {% if add and not selected_discipline %} <p>请先选择Disciplina,点击下方「过滤科目」按钮加载对应科目选项</p> {% elif add and selected_discipline %} <p>已选择Disciplina: {{ selected_discipline.name }},科目列表已过滤</p> {% endif %} {% endblock %} {% block submit_buttons_bottom %} {% if add %} <div class="submit-row"> <input type="submit" value="过滤科目" name="_filter-subjects" /> </div> {% endif %} {{ block.super }} {% endblock %}
工作流程
- 进入Question添加页面,此时Subject下拉框为空,提示先选择Disciplina
- 选择目标Disciplina,点击「过滤科目」按钮
- 页面刷新,URL携带
discipline参数,Subject下拉框仅显示该Disciplina下的关联数据 - 填写剩余字段后保存即可
注意事项
- 无JavaScript情况下需手动触发过滤,修改Disciplina后需重新点击按钮更新Subject列表
- 确保应用已添加到
INSTALLED_APPS,且模板目录配置正确
内容的提问来源于stack exchange,提问作者ftrajano
相关产品推荐
相关产品推荐

