Django中实现用户自定义Hero组合的Deck复用与统计需求
嘿,我来帮你梳理下这个问题的解决思路,毕竟刚接触Django的时候,确实容易在多对多关联和实例复用这块绕晕😉
核心问题拆解
先明确你的需求核心矛盾:
- 用户需要自主选择4个英雄,同时关联到
Match记录,还要自动生成/复用对应的Deck实例 - 之前直接把
Deck关联到Match的方案,把选择权限制成了“只能用已有Deck”,完全不符合用户自主选新组合的需求,所以得换个思路:让表单接收英雄选择,后端处理Deck的复用/创建逻辑,再关联到Match
模型调整:确保Deck组合的唯一性
首先要解决的是“相同英雄组合复用Deck”的问题,我们可以给Deck加一个唯一标识字段,通过排序后的英雄ID生成字符串,保证同一组英雄不管选择顺序如何,都对应同一个Deck实例。
修改models.py:
from django.db import models class Hero(models.Model): heroname = models.CharField(max_length=20) summary = models.TextField(max_length=2000, null=True) def __str__(self): return self.heroname class Deck(models.Model): heroes = models.ManyToManyField(Hero) # 新增唯一字段:存储排序后的英雄ID字符串,用于判断组合是否重复 hero_combination = models.CharField(max_length=100, unique=True) def save(self, *args, **kwargs): # 保存前自动生成唯一标识:把关联英雄的ID升序排序后用逗号拼接 if self.heroes.exists(): sorted_hero_ids = sorted(self.heroes.values_list('id', flat=True)) self.hero_combination = ','.join(map(str, sorted_hero_ids)) super().save(*args, **kwargs) class Match(models.Model): # Match关联Deck,后续统计使用次数直接基于这个关联查询 deck = models.ForeignKey(Deck, on_delete=models.CASCADE) # 可按需添加其他字段,比如比赛时间、结果等 created_at = models.DateTimeField(auto_now_add=True)
表单调整:让用户直接选英雄
之前的DeckForm是直接操作Deck模型,现在我们需要让用户直接选英雄,所以改用普通Form(而非ModelForm),只负责验证选择数量:
修改forms.py:
from django import forms from .models import Hero class HeroSelectionForm(forms.Form): heroes = forms.ModelMultipleChoiceField( queryset=Hero.objects.all(), widget=forms.CheckboxSelectMultiple(), label="Select 4 Heroes" ) def clean_heroes(self): selected_heroes = self.cleaned_data.get('heroes') if len(selected_heroes) != 4: raise forms.ValidationError("You must select exactly 4 heroes.") return selected_heroes
视图核心逻辑:处理Deck复用/创建+关联Match
这一步是关键,我们要在视图里接收用户选的英雄,判断对应的Deck是否存在:存在就复用,不存在就创建,最后关联到Match。
示例视图代码:
from django.shortcuts import render, redirect from .forms import HeroSelectionForm from .models import Deck, Match def create_match(request): if request.method == 'POST': form = HeroSelectionForm(request.POST) if form.is_valid(): selected_heroes = form.cleaned_data['heroes'] # 生成和Deck模型一致的唯一组合标识 sorted_hero_ids = sorted([hero.id for hero in selected_heroes]) hero_combination = ','.join(map(str, sorted_hero_ids)) # 复用已有Deck或创建新Deck:get_or_create会返回(实例, 是否新创建) deck, is_new_deck = Deck.objects.get_or_create( hero_combination=hero_combination, defaults={} ) # 如果是新创建的Deck,把选中的英雄关联进去 if is_new_deck: deck.heroes.add(*selected_heroes) # 创建Match并关联Deck Match.objects.create(deck=deck) return redirect('match_success') # 替换成你的成功页面URL else: form = HeroSelectionForm() return render(request, 'create_match.html', {'form': form})
额外需求实现:Deck组合对比+使用次数统计
1. Deck组合对比
给Deck模型加个方法,方便直接对比两个Deck的英雄组合是否一致:
class Deck(models.Model): # ... 其他字段和方法 ... def get_hero_id_set(self): return set(self.heroes.values_list('id', flat=True)) def has_same_combination(self, other_deck): # 比较两个Deck的英雄ID集合是否相同 return self.get_hero_id_set() == other_deck.get_hero_id_set()
使用时直接调用deck1.has_same_combination(deck2)就能判断组合是否一致。
2. 特定组合使用次数统计
因为Match关联了Deck,统计非常简单:
# 统计某个Deck的使用次数 def get_deck_usage_count(target_deck): return Match.objects.filter(deck=target_deck).count() # 统计任意英雄组合的使用次数(传入英雄ID列表) def get_combination_usage_count(hero_ids): sorted_ids = sorted(hero_ids) combination_str = ','.join(map(str, sorted_ids)) try: deck = Deck.objects.get(hero_combination=combination_str) return get_deck_usage_count(deck) except Deck.DoesNotExist: return 0
之前思路的问题总结
你之前直接把Deck关联到Match,然后让用户选Deck,本质是把“选英雄组合”变成了“选已有Deck”,完全锁死了用户创建新组合的可能。现在的方案是让用户直接选英雄,后端自动处理Deck的复用/创建,既满足了用户需求,又保留了Deck模型的统计、对比价值。
内容的提问来源于stack exchange,提问作者Amiga85
相关产品推荐
相关产品推荐

