Django:根据已选Game模型过滤League模型的可选选项
问题解决:Django中实现Game与League的联动过滤
问题背景
现有以下Django模型:
class Game(models.Model): name = models.CharField('Nom du jeu', max_length=100) logo = models.ImageField('Logo', blank=True, upload_to='game_logos') class League(models.Model): game = models.ForeignKey(Game, verbose_name='Jeu', null=True, on_delete=models.SET_NULL) league = models.CharField('Ligue', max_length=20) hexcolor = models.CharField('Code couleur Hex', max_length=7, blank=True, default='#000000') class Event(models.Model): game = models.ForeignKey(Game, verbose_name='Nom du jeu', null=True, on_delete=models.SET_NULL) event_type = models.CharField(verbose_name="Type d'événement", max_length=50, choices=type_course) league = models.ForeignKey(League, verbose_name='Ligue', null=True, on_delete=models.SET_NULL) circuit = models.ForeignKey(Circuit, verbose_name='Circuit', null=True, on_delete=models.SET_NULL) event_date = models.DateTimeField(verbose_name="Date de la course")
需求:创建Event时,选择Game后,League下拉框仅显示该Game关联的联赛(不同Game的League相互独立)。
实现方案
方案1:Django Admin中实现联动
1. 自定义Admin表单
编写自定义表单,初始化时根据已选Game过滤League选项:
from django import forms from .models import Event, League class EventAdminForm(forms.ModelForm): class Meta: model = Event fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 编辑已有Event时,自动过滤对应Game的League if self.instance.game: self.fields['league'].queryset = League.objects.filter(game=self.instance.game) else: self.fields['league'].queryset = League.objects.none()
2. 注册Admin并引入JS
在Admin中使用该表单,同时添加自定义JS实现动态联动:
from django.contrib import admin from .models import Event from .forms import EventAdminForm class EventAdmin(admin.ModelAdmin): form = EventAdminForm # 引入静态JS文件 class Media: js = ('js/event_admin.js',) admin.site.register(Event, EventAdmin)
3. 编写前端联动JS
在static/js/event_admin.js中添加监听逻辑:
document.addEventListener('DOMContentLoaded', () => { const gameSelect = document.getElementById('id_game'); const leagueSelect = document.getElementById('id_league'); // 监听Game选择变化 gameSelect.addEventListener('change', () => { const gameId = gameSelect.value; // 清空League选项 leagueSelect.innerHTML = '<option value="">---------</option>'; if (gameId) { // 请求对应Game的League列表 fetch(`/api/leagues/?game_id=${gameId}`) .then(res => res.json()) .then(data => { data.forEach(league => { const option = document.createElement('option'); option.value = league.id; option.textContent = league.league; leagueSelect.appendChild(option); }); }); } }); });
4. 编写API接口提供League数据
在views.py中添加接口:
from django.http import JsonResponse from .models import League def league_list(request): game_id = request.GET.get('game_id') leagues = League.objects.filter(game_id=game_id).values('id', 'league') return JsonResponse(list(leagues), safe=False)
在urls.py中配置路由:
from django.urls import path from .views import league_list urlpatterns = [ # 其他路由... path('api/leagues/', league_list, name='league-list'), ]
方案2:自定义前端表单实现
如果是自主开发的前端页面,逻辑类似:
1. 自定义表单
from django import forms from .models import Event, League class EventForm(forms.ModelForm): class Meta: model = Event fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['league'].queryset = League.objects.none() # 提交表单时根据选中的Game过滤League if 'game' in self.data: try: game_id = int(self.data.get('game')) self.fields['league'].queryset = League.objects.filter(game_id=game_id) except (ValueError, TypeError): pass # 编辑已有Event时自动过滤 elif self.instance.pk and self.instance.game: self.fields['league'].queryset = League.objects.filter(game=self.instance.game)
2. 视图与模板
视图函数:
from django.shortcuts import render, redirect from .forms import EventForm def create_event(request): form = EventForm(request.POST or None) if request.method == 'POST' and form.is_valid(): form.save() return redirect('event-list') # 替换为你的列表页路由 return render(request, 'create_event.html', {'form': form})
模板create_event.html:
<form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">保存赛事</button> </form> <script> document.addEventListener('DOMContentLoaded', () => { const gameSelect = document.getElementById('id_game'); const leagueSelect = document.getElementById('id_league'); gameSelect.addEventListener('change', () => { const gameId = gameSelect.value; leagueSelect.innerHTML = '<option value="">---------</option>'; if (gameId) { fetch(`/api/leagues/?game_id=${gameId}`) .then(res => res.json()) .then(data => { data.forEach(league => { const option = document.createElement('option'); option.value = league.id; option.textContent = league.league; leagueSelect.appendChild(option); }); }); } }); // 页面加载时若已有选中Game,自动触发一次过滤 if (gameSelect.value) { gameSelect.dispatchEvent(new Event('change')); } }); </script>
同样需要配置之前的API接口和路由。
优化建议
给League模型添加__str__方法,让下拉选项显示更直观:
class League(models.Model): # 现有字段... def __str__(self): return self.league
内容的提问来源于stack exchange,提问作者Madal
相关产品推荐
相关产品推荐

