如何在Django应用中限制用户最多借阅3款游戏?
解决Django游戏借阅应用的用户借阅数量限制问题
我开发了一款可展示游戏并支持用户借阅的Django应用,希望限制每位用户最多借阅3款游戏。目前已创建Boardgame(游戏)和Borrow(借阅)两个模型,现有视图方法可正常展示用户已借阅的游戏、处理单款游戏的借阅请求,但借阅数量限制功能尚未实现。尝试使用[:3]仅能限制前端展示,无法阻止用户在后端向数据库提交更多借阅数据,需要可行的解决方案。
现有代码实现
展示已借阅游戏的视图
@login_required def borrows(request): borrows = Borrow.objects.filter(owner = request.user).order_by('date_borrowed') context = {'borrows':borrows} return render(request, 'gameapp/borrows.html', context)
处理借阅请求的视图
@login_required def borrow(request, boardgame_id): boardgame = Boardgame.objects.get(id=boardgame_id) if request.method != 'POST': form = BorrowForm() else: form = BorrowForm(data = request.POST) if form.is_valid(): borrow_boardgame = form.save(commit = False) borrow_boardgame.owner = request.user # 将Borrow模型中的borrow字段关联到对应游戏 borrow_boardgame.borrow = boardgame form.save() return redirect('gameapp:borrows') context = {'boardgame': boardgame, 'form':form} return render(request, 'gameapp/borrow.html', context)
Boardgame模型
class Boardgame(models.Model): name = models.CharField(max_length=50) date_added = models.DateTimeField(auto_now_add=True) date_modified = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): return self.name
Borrow模型
class Borrow(models.Model): borrow = models.ForeignKey(Boardgame, on_delete=models.CASCADE) date_borrowed = models.DateTimeField(auto_now_add=True) date_returned = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User, on_delete=models.CASCADE, default=3) def __str__(self): return str(self.borrow)
解决方案
首先需要修正Borrow模型的date_returned字段——原设置auto_now_add=True会导致创建借阅记录时自动填充归还时间,无法区分已归还和未归还状态,这是实现数量限制的基础:
1. 修正Borrow模型
class Borrow(models.Model): borrow = models.ForeignKey(Boardgame, on_delete=models.CASCADE) date_borrowed = models.DateTimeField(auto_now_add=True) # 修改为允许为空,未归还时为null date_returned = models.DateTimeField(null=True, blank=True) owner = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): return str(self.borrow)
注意:移除
default=3,用户外键应关联当前登录用户,默认值设置为3不符合逻辑。
2. 视图层校验(阻止前端提交)
在处理借阅请求的视图中,添加数量检查逻辑,当用户未归还的游戏数量达到3时,拒绝提交并给出提示:
@login_required def borrow(request, boardgame_id): boardgame = Boardgame.objects.get(id=boardgame_id) # 查询当前用户未归还的借阅数量 active_borrows_count = Borrow.objects.filter( owner=request.user, date_returned__isnull=True ).count() if request.method != 'POST': form = BorrowForm() else: form = BorrowForm(data=request.POST) if form.is_valid(): # 检查是否已达到借阅上限 if active_borrows_count >= 3: form.add_error(None, "您最多只能借阅3款游戏,请先归还已借阅的游戏再申请新的借阅") else: borrow_boardgame = form.save(commit=False) borrow_boardgame.owner = request.user borrow_boardgame.borrow = boardgame borrow_boardgame.save() return redirect('gameapp:borrows') context = { 'boardgame': boardgame, 'form': form, 'active_borrows_count': active_borrows_count, 'remaining_quota': 3 - active_borrows_count } return render(request, 'gameapp/borrow.html', context)
3. 模型层校验(阻止后端/管理后台提交)
仅靠视图层校验不足以完全阻止后端操作(比如通过Django admin直接添加记录),需要在模型层添加校验逻辑:
方法一:重写模型的clean方法
from django.core.exceptions import ValidationError class Borrow(models.Model): # 字段定义同上... def clean(self): # 检查当前用户未归还的借阅数量,排除自身避免编辑时误判 active_borrows_count = Borrow.objects.filter( owner=self.owner, date_returned__isnull=True ).exclude(id=self.id).count() if active_borrows_count >= 3: raise ValidationError("每位用户最多只能同时借阅3款游戏") super().clean() def save(self, *args, **kwargs): self.full_clean() # 保存前触发clean校验 super().save(*args, **kwargs)
方法二:使用信号量(可选)
如果需要更灵活的校验,也可以使用Django的信号量在保存前检查:
from django.db.models.signals import pre_save from django.dispatch import receiver @receiver(pre_save, sender=Borrow) def check_borrow_quota(sender, instance, **kwargs): active_borrows_count = Borrow.objects.filter( owner=instance.owner, date_returned__isnull=True ).exclude(id=instance.id).count() if active_borrows_count >= 3: raise ValidationError("每位用户最多只能同时借阅3款游戏")
4. 前端提示优化
在借阅页面(borrow.html)中,显示用户当前的借阅状态,提前提示:
<!-- 在表单前添加状态提示 --> <p>您当前已借阅 {{ active_borrows_count }} 款游戏,还可借阅 {{ remaining_quota }} 款</p> <!-- 显示错误提示 --> {% if form.non_field_errors %} <div class="alert alert-danger"> {{ form.non_field_errors }} </div> {% endif %} <!-- 表单内容... -->
内容的提问来源于stack exchange,提问作者Sami Wazni
相关产品推荐
相关产品推荐

