From 12bedaf6a81c570d076e3861a475093299bf6d61 Mon Sep 17 00:00:00 2001 From: Raphael Bitton Date: Mon, 29 Sep 2025 23:37:58 -0500 Subject: [PATCH] Added checks to Flight creation --- simulator/admin.py | 12 ++++- .../0002_equipment_base_location.py | 19 +++++++ simulator/models/aircraft.py | 34 ++++++++++++ simulator/models/flight.py | 52 ++++++++++++++++--- 4 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 simulator/migrations/0002_equipment_base_location.py diff --git a/simulator/admin.py b/simulator/admin.py index bc91361..a0a88d1 100644 --- a/simulator/admin.py +++ b/simulator/admin.py @@ -16,10 +16,18 @@ class FlightAdmin(admin.ModelAdmin): # type: ignore[type-arg] "origin", "destination", "departure_time", - "arrival_time", - "status", + "arrival_time_display", + "status_display", ) @admin.display(description="Flight") def flight_number_display(self, obj): return f"{obj.carrier.icao}{obj.flight_number}" + + @admin.display(description="Arrival", ordering="departure_time") + def arrival_time_display(self, obj): + return obj.arrival_time + + @admin.display(description="Status") + def status_display(self, obj): + return obj.status diff --git a/simulator/migrations/0002_equipment_base_location.py b/simulator/migrations/0002_equipment_base_location.py new file mode 100644 index 0000000..4aa205d --- /dev/null +++ b/simulator/migrations/0002_equipment_base_location.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.6 on 2025-09-30 04:19 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('simulator', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='equipment', + name='base_location', + field=models.ForeignKey(blank=True, help_text='Home base for this equipment', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='based_equipment', to='simulator.aerodrome'), + ), + ] diff --git a/simulator/models/aircraft.py b/simulator/models/aircraft.py index 158fac4..9a89b70 100644 --- a/simulator/models/aircraft.py +++ b/simulator/models/aircraft.py @@ -18,6 +18,40 @@ class Equipment(models.Model): "AircraftBase", on_delete=models.CASCADE, related_name="aircraft" ) owner = models.ForeignKey("Carrier", on_delete=models.CASCADE, related_name="fleet") + base_location = models.ForeignKey( + "Aerodrome", + on_delete=models.PROTECT, + related_name="based_equipment", + help_text="Home base for this equipment", + null=True, + blank=True, + ) def __str__(self): return f"{self.registration} ({self.model})" + + @property + def current_location(self): + """Get the current location of this equipment based on flight history.""" + from .flight import Flight + from django.utils import timezone + + now = timezone.now() + + # Get the most recent flight relative to now + last_flight = ( + self.flights.filter(departure_time__lte=now) + .order_by("-departure_time") + .first() + ) + + if last_flight: + # Check if plane is currently in flight + if last_flight.departure_time <= now < last_flight.arrival_time: + return "In-Flight" + # Plane has arrived at destination + elif last_flight.arrival_time <= now: + return last_flight.destination + + # No flights yet or no flights have departed, equipment is at base + return self.base_location diff --git a/simulator/models/flight.py b/simulator/models/flight.py index c3e6d5c..46500ff 100644 --- a/simulator/models/flight.py +++ b/simulator/models/flight.py @@ -58,17 +58,53 @@ class Flight(models.Model): return self.departure_time + self.duration def clean(self): - overlapping = Flight.objects.filter( - equipment=self.equipment, - departure_time__lt=self.arrival_time, - arrival_time__gt=self.departure_time, - ).exclude(pk=self.pk) - - if overlapping.exists(): + # Validate flight distance is within aircraft range + if self.distance_nm > self.equipment.model.range_nm: raise ValidationError( - f"{self.equipment} is already assigned to another flight in this timeframe." + f"Flight distance ({self.distance_nm:.0f} nm) exceeds {self.equipment.model} " + f"maximum range ({self.equipment.model.range_nm} nm)." ) + # Validate equipment is at the origin airport + # Get the last flight before this one chronologically + previous_flight = ( + Flight.objects.filter( + equipment=self.equipment, departure_time__lt=self.departure_time + ) + .order_by("-departure_time") + .exclude(pk=self.pk) + .first() + ) + + if previous_flight: + # Equipment must be at the destination of the 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}." + ) + + # Ensure enough time between arrival and next departure (at least arrival happens before departure) + 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: + # No previous flights, equipment must be at base location + 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}." + ) + + # Calculate arrival time for this flight + arrival = self.arrival_time + @property def status(self): now = timezone.now()