Django中如何从关联外键的三张表获取特定用户的Boost等级数据
模型结构优化方案
先把冗余和不合理的字段调整,让后续查询更高效:
from django.db import models from django.contrib.auth.models import User class Boost(models.Model): title = models.CharField(max_length=50, null=False) # 比如point、storage这类增益名称 expire = models.BooleanField(default=False) expire_date = models.DateTimeField(null=True) # 改成下划线命名符合Django规范 visibility = models.BooleanField(default=True) # 修正原拼写错误(visiblity→visibility) def __str__(self): return self.title # 直接返回标题更直观 class BoostLevel(models.Model): boost = models.ForeignKey(Boost, on_delete=models.CASCADE, related_name="levels") level = models.IntegerField(null=False) class Meta: unique_together = ("boost", "level") # 确保同一个Boost下等级不重复 ordering = ["level"] # 按等级排序,方便后续取上下等级 def __str__(self): return f"{self.boost.title} level: {self.level}" class UserBoost(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="boosts") boost_level = models.ForeignKey(BoostLevel, on_delete=models.CASCADE) class Meta: unique_together = ("user", "boost_level__boost") # 确保用户对同一个Boost只存当前最高等级 def __str__(self): return f"{self.user.username} - {self.boost_level}"
调整点说明:
- 删除
UserBoostList里冗余的boostId:boost_level已关联BoostLevel,可间接关联Boost,无需重复存储 - 将
userId改为ForeignKey关联User:利用Django ORM的关联特性,避免手动维护ID一致性 - 修正字段命名与拼写:统一为下划线命名风格,修正
visibility拼写错误 - 给
BoostLevel加约束与排序:unique_together防止重复等级,ordering方便按等级顺序查询 - 重命名
UserBoostList为UserBoost并加约束:贴合语义,同时确保用户对每个Boost仅存储当前最高等级(用户依次升级,无需存历史等级,减少冗余)
最优查询方法
方法1:批量查询(单次DB请求,性能最优)
利用Django ORM的聚合、子查询特性,一次性获取单个用户所有Boost的状态:
from django.db.models import Max, Subquery, OuterRef, Case, When, Value, IntegerField # 子查询:获取每个Boost的最高等级 max_boost_levels = BoostLevel.objects.filter(boost=OuterRef("pk")).values("boost").annotate(max_level=Max("level")).values("max_level") # 查询所有Boost并标注用户的状态 user_boost_status = Boost.objects.annotate( # 获取用户当前拥有的该Boost最高等级,无则为0 user_current_level=Case( When(boosts__user=request.user, then=Max("boosts__boost_level__level")), default=Value(0), output_field=IntegerField() ), # 获取该Boost的总最高等级 boost_max_level=Subquery(max_boost_levels, output_field=IntegerField()), # 根据需求计算最终返回的等级 current_display_level=Case( When(user_current_level=0, then=Value(1)), When(user_current_level__lt=OuterRef("boost_max_level"), then=user_current_level + 1), default="user_current_level", output_field=IntegerField() ) ).values("title", "user_current_level", "boost_max_level", "current_display_level")
返回结果包含每个Boost的标题、用户当前拥有等级、Boost总最高等级、最终应显示的等级。
方法2:单个Boost查询(简洁直观)
在Boost模型中封装方法,用于查询单个用户对该Boost的状态:
class Boost(models.Model): # ... 其他字段 ... def get_user_display_level(self, user): # 获取用户该Boost的最高等级,无则为0 user_max_level = UserBoost.objects.filter( user=user, boost_level__boost=self ).aggregate(max_level=Max("boost_level__level"))["max_level"] or 0 # 获取该Boost的总最高等级,无则默认1 boost_max_level = self.levels.aggregate(max_level=Max("level"))["max_level"] or 1 if user_max_level == 0: return 1 elif user_max_level < boost_max_level: return user_max_level + 1 else: return user_max_level
使用示例:
for boost in Boost.objects.all(): print(f"{boost.title}: 当前显示等级 {boost.get_user_display_level(request.user)}")
注意:方法2循环调用会产生N+1查询,批量查询建议用方法1。
内容的提问来源于stack exchange,提问作者farshid mousavi
相关产品推荐
相关产品推荐

