Restructurate GTFS feeds into dedicated models
This commit is contained in:
@ -2,15 +2,58 @@ from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class TransportType(models.TextChoices):
|
||||
TGV = "TGV", _("TGV")
|
||||
TER = "TER", _("TER")
|
||||
INTERCITES = "IC", _("Intercités")
|
||||
TRANSILIEN = "TN", _("Transilien")
|
||||
EUROSTAR = "ES", _("Eurostar")
|
||||
TRENITALIA = "TI", _("Trenitalia")
|
||||
RENFE = "RENFE", _("Renfe")
|
||||
OBB = "OBB", _("ÖBB")
|
||||
class Country(models.TextChoices):
|
||||
"""
|
||||
Country list by ISO 3166-1 alpha-2 code.
|
||||
Only countries that are member of the Council of Europe
|
||||
are listed for now.
|
||||
"""
|
||||
ALBANIA = "AL", _("Albania")
|
||||
ANDORRA = "AD", _("Andorra")
|
||||
ARMENIA = "AM", _("Armenia")
|
||||
AUSTRIA = "AT", _("Austria")
|
||||
AZERBAIJAN = "AZ", _("Azerbaijan")
|
||||
BELGIUM = "BE", _("Belgium")
|
||||
BOSNIA_AND_HERZEGOVINA = "BA", _("Bosnia and Herzegovina")
|
||||
BULGARIA = "BG", _("Bulgaria")
|
||||
CROATIA = "HR", _("Croatia")
|
||||
CYPRUS = "CY", _("Cyprus")
|
||||
CZECH_REPUBLIC = "CZ", _("Czech Republic")
|
||||
DENMARK = "DK", _("Denmark")
|
||||
ESTONIA = "EE", _("Estonia")
|
||||
FINLAND = "FI", _("Finland")
|
||||
FRANCE = "FR", _("France")
|
||||
GEORGIA = "GE", _("Georgia")
|
||||
GERMANY = "DE", _("Germany")
|
||||
GREECE = "GR", _("Greece")
|
||||
HUNGARY = "HU", _("Hungary")
|
||||
ICELAND = "IS", _("Iceland")
|
||||
IRELAND = "IE", _("Ireland")
|
||||
ITALY = "IT", _("Italy")
|
||||
LATVIA = "LV", _("Latvia")
|
||||
LIECHTENSTEIN = "LI", _("Liechtenstein")
|
||||
LITHUANIA = "LT", _("Lithuania")
|
||||
LUXEMBOURG = "LU", _("Luxembourg")
|
||||
MALTA = "MT", _("Malta")
|
||||
MOLDOVA = "MD", _("Moldova")
|
||||
MONACO = "MC", _("Monaco")
|
||||
MONTENEGRO = "ME", _("Montenegro")
|
||||
NETHERLANDS = "NL", _("Netherlands")
|
||||
NORTH_MACEDONIA = "MK", _("North Macedonia")
|
||||
NORWAY = "NO", _("Norway")
|
||||
POLAND = "PL", _("Poland")
|
||||
PORTUGAL = "PT", _("Portugal")
|
||||
ROMANIA = "RO", _("Romania")
|
||||
SAN_MARINO = "SM", _("San Marino")
|
||||
SERBIA = "RS", _("Serbia")
|
||||
SLOVAKIA = "SK", _("Slovakia")
|
||||
SLOVENIA = "SI", _("Slovenia")
|
||||
SPAIN = "ES", _("Spain")
|
||||
SWEDEN = "SE", _("Sweden")
|
||||
SWITZERLAND = "CH", _("Switzerland")
|
||||
TURKEY = "TR", _("Turkey")
|
||||
UNITED_KINGDOM = "GB", _("United Kingdom")
|
||||
UKRAINE = "UA", _("Ukraine")
|
||||
|
||||
|
||||
class LocationType(models.IntegerChoices):
|
||||
@ -79,6 +122,66 @@ class StopScheduleRelationship(models.IntegerChoices):
|
||||
UNSCHEDULED = 3, _("Unscheduled")
|
||||
|
||||
|
||||
class GTFSFeed(models.Model):
|
||||
code = models.CharField(
|
||||
primary_key=True,
|
||||
max_length=64,
|
||||
verbose_name=_("code"),
|
||||
help_text=_("Unique code of the feed.")
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("name"),
|
||||
unique=True,
|
||||
help_text=_("Full name that describes the feed."),
|
||||
)
|
||||
|
||||
country = models.CharField(
|
||||
max_length=2,
|
||||
verbose_name=_("country"),
|
||||
choices=Country,
|
||||
)
|
||||
|
||||
feed_url = models.URLField(
|
||||
verbose_name=_("feed URL"),
|
||||
help_text=_("URL to download the GTFS feed. Must point to a ZIP archive. "
|
||||
"See https://gtfs.org/schedule/ for more information."),
|
||||
)
|
||||
|
||||
rt_feed_url = models.URLField(
|
||||
verbose_name=_("realtime feed URL"),
|
||||
blank=True,
|
||||
default="",
|
||||
help_text=_("URL to download the GTFS-Realtime feed, in the GTFS-RT format. "
|
||||
"See https://gtfs.org/realtime/ for more information."),
|
||||
)
|
||||
|
||||
last_modified = models.DateTimeField(
|
||||
verbose_name=_("last modified date"),
|
||||
null=True,
|
||||
default=None,
|
||||
)
|
||||
|
||||
etag = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("ETag"),
|
||||
blank=True,
|
||||
default="",
|
||||
help_text=_("If applicable, corresponds to the tag of the last downloaded file. "
|
||||
"If it is not modified, the file is the same."),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.code})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("GTFS feed")
|
||||
verbose_name_plural = _("GTFS feeds")
|
||||
ordering = ('country', 'name',)
|
||||
indexes = (models.Index(fields=['name']),)
|
||||
|
||||
|
||||
class Agency(models.Model):
|
||||
id = models.CharField(
|
||||
max_length=255,
|
||||
@ -117,6 +220,12 @@ class Agency(models.Model):
|
||||
blank=True,
|
||||
)
|
||||
|
||||
gtfs_feed = models.ForeignKey(
|
||||
GTFSFeed,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("GTFS feed"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@ -124,6 +233,7 @@ class Agency(models.Model):
|
||||
verbose_name = _("Agency")
|
||||
verbose_name_plural = _("Agencies")
|
||||
ordering = ("name",)
|
||||
indexes = (models.Index(fields=['name']), models.Index(fields=['gtfs_feed']),)
|
||||
|
||||
|
||||
class Stop(models.Model):
|
||||
@ -161,6 +271,7 @@ class Stop(models.Model):
|
||||
zone_id = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Zone ID"),
|
||||
blank=True,
|
||||
)
|
||||
|
||||
url = models.URLField(
|
||||
@ -209,10 +320,10 @@ class Stop(models.Model):
|
||||
blank=True,
|
||||
)
|
||||
|
||||
transport_type = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Transport type"),
|
||||
choices=TransportType,
|
||||
gtfs_feed = models.ForeignKey(
|
||||
GTFSFeed,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("GTFS feed"),
|
||||
)
|
||||
|
||||
@property
|
||||
@ -227,6 +338,9 @@ class Stop(models.Model):
|
||||
verbose_name = _("Stop")
|
||||
verbose_name_plural = _("Stops")
|
||||
ordering = ("id",)
|
||||
indexes = (models.Index(fields=['name']),
|
||||
models.Index(fields=['code']),
|
||||
models.Index(fields=['gtfs_feed']),)
|
||||
|
||||
|
||||
class Route(models.Model):
|
||||
@ -241,6 +355,9 @@ class Route(models.Model):
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("Agency"),
|
||||
related_name="routes",
|
||||
null=True,
|
||||
blank=True,
|
||||
default=None,
|
||||
)
|
||||
|
||||
short_name = models.CharField(
|
||||
@ -251,6 +368,7 @@ class Route(models.Model):
|
||||
long_name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Route long name"),
|
||||
blank=True,
|
||||
)
|
||||
|
||||
desc = models.CharField(
|
||||
@ -281,19 +399,20 @@ class Route(models.Model):
|
||||
blank=True,
|
||||
)
|
||||
|
||||
transport_type = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Transport type"),
|
||||
choices=TransportType,
|
||||
gtfs_feed = models.ForeignKey(
|
||||
GTFSFeed,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("GTFS feed"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.long_name}"
|
||||
return self.long_name or self.short_name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Route")
|
||||
verbose_name_plural = _("Routes")
|
||||
ordering = ("id",)
|
||||
indexes = (models.Index(fields=['gtfs_feed']),)
|
||||
|
||||
|
||||
class Trip(models.Model):
|
||||
@ -361,21 +480,24 @@ class Trip(models.Model):
|
||||
null=True,
|
||||
)
|
||||
|
||||
last_update = models.DateTimeField(
|
||||
verbose_name=_("Last update"),
|
||||
null=True,
|
||||
gtfs_feed = models.ForeignKey(
|
||||
GTFSFeed,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("GTFS feed"),
|
||||
)
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self.stop_times.order_by('stop_sequence').first().stop
|
||||
def origin(self) -> Stop | None:
|
||||
return self.stop_times.order_by('stop_sequence').first().stop if self.stop_times.exists() else None
|
||||
|
||||
@property
|
||||
def destination(self):
|
||||
return self.stop_times.order_by('-stop_sequence').first().stop
|
||||
def destination(self) -> Stop | None:
|
||||
return self.stop_times.order_by('-stop_sequence').first().stop if self.stop_times.exists() else None
|
||||
|
||||
@property
|
||||
def departure_time(self):
|
||||
if not self.stop_times.exists():
|
||||
return _("Unknown")
|
||||
dep_time = self.stop_times.order_by('stop_sequence').first().departure_time
|
||||
hours = int(dep_time.total_seconds() // 3600)
|
||||
minutes = int((dep_time.total_seconds() % 3600) // 60)
|
||||
@ -383,6 +505,8 @@ class Trip(models.Model):
|
||||
|
||||
@property
|
||||
def arrival_time(self):
|
||||
if not self.stop_times.exists():
|
||||
return _("Unknown")
|
||||
arr_time = self.stop_times.order_by('-stop_sequence').first().arrival_time
|
||||
hours = int(arr_time.total_seconds() // 3600)
|
||||
minutes = int((arr_time.total_seconds() % 3600) // 60)
|
||||
@ -390,14 +514,14 @@ class Trip(models.Model):
|
||||
|
||||
@property
|
||||
def train_type(self):
|
||||
if self.route.transport_type == TransportType.TRANSILIEN:
|
||||
if self.gtfs_feed.code == "FR-IDF-TN":
|
||||
return self.route.short_name
|
||||
else:
|
||||
return self.origin.stop_type
|
||||
|
||||
@property
|
||||
def train_number(self):
|
||||
if self.route.transport_type == TransportType.TRANSILIEN:
|
||||
if self.gtfs_feed.code == "FR-IDF-TN":
|
||||
return self.short_name
|
||||
else:
|
||||
return self.headsign
|
||||
@ -422,13 +546,23 @@ class Trip(models.Model):
|
||||
return "404042"
|
||||
return "000000"
|
||||
|
||||
@property
|
||||
def origin_destination(self):
|
||||
origin = self.origin
|
||||
origin = origin.name if origin else _("Unknown")
|
||||
destination = self.destination
|
||||
destination = destination.name if destination else _("Unknown")
|
||||
return f"{origin} {self.departure_time} → {destination} {self.arrival_time}"
|
||||
|
||||
origin_destination.fget.short_description = _("Origin → Destination")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.origin.name} {self.departure_time} → {self.destination.name} {self.arrival_time}" \
|
||||
f" - {self.service_id}"
|
||||
return self.origin_destination
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Trip")
|
||||
verbose_name_plural = _("Trips")
|
||||
indexes = (models.Index(fields=['route']), models.Index(fields=['gtfs_feed']),)
|
||||
|
||||
|
||||
class StopTime(models.Model):
|
||||
@ -510,6 +644,7 @@ class StopTime(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _("Stop time")
|
||||
verbose_name_plural = _("Stop times")
|
||||
indexes = (models.Index(fields=['stop']), models.Index(fields=['trip']),)
|
||||
|
||||
|
||||
class Calendar(models.Model):
|
||||
@ -555,10 +690,10 @@ class Calendar(models.Model):
|
||||
verbose_name=_("End date"),
|
||||
)
|
||||
|
||||
transport_type = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Transport type"),
|
||||
choices=TransportType,
|
||||
gtfs_feed = models.ForeignKey(
|
||||
GTFSFeed,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("GTFS feed"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
@ -568,6 +703,7 @@ class Calendar(models.Model):
|
||||
verbose_name = _("Calendar")
|
||||
verbose_name_plural = _("Calendars")
|
||||
ordering = ("id",)
|
||||
indexes = (models.Index(fields=['gtfs_feed']),)
|
||||
|
||||
|
||||
class CalendarDate(models.Model):
|
||||
@ -600,6 +736,7 @@ class CalendarDate(models.Model):
|
||||
verbose_name = _("Calendar date")
|
||||
verbose_name_plural = _("Calendar dates")
|
||||
ordering = ("id",)
|
||||
indexes = (models.Index(fields=['service']), models.Index(fields=['date']),)
|
||||
|
||||
|
||||
class Transfer(models.Model):
|
||||
@ -668,10 +805,17 @@ class FeedInfo(models.Model):
|
||||
verbose_name=_("Feed version"),
|
||||
)
|
||||
|
||||
gtfs_feed = models.ForeignKey(
|
||||
GTFSFeed,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("GTFS feed"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Feed info")
|
||||
verbose_name_plural = _("Feed infos")
|
||||
ordering = ("publisher_name",)
|
||||
indexes = (models.Index(fields=['gtfs_feed']),)
|
||||
|
||||
|
||||
class TripUpdate(models.Model):
|
||||
@ -705,6 +849,7 @@ class TripUpdate(models.Model):
|
||||
verbose_name_plural = _("Trip updates")
|
||||
ordering = ("start_date", "trip",)
|
||||
unique_together = ("trip", "start_date", "start_time",)
|
||||
indexes = (models.Index(fields=['trip']),)
|
||||
|
||||
|
||||
class StopTimeUpdate(models.Model):
|
||||
@ -753,3 +898,4 @@ class StopTimeUpdate(models.Model):
|
||||
verbose_name_plural = _("Stop time updates")
|
||||
ordering = ("trip_update", "stop_time",)
|
||||
unique_together = ("trip_update", "stop_time",)
|
||||
indexes = (models.Index(fields=['trip_update']), models.Index(fields=['stop_time']),)
|
||||
|
Reference in New Issue
Block a user