如何在Django forms中实现级联下拉菜单?
嘿,这个需求我之前在项目里实操过,用Django Forms配合前端AJAX完全能搞定,给你拆解下完整的实现步骤,分后端和前端两部分来捋:
后端实现步骤
1. 先搞定基础数据模型
首先得确保你的数据模型是关联好的,这是级联功能的基础:
# models.py from django.db import models class Book(models.Model): title = models.CharField(max_length=100) def __str__(self): return self.title class Chapter(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE) name = models.CharField(max_length=100) def __str__(self): return self.name
2. 定义表单类
创建一个Form,其中书籍下拉框正常渲染,章节下拉框初始留空,后续通过AJAX动态填充:
# forms.py from django import forms from .models import Book, Chapter class BookChapterForm(forms.Form): book = forms.ModelChoiceField(queryset=Book.objects.all(), empty_label="请选择书籍") # 初始章节下拉框无数据,等待AJAX填充 chapter = forms.ModelChoiceField(queryset=Chapter.objects.none(), empty_label="请先选择书籍")
3. 编写视图函数
需要两个核心视图:一个渲染表单页面,另一个作为AJAX接口返回对应章节数据:
# views.py from django.shortcuts import render from django.http import JsonResponse from .models import Chapter from .forms import BookChapterForm def book_chapter_form(request): form = BookChapterForm() return render(request, 'book_chapter_template.html', {'form': form}) def get_chapters(request): # 从GET参数中获取选中的书籍ID book_id = request.GET.get('book_id') if book_id: # 查询该书籍对应的所有章节,返回id和名称 chapters = Chapter.objects.filter(book_id=book_id).values('id', 'name') chapter_list = [{'id': item['id'], 'name': item['name']} for item in chapters] return JsonResponse({'chapters': chapter_list}) # 未传入书籍ID时返回空列表 return JsonResponse({'chapters': []})
4. 配置URL路由
把视图映射到具体URL,方便前端调用:
# urls.py from django.urls import path from . import views urlpatterns = [ path('book-chapter/', views.book_chapter_form, name='book_chapter_form'), path('get-chapters/', views.get_chapters, name='get_chapters'), ]
前端实现步骤
在模板里渲染表单,并用jQuery监听下拉框变化,发送AJAX请求填充章节:
<!-- book_chapter_template.html --> <!DOCTYPE html> <html> <head> <title>书籍章节选择</title> <!-- 引入jQuery,项目已有则无需重复引入 --> <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script> </head> <body> <form method="post"> {% csrf_token %} {{ form.book.label_tag }} {{ form.book }} <br><br> {{ form.chapter.label_tag }} {{ form.chapter }} <br><br> <button type="submit">提交</button> </form> <script> $(document).ready(function() { const $bookSelect = $('#id_book'); const $chapterSelect = $('#id_chapter'); // 页面加载时默认禁用章节下拉框 $chapterSelect.prop('disabled', true); // 监听书籍下拉框的变化事件 $bookSelect.on('change', function() { const selectedBookId = $(this).val(); if (selectedBookId) { // 发送AJAX请求获取对应章节 $.ajax({ url: "{% url 'get_chapters' %}", data: { 'book_id': selectedBookId }, dataType: 'json', success: function(res) { // 清空现有选项 $chapterSelect.empty(); // 添加默认提示 $chapterSelect.append('<option value="">请选择章节</option>'); // 循环添加章节选项 $.each(res.chapters, function(idx, chapter) { $chapterSelect.append(`<option value="${chapter.id}">${chapter.name}</option>`); }); // 启用章节下拉框 $chapterSelect.prop('disabled', false); } }); } else { // 未选中书籍时重置章节下拉框 $chapterSelect.empty(); $chapterSelect.append('<option value="">请先选择书籍</option>'); $chapterSelect.prop('disabled', true); } }); }); </script> </body> </html>
额外注意事项
- 表单安全验证:提交表单时,后端要额外校验选中的章节确实属于选中的书籍,防止前端篡改数据:
# 在book_chapter_form视图的POST处理中添加 if request.method == 'POST': form = BookChapterForm(request.POST) if form.is_valid(): selected_book = form.cleaned_data['book'] selected_chapter = form.cleaned_data['chapter'] if selected_chapter.book != selected_book: form.add_error('chapter', '该章节不属于选中的书籍') # 后续业务逻辑处理 - 用户体验优化:可以在AJAX请求时添加loading状态提示,避免用户重复操作;如果有默认选中的书籍,页面加载时自动触发一次change事件填充章节。
内容的提问来源于stack exchange,提问作者Viraj
相关产品推荐
相关产品推荐

