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

如何用外键过滤Django场地预订记录?遇RelatedObjectDoesNotExist报错

足球场地预订系统时间冲突校验问题解决方案

问题描述

我开发了一款足球场地预订系统,用户登录后选择目标场地、预订日期及时长完成下单,核心需求是避免同一场地在同一日期的同一时段被重复预订。尝试在OpeningHours模型的clean方法中添加外键pitche过滤条件来排查冲突时,触发报错:RelatedObjectDoesNotExist: OpeningHours has no pitche。移除pitche=self.pitche条件后,过滤逻辑会校验所有场地的时间冲突,不符合业务需求。

# 报错的过滤逻辑
filter = OpeningHours.objects.filter(made_on=self.made_on,pitche=self.pitche,period = self.period).filter(Q(from_hour__range=in_range) | Q(to_hour__range=in_range)).exists()

现有代码

模型代码

from django.db import models
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db.models import Q
import datetime

class Pitche(models.Model):
    Name = models.CharField(max_length=50)
    city = models.CharField(max_length=50)
    photo = models.ImageField(upload_to='photos')
    price = models.DecimalField(max_digits=5,decimal_places=2)
    location = models.CharField(max_length=500)
    name_of_supervisor = models.CharField(max_length=50)
    phone_of_supervisor = models.CharField(max_length=11)
    manager = models.ForeignKey(Manager,on_delete=models.DO_NOTHING)

class OpeningHours(models.Model):
    user = models.ForeignKey(User,on_delete=models.CASCADE)
    pitche = models.ForeignKey(Pitche,on_delete=models.DO_NOTHING)
    made_on = models.DateField()
    period = models.CharField(max_length=50,choices=period)
    from_hour = models.TimeField()
    to_hour = models.TimeField()
    timing = models.CharField(max_length=50,choices=timing)

    def __str__(self):
        return f"{self.pitche}-{self.from_hour} to {self.to_hour}"

    def clean(self):
        if self.from_hour == self.to_hour:
            raise ValidationError("Wrong Time")
        if self.made_on < datetime.date.today():
            raise ValidationError("InVaild Date")
        #####################
        in_range = (self.from_hour, self.to_hour)
        filter = OpeningHours.objects.filter(made_on=self.made_on,period = self.period).filter(Q(from_hour__range=in_range) | Q(to_hour__range=in_range)).exists()
        if filter:
            raise ValidationError(f"The booked time ({self.from_hour} to {self.to_hour}) is occupied.")
        return super().clean()

视图代码

from django.shortcuts import render, get_object_or_404
from django.contrib import messages
from .models import Pitche, OpeningHours
from .forms import RestaurantForm

def pitche_page(request,id):
    if request.method == 'POST' and request.user.is_authenticated :
        pit_id = Pitche.objects.get(id =id )
        add_date = RestaurantForm(request.POST)
        if add_date.is_valid():
            info = add_date.save(commit=False)
            login_user = User.objects.get(username=request.user.username)
            info.user = login_user
            info.pitche = pit_id
            info.save()
            messages.success(request,"Booking Successful")
        else:
            messages.error(request,"this time is booked befor") 

    context ={
        'pit':get_object_or_404(Pitche,id=id),
        'form':RestaurantForm(),
    }
    return render(request,'pitches/pitche.html',context)

问题原因

视图中add_date.is_valid()表单验证执行时,info.pitche = pit_id这一步还未执行——pitche字段是在表单验证通过后才绑定到OpeningHours实例上的。因此clean方法运行时,当前实例的pitche外键还未赋值,导致触发RelatedObjectDoesNotExist错误。

解决方案

1. 提前绑定pitche到表单实例

修改视图代码,在调用is_valid()之前就把pitche和user绑定到表单实例上,确保clean方法执行时能获取到pitche字段的值:

def pitche_page(request,id):
    if request.method == 'POST' and request.user.is_authenticated :
        pit_id = get_object_or_404(Pitche, id=id)
        add_date = RestaurantForm(request.POST)
        # 提前绑定pitche和user到表单实例
        add_date.instance.pitche = pit_id
        add_date.instance.user = request.user
        if add_date.is_valid():
            add_date.save()  # 无需再用commit=False,已完成字段赋值
            messages.success(request,"预订成功")
        else:
            messages.error(request,"该时间段已被预订") 

    context ={
        'pit':get_object_or_404(Pitche,id=id),
        'form':RestaurantForm(),
    }
    return render(request,'pitches/pitche.html',context)

2. 修正clean方法的过滤逻辑

在clean方法中添加pitche=self.pitche条件,同时增加排除当前实例的逻辑(处理更新预订的场景):

def clean(self):
    # 基础校验
    if self.from_hour == self.to_hour:
        raise ValidationError("时间起止不能相同")
    if self.made_on < datetime.date.today():
        raise ValidationError("预订日期不能早于今天")
    
    # 构建时间冲突查询
    in_range = (self.from_hour, self.to_hour)
    conflict_qs = OpeningHours.objects.filter(
        made_on=self.made_on,
        pitche=self.pitche,
        period=self.period
    ).filter(
        Q(from_hour__range=in_range) | Q(to_hour__range=in_range)
    )
    
    # 如果是更新操作,排除当前实例本身
    if self.pk:
        conflict_qs = conflict_qs.exclude(pk=self.pk)
    
    if conflict_qs.exists():
        raise ValidationError(f"时间段 {self.from_hour} 到 {self.to_hour} 已被占用")
    
    return super().clean()

额外优化点

  • 视图中无需通过User.objects.get(username=request.user.username)获取用户,直接使用request.user即可(用户已登录)。
  • 使用get_object_or_404替代Pitche.objects.get(id=id),避免因场地ID不存在触发异常。

内容的提问来源于stack exchange,提问作者Osama meky

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 00:47:06