在模型中创建一个自定义函数来比较两个外键最大时间戳

Creating a custom function in model to compare two foreign key max timestamps

在我的 Django 项目中,我试图创建一个自定义模型字段,该字段依赖于比较两个反向外键时间戳的值。

在这个玩具示例中,我经营一些汽车经销店。汽车被出租给人们,但每次都需要 'serviced' 才能再次出租。我正在尝试将 'currently_serviced' 字段添加到我的 Car 模型中。如果一辆车在最近的 'HireEvent'.

之后有关联的 'ServiceEvent',则该车被视为当前正在维修

我一直在关注 documentation on custom model fields 但这并不能帮助我解决反向外键的棘手问题并确定最新的(在当前时间之前,因为我想忽略未来的雇员)时间戳。

用法

然后我就可以在我的视图中利用这个字段 'car.currently_serviced',例如通过一个简单的个人汽车页面,显示汽车的名称 { { car.car_name }} 以及它的服务状态 {{ car.currently_serviced }} 或在更复杂的页面中,例如在经销商页面上输出“经销商维修过的汽车数量”。

当前进度

我一直试图在模型中声明此字段,请参阅我在 models.py 中声明函数 'currently_serviced' 的努力。 我已经尝试编写模型管理器 类 并使用 F() 比较将逻辑写入我的视图,尽管我更喜欢作为一种 DRY 方法在模型本身上声明 'currently_serviced' 属性,因为它会动态更新为以及何时租用和维修汽车。

当前显示的方法导致属性错误:

'RelatedManager' object has no attribute 'service_time'.

谢谢!

models.py

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone


class Dealer(models.Model):
    dealer_name = models.CharField(max_length=200)

    def __str__(self):
        return self.dealer_name

class Car(models.Model):
    car_name = models.CharField(max_length=200)
    dealership = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="cars")

    # This section should return a Boolean field accessible from the Car model.
    def currently_serviced(self):
        # This was another failed approach.
        # Car.objects.filter(serviceevent__service_time__lte=timezone.now(), serviceevent__service_time__lte= F('hireevent__hire_time')).count()
        if self.service_events.service_time.latest() > self.hire_events.hire_end_time.latest() and self.hire_events.hire_end_time < timezone.now():
            return True
        else:
            return False

    def __str__(self):
        return self.car_name

class HireEvent(models.Model):
    hire_id = models.AutoField(primary_key=True)
    car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name="hire_events")
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    hire_end_time = models.DateTimeField(null=True,blank=True)

    def __str__(self):
        return self.hire_id

class ServiceEvent(models.Model):
    event_id = models.AutoField(primary_key=True)
    car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name="service_events")
    service_time = models.DateTimeField(null=True,blank=True)

    def __str__(self):
        return self.event_id

views.py

from django.shortcuts import get_object_or_404, render
from .models import Dealer, Car, HireEvent, ServiceEvent
from django.db.models import Count, When, Sum, Case, IntegerField, BooleanField, F, Max, Window
import json
from django.http import JsonResponse
from django.utils import timezone
from datetime import timedelta

def car(request, car_name):
    car_name = car_name.replace('-', ' ')
    car = Car.get(car_name__iexact=car_name)
    # Commented out as this approach does not work and ideally I'd like to not have to work out the currently
    # serviced status every time in the views, but rather pull it from the model dynamically.
    # dealership = Dealer.objects.annotate(
    #   currently_serviced = Sum(Case(
    #   When(
    #       cars__hire_events__hire_end_time__lte=timezone.now(),
    #       cars__service_events__service_time__gte = Max(F('hire_events__end_date')),
    #       then=1),
    #   output_field=IntegerField(),))
    #   ).get(cars__car_name__iexact=car_name)

    all_hire_events = HireEvent.objects.filter(car = car)
    last_hire_event = all_hire_events.filter(hire_end_time__lte=timezone.now()).order_by('hire_end_time').first()
    
    service_events = ServiceEvent.objects.filter(car = car, service_time__lte=timezone.now())
    last_service = service_events.filter(car = car, service_time__lte=timezone.now()).order_by('service_time').first()
    if last_service is not None:
        if last_hire_event.hire_end_time < last_service.service_time:
            car.currently_serviced = True
        else:
            car.currently_serviced = False
    else:
        car.currently_serviced = False

    return render(request, 'dealership_app/car.html', {'car': car,  "all_hire_events":all_hire_events, "service_events":service_events})

我解决了 :) 多亏了另一个问题中的 。放入我的代码,希望它能帮助其他人。

我的代码

class Car(models.Model):
car_name = models.CharField(max_length=200)
dealership = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="cars")

@property
def currently_serviced(self):
    last_service = ServiceEvent.objects.filter(car__car_name=self.car_name, service_time__lte=timezone.now()).latest('service_time')
    last_hire = HireEvent.objects.filter(car__car_name=self.desk_name, hire_end_date__lte=timezone.now()).latest('hire_end_time')
    if last_service.service_time < timezone.now() and last_service.service_time > last_hire.hire_end_time:
        return True
    else:
        return False

def __str__(self):
    return self.car_name

将这个属性添加到我的汽车模型class中,首先识别ServiceEvent的单个实例,通过[=20=过滤器为同一辆车获取最新的服务时间]self 对象。最后一次雇用时间也是如此。

然后我比较 if 语句中时间戳的差异,return 根据比较的输出是对还是错。