You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 13:35:19