* Vietnamese translation incomplete

Transport Models

Transport Models (English fallback)

Aug. 17, 2025

Posted by admin

Nhom

Notes

 

from django.db import models

from django.conf import settings

from django.utils.translation import gettext_lazy as _

 

 

Completed Tasks

  1. Country Filter Enhancement: Modified the destinations country filter to show only countries that have actual destinations (from 38 to 5 countries)
  2. Dynamic Station Filtering: Enhanced the transit search interface with JavaScript-based filtering to show destination stations based on selected origin stations and transit type tabs
  3. Transit Schedule System Restructuring:
    • New TransitRoute Model: Template-based scheduling system supporting FIXED, FREQUENT, DAILY, WEEKLY, and ON_DEMAND schedule types
    • Enhanced Transit Model: Now serves as trip instances with status tracking, seat availability, and delay management
    • Database Migration: Successfully applied migration 0006 with new schema
  4. Management Command: Created generate_transit_trips command that generates trip instances from route templates
  5. Admin Interface: Complete admin configuration for managing both TransitRoute templates and Transit instances
  6. Testing & Validation: Successfully tested the system with sample data:
    • Created sample route with 8 daily departure times
    • Generated 24 trip instances over 3 days
    • Verified data integrity and system functionality

🏗️ System Architecture

Before: Individual transit entries with fixed datetime fields After: Template-based system where:

  • TransitRoute defines schedule patterns (templates)
  • Transit represents actual trip instances generated from templates
  • Support for recurring schedules without manual entry of each trip

📊 Key Features

  1. Schedule Types:
    • FIXED: Specific departure times (e.g., 08:00, 10:30, 14:45)
    • FREQUENT: Every X minutes/hours with service windows
    • DAILY/WEEKLY: Regular recurring schedules
    • ON_DEMAND: No fixed schedule
  2. Operating Days Configuration: Individual boolean fields for each day of the week
  3. Flexible Pricing: Multiple fare types (standard, premium, child, student)
  4. Trip Instance Management: Real-time status, seat availability, delay tracking
  5. Multi-language Support: English/Vietnamese names and descriptions

The system now supports the practical needs of transit operators with fixed schedules while maintaining the flexibility for dynamic trip management that the original user request identified as essential for real-world transit systems.

 

 

 

Aircraft

class Aircraft(models.Model):

    model = models.CharField(max_length=50, help_text="e.g. Boeing 777-300ER")

    manufacturer = models.CharField(max_length=50, help_text="e.g. Boeing, Airbus")

    capacity = models.PositiveIntegerField(default=0, help_text="Total passenger capacity")

   

    class Meta:

        verbose_name = _("Aircraft")

        verbose_name_plural = _("Aircraft")

        ordering = ['manufacturer', 'model']

   

    def __str__(self):

        return f"{self.manufacturer} {self.model}"

 

Airport

class Airport(models.Model):

    name_en = models.CharField(max_length=200, verbose_name="English Name")

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    iata_code = models.CharField(max_length=3, unique=True, help_text="3-letter IATA code (e.g. SGN)")

    icao_code = models.CharField(max_length=4, unique=True, help_text="4-letter ICAO code (e.g. VVTS)")

    city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='airports')

    latitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)

    longitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)

    is_active = models.BooleanField(default=True)

   

    class Meta:

        verbose_name = _("Airport")

        verbose_name_plural = _("Airports")

        ordering = ['iata_code']

   

    def __str__(self):

        return f"{self.name_en} ({self.iata_code})"

 

Airport Flight

class Flight(models.Model):

    STATUS_CHOICES = [

        ('SCHEDULED', 'Scheduled'),

        ('DELAYED', 'Delayed'),

        ('CANCELLED', 'Cancelled'),

        ('BOARDING', 'Boarding'),

        ('DEPARTED', 'Departed'),

        ('ARRIVED', 'Arrived'),

    ]

   

    flight_number = models.CharField(max_length=20, help_text="e.g. VN151")

    airline = models.ForeignKey(Airline, on_delete=models.CASCADE, related_name='flights', null=True)

    aircraft = models.ForeignKey(Aircraft, on_delete=models.SET_NULL, null=True, blank=True)

   

    origin_airport = models.ForeignKey(Airport, on_delete=models.CASCADE, related_name='departing_flights', null=True)

    destination_airport = models.ForeignKey(Airport, on_delete=models.CASCADE, related_name='arriving_flights', null=True)

   

    departure_time = models.DateTimeField()

    arrival_time = models.DateTimeField()

    duration_minutes = models.PositiveIntegerField(default=0, help_text="Flight duration in minutes")

   

    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='SCHEDULED')

    gate = models.CharField(max_length=10, blank=True, help_text="Departure gate")

    terminal = models.CharField(max_length=10, blank=True, help_text="Departure terminal")

   

    # Pricing

    economy_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    business_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    first_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

   

    created_at = models.DateTimeField(auto_now_add=True, null=True)

    updated_at = models.DateTimeField(auto_now=True, null=True)

 

    class Meta:

        verbose_name = _("Flight")

        verbose_name_plural = _("Flights")

        ordering = ['departure_time']

        unique_together = ['flight_number', 'departure_time']

 

    def __str__(self):

        return f"{self.airline.iata_code}{self.flight_number}: {self.origin_airport.iata_code} → {self.destination_airport.iata_code}"

 

    @property

    def route(self):

        return f"{self.origin_airport.city.name_en} → {self.destination_airport.city.name_en}"

 

Country

class Country(models.Model):

    name_en = models.CharField(max_length=100, verbose_name=_("English Name"))

    name_vi = models.CharField(max_length=100, blank=True, verbose_name=_("Vietnamese Name"))

    country_code = models.CharField(max_length=2, unique=True, help_text=_("ISO 3166-1 alpha-2 code"))

   

    class Meta:

        verbose_name = _("Country")

        verbose_name_plural = _("Countries")

        ordering = ['name_en']

   

    def __str__(self):

        return self.name_en

 

Country airline

class Airline(models.Model):

    name_en = models.CharField(max_length=100, verbose_name="English Name")

    name_vi = models.CharField(max_length=100, blank=True, verbose_name="Vietnamese Name")

    iata_code = models.CharField(max_length=2, unique=True, help_text="2-letter IATA code (e.g. VN)")

    icao_code = models.CharField(max_length=3, unique=True, help_text="3-letter ICAO code (e.g. HVN)")

    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='airlines')

    logo = models.ImageField(upload_to='airlines/', blank=True, null=True)

    is_active = models.BooleanField(default=True)

   

    class Meta:

        verbose_name = _("Airline")

        verbose_name_plural = _("Airlines")

        ordering = ['name_en']

   

    def __str__(self):

        return f"{self.name_en} ({self.iata_code})"

 

Country City

class City(models.Model):

    name_en = models.CharField(max_length=100, verbose_name="English Name")

    name_vi = models.CharField(max_length=100, blank=True, verbose_name="Vietnamese Name")

    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='cities')

    timezone = models.CharField(max_length=50, blank=True, help_text="e.g. Asia/Ho_Chi_Minh")

   

    class Meta:

        verbose_name = _("City")

        verbose_name_plural = _("Cities")

        ordering = ['name_en']

        unique_together = ['name_en', 'country']

   

    def __str__(self):

        return f"{self.name_en}, {self.country.name_en}"

 

 

Country Transport Operator

class TransportOperator(models.Model):

    name_en = models.CharField(max_length=100, verbose_name="English Name")

    name_vi = models.CharField(max_length=100, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=10, choices=TransportStation.STATION_TYPES)

    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='transport_operators')

    website = models.URLField(blank=True)

    phone = models.CharField(max_length=20, blank=True)

    is_active = models.BooleanField(default=True)

   

    class Meta:

        ordering = ['name_en']

   

    def __str__(self):

        return self.name_en

 

Station

class TransportStation(models.Model):

    STATION_TYPES = [

        ('BUS', 'Bus Station'),

        ('TRAIN', 'Train Station'),

        ('FERRY', 'Ferry Terminal'),

        ('METRO', 'Metro Station'),

    ]

   

    name_en = models.CharField(max_length=200, verbose_name="English Name")

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=10, choices=STATION_TYPES)

    city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='transport_stations')

    address_en = models.TextField(blank=True, verbose_name="English Address")

    address_vi = models.TextField(blank=True, verbose_name="Vietnamese Address")

    latitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)

    longitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)

    is_active = models.BooleanField(default=True)

   

    class Meta:

        ordering = ['city', 'name_en']

   

    def __str__(self):

        return f"{self.name_en} ({self.get_type_display()})"

 

Station Transit

 

class Transit(models.Model):

    """Individual transit trip instances (generated from TransitRoute schedules)"""

   

    STATUS_CHOICES = [

        ('SCHEDULED', 'Scheduled'),

        ('DELAYED', 'Delayed'),

        ('CANCELLED', 'Cancelled'),

        ('BOARDING', 'Boarding'),

        ('DEPARTED', 'Departed'),

        ('ARRIVED', 'Arrived'),

    ]

   

    # Link to route template (optional - for generated trips)

    route = models.ForeignKey(TransitRoute, on_delete=models.CASCADE, null=True, blank=True, related_name='trips')

   

    # Legacy fields (for backward compatibility or manual entries)

    route_name = models.CharField(max_length=100, blank=True, help_text="e.g. Route 109, Express Line")

    type = models.CharField(max_length=10, choices=TransitRoute.TRANSIT_TYPES)

    operator = models.ForeignKey(TransportOperator, on_delete=models.CASCADE, related_name='transits', null=True)

   

    origin_station = models.ForeignKey(TransportStation, on_delete=models.CASCADE, related_name='departing_transits', null=True)

    destination_station = models.ForeignKey(TransportStation, on_delete=models.CASCADE, related_name='arriving_transits', null=True)

   

    # Specific trip timing

    departure_time = models.DateTimeField()

    arrival_time = models.DateTimeField()

    duration_minutes = models.PositiveIntegerField(default=0, help_text="Transit duration in minutes")

   

    # Trip status and real-time info

    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='SCHEDULED')

    delay_minutes = models.IntegerField(default=0, help_text="Delay in minutes (negative for early)")

    actual_departure_time = models.DateTimeField(null=True, blank=True)

    actual_arrival_time = models.DateTimeField(null=True, blank=True)

   

    # Vehicle/service info

    vehicle_number = models.CharField(max_length=20, blank=True)

    platform = models.CharField(max_length=10, blank=True)

    gate = models.CharField(max_length=10, blank=True)

   

    # Dynamic pricing (can override route pricing)

    standard_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    premium_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

   

    # Capacity and booking

    total_seats = models.PositiveIntegerField(null=True, blank=True)

    available_seats = models.PositiveIntegerField(null=True, blank=True)

    is_bookable = models.BooleanField(default=True)

   

    # Additional info

    notes_en = models.TextField(blank=True, verbose_name="English Notes")

    notes_vi = models.TextField(blank=True, verbose_name="Vietnamese Notes")

   

    created_at = models.DateTimeField(auto_now_add=True, null=True)

    updated_at = models.DateTimeField(auto_now=True, null=True)

 

    class Meta:

        verbose_name = _("Transit")

        verbose_name_plural = _("Transits")

        ordering = ['departure_time']

 

    def __str__(self):

        if self.route:

            return f"{self.route.route_name}: {self.departure_time.strftime('%H:%M')} - {self.get_status_display()}"

        else:

            return f"{self.get_type_display()}: {self.origin_station.city.name_en} → {self.destination_station.city.name_en}"

 

    @property

    def route_display(self):

        if self.route:

            return self.route.route_name

        return self.route_name or f"{self.origin_station.name_en} → {self.destination_station.name_en}"

   

    @property

    def effective_departure_time(self):

        """Return actual departure time if available, otherwise scheduled time"""

        return self.actual_departure_time or self.departure_time

   

    @property

    def effective_arrival_time(self):

        """Return actual arrival time if available, otherwise scheduled time"""

        return self.actual_arrival_time or self.arrival_time

   

    @property

    def is_delayed(self):

        """Check if transit is delayed"""

        return self.delay_minutes > 0

   

    @property

    def is_cancelled(self):

        """Check if transit is cancelled"""

        return self.status == 'CANCELLED'

 

    def get_price(self, ticket_type='standard'):

        """Get price for ticket type, fallback to route pricing if not set"""

        if ticket_type == 'premium':

            return self.premium_price or (self.route.premium_price if self.route else None)

        else:

            return self.standard_price or (self.route.standard_price if self.route else None)

 

 

Station Transit R

 

class TransitRoute(models.Model):

    """Base transit route information with fixed schedules"""

    TRANSIT_TYPES = (

        ('BUS', 'Bus'),

        ('TRAIN', 'Train'),

        ('FERRY', 'Ferry'),

        ('METRO', 'Metro'),

        ('CAR', 'Car Rental'),

        ('TAXI', 'Taxi'),

        ('OTHER', 'Other'),

    )

   

    SCHEDULE_TYPES = [

        ('FIXED', 'Fixed Schedule'),  # Specific times (e.g., 08:00, 10:30, 14:45)

        ('FREQUENT', 'Frequent Service'),  # Every X minutes (e.g., every 15 minutes)

        ('DAILY', 'Daily Service'),  # Same time daily

        ('WEEKLY', 'Weekly Service'),  # Specific days of week

        ('ON_DEMAND', 'On Demand'),  # No fixed schedule

    ]

   

    FREQUENCY_UNITS = [

        ('MINUTES', 'Minutes'),

        ('HOURS', 'Hours'),

        ('DAYS', 'Days'),

    ]

   

    DAYS_OF_WEEK = [

        ('MON', 'Monday'),

        ('TUE', 'Tuesday'),

        ('WED', 'Wednesday'),

        ('THU', 'Thursday'),

        ('FRI', 'Friday'),

        ('SAT', 'Saturday'),

        ('SUN', 'Sunday'),

    ]

   

    route_name = models.CharField(max_length=100, help_text="e.g. Route 109, Express Line")

    type = models.CharField(max_length=10, choices=TRANSIT_TYPES)

    operator = models.ForeignKey(TransportOperator, on_delete=models.CASCADE, related_name='routes')

   

    origin_station = models.ForeignKey(TransportStation, on_delete=models.CASCADE, related_name='departing_routes')

    destination_station = models.ForeignKey(TransportStation, on_delete=models.CASCADE, related_name='arriving_routes')

   

    # Schedule configuration

    schedule_type = models.CharField(max_length=20, choices=SCHEDULE_TYPES, default='FIXED')

   

    # For FREQUENT service (every X minutes/hours)

    frequency_interval = models.PositiveIntegerField(null=True, blank=True, help_text="e.g., 15 for every 15 minutes")

    frequency_unit = models.CharField(max_length=10, choices=FREQUENCY_UNITS, blank=True)

   

    # Service hours (for FREQUENT service)

    service_start_time = models.TimeField(null=True, blank=True, help_text="First departure time")

    service_end_time = models.TimeField(null=True, blank=True, help_text="Last departure time")

   

    # Operating days

    operates_monday = models.BooleanField(default=True)

    operates_tuesday = models.BooleanField(default=True)

    operates_wednesday = models.BooleanField(default=True)

    operates_thursday = models.BooleanField(default=True)

    operates_friday = models.BooleanField(default=True)

    operates_saturday = models.BooleanField(default=True)

    operates_sunday = models.BooleanField(default=False)

   

    # Fixed schedule times (JSON format for multiple departure times)

    fixed_departure_times = models.JSONField(default=list, blank=True,

        help_text="List of departure times in HH:MM format, e.g. ['08:00', '10:30', '14:45']")

   

    # Basic route info

    duration_minutes = models.PositiveIntegerField(help_text="Typical journey duration in minutes")

    distance_km = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)

   

    # Pricing

    standard_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    premium_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    child_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    student_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

   

    # Route details

    stops_en = models.JSONField(default=list, blank=True, help_text="List of intermediate stops (English)")

    stops_vi = models.JSONField(default=list, blank=True, help_text="List of intermediate stops (Vietnamese)")

    notes_en = models.TextField(blank=True, verbose_name="English Notes")

    notes_vi = models.TextField(blank=True, verbose_name="Vietnamese Notes")

   

    # Status

    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

   

    class Meta:

        verbose_name = "Transit Route"

        verbose_name_plural = "Transit Routes"

        ordering = ['type', 'route_name']

        unique_together = ['route_name', 'origin_station', 'destination_station']

   

    def __str__(self):

        return f"{self.route_name}: {self.origin_station.name_en} → {self.destination_station.name_en}"

   

    def get_operating_days(self):

        """Return list of operating days"""

        days = []

        if self.operates_monday: days.append('Monday')

        if self.operates_tuesday: days.append('Tuesday')

        if self.operates_wednesday: days.append('Wednesday')

        if self.operates_thursday: days.append('Thursday')

        if self.operates_friday: days.append('Friday')

        if self.operates_saturday: days.append('Saturday')

        if self.operates_sunday: days.append('Sunday')

        return days

   

    def get_departures_for_date(self, date):

        """Generate departure times for a specific date"""

        from datetime import datetime, timedelta

        import json

       

        # Check if route operates on this day

        weekday = date.weekday()  # 0 = Monday, 6 = Sunday

        operating_days = [

            self.operates_monday, self.operates_tuesday, self.operates_wednesday,

            self.operates_thursday, self.operates_friday, self.operates_saturday, self.operates_sunday

        ]

       

        if not operating_days[weekday]:

            return []

       

        departures = []

       

        if self.schedule_type == 'FIXED' and self.fixed_departure_times:

            # Fixed departure times

            for time_str in self.fixed_departure_times:

                try:

                    hour, minute = map(int, time_str.split(':'))

                    departure_datetime = datetime.combine(date, datetime.min.time().replace(hour=hour, minute=minute))

                    arrival_datetime = departure_datetime + timedelta(minutes=self.duration_minutes)

                    departures.append({

                        'departure_time': departure_datetime,

                        'arrival_time': arrival_datetime

                    })

                except ValueError:

                    continue

                   

        elif self.schedule_type == 'FREQUENT' and self.service_start_time and self.service_end_time:

            # Frequent service (every X minutes)

            current_time = datetime.combine(date, self.service_start_time)

            end_time = datetime.combine(date, self.service_end_time)

           

            interval_minutes = self.frequency_interval or 30

            if self.frequency_unit == 'HOURS':

                interval_minutes *= 60

            elif self.frequency_unit == 'DAYS':

                interval_minutes *= 1440

               

            while current_time <= end_time:

                arrival_time = current_time + timedelta(minutes=self.duration_minutes)

                departures.append({

                    'departure_time': current_time,

                    'arrival_time': arrival_time

                })

                current_time += timedelta(minutes=interval_minutes)

       

        elif self.schedule_type == 'DAILY' and self.service_start_time:

            # Daily service at fixed time

            departure_datetime = datetime.combine(date, self.service_start_time)

            arrival_datetime = departure_datetime + timedelta(minutes=self.duration_minutes)

            departures.append({

                'departure_time': departure_datetime,

                'arrival_time': arrival_datetime

            })

       

        return departures

 

Vietnamese translation is not available for this article. Showing English content:

Nhom

Notes

 

from django.db import models

from django.conf import settings

from django.utils.translation import gettext_lazy as _

 

 

Completed Tasks

  1. Country Filter Enhancement: Modified the destinations country filter to show only countries that have actual destinations (from 38 to 5 countries)
  2. Dynamic Station Filtering: Enhanced the transit search interface with JavaScript-based filtering to show destination stations based on selected origin stations and transit type tabs
  3. Transit Schedule System Restructuring:
    • New TransitRoute Model: Template-based scheduling system supporting FIXED, FREQUENT, DAILY, WEEKLY, and ON_DEMAND schedule types
    • Enhanced Transit Model: Now serves as trip instances with status tracking, seat availability, and delay management
    • Database Migration: Successfully applied migration 0006 with new schema
  4. Management Command: Created generate_transit_trips command that generates trip instances from route templates
  5. Admin Interface: Complete admin configuration for managing both TransitRoute templates and Transit instances
  6. Testing & Validation: Successfully tested the system with sample data:
    • Created sample route with 8 daily departure times
    • Generated 24 trip instances over 3 days
    • Verified data integrity and system functionality

🏗️ System Architecture

Before: Individual transit entries with fixed datetime fields After: Template-based system where:

  • TransitRoute defines schedule patterns (templates)
  • Transit represents actual trip instances generated from templates
  • Support for recurring schedules without manual entry of each trip

📊 Key Features

  1. Schedule Types:
    • FIXED: Specific departure times (e.g., 08:00, 10:30, 14:45)
    • FREQUENT: Every X minutes/hours with service windows
    • DAILY/WEEKLY: Regular recurring schedules
    • ON_DEMAND: No fixed schedule
  2. Operating Days Configuration: Individual boolean fields for each day of the week
  3. Flexible Pricing: Multiple fare types (standard, premium, child, student)
  4. Trip Instance Management: Real-time status, seat availability, delay tracking
  5. Multi-language Support: English/Vietnamese names and descriptions

The system now supports the practical needs of transit operators with fixed schedules while maintaining the flexibility for dynamic trip management that the original user request identified as essential for real-world transit systems.

 

 

 

Aircraft

class Aircraft(models.Model):

    model = models.CharField(max_length=50, help_text="e.g. Boeing 777-300ER")

    manufacturer = models.CharField(max_length=50, help_text="e.g. Boeing, Airbus")

    capacity = models.PositiveIntegerField(default=0, help_text="Total passenger capacity")

   

    class Meta:

        verbose_name = _("Aircraft")

        verbose_name_plural = _("Aircraft")

        ordering = ['manufacturer', 'model']

   

    def __str__(self):

        return f"{self.manufacturer} {self.model}"

 

Airport

class Airport(models.Model):

    name_en = models.CharField(max_length=200, verbose_name="English Name")

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    iata_code = models.CharField(max_length=3, unique=True, help_text="3-letter IATA code (e.g. SGN)")

    icao_code = models.CharField(max_length=4, unique=True, help_text="4-letter ICAO code (e.g. VVTS)")

    city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='airports')

    latitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)

    longitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)

    is_active = models.BooleanField(default=True)

   

    class Meta:

        verbose_name = _("Airport")

        verbose_name_plural = _("Airports")

        ordering = ['iata_code']

   

    def __str__(self):

        return f"{self.name_en} ({self.iata_code})"

 

Airport Flight

class Flight(models.Model):

    STATUS_CHOICES = [

        ('SCHEDULED', 'Scheduled'),

        ('DELAYED', 'Delayed'),

        ('CANCELLED', 'Cancelled'),

        ('BOARDING', 'Boarding'),

        ('DEPARTED', 'Departed'),

        ('ARRIVED', 'Arrived'),

    ]

   

    flight_number = models.CharField(max_length=20, help_text="e.g. VN151")

    airline = models.ForeignKey(Airline, on_delete=models.CASCADE, related_name='flights', null=True)

    aircraft = models.ForeignKey(Aircraft, on_delete=models.SET_NULL, null=True, blank=True)

   

    origin_airport = models.ForeignKey(Airport, on_delete=models.CASCADE, related_name='departing_flights', null=True)

    destination_airport = models.ForeignKey(Airport, on_delete=models.CASCADE, related_name='arriving_flights', null=True)

   

    departure_time = models.DateTimeField()

    arrival_time = models.DateTimeField()

    duration_minutes = models.PositiveIntegerField(default=0, help_text="Flight duration in minutes")

   

    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='SCHEDULED')

    gate = models.CharField(max_length=10, blank=True, help_text="Departure gate")

    terminal = models.CharField(max_length=10, blank=True, help_text="Departure terminal")

   

    # Pricing

    economy_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    business_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    first_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

   

    created_at = models.DateTimeField(auto_now_add=True, null=True)

    updated_at = models.DateTimeField(auto_now=True, null=True)

 

    class Meta:

        verbose_name = _("Flight")

        verbose_name_plural = _("Flights")

        ordering = ['departure_time']

        unique_together = ['flight_number', 'departure_time']

 

    def __str__(self):

        return f"{self.airline.iata_code}{self.flight_number}: {self.origin_airport.iata_code} → {self.destination_airport.iata_code}"

 

    @property

    def route(self):

        return f"{self.origin_airport.city.name_en} → {self.destination_airport.city.name_en}"

 

Country

class Country(models.Model):

    name_en = models.CharField(max_length=100, verbose_name=_("English Name"))

    name_vi = models.CharField(max_length=100, blank=True, verbose_name=_("Vietnamese Name"))

    country_code = models.CharField(max_length=2, unique=True, help_text=_("ISO 3166-1 alpha-2 code"))

   

    class Meta:

        verbose_name = _("Country")

        verbose_name_plural = _("Countries")

        ordering = ['name_en']

   

    def __str__(self):

        return self.name_en

 

Country airline

class Airline(models.Model):

    name_en = models.CharField(max_length=100, verbose_name="English Name")

    name_vi = models.CharField(max_length=100, blank=True, verbose_name="Vietnamese Name")

    iata_code = models.CharField(max_length=2, unique=True, help_text="2-letter IATA code (e.g. VN)")

    icao_code = models.CharField(max_length=3, unique=True, help_text="3-letter ICAO code (e.g. HVN)")

    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='airlines')

    logo = models.ImageField(upload_to='airlines/', blank=True, null=True)

    is_active = models.BooleanField(default=True)

   

    class Meta:

        verbose_name = _("Airline")

        verbose_name_plural = _("Airlines")

        ordering = ['name_en']

   

    def __str__(self):

        return f"{self.name_en} ({self.iata_code})"

 

Country City

class City(models.Model):

    name_en = models.CharField(max_length=100, verbose_name="English Name")

    name_vi = models.CharField(max_length=100, blank=True, verbose_name="Vietnamese Name")

    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='cities')

    timezone = models.CharField(max_length=50, blank=True, help_text="e.g. Asia/Ho_Chi_Minh")

   

    class Meta:

        verbose_name = _("City")

        verbose_name_plural = _("Cities")

        ordering = ['name_en']

        unique_together = ['name_en', 'country']

   

    def __str__(self):

        return f"{self.name_en}, {self.country.name_en}"

 

 

Country Transport Operator

class TransportOperator(models.Model):

    name_en = models.CharField(max_length=100, verbose_name="English Name")

    name_vi = models.CharField(max_length=100, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=10, choices=TransportStation.STATION_TYPES)

    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='transport_operators')

    website = models.URLField(blank=True)

    phone = models.CharField(max_length=20, blank=True)

    is_active = models.BooleanField(default=True)

   

    class Meta:

        ordering = ['name_en']

   

    def __str__(self):

        return self.name_en

 

Station

class TransportStation(models.Model):

    STATION_TYPES = [

        ('BUS', 'Bus Station'),

        ('TRAIN', 'Train Station'),

        ('FERRY', 'Ferry Terminal'),

        ('METRO', 'Metro Station'),

    ]

   

    name_en = models.CharField(max_length=200, verbose_name="English Name")

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=10, choices=STATION_TYPES)

    city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='transport_stations')

    address_en = models.TextField(blank=True, verbose_name="English Address")

    address_vi = models.TextField(blank=True, verbose_name="Vietnamese Address")

    latitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)

    longitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)

    is_active = models.BooleanField(default=True)

   

    class Meta:

        ordering = ['city', 'name_en']

   

    def __str__(self):

        return f"{self.name_en} ({self.get_type_display()})"

 

Station Transit

 

class Transit(models.Model):

    """Individual transit trip instances (generated from TransitRoute schedules)"""

   

    STATUS_CHOICES = [

        ('SCHEDULED', 'Scheduled'),

        ('DELAYED', 'Delayed'),

        ('CANCELLED', 'Cancelled'),

        ('BOARDING', 'Boarding'),

        ('DEPARTED', 'Departed'),

        ('ARRIVED', 'Arrived'),

    ]

   

    # Link to route template (optional - for generated trips)

    route = models.ForeignKey(TransitRoute, on_delete=models.CASCADE, null=True, blank=True, related_name='trips')

   

    # Legacy fields (for backward compatibility or manual entries)

    route_name = models.CharField(max_length=100, blank=True, help_text="e.g. Route 109, Express Line")

    type = models.CharField(max_length=10, choices=TransitRoute.TRANSIT_TYPES)

    operator = models.ForeignKey(TransportOperator, on_delete=models.CASCADE, related_name='transits', null=True)

   

    origin_station = models.ForeignKey(TransportStation, on_delete=models.CASCADE, related_name='departing_transits', null=True)

    destination_station = models.ForeignKey(TransportStation, on_delete=models.CASCADE, related_name='arriving_transits', null=True)

   

    # Specific trip timing

    departure_time = models.DateTimeField()

    arrival_time = models.DateTimeField()

    duration_minutes = models.PositiveIntegerField(default=0, help_text="Transit duration in minutes")

   

    # Trip status and real-time info

    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='SCHEDULED')

    delay_minutes = models.IntegerField(default=0, help_text="Delay in minutes (negative for early)")

    actual_departure_time = models.DateTimeField(null=True, blank=True)

    actual_arrival_time = models.DateTimeField(null=True, blank=True)

   

    # Vehicle/service info

    vehicle_number = models.CharField(max_length=20, blank=True)

    platform = models.CharField(max_length=10, blank=True)

    gate = models.CharField(max_length=10, blank=True)

   

    # Dynamic pricing (can override route pricing)

    standard_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    premium_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

   

    # Capacity and booking

    total_seats = models.PositiveIntegerField(null=True, blank=True)

    available_seats = models.PositiveIntegerField(null=True, blank=True)

    is_bookable = models.BooleanField(default=True)

   

    # Additional info

    notes_en = models.TextField(blank=True, verbose_name="English Notes")

    notes_vi = models.TextField(blank=True, verbose_name="Vietnamese Notes")

   

    created_at = models.DateTimeField(auto_now_add=True, null=True)

    updated_at = models.DateTimeField(auto_now=True, null=True)

 

    class Meta:

        verbose_name = _("Transit")

        verbose_name_plural = _("Transits")

        ordering = ['departure_time']

 

    def __str__(self):

        if self.route:

            return f"{self.route.route_name}: {self.departure_time.strftime('%H:%M')} - {self.get_status_display()}"

        else:

            return f"{self.get_type_display()}: {self.origin_station.city.name_en} → {self.destination_station.city.name_en}"

 

    @property

    def route_display(self):

        if self.route:

            return self.route.route_name

        return self.route_name or f"{self.origin_station.name_en} → {self.destination_station.name_en}"

   

    @property

    def effective_departure_time(self):

        """Return actual departure time if available, otherwise scheduled time"""

        return self.actual_departure_time or self.departure_time

   

    @property

    def effective_arrival_time(self):

        """Return actual arrival time if available, otherwise scheduled time"""

        return self.actual_arrival_time or self.arrival_time

   

    @property

    def is_delayed(self):

        """Check if transit is delayed"""

        return self.delay_minutes > 0

   

    @property

    def is_cancelled(self):

        """Check if transit is cancelled"""

        return self.status == 'CANCELLED'

 

    def get_price(self, ticket_type='standard'):

        """Get price for ticket type, fallback to route pricing if not set"""

        if ticket_type == 'premium':

            return self.premium_price or (self.route.premium_price if self.route else None)

        else:

            return self.standard_price or (self.route.standard_price if self.route else None)

 

 

Station Transit R

 

class TransitRoute(models.Model):

    """Base transit route information with fixed schedules"""

    TRANSIT_TYPES = (

        ('BUS', 'Bus'),

        ('TRAIN', 'Train'),

        ('FERRY', 'Ferry'),

        ('METRO', 'Metro'),

        ('CAR', 'Car Rental'),

        ('TAXI', 'Taxi'),

        ('OTHER', 'Other'),

    )

   

    SCHEDULE_TYPES = [

        ('FIXED', 'Fixed Schedule'),  # Specific times (e.g., 08:00, 10:30, 14:45)

        ('FREQUENT', 'Frequent Service'),  # Every X minutes (e.g., every 15 minutes)

        ('DAILY', 'Daily Service'),  # Same time daily

        ('WEEKLY', 'Weekly Service'),  # Specific days of week

        ('ON_DEMAND', 'On Demand'),  # No fixed schedule

    ]

   

    FREQUENCY_UNITS = [

        ('MINUTES', 'Minutes'),

        ('HOURS', 'Hours'),

        ('DAYS', 'Days'),

    ]

   

    DAYS_OF_WEEK = [

        ('MON', 'Monday'),

        ('TUE', 'Tuesday'),

        ('WED', 'Wednesday'),

        ('THU', 'Thursday'),

        ('FRI', 'Friday'),

        ('SAT', 'Saturday'),

        ('SUN', 'Sunday'),

    ]

   

    route_name = models.CharField(max_length=100, help_text="e.g. Route 109, Express Line")

    type = models.CharField(max_length=10, choices=TRANSIT_TYPES)

    operator = models.ForeignKey(TransportOperator, on_delete=models.CASCADE, related_name='routes')

   

    origin_station = models.ForeignKey(TransportStation, on_delete=models.CASCADE, related_name='departing_routes')

    destination_station = models.ForeignKey(TransportStation, on_delete=models.CASCADE, related_name='arriving_routes')

   

    # Schedule configuration

    schedule_type = models.CharField(max_length=20, choices=SCHEDULE_TYPES, default='FIXED')

   

    # For FREQUENT service (every X minutes/hours)

    frequency_interval = models.PositiveIntegerField(null=True, blank=True, help_text="e.g., 15 for every 15 minutes")

    frequency_unit = models.CharField(max_length=10, choices=FREQUENCY_UNITS, blank=True)

   

    # Service hours (for FREQUENT service)

    service_start_time = models.TimeField(null=True, blank=True, help_text="First departure time")

    service_end_time = models.TimeField(null=True, blank=True, help_text="Last departure time")

   

    # Operating days

    operates_monday = models.BooleanField(default=True)

    operates_tuesday = models.BooleanField(default=True)

    operates_wednesday = models.BooleanField(default=True)

    operates_thursday = models.BooleanField(default=True)

    operates_friday = models.BooleanField(default=True)

    operates_saturday = models.BooleanField(default=True)

    operates_sunday = models.BooleanField(default=False)

   

    # Fixed schedule times (JSON format for multiple departure times)

    fixed_departure_times = models.JSONField(default=list, blank=True,

        help_text="List of departure times in HH:MM format, e.g. ['08:00', '10:30', '14:45']")

   

    # Basic route info

    duration_minutes = models.PositiveIntegerField(help_text="Typical journey duration in minutes")

    distance_km = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)

   

    # Pricing

    standard_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    premium_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    child_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    student_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

   

    # Route details

    stops_en = models.JSONField(default=list, blank=True, help_text="List of intermediate stops (English)")

    stops_vi = models.JSONField(default=list, blank=True, help_text="List of intermediate stops (Vietnamese)")

    notes_en = models.TextField(blank=True, verbose_name="English Notes")

    notes_vi = models.TextField(blank=True, verbose_name="Vietnamese Notes")

   

    # Status

    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

   

    class Meta:

        verbose_name = "Transit Route"

        verbose_name_plural = "Transit Routes"

        ordering = ['type', 'route_name']

        unique_together = ['route_name', 'origin_station', 'destination_station']

   

    def __str__(self):

        return f"{self.route_name}: {self.origin_station.name_en} → {self.destination_station.name_en}"

   

    def get_operating_days(self):

        """Return list of operating days"""

        days = []

        if self.operates_monday: days.append('Monday')

        if self.operates_tuesday: days.append('Tuesday')

        if self.operates_wednesday: days.append('Wednesday')

        if self.operates_thursday: days.append('Thursday')

        if self.operates_friday: days.append('Friday')

        if self.operates_saturday: days.append('Saturday')

        if self.operates_sunday: days.append('Sunday')

        return days

   

    def get_departures_for_date(self, date):

        """Generate departure times for a specific date"""

        from datetime import datetime, timedelta

        import json

       

        # Check if route operates on this day

        weekday = date.weekday()  # 0 = Monday, 6 = Sunday

        operating_days = [

            self.operates_monday, self.operates_tuesday, self.operates_wednesday,

            self.operates_thursday, self.operates_friday, self.operates_saturday, self.operates_sunday

        ]

       

        if not operating_days[weekday]:

            return []

       

        departures = []

       

        if self.schedule_type == 'FIXED' and self.fixed_departure_times:

            # Fixed departure times

            for time_str in self.fixed_departure_times:

                try:

                    hour, minute = map(int, time_str.split(':'))

                    departure_datetime = datetime.combine(date, datetime.min.time().replace(hour=hour, minute=minute))

                    arrival_datetime = departure_datetime + timedelta(minutes=self.duration_minutes)

                    departures.append({

                        'departure_time': departure_datetime,

                        'arrival_time': arrival_datetime

                    })

                except ValueError:

                    continue

                   

        elif self.schedule_type == 'FREQUENT' and self.service_start_time and self.service_end_time:

            # Frequent service (every X minutes)

            current_time = datetime.combine(date, self.service_start_time)

            end_time = datetime.combine(date, self.service_end_time)

           

            interval_minutes = self.frequency_interval or 30

            if self.frequency_unit == 'HOURS':

                interval_minutes *= 60

            elif self.frequency_unit == 'DAYS':

                interval_minutes *= 1440

               

            while current_time <= end_time:

                arrival_time = current_time + timedelta(minutes=self.duration_minutes)

                departures.append({

                    'departure_time': current_time,

                    'arrival_time': arrival_time

                })

                current_time += timedelta(minutes=interval_minutes)

       

        elif self.schedule_type == 'DAILY' and self.service_start_time:

            # Daily service at fixed time

            departure_datetime = datetime.combine(date, self.service_start_time)

            arrival_datetime = departure_datetime + timedelta(minutes=self.duration_minutes)

            departures.append({

                'departure_time': departure_datetime,

                'arrival_time': arrival_datetime

            })

       

        return departures

 

Attached Files

0 files found.

You are viewing this article in public mode. Some features may be limited.