Files
FlightGrid/simulator/models/flight.py.save

152 lines
5.3 KiB
Plaintext

from django.db import models
from .carrier import Carrier
from .aircraft import Equipment
from .aerodrome import Aerodrome
from django.core.validators import MinValueValidator, MaxValueValidator
from simulator.utils import haversine_nm
from datetime import timedelta
from django.core.exceptions import ValidationError
from django.utils import timezone
from decimal import Decimal
class Flight(models.Model):
carrier = models.ForeignKey(
Carrier, on_delete=models.CASCADE, related_name="flights"
)
equipment = models.ForeignKey(
Equipment, on_delete=models.CASCADE, related_name="flights"
)
origin = models.ForeignKey(
Aerodrome, on_delete=models.CASCADE, related_name="departures"
)
destination = models.ForeignKey(
Aerodrome, on_delete=models.CASCADE, related_name="arrivals"
)
flight_number = models.PositiveIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(9999)]
)
departure_time = models.DateTimeField()
canceled = models.BooleanField(default=False)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["carrier", "flight_number"], name="unique_flight_per_carrier"
)
]
def __str__(self):
return f"{self.carrier.icao}{self.flight_number} {self.origin.icao} > {self.destination.icao}"
@property
def distance_nm(self) -> float:
return haversine_nm(
self.origin.latitude,
self.origin.longitude,
self.destination.latitude,
self.destination.longitude,
)
@property
def duration(self) -> timedelta:
speed = self.equipment.model.cruise_speed_kt
hours = self.distance_nm / speed
return timedelta(hours=hours)
@property
def arrival_time(self):
return self.departure_time + self.duration
def clean(self):
if self.origin == self.destination:
raise ValidationError("Origin and destination airports cannot be the same.")
if self.equipment.owner != self.carrier:
raise ValidationError(
f"{self.equipment} is owned by {self.equipment.owner}, "
f"not {self.carrier}. Cannot assign equipment to this flight."
)
if self.distance_nm > self.equipment.model.range_nm:
raise ValidationError(
f"Flight distance ({self.distance_nm:.0f} nm) exceeds {self.equipment.model} "
f"maximum range ({self.equipment.model.range_nm} nm)."
)
previous_flights = Flight.objects.filter(
equipment=self.equipment,
departure_time__lt=self.departure_time,
canceled=False,
).exclude(pk=self.pk)
previous_flight = None
latest_arrival = None
for flight in previous_flights:
if latest_arrival is None or flight.arrival_time > latest_arrival:
latest_arrival = flight.arrival_time
previous_flight = flight
if previous_flight:
if previous_flight.destination != self.origin:
raise ValidationError(
f"{self.equipment} will be at {previous_flight.destination.icao} "
f"after flight {previous_flight.carrier.icao}{previous_flight.flight_number}. "
f"Cannot depart from {self.origin.icao}."
)
if previous_flight.arrival_time > self.departure_time:
raise ValidationError(
f"{self.equipment} arrives at {self.origin.icao} at {previous_flight.arrival_time}. "
f"Cannot depart before arrival."
)
else:
if (
self.equipment.base_location
and self.equipment.base_location != self.origin
):
raise ValidationError(
f"{self.equipment} is based at {self.equipment.base_location.icao}. "
f"First flight must depart from base location, not {self.origin.icao}."
)
arrival = self.arrival_time
@property
def status(self):
if self.canceled:
return "Canceled"
now = timezone.now()
if now < self.departure_time:
return "Scheduled"
elif self.departure_time <= now < self.arrival_time:
return "In-Flight"
else:
return "Arrived"
def save(self, *args, **kwargs):
"""Override save to update equipment cycles and air time when flight completes."""
is_new = self.pk is None
super().save(*args, **kwargs)
if not is_new and self.status == "Arrived":
if not hasattr(self, "_stats_updated"):
flight_hours = Decimal(str(self.duration.total_seconds() / 3600))
self.equipment.cycles += 1
self.equipment.air_time_hours += flight_hours
self.equipment.save(update_fields=["cycles", "air_time_hours"])
self._stats_updated = True
class Route(models.Model):
"""regulary scheduled flights"""
carrier = models.ForeignKey(
Carrier, on_delete=models.CASCADE, related_name="routes"
)
name = models.CharField()
days_of_week = models.JSONField(
default=list, help_text="[0,1,2,3,4] for weekdays, [5,6] for weekends, etc"
)