Restructurate GTFS feeds into dedicated models
This commit is contained in:
parent
820fc0cc19
commit
11949228ee
|
@ -35,6 +35,7 @@ coverage
|
||||||
secrets.py
|
secrets.py
|
||||||
settings_local.py
|
settings_local.py
|
||||||
*.log
|
*.log
|
||||||
|
*.txt
|
||||||
media/
|
media/
|
||||||
output/
|
output/
|
||||||
/static/
|
/static/
|
||||||
|
|
|
@ -40,23 +40,23 @@ function AutocompleteStop(params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOptionGroup(option) {
|
function getOptionGroup(option) {
|
||||||
switch (option.transport_type) {
|
switch (option.gtfs_feed) {
|
||||||
case "TGV":
|
case "FR-SNCF-TGV":
|
||||||
case "IC":
|
case "FR-SNCF-IC":
|
||||||
case "TER":
|
case "FR-SNCF-TER":
|
||||||
return "TGV/TER/Intercités"
|
return "TGV/TER/Intercités"
|
||||||
case "TN":
|
case "FR-IDF-TN":
|
||||||
return "Transilien"
|
return "Transilien"
|
||||||
case "ES":
|
case "FR-EUROSTAR":
|
||||||
return "Eurostar"
|
return "Eurostar"
|
||||||
case "TI":
|
case "IT-FRA-TI":
|
||||||
return "Trenitalia France"
|
return "Trenitalia France"
|
||||||
case "RENFE":
|
case "ES-RENFE":
|
||||||
return "RENFE"
|
return "RENFE"
|
||||||
case "OBB":
|
case "AT-OBB":
|
||||||
return "ÖBB"
|
return "ÖBB"
|
||||||
default:
|
default:
|
||||||
return option.transport_type
|
return option.gtfs_feed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,10 +229,10 @@ function TrainRow({train, tableType, date, time}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTrainType(train, trip, route) {
|
function getTrainType(train, trip, route) {
|
||||||
switch (route.transport_type) {
|
switch (route.gtfs_feed) {
|
||||||
case "TGV":
|
case "FR-SNCF-TGV":
|
||||||
case "TER":
|
case "FR-SNCF-IC":
|
||||||
case "IC":
|
case "FR-SNCF-TER":
|
||||||
let trainType = train.stop.split("StopPoint:OCE")[1].split("-")[0]
|
let trainType = train.stop.split("StopPoint:OCE")[1].split("-")[0]
|
||||||
switch (trainType) {
|
switch (trainType) {
|
||||||
case "Train TER":
|
case "Train TER":
|
||||||
|
@ -244,20 +244,20 @@ function getTrainType(train, trip, route) {
|
||||||
default:
|
default:
|
||||||
return trainType
|
return trainType
|
||||||
}
|
}
|
||||||
case "TN":
|
case "FR-IDF-TN":
|
||||||
return route.short_name
|
return route.short_name
|
||||||
case "ES":
|
case "FR-EUROSTAR":
|
||||||
return "Eurostar"
|
return "Eurostar"
|
||||||
case "TI":
|
case "IT-FRA-TI":
|
||||||
return "Trenitalia"
|
return "Trenitalia France"
|
||||||
case "RENFE":
|
case "ES-RENFE":
|
||||||
return "RENFE"
|
return "RENFE"
|
||||||
case "OBB":
|
case "AT-OBB":
|
||||||
if (trip.short_name.startsWith("NJ"))
|
if (trip.short_name?.startsWith("NJ"))
|
||||||
return "NJ"
|
return "NJ"
|
||||||
return "ÖBB"
|
return "ÖBB"
|
||||||
default:
|
default:
|
||||||
return ""
|
return trip.short_name?.split(" ")[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,6 +280,7 @@ function getTrainTypeDisplay(trainType) {
|
||||||
case "Eurostar":
|
case "Eurostar":
|
||||||
return <img src="/eurostar_mini.svg" alt="Eurostar" width="80%" />
|
return <img src="/eurostar_mini.svg" alt="Eurostar" width="80%" />
|
||||||
case "Trenitalia":
|
case "Trenitalia":
|
||||||
|
case "Trenitalia France":
|
||||||
return <img src="/trenitalia.svg" alt="Frecciarossa" width="80%" />
|
return <img src="/trenitalia.svg" alt="Frecciarossa" width="80%" />
|
||||||
case "RENFE":
|
case "RENFE":
|
||||||
return <img src="/renfe.svg" alt="RENFE" width="80%" />
|
return <img src="/renfe.svg" alt="RENFE" width="80%" />
|
||||||
|
|
|
@ -11,12 +11,11 @@ from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
from sncf.api.serializers import AgencySerializer, StopSerializer, RouteSerializer, TripSerializer, \
|
from sncf.api.serializers import AgencySerializer, StopSerializer, RouteSerializer, TripSerializer, \
|
||||||
StopTimeSerializer, CalendarSerializer, CalendarDateSerializer, TransferSerializer, \
|
StopTimeSerializer, CalendarSerializer, CalendarDateSerializer, TransferSerializer, \
|
||||||
FeedInfoSerializer, TripUpdateSerializer, StopTimeUpdateSerializer
|
FeedInfoSerializer, TripUpdateSerializer, StopTimeUpdateSerializer
|
||||||
from sncfgtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
from sncfgtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, StopTimeUpdate, \
|
||||||
Transfer, FeedInfo, TripUpdate, StopTimeUpdate
|
Transfer, Trip, TripUpdate
|
||||||
|
|
||||||
CACHE_CONTROL = cache_control(max_age=7200)
|
CACHE_CONTROL = cache_control(max_age=7200)
|
||||||
LAST_MODIFIED = last_modified(lambda *args, **kwargs: datetime.fromisoformat(
|
LAST_MODIFIED = last_modified(lambda *args, **kwargs: GTFSFeed.objects.order_by('-last_modified').first().last_modified)
|
||||||
FeedInfo.objects.get(publisher_name="SNCF_default").version))
|
|
||||||
LOOKUP_VALUE_REGEX = r"[\w.: |-]+"
|
LOOKUP_VALUE_REGEX = r"[\w.: |-]+"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,38 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.forms import BaseInlineFormSet
|
||||||
|
|
||||||
from sncfgtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
from sncfgtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, \
|
||||||
Transfer, FeedInfo, StopTimeUpdate, TripUpdate
|
Route, Stop, StopTime, StopTimeUpdate, Transfer, Trip, TripUpdate
|
||||||
|
|
||||||
|
|
||||||
|
class LimitModelFormset(BaseInlineFormSet):
|
||||||
|
""" Base Inline formset to limit inline Model query results. """
|
||||||
|
def get_queryset(self):
|
||||||
|
return super(LimitModelFormset, self).get_queryset()[:50]
|
||||||
|
|
||||||
|
|
||||||
class CalendarDateInline(admin.TabularInline):
|
class CalendarDateInline(admin.TabularInline):
|
||||||
model = CalendarDate
|
model = CalendarDate
|
||||||
extra = 0
|
extra = 0
|
||||||
|
formset = LimitModelFormset
|
||||||
|
|
||||||
|
|
||||||
class TripInline(admin.TabularInline):
|
class TripInline(admin.TabularInline):
|
||||||
model = Trip
|
model = Trip
|
||||||
extra = 0
|
extra = 0
|
||||||
|
formset = LimitModelFormset
|
||||||
autocomplete_fields = ('route', 'service',)
|
autocomplete_fields = ('route', 'service',)
|
||||||
show_change_link = True
|
show_change_link = True
|
||||||
ordering = ('service',)
|
ordering = ('service',)
|
||||||
|
readonly_fields = ('gtfs_feed',)
|
||||||
|
|
||||||
|
|
||||||
class StopTimeInline(admin.TabularInline):
|
class StopTimeInline(admin.TabularInline):
|
||||||
model = StopTime
|
model = StopTime
|
||||||
extra = 0
|
extra = 0
|
||||||
|
formset = LimitModelFormset
|
||||||
autocomplete_fields = ('stop',)
|
autocomplete_fields = ('stop',)
|
||||||
|
readonly_fields = ('id',)
|
||||||
show_change_link = True
|
show_change_link = True
|
||||||
ordering = ('stop_sequence',)
|
ordering = ('stop_sequence',)
|
||||||
|
|
||||||
|
@ -28,47 +40,59 @@ class StopTimeInline(admin.TabularInline):
|
||||||
class TripUpdateInline(admin.StackedInline):
|
class TripUpdateInline(admin.StackedInline):
|
||||||
model = TripUpdate
|
model = TripUpdate
|
||||||
extra = 0
|
extra = 0
|
||||||
|
formset = LimitModelFormset
|
||||||
autocomplete_fields = ('trip',)
|
autocomplete_fields = ('trip',)
|
||||||
|
|
||||||
|
|
||||||
class StopTimeUpdateInline(admin.StackedInline):
|
class StopTimeUpdateInline(admin.StackedInline):
|
||||||
model = StopTimeUpdate
|
model = StopTimeUpdate
|
||||||
extra = 0
|
extra = 0
|
||||||
|
formset = LimitModelFormset
|
||||||
autocomplete_fields = ('trip_update', 'stop_time',)
|
autocomplete_fields = ('trip_update', 'stop_time',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(GTFSFeed)
|
||||||
|
class GTFSFeedAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'code', 'country', 'last_modified',)
|
||||||
|
list_filter = ('country', 'last_modified',)
|
||||||
|
search_fields = ('name', 'code',)
|
||||||
|
readonly_fields = ('code',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Agency)
|
@admin.register(Agency)
|
||||||
class AgencyAdmin(admin.ModelAdmin):
|
class AgencyAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'id', 'url', 'timezone',)
|
list_display = ('name', 'id', 'url', 'timezone', 'gtfs_feed',)
|
||||||
|
list_filter = ('gtfs_feed', 'timezone',)
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
|
autocomplete_fields = ('gtfs_feed',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Stop)
|
@admin.register(Stop)
|
||||||
class StopAdmin(admin.ModelAdmin):
|
class StopAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'id', 'lat', 'lon', 'location_type',)
|
list_display = ('name', 'id', 'lat', 'lon', 'location_type',)
|
||||||
list_filter = ('location_type', 'transport_type',)
|
list_filter = ('location_type', 'gtfs_feed',)
|
||||||
search_fields = ('name', 'id',)
|
search_fields = ('name', 'id',)
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
autocomplete_fields = ('parent_station',)
|
autocomplete_fields = ('parent_station', 'gtfs_feed',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Route)
|
@admin.register(Route)
|
||||||
class RouteAdmin(admin.ModelAdmin):
|
class RouteAdmin(admin.ModelAdmin):
|
||||||
list_display = ('short_name', 'long_name', 'id', 'type',)
|
list_display = ('__str__', 'id', 'type', 'gtfs_feed',)
|
||||||
list_filter = ('transport_type', 'type', 'agency',)
|
list_filter = ('gtfs_feed', 'type', 'agency',)
|
||||||
search_fields = ('long_name', 'short_name', 'id',)
|
search_fields = ('long_name', 'short_name', 'id',)
|
||||||
ordering = ('long_name',)
|
ordering = ('long_name',)
|
||||||
autocomplete_fields = ('agency',)
|
autocomplete_fields = ('agency', 'gtfs_feed',)
|
||||||
inlines = (TripInline,)
|
inlines = (TripInline,)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Trip)
|
@admin.register(Trip)
|
||||||
class TripAdmin(admin.ModelAdmin):
|
class TripAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'route', 'service', 'headsign', 'direction_id',)
|
list_display = ('id', 'origin_destination', 'route', 'service', 'headsign', 'direction_id',)
|
||||||
list_filter = ('direction_id', 'route__transport_type',)
|
list_filter = ('direction_id', 'route__gtfs_feed',)
|
||||||
search_fields = ('id', 'route__id', 'route__long_name', 'service__id', 'headsign',)
|
search_fields = ('id', 'route__id', 'route__long_name', 'service__id', 'headsign',)
|
||||||
ordering = ('route', 'service',)
|
ordering = ('route', 'service',)
|
||||||
autocomplete_fields = ('route', 'service',)
|
autocomplete_fields = ('route', 'service', 'gtfs_feed',)
|
||||||
inlines = (StopTimeInline, TripUpdateInline,)
|
inlines = (StopTimeInline, TripUpdateInline,)
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,28 +100,30 @@ class TripAdmin(admin.ModelAdmin):
|
||||||
class StopTimeAdmin(admin.ModelAdmin):
|
class StopTimeAdmin(admin.ModelAdmin):
|
||||||
list_display = ('trip', 'stop', 'arrival_time', 'departure_time',
|
list_display = ('trip', 'stop', 'arrival_time', 'departure_time',
|
||||||
'stop_sequence', 'pickup_type', 'drop_off_type',)
|
'stop_sequence', 'pickup_type', 'drop_off_type',)
|
||||||
list_filter = ('pickup_type', 'drop_off_type', 'trip__route__transport_type',)
|
list_filter = ('pickup_type', 'drop_off_type', 'trip__route__gtfs_feed',)
|
||||||
search_fields = ('trip__id', 'stop__name', 'arrival_time', 'departure_time',)
|
search_fields = ('trip__id', 'stop__name', 'arrival_time', 'departure_time',)
|
||||||
ordering = ('trip', 'stop_sequence',)
|
ordering = ('trip', 'stop_sequence',)
|
||||||
autocomplete_fields = ('trip', 'stop',)
|
autocomplete_fields = ('trip', 'stop',)
|
||||||
|
readonly_fields = ('id',)
|
||||||
inlines = (StopTimeUpdateInline,)
|
inlines = (StopTimeUpdateInline,)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Calendar)
|
@admin.register(Calendar)
|
||||||
class CalendarAdmin(admin.ModelAdmin):
|
class CalendarAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'transport_type', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday',
|
list_display = ('id', 'gtfs_feed', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday',
|
||||||
'saturday', 'sunday', 'start_date', 'end_date',)
|
'saturday', 'sunday', 'start_date', 'end_date',)
|
||||||
list_filter = ('transport_type', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday',
|
list_filter = ('gtfs_feed', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday',
|
||||||
'start_date', 'end_date',)
|
'start_date', 'end_date',)
|
||||||
search_fields = ('id', 'start_date', 'end_date',)
|
search_fields = ('id', 'start_date', 'end_date',)
|
||||||
ordering = ('transport_type', 'id',)
|
autocomplete_fields = ('gtfs_feed',)
|
||||||
|
ordering = ('gtfs_feed', 'id',)
|
||||||
inlines = (CalendarDateInline, TripInline,)
|
inlines = (CalendarDateInline, TripInline,)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CalendarDate)
|
@admin.register(CalendarDate)
|
||||||
class CalendarDateAdmin(admin.ModelAdmin):
|
class CalendarDateAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'service_id', 'date', 'exception_type',)
|
list_display = ('id', 'service_id', 'date', 'exception_type',)
|
||||||
list_filter = ('exception_type', 'date', 'service__transport_type',)
|
list_filter = ('exception_type', 'date', 'service__gtfs_feed',)
|
||||||
search_fields = ('id', 'date',)
|
search_fields = ('id', 'date',)
|
||||||
ordering = ('date', 'service_id',)
|
ordering = ('date', 'service_id',)
|
||||||
|
|
||||||
|
@ -116,6 +142,7 @@ class FeedInfoAdmin(admin.ModelAdmin):
|
||||||
'end_date', 'version',)
|
'end_date', 'version',)
|
||||||
search_fields = ('publisher_name', 'publisher_url', 'lang', 'start_date',
|
search_fields = ('publisher_name', 'publisher_url', 'lang', 'start_date',
|
||||||
'end_date', 'version',)
|
'end_date', 'version',)
|
||||||
|
autocomplete_fields = ('gtfs_feed',)
|
||||||
ordering = ('publisher_name',)
|
ordering = ('publisher_name',)
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,7 +150,7 @@ class FeedInfoAdmin(admin.ModelAdmin):
|
||||||
class StopTimeUpdateAdmin(admin.ModelAdmin):
|
class StopTimeUpdateAdmin(admin.ModelAdmin):
|
||||||
list_display = ('trip_update', 'stop_time', 'arrival_delay', 'arrival_time',
|
list_display = ('trip_update', 'stop_time', 'arrival_delay', 'arrival_time',
|
||||||
'departure_delay', 'departure_time', 'schedule_relationship',)
|
'departure_delay', 'departure_time', 'schedule_relationship',)
|
||||||
list_filter = ('schedule_relationship',)
|
list_filter = ('schedule_relationship', 'trip_update__trip__gtfs_feed',)
|
||||||
search_fields = ('trip_update__trip__id', 'stop_time__stop__name', 'arrival_time', 'departure_time',)
|
search_fields = ('trip_update__trip__id', 'stop_time__stop__name', 'arrival_time', 'departure_time',)
|
||||||
ordering = ('trip_update', 'stop_time',)
|
ordering = ('trip_update', 'stop_time',)
|
||||||
autocomplete_fields = ('trip_update', 'stop_time',)
|
autocomplete_fields = ('trip_update', 'stop_time',)
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "sncfgtfs.gtfsfeed",
|
||||||
|
"pk": "FR-SNCF-TGV",
|
||||||
|
"fields": {
|
||||||
|
"name": "SNCF - TGV",
|
||||||
|
"country": "FR",
|
||||||
|
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export_gtfs_voyages.zip",
|
||||||
|
"rt_feed_url": "https://proxy.transport.data.gouv.fr/resource/sncf-tgv-gtfs-rt-trip-updates"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "sncfgtfs.gtfsfeed",
|
||||||
|
"pk": "FR-SNCF-IC",
|
||||||
|
"fields": {
|
||||||
|
"name": "SNCF - Intercités",
|
||||||
|
"country": "FR",
|
||||||
|
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-intercites-gtfs-last.zip",
|
||||||
|
"rt_feed_url": "https://proxy.transport.data.gouv.fr/resource/sncf-ic-gtfs-rt-trip-updates"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "sncfgtfs.gtfsfeed",
|
||||||
|
"pk": "FR-SNCF-TER",
|
||||||
|
"fields": {
|
||||||
|
"name": "SNCF - TER",
|
||||||
|
"country": "FR",
|
||||||
|
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-ter-gtfs-last.zip",
|
||||||
|
"rt_feed_url": "https://proxy.transport.data.gouv.fr/resource/sncf-ter-gtfs-rt-trip-updates"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "sncfgtfs.gtfsfeed",
|
||||||
|
"pk": "FR-IDF-TN",
|
||||||
|
"fields": {
|
||||||
|
"name": "SNCF - Transilien",
|
||||||
|
"country": "FR",
|
||||||
|
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/transilien-gtfs.zip",
|
||||||
|
"rt_feed_url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "sncfgtfs.gtfsfeed",
|
||||||
|
"pk": "FR-EUROSTAR",
|
||||||
|
"fields": {
|
||||||
|
"name": "Eurostar",
|
||||||
|
"country": "FR",
|
||||||
|
"feed_url": "https://www.data.gouv.fr/fr/datasets/r/9089b550-696e-4ae0-87b5-40ea55a14292",
|
||||||
|
"rt_feed_url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "sncfgtfs.gtfsfeed",
|
||||||
|
"pk": "IT-FRA-TI",
|
||||||
|
"fields": {
|
||||||
|
"name": "Trenitalia France",
|
||||||
|
"country": "FR",
|
||||||
|
"feed_url": "https://thello.axelor.com/public/gtfs/gtfs.zip",
|
||||||
|
"rt_feed_url": "https://thello.axelor.com/public/gtfs/GTFS-RT.bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "sncfgtfs.gtfsfeed",
|
||||||
|
"pk": "ES-RENFE",
|
||||||
|
"fields": {
|
||||||
|
"name": "Renfe",
|
||||||
|
"country": "ES",
|
||||||
|
"feed_url": "https://ssl.renfe.com/gtransit/Fichero_AV_LD/google_transit.zip",
|
||||||
|
"rt_feed_url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "sncfgtfs.gtfsfeed",
|
||||||
|
"pk": "AT-ÖBB",
|
||||||
|
"fields": {
|
||||||
|
"name": "ÖBB",
|
||||||
|
"country": "AT",
|
||||||
|
"feed_url": "https://static.oebb.at/open-data/soll-fahrplan-gtfs/GTFS_OP_2024_obb.zip",
|
||||||
|
"rt_feed_url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "sncfgtfs.gtfsfeed",
|
||||||
|
"pk": "CH-ALL",
|
||||||
|
"fields": {
|
||||||
|
"name": "Transports suisses",
|
||||||
|
"country": "CH",
|
||||||
|
"feed_url": "https://opentransportdata.swiss/fr/dataset/timetable-2024-gtfs2020/permalink",
|
||||||
|
"rt_feed_url": "https://api.opentransportdata.swiss/gtfsrt2020"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -2,7 +2,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 1.0\n"
|
"Project-Id-Version: 1.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-02-10 19:57+0100\n"
|
"POT-Creation-Date: 2024-05-09 19:27+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Emmy D'Anello <ynerant@emy.lu>\n"
|
"Last-Translator: Emmy D'Anello <ynerant@emy.lu>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -12,555 +12,808 @@ msgstr ""
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:6
|
|
||||||
msgid "TGV"
|
|
||||||
msgstr "TGV"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:7
|
|
||||||
msgid "TER"
|
|
||||||
msgstr "TER"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:8
|
|
||||||
msgid "Intercités"
|
|
||||||
msgstr "Intercités"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:9
|
|
||||||
msgid "Transilien"
|
|
||||||
msgstr "Transilien"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:10
|
|
||||||
msgid "Eurostar"
|
|
||||||
msgstr "Eurostar"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:11
|
#: sncfgtfs/models.py:11
|
||||||
msgid "Trenitalia"
|
msgid "Albania"
|
||||||
msgstr "Trenitalia"
|
msgstr "Albanie"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:12
|
#: sncfgtfs/models.py:12
|
||||||
msgid "Renfe"
|
msgid "Andorra"
|
||||||
msgstr "Renfe"
|
msgstr "Andorre"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:13
|
#: sncfgtfs/models.py:13
|
||||||
msgid "ÖBB"
|
msgid "Armenia"
|
||||||
msgstr "ÖBB"
|
msgstr "Arménie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:14
|
||||||
|
msgid "Austria"
|
||||||
|
msgstr "Autriche"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:15
|
||||||
|
msgid "Azerbaijan"
|
||||||
|
msgstr "Azerbaijan"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:16
|
||||||
|
msgid "Belgium"
|
||||||
|
msgstr "Belgique"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:17
|
#: sncfgtfs/models.py:17
|
||||||
|
msgid "Bosnia and Herzegovina"
|
||||||
|
msgstr " Bosnie-Herzégovine"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:18
|
||||||
|
msgid "Bulgaria"
|
||||||
|
msgstr "Bulgarie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:19
|
||||||
|
msgid "Croatia"
|
||||||
|
msgstr "Croatie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:20
|
||||||
|
msgid "Cyprus"
|
||||||
|
msgstr "Chypre"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:21
|
||||||
|
msgid "Czech Republic"
|
||||||
|
msgstr "République Tchèque"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:22
|
||||||
|
msgid "Denmark"
|
||||||
|
msgstr "Danemark"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:23
|
||||||
|
msgid "Estonia"
|
||||||
|
msgstr "Estonie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:24
|
||||||
|
msgid "Finland"
|
||||||
|
msgstr "Finlande"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:25
|
||||||
|
msgid "France"
|
||||||
|
msgstr "France"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:26
|
||||||
|
msgid "Georgia"
|
||||||
|
msgstr "Géorgie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:27
|
||||||
|
msgid "Germany"
|
||||||
|
msgstr "Allemagne"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:28
|
||||||
|
msgid "Greece"
|
||||||
|
msgstr "Grèce"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:29
|
||||||
|
msgid "Hungary"
|
||||||
|
msgstr "Hongrie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:30
|
||||||
|
msgid "Iceland"
|
||||||
|
msgstr "Islande"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:31
|
||||||
|
msgid "Ireland"
|
||||||
|
msgstr "Irlande"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:32
|
||||||
|
msgid "Italy"
|
||||||
|
msgstr "Italie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:33
|
||||||
|
msgid "Latvia"
|
||||||
|
msgstr "Lettonie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:34
|
||||||
|
msgid "Liechtenstein"
|
||||||
|
msgstr "Liechtenstein"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:35
|
||||||
|
msgid "Lithuania"
|
||||||
|
msgstr "Lituanie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:36
|
||||||
|
msgid "Luxembourg"
|
||||||
|
msgstr "Luxembourg"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:37
|
||||||
|
msgid "Malta"
|
||||||
|
msgstr "Malte"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:38
|
||||||
|
msgid "Moldova"
|
||||||
|
msgstr "Moldavie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:39
|
||||||
|
msgid "Monaco"
|
||||||
|
msgstr "Monaco"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:40
|
||||||
|
msgid "Montenegro"
|
||||||
|
msgstr "Monténégro"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:41
|
||||||
|
msgid "Netherlands"
|
||||||
|
msgstr "Pays-Bas"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:42
|
||||||
|
msgid "North Macedonia"
|
||||||
|
msgstr "Macédoine du Nord"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:43
|
||||||
|
msgid "Norway"
|
||||||
|
msgstr "Norvège"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:44
|
||||||
|
msgid "Poland"
|
||||||
|
msgstr "Pologne"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:45
|
||||||
|
msgid "Portugal"
|
||||||
|
msgstr "Portugal"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:46
|
||||||
|
msgid "Romania"
|
||||||
|
msgstr "Roumanie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:47
|
||||||
|
msgid "San Marino"
|
||||||
|
msgstr "Saint-Marin"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:48
|
||||||
|
msgid "Serbia"
|
||||||
|
msgstr "Serbie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:49
|
||||||
|
msgid "Slovakia"
|
||||||
|
msgstr "Slovaquie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:50
|
||||||
|
msgid "Slovenia"
|
||||||
|
msgstr "Slovénie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:51
|
||||||
|
msgid "Spain"
|
||||||
|
msgstr "Espagne"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:52
|
||||||
|
msgid "Sweden"
|
||||||
|
msgstr "Suède"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:53
|
||||||
|
msgid "Switzerland"
|
||||||
|
msgstr "Suisse"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:54
|
||||||
|
msgid "Turkey"
|
||||||
|
msgstr "Turquie"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:55
|
||||||
|
msgid "United Kingdom"
|
||||||
|
msgstr "Royaume-Uni"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:56
|
||||||
|
msgid "Ukraine"
|
||||||
|
msgstr "Ukraine"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:60
|
||||||
msgid "Stop/platform"
|
msgid "Stop/platform"
|
||||||
msgstr "Arrêt / quai"
|
msgstr "Arrêt / quai"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:18
|
#: sncfgtfs/models.py:61
|
||||||
msgid "Station"
|
msgid "Station"
|
||||||
msgstr "Gare"
|
msgstr "Gare"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:19
|
#: sncfgtfs/models.py:62
|
||||||
msgid "Entrance/exit"
|
msgid "Entrance/exit"
|
||||||
msgstr "Entrée / sortie"
|
msgstr "Entrée / sortie"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:20
|
#: sncfgtfs/models.py:63
|
||||||
msgid "Generic node"
|
msgid "Generic node"
|
||||||
msgstr "Nœud générique"
|
msgstr "Nœud générique"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:21
|
#: sncfgtfs/models.py:64
|
||||||
msgid "Boarding area"
|
msgid "Boarding area"
|
||||||
msgstr "Zone d'embarquement"
|
msgstr "Zone d'embarquement"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:25
|
#: sncfgtfs/models.py:68
|
||||||
msgid "No information"
|
msgid "No information"
|
||||||
msgstr "Pas d'information"
|
msgstr "Pas d'information"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:26
|
#: sncfgtfs/models.py:69
|
||||||
msgid "Possible"
|
msgid "Possible"
|
||||||
msgstr "Possible"
|
msgstr "Possible"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:27 sncfgtfs/models.py:57
|
#: sncfgtfs/models.py:70 sncfgtfs/models.py:100
|
||||||
msgid "Not possible"
|
msgid "Not possible"
|
||||||
msgstr "Impossible"
|
msgstr "Impossible"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:31
|
#: sncfgtfs/models.py:74
|
||||||
msgid "Regular"
|
msgid "Regular"
|
||||||
msgstr "Régulier"
|
msgstr "Régulier"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:32
|
#: sncfgtfs/models.py:75
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Aucun"
|
msgstr "Aucun"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:33
|
#: sncfgtfs/models.py:76
|
||||||
msgid "Must phone agency"
|
msgid "Must phone agency"
|
||||||
msgstr "Doit téléphoner à l'agence"
|
msgstr "Doit téléphoner à l'agence"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:34
|
#: sncfgtfs/models.py:77
|
||||||
msgid "Must coordinate with driver"
|
msgid "Must coordinate with driver"
|
||||||
msgstr "Doit se coordonner avec læ conducteurice"
|
msgstr "Doit se coordonner avec læ conducteurice"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:38
|
#: sncfgtfs/models.py:81
|
||||||
msgid "Tram"
|
msgid "Tram"
|
||||||
msgstr "Tram"
|
msgstr "Tram"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:39
|
#: sncfgtfs/models.py:82
|
||||||
msgid "Metro"
|
msgid "Metro"
|
||||||
msgstr "Métro"
|
msgstr "Métro"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:40
|
#: sncfgtfs/models.py:83
|
||||||
msgid "Rail"
|
msgid "Rail"
|
||||||
msgstr "Rail"
|
msgstr "Rail"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:41
|
#: sncfgtfs/models.py:84
|
||||||
msgid "Bus"
|
msgid "Bus"
|
||||||
msgstr "Bus"
|
msgstr "Bus"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:42
|
#: sncfgtfs/models.py:85
|
||||||
msgid "Ferry"
|
msgid "Ferry"
|
||||||
msgstr "Ferry"
|
msgstr "Ferry"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:43
|
#: sncfgtfs/models.py:86
|
||||||
msgid "Cable car"
|
msgid "Cable car"
|
||||||
msgstr "Câble"
|
msgstr "Câble"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:44
|
#: sncfgtfs/models.py:87
|
||||||
msgid "Gondola"
|
msgid "Gondola"
|
||||||
msgstr "Gondole"
|
msgstr "Gondole"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:45
|
#: sncfgtfs/models.py:88
|
||||||
msgid "Funicular"
|
msgid "Funicular"
|
||||||
msgstr "Funiculaire"
|
msgstr "Funiculaire"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:49
|
#: sncfgtfs/models.py:92
|
||||||
msgid "Outbound"
|
msgid "Outbound"
|
||||||
msgstr "Vers l'extérieur"
|
msgstr "Vers l'extérieur"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:50
|
#: sncfgtfs/models.py:93
|
||||||
msgid "Inbound"
|
msgid "Inbound"
|
||||||
msgstr "Vers l'intérieur"
|
msgstr "Vers l'intérieur"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:54
|
#: sncfgtfs/models.py:97
|
||||||
msgid "Recommended"
|
msgid "Recommended"
|
||||||
msgstr "Recommandé"
|
msgstr "Recommandé"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:55
|
#: sncfgtfs/models.py:98
|
||||||
msgid "Timed"
|
msgid "Timed"
|
||||||
msgstr "Correspondance programmée"
|
msgstr "Correspondance programmée"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:56
|
#: sncfgtfs/models.py:99
|
||||||
msgid "Minimum time"
|
msgid "Minimum time"
|
||||||
msgstr "Temps de correspondance minimum requis"
|
msgstr "Temps de correspondance minimum requis"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:61 sncfgtfs/models.py:67
|
#: sncfgtfs/models.py:104 sncfgtfs/models.py:110
|
||||||
msgid "Added"
|
msgid "Added"
|
||||||
msgstr "Ajouté"
|
msgstr "Ajouté"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:62
|
#: sncfgtfs/models.py:105
|
||||||
msgid "Removed"
|
msgid "Removed"
|
||||||
msgstr "Supprimé"
|
msgstr "Supprimé"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:66 sncfgtfs/models.py:76
|
#: sncfgtfs/models.py:109 sncfgtfs/models.py:119
|
||||||
msgid "Scheduled"
|
msgid "Scheduled"
|
||||||
msgstr "Planifié"
|
msgstr "Planifié"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:68 sncfgtfs/models.py:79
|
#: sncfgtfs/models.py:111 sncfgtfs/models.py:122
|
||||||
msgid "Unscheduled"
|
msgid "Unscheduled"
|
||||||
msgstr "Non planifié"
|
msgstr "Non planifié"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:69
|
#: sncfgtfs/models.py:112
|
||||||
msgid "Canceled"
|
msgid "Canceled"
|
||||||
msgstr "Annulé"
|
msgstr "Annulé"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:70
|
#: sncfgtfs/models.py:113
|
||||||
msgid "Replacement"
|
msgid "Replacement"
|
||||||
msgstr "Remplacé"
|
msgstr "Remplacé"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:71
|
#: sncfgtfs/models.py:114
|
||||||
msgid "Duplicated"
|
msgid "Duplicated"
|
||||||
msgstr "Dupliqué"
|
msgstr "Dupliqué"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:72
|
#: sncfgtfs/models.py:115
|
||||||
msgid "Deleted"
|
msgid "Deleted"
|
||||||
msgstr "Supprimé"
|
msgstr "Supprimé"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:77
|
#: sncfgtfs/models.py:120
|
||||||
msgid "Skipped"
|
msgid "Skipped"
|
||||||
msgstr "Sauté"
|
msgstr "Sauté"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:78
|
#: sncfgtfs/models.py:121
|
||||||
msgid "No data"
|
msgid "No data"
|
||||||
msgstr "Pas de données"
|
msgstr "Pas de données"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:86
|
#: sncfgtfs/models.py:129
|
||||||
|
msgid "code"
|
||||||
|
msgstr "code"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:130
|
||||||
|
msgid "Unique code of the feed."
|
||||||
|
msgstr "Code unique du flux."
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:135
|
||||||
|
msgid "name"
|
||||||
|
msgstr "nom"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:137
|
||||||
|
msgid "Full name that describes the feed."
|
||||||
|
msgstr "Nom complet qui décrit le flux."
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:142
|
||||||
|
msgid "country"
|
||||||
|
msgstr "pays"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:147
|
||||||
|
msgid "feed URL"
|
||||||
|
msgstr "URL du flux"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:148
|
||||||
|
msgid ""
|
||||||
|
"URL to download the GTFS feed. Must point to a ZIP archive. See https://gtfs."
|
||||||
|
"org/schedule/ for more information."
|
||||||
|
msgstr ""
|
||||||
|
"URL où télécharger le flux GTFS. Doit pointer vers une archive ZIP. Voir "
|
||||||
|
"https://gtfs.org/fr/schedule/ pour plus d'informations."
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:153
|
||||||
|
msgid "realtime feed URL"
|
||||||
|
msgstr "URL du flux temps réel"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:156
|
||||||
|
msgid ""
|
||||||
|
"URL to download the GTFS-Realtime feed, in the GTFS-RT format. See https://"
|
||||||
|
"gtfs.org/realtime/ for more information."
|
||||||
|
msgstr ""
|
||||||
|
"URL où télécharger le flux GTFS-Temps réel, au format GTFS-RT. Voir https://"
|
||||||
|
"gtfs.org/fr/realtime/ pour plus d'informations."
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:161
|
||||||
|
msgid "last modified date"
|
||||||
|
msgstr "Date de dernière modification"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:168
|
||||||
|
msgid "ETag"
|
||||||
|
msgstr "ETag"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:171
|
||||||
|
msgid ""
|
||||||
|
"If applicable, corresponds to the tag of the last downloaded file. If it is "
|
||||||
|
"not modified, the file is the same."
|
||||||
|
msgstr ""
|
||||||
|
"Si applicable, correspond au tag du dernier fichier téléchargé. S'il n'est "
|
||||||
|
"pas modifié, le fichier est considéré comme identique."
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:179 sncfgtfs/models.py:226 sncfgtfs/models.py:326
|
||||||
|
#: sncfgtfs/models.py:405 sncfgtfs/models.py:486 sncfgtfs/models.py:696
|
||||||
|
#: sncfgtfs/models.py:811
|
||||||
|
msgid "GTFS feed"
|
||||||
|
msgstr "flux GTFS"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:180
|
||||||
|
msgid "GTFS feeds"
|
||||||
|
msgstr "flux GTFS"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:189
|
||||||
msgid "Agency ID"
|
msgid "Agency ID"
|
||||||
msgstr "ID de l'agence"
|
msgstr "ID de l'agence"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:91
|
#: sncfgtfs/models.py:194
|
||||||
msgid "Agency name"
|
msgid "Agency name"
|
||||||
msgstr "Nom de l'agence"
|
msgstr "Nom de l'agence"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:95
|
#: sncfgtfs/models.py:198
|
||||||
msgid "Agency URL"
|
msgid "Agency URL"
|
||||||
msgstr "URL de l'agence"
|
msgstr "URL de l'agence"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:100
|
#: sncfgtfs/models.py:203
|
||||||
msgid "Agency timezone"
|
msgid "Agency timezone"
|
||||||
msgstr "Fuseau horaire de l'agence"
|
msgstr "Fuseau horaire de l'agence"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:105
|
#: sncfgtfs/models.py:208
|
||||||
msgid "Agency language"
|
msgid "Agency language"
|
||||||
msgstr "Langue de l'agence"
|
msgstr "Langue de l'agence"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:111
|
#: sncfgtfs/models.py:214
|
||||||
msgid "Agency phone"
|
msgid "Agency phone"
|
||||||
msgstr "Téléphone de l'agence"
|
msgstr "Téléphone de l'agence"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:116
|
#: sncfgtfs/models.py:219
|
||||||
msgid "Agency email"
|
msgid "Agency email"
|
||||||
msgstr "Adresse email de l'agence"
|
msgstr "Adresse email de l'agence"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:124 sncfgtfs/models.py:242
|
#: sncfgtfs/models.py:233 sncfgtfs/models.py:356
|
||||||
msgid "Agency"
|
msgid "Agency"
|
||||||
msgstr "Agence"
|
msgstr "Agence"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:125
|
#: sncfgtfs/models.py:234
|
||||||
msgid "Agencies"
|
msgid "Agencies"
|
||||||
msgstr "Agences"
|
msgstr "Agences"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:133 sncfgtfs/models.py:459
|
#: sncfgtfs/models.py:243 sncfgtfs/models.py:593
|
||||||
msgid "Stop ID"
|
msgid "Stop ID"
|
||||||
msgstr "ID de l'arrêt"
|
msgstr "ID de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:138
|
#: sncfgtfs/models.py:248
|
||||||
msgid "Stop code"
|
msgid "Stop code"
|
||||||
msgstr "Code de l'arrêt"
|
msgstr "Code de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:144
|
#: sncfgtfs/models.py:254
|
||||||
msgid "Stop name"
|
msgid "Stop name"
|
||||||
msgstr "Nom de l'arrêt"
|
msgstr "Nom de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:149
|
#: sncfgtfs/models.py:259
|
||||||
msgid "Stop description"
|
msgid "Stop description"
|
||||||
msgstr "Description de l'arrêt"
|
msgstr "Description de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:154
|
#: sncfgtfs/models.py:264
|
||||||
msgid "Stop longitude"
|
msgid "Stop longitude"
|
||||||
msgstr "Longitude de l'arrêt"
|
msgstr "Longitude de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:158
|
#: sncfgtfs/models.py:268
|
||||||
msgid "Stop latitude"
|
msgid "Stop latitude"
|
||||||
msgstr "Latitude de l'arrêt"
|
msgstr "Latitude de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:163
|
#: sncfgtfs/models.py:273
|
||||||
msgid "Zone ID"
|
msgid "Zone ID"
|
||||||
msgstr "ID de la zone"
|
msgstr "ID de la zone"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:167
|
#: sncfgtfs/models.py:278
|
||||||
msgid "Stop URL"
|
msgid "Stop URL"
|
||||||
msgstr "URL de l'arrêt"
|
msgstr "URL de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:172
|
#: sncfgtfs/models.py:283
|
||||||
msgid "Location type"
|
msgid "Location type"
|
||||||
msgstr "Type de localisation"
|
msgstr "Type de localisation"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:181
|
#: sncfgtfs/models.py:292
|
||||||
msgid "Parent station"
|
msgid "Parent station"
|
||||||
msgstr "Gare parente"
|
msgstr "Gare parente"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:189
|
#: sncfgtfs/models.py:300
|
||||||
msgid "Stop timezone"
|
msgid "Stop timezone"
|
||||||
msgstr "Fuseau horaire de l'arrêt"
|
msgstr "Fuseau horaire de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:195
|
#: sncfgtfs/models.py:306
|
||||||
msgid "Level ID"
|
msgid "Level ID"
|
||||||
msgstr "ID du niveau"
|
msgstr "ID du niveau"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:200
|
#: sncfgtfs/models.py:311
|
||||||
msgid "Wheelchair boarding"
|
msgid "Wheelchair boarding"
|
||||||
msgstr "Embarquement en fauteuil roulant"
|
msgstr "Embarquement en fauteuil roulant"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:208
|
#: sncfgtfs/models.py:319
|
||||||
msgid "Platform code"
|
msgid "Platform code"
|
||||||
msgstr "Code du quai"
|
msgstr "Code du quai"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:214 sncfgtfs/models.py:286 sncfgtfs/models.py:560
|
#: sncfgtfs/models.py:338
|
||||||
msgid "Transport type"
|
|
||||||
msgstr "Type de transport"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:227
|
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Arrêt"
|
msgstr "Arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:228
|
#: sncfgtfs/models.py:339
|
||||||
msgid "Stops"
|
msgid "Stops"
|
||||||
msgstr "Arrêts"
|
msgstr "Arrêts"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:236 sncfgtfs/models.py:438 sncfgtfs/models.py:577
|
#: sncfgtfs/models.py:350 sncfgtfs/models.py:572 sncfgtfs/models.py:713
|
||||||
#: sncfgtfs/models.py:609
|
#: sncfgtfs/models.py:746
|
||||||
msgid "ID"
|
msgid "ID"
|
||||||
msgstr "Identifiant"
|
msgstr "Identifiant"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:248
|
#: sncfgtfs/models.py:365
|
||||||
msgid "Route short name"
|
msgid "Route short name"
|
||||||
msgstr "Nom court de la ligne"
|
msgstr "Nom court de la ligne"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:253
|
#: sncfgtfs/models.py:370
|
||||||
msgid "Route long name"
|
msgid "Route long name"
|
||||||
msgstr "Nom long de la ligne"
|
msgstr "Nom long de la ligne"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:258
|
#: sncfgtfs/models.py:376
|
||||||
msgid "Route description"
|
msgid "Route description"
|
||||||
msgstr "Description de la ligne"
|
msgstr "Description de la ligne"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:263
|
#: sncfgtfs/models.py:381
|
||||||
msgid "Route type"
|
msgid "Route type"
|
||||||
msgstr "Type de ligne"
|
msgstr "Type de ligne"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:268
|
#: sncfgtfs/models.py:386
|
||||||
msgid "Route URL"
|
msgid "Route URL"
|
||||||
msgstr "URL de la ligne"
|
msgstr "URL de la ligne"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:274
|
#: sncfgtfs/models.py:392
|
||||||
msgid "Route color"
|
msgid "Route color"
|
||||||
msgstr "Couleur de la ligne"
|
msgstr "Couleur de la ligne"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:280
|
#: sncfgtfs/models.py:398
|
||||||
msgid "Route text color"
|
msgid "Route text color"
|
||||||
msgstr "Couleur du texte de la ligne"
|
msgstr "Couleur du texte de la ligne"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:294 sncfgtfs/models.py:309
|
#: sncfgtfs/models.py:412 sncfgtfs/models.py:428
|
||||||
msgid "Route"
|
msgid "Route"
|
||||||
msgstr "Ligne"
|
msgstr "Ligne"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:295
|
#: sncfgtfs/models.py:413
|
||||||
msgid "Routes"
|
msgid "Routes"
|
||||||
msgstr "Lignes"
|
msgstr "Lignes"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:303
|
#: sncfgtfs/models.py:422
|
||||||
msgid "Trip ID"
|
msgid "Trip ID"
|
||||||
msgstr "ID du trajet"
|
msgstr "ID du trajet"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:316 sncfgtfs/models.py:583
|
#: sncfgtfs/models.py:435 sncfgtfs/models.py:719
|
||||||
msgid "Service"
|
msgid "Service"
|
||||||
msgstr "Service"
|
msgstr "Service"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:322
|
#: sncfgtfs/models.py:441
|
||||||
msgid "Trip headsign"
|
msgid "Trip headsign"
|
||||||
msgstr "Destination du trajet"
|
msgstr "Destination du trajet"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:328
|
#: sncfgtfs/models.py:447
|
||||||
msgid "Trip short name"
|
msgid "Trip short name"
|
||||||
msgstr "Nom court du trajet"
|
msgstr "Nom court du trajet"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:333
|
#: sncfgtfs/models.py:452
|
||||||
msgid "Direction"
|
msgid "Direction"
|
||||||
msgstr "Direction"
|
msgstr "Direction"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:340
|
#: sncfgtfs/models.py:459
|
||||||
msgid "Block ID"
|
msgid "Block ID"
|
||||||
msgstr "ID du bloc"
|
msgstr "ID du bloc"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:346
|
#: sncfgtfs/models.py:465
|
||||||
msgid "Shape ID"
|
msgid "Shape ID"
|
||||||
msgstr "ID de la forme"
|
msgstr "ID de la forme"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:351
|
#: sncfgtfs/models.py:470
|
||||||
msgid "Wheelchair accessible"
|
msgid "Wheelchair accessible"
|
||||||
msgstr "Accessible en fauteuil roulant"
|
msgstr "Accessible en fauteuil roulant"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:358
|
#: sncfgtfs/models.py:477
|
||||||
msgid "Bikes allowed"
|
msgid "Bikes allowed"
|
||||||
msgstr "Vélos autorisés"
|
msgstr "Vélos autorisés"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:365
|
#: sncfgtfs/models.py:500 sncfgtfs/models.py:509 sncfgtfs/models.py:552
|
||||||
msgid "Last update"
|
#: sncfgtfs/models.py:554
|
||||||
msgstr "Dernière mise à jour"
|
msgid "Unknown"
|
||||||
|
msgstr "Inconnu"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:430 sncfgtfs/models.py:444 sncfgtfs/models.py:681
|
#: sncfgtfs/models.py:557
|
||||||
|
msgid "Origin → Destination"
|
||||||
|
msgstr "Origine → Destination"
|
||||||
|
|
||||||
|
#: sncfgtfs/models.py:563 sncfgtfs/models.py:578 sncfgtfs/models.py:825
|
||||||
msgid "Trip"
|
msgid "Trip"
|
||||||
msgstr "Trajet"
|
msgstr "Trajet"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:431
|
#: sncfgtfs/models.py:564
|
||||||
msgid "Trips"
|
msgid "Trips"
|
||||||
msgstr "Trajets"
|
msgstr "Trajets"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:449 sncfgtfs/models.py:731
|
#: sncfgtfs/models.py:583 sncfgtfs/models.py:876
|
||||||
msgid "Arrival time"
|
msgid "Arrival time"
|
||||||
msgstr "Heure d'arrivée"
|
msgstr "Heure d'arrivée"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:453 sncfgtfs/models.py:739
|
#: sncfgtfs/models.py:587 sncfgtfs/models.py:884
|
||||||
msgid "Departure time"
|
msgid "Departure time"
|
||||||
msgstr "Heure de départ"
|
msgstr "Heure de départ"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:464
|
#: sncfgtfs/models.py:598
|
||||||
msgid "Stop sequence"
|
msgid "Stop sequence"
|
||||||
msgstr "Séquence de l'arrêt"
|
msgstr "Séquence de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:469
|
#: sncfgtfs/models.py:603
|
||||||
msgid "Stop headsign"
|
msgid "Stop headsign"
|
||||||
msgstr "Destination de l'arrêt"
|
msgstr "Destination de l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:474
|
#: sncfgtfs/models.py:608
|
||||||
msgid "Pickup type"
|
msgid "Pickup type"
|
||||||
msgstr "Type de prise en charge"
|
msgstr "Type de prise en charge"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:481
|
#: sncfgtfs/models.py:615
|
||||||
msgid "Drop off type"
|
msgid "Drop off type"
|
||||||
msgstr "Type de dépose"
|
msgstr "Type de dépose"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:488
|
#: sncfgtfs/models.py:622
|
||||||
msgid "Timepoint"
|
msgid "Timepoint"
|
||||||
msgstr "Ponctualité"
|
msgstr "Ponctualité"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:511 sncfgtfs/models.py:721
|
#: sncfgtfs/models.py:645 sncfgtfs/models.py:866
|
||||||
msgid "Stop time"
|
msgid "Stop time"
|
||||||
msgstr "Heure d'arrêt"
|
msgstr "Heure d'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:512
|
#: sncfgtfs/models.py:646
|
||||||
msgid "Stop times"
|
msgid "Stop times"
|
||||||
msgstr "Heures d'arrêt"
|
msgstr "Heures d'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:519
|
#: sncfgtfs/models.py:654
|
||||||
msgid "Service ID"
|
msgid "Service ID"
|
||||||
msgstr "ID du service"
|
msgstr "ID du service"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:523
|
#: sncfgtfs/models.py:658
|
||||||
msgid "Monday"
|
msgid "Monday"
|
||||||
msgstr "Lundi"
|
msgstr "Lundi"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:527
|
#: sncfgtfs/models.py:662
|
||||||
msgid "Tuesday"
|
msgid "Tuesday"
|
||||||
msgstr "Mardi"
|
msgstr "Mardi"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:531
|
#: sncfgtfs/models.py:666
|
||||||
msgid "Wednesday"
|
msgid "Wednesday"
|
||||||
msgstr "Mercredi"
|
msgstr "Mercredi"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:535
|
#: sncfgtfs/models.py:670
|
||||||
msgid "Thursday"
|
msgid "Thursday"
|
||||||
msgstr "Jeudi"
|
msgstr "Jeudi"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:539
|
#: sncfgtfs/models.py:674
|
||||||
msgid "Friday"
|
msgid "Friday"
|
||||||
msgstr "Vendredi"
|
msgstr "Vendredi"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:543
|
#: sncfgtfs/models.py:678
|
||||||
msgid "Saturday"
|
msgid "Saturday"
|
||||||
msgstr "Samedi"
|
msgstr "Samedi"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:547
|
#: sncfgtfs/models.py:682
|
||||||
msgid "Sunday"
|
msgid "Sunday"
|
||||||
msgstr "Dimanche"
|
msgstr "Dimanche"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:551 sncfgtfs/models.py:687
|
#: sncfgtfs/models.py:686 sncfgtfs/models.py:831
|
||||||
msgid "Start date"
|
msgid "Start date"
|
||||||
msgstr "Date de début"
|
msgstr "Date de début"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:555
|
#: sncfgtfs/models.py:690
|
||||||
msgid "End date"
|
msgid "End date"
|
||||||
msgstr "Date de fin"
|
msgstr "Date de fin"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:568
|
#: sncfgtfs/models.py:703
|
||||||
msgid "Calendar"
|
msgid "Calendar"
|
||||||
msgstr "Calendrier"
|
msgstr "Calendrier"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:569
|
#: sncfgtfs/models.py:704
|
||||||
msgid "Calendars"
|
msgid "Calendars"
|
||||||
msgstr "Calendriers"
|
msgstr "Calendriers"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:588
|
#: sncfgtfs/models.py:724
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Date"
|
msgstr "Date"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:592
|
#: sncfgtfs/models.py:728
|
||||||
msgid "Exception type"
|
msgid "Exception type"
|
||||||
msgstr "Type d'exception"
|
msgstr "Type d'exception"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:600
|
#: sncfgtfs/models.py:736
|
||||||
msgid "Calendar date"
|
msgid "Calendar date"
|
||||||
msgstr "Date du calendrier"
|
msgstr "Date du calendrier"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:601
|
#: sncfgtfs/models.py:737
|
||||||
msgid "Calendar dates"
|
msgid "Calendar dates"
|
||||||
msgstr "Dates du calendrier"
|
msgstr "Dates du calendrier"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:615
|
#: sncfgtfs/models.py:752
|
||||||
msgid "From stop"
|
msgid "From stop"
|
||||||
msgstr "Depuis l'arrêt"
|
msgstr "Depuis l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:622
|
#: sncfgtfs/models.py:759
|
||||||
msgid "To stop"
|
msgid "To stop"
|
||||||
msgstr "Jusqu'à l'arrêt"
|
msgstr "Jusqu'à l'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:627
|
#: sncfgtfs/models.py:764
|
||||||
msgid "Transfer type"
|
msgid "Transfer type"
|
||||||
msgstr "Type de correspondance"
|
msgstr "Type de correspondance"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:633
|
#: sncfgtfs/models.py:770
|
||||||
msgid "Minimum transfer time"
|
msgid "Minimum transfer time"
|
||||||
msgstr "Temps de correspondance minimum"
|
msgstr "Temps de correspondance minimum"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:638
|
#: sncfgtfs/models.py:775
|
||||||
msgid "Transfer"
|
msgid "Transfer"
|
||||||
msgstr "Correspondance"
|
msgstr "Correspondance"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:639
|
#: sncfgtfs/models.py:776
|
||||||
msgid "Transfers"
|
msgid "Transfers"
|
||||||
msgstr "Correspondances"
|
msgstr "Correspondances"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:646
|
#: sncfgtfs/models.py:783
|
||||||
msgid "Feed publisher name"
|
msgid "Feed publisher name"
|
||||||
msgstr "Nom de l'éditeur du flux"
|
msgstr "Nom de l'éditeur du flux"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:650
|
#: sncfgtfs/models.py:787
|
||||||
msgid "Feed publisher URL"
|
msgid "Feed publisher URL"
|
||||||
msgstr "URL de l'éditeur du flux"
|
msgstr "URL de l'éditeur du flux"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:655
|
#: sncfgtfs/models.py:792
|
||||||
msgid "Feed language"
|
msgid "Feed language"
|
||||||
msgstr "Langue du flux"
|
msgstr "Langue du flux"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:659
|
#: sncfgtfs/models.py:796
|
||||||
msgid "Feed start date"
|
msgid "Feed start date"
|
||||||
msgstr "Date de début du flux"
|
msgstr "Date de début du flux"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:663
|
#: sncfgtfs/models.py:800
|
||||||
msgid "Feed end date"
|
msgid "Feed end date"
|
||||||
msgstr "Date de fin du flux"
|
msgstr "Date de fin du flux"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:668
|
#: sncfgtfs/models.py:805
|
||||||
msgid "Feed version"
|
msgid "Feed version"
|
||||||
msgstr "Version du flux"
|
msgstr "Version du flux"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:672
|
#: sncfgtfs/models.py:815
|
||||||
msgid "Feed info"
|
msgid "Feed info"
|
||||||
msgstr "Information du flux"
|
msgstr "Information du flux"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:673
|
#: sncfgtfs/models.py:816
|
||||||
msgid "Feed infos"
|
msgid "Feed infos"
|
||||||
msgstr "Informations du flux"
|
msgstr "Informations du flux"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:691
|
#: sncfgtfs/models.py:835
|
||||||
msgid "Start time"
|
msgid "Start time"
|
||||||
msgstr "Heure de début"
|
msgstr "Heure de début"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:695 sncfgtfs/models.py:743
|
#: sncfgtfs/models.py:839 sncfgtfs/models.py:888
|
||||||
msgid "Schedule relationship"
|
msgid "Schedule relationship"
|
||||||
msgstr "Relation de la planification"
|
msgstr "Relation de la planification"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:704 sncfgtfs/models.py:714
|
#: sncfgtfs/models.py:848 sncfgtfs/models.py:859
|
||||||
msgid "Trip update"
|
msgid "Trip update"
|
||||||
msgstr "Mise à jour du trajet"
|
msgstr "Mise à jour du trajet"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:705
|
#: sncfgtfs/models.py:849
|
||||||
msgid "Trip updates"
|
msgid "Trip updates"
|
||||||
msgstr "Mises à jour des trajets"
|
msgstr "Mises à jour des trajets"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:727
|
#: sncfgtfs/models.py:872
|
||||||
msgid "Arrival delay"
|
msgid "Arrival delay"
|
||||||
msgstr "Retard à l'arrivée"
|
msgstr "Retard à l'arrivée"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:735
|
#: sncfgtfs/models.py:880
|
||||||
msgid "Departure delay"
|
msgid "Departure delay"
|
||||||
msgstr "Retard au départ"
|
msgstr "Retard au départ"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:752
|
#: sncfgtfs/models.py:897
|
||||||
msgid "Stop time update"
|
msgid "Stop time update"
|
||||||
msgstr "Mise à jour du temps d'arrêt"
|
msgstr "Mise à jour du temps d'arrêt"
|
||||||
|
|
||||||
#: sncfgtfs/models.py:753
|
#: sncfgtfs/models.py:898
|
||||||
msgid "Stop time updates"
|
msgid "Stop time updates"
|
||||||
msgstr "Mises à jour des temps d'arrêt"
|
msgstr "Mises à jour des temps d'arrêt"
|
||||||
|
|
||||||
|
#~ msgid "TGV"
|
||||||
|
#~ msgstr "TGV"
|
||||||
|
|
||||||
|
#~ msgid "TER"
|
||||||
|
#~ msgstr "TER"
|
||||||
|
|
||||||
|
#~ msgid "Intercités"
|
||||||
|
#~ msgstr "Intercités"
|
||||||
|
|
||||||
|
#~ msgid "Transilien"
|
||||||
|
#~ msgstr "Transilien"
|
||||||
|
|
||||||
|
#~ msgid "Eurostar"
|
||||||
|
#~ msgstr "Eurostar"
|
||||||
|
|
||||||
|
#~ msgid "Trenitalia"
|
||||||
|
#~ msgstr "Trenitalia"
|
||||||
|
|
||||||
|
#~ msgid "Renfe"
|
||||||
|
#~ msgstr "Renfe"
|
||||||
|
|
||||||
|
#~ msgid "ÖBB"
|
||||||
|
#~ msgstr "ÖBB"
|
||||||
|
|
||||||
|
#~ msgid "Last update"
|
||||||
|
#~ msgstr "Dernière mise à jour"
|
||||||
|
|
||||||
|
#~ msgid "Transport type"
|
||||||
|
#~ msgstr "Type de transport"
|
||||||
|
|
|
@ -2,27 +2,18 @@ import csv
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
|
|
||||||
from sncfgtfs.models import Agency, Calendar, CalendarDate, FeedInfo, Route, Stop, StopTime, Transfer, Trip
|
from sncfgtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, Transfer, Trip, \
|
||||||
|
PickupType
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Update the SNCF GTFS database."
|
help = "Update the SNCF GTFS database."
|
||||||
|
|
||||||
GTFS_FEEDS = {
|
|
||||||
"TGV": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export_gtfs_voyages.zip",
|
|
||||||
"IC": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-intercites-gtfs-last.zip",
|
|
||||||
"TER": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-ter-gtfs-last.zip",
|
|
||||||
"TN": "https://eu.ftp.opendatasoft.com/sncf/gtfs/transilien-gtfs.zip",
|
|
||||||
"ES": "https://www.data.gouv.fr/fr/datasets/r/9089b550-696e-4ae0-87b5-40ea55a14292",
|
|
||||||
"TI": "https://thello.axelor.com/public/gtfs/gtfs.zip",
|
|
||||||
"RENFE": "https://ssl.renfe.com/gtransit/Fichero_AV_LD/google_transit.zip",
|
|
||||||
"OBB": "https://static.oebb.at/open-data/soll-fahrplan-gtfs/GTFS_OP_2024_obb.zip",
|
|
||||||
}
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('--debug', '-d', action='store_true', help="Activate debug mode")
|
parser.add_argument('--debug', '-d', action='store_true', help="Activate debug mode")
|
||||||
parser.add_argument('--bulk_size', type=int, default=1000, help="Number of objects to create in bulk.")
|
parser.add_argument('--bulk_size', type=int, default=1000, help="Number of objects to create in bulk.")
|
||||||
|
@ -30,36 +21,36 @@ class Command(BaseCommand):
|
||||||
help="Do not update the database, only print what would be done.")
|
help="Do not update the database, only print what would be done.")
|
||||||
parser.add_argument('--force', '-f', action='store_true', help="Force the update of the database.")
|
parser.add_argument('--force', '-f', action='store_true', help="Force the update of the database.")
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, debug: bool = False, bulk_size: int = 100, dry_run: bool = False, force: bool = False,
|
||||||
bulk_size = options['bulk_size']
|
verbosity: int = 1, *args, **options):
|
||||||
dry_run = options['dry_run']
|
|
||||||
force = options['force']
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
self.stdout.write(self.style.WARNING("Dry run mode activated."))
|
self.stdout.write(self.style.WARNING("Dry run mode activated."))
|
||||||
|
|
||||||
if not FeedInfo.objects.exists():
|
|
||||||
last_update_date = "1970-01-01"
|
|
||||||
else:
|
|
||||||
last_update_date = FeedInfo.objects.get(publisher_name='SNCF_default').version
|
|
||||||
|
|
||||||
for url in self.GTFS_FEEDS.values():
|
|
||||||
resp = requests.head(url)
|
|
||||||
if "Last-Modified" not in resp.headers:
|
|
||||||
continue
|
|
||||||
last_modified = resp.headers["Last-Modified"]
|
|
||||||
last_modified = datetime.strptime(last_modified, "%a, %d %b %Y %H:%M:%S %Z")
|
|
||||||
if last_modified.date().isoformat() > last_update_date:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if not force:
|
|
||||||
self.stdout.write(self.style.WARNING("Database already up-to-date."))
|
|
||||||
return
|
|
||||||
|
|
||||||
self.stdout.write("Updating database...")
|
self.stdout.write("Updating database...")
|
||||||
|
|
||||||
for transport_type, feed_url in self.GTFS_FEEDS.items():
|
for gtfs_feed in GTFSFeed.objects.all():
|
||||||
self.stdout.write(f"Downloading {transport_type} GTFS feed...")
|
gtfs_code = gtfs_feed.code
|
||||||
with ZipFile(BytesIO(requests.get(feed_url).content)) as zipfile:
|
|
||||||
|
if not force:
|
||||||
|
# Check if the source file was updated
|
||||||
|
resp = requests.head(gtfs_feed.feed_url, allow_redirects=True)
|
||||||
|
if 'ETag' in resp.headers and gtfs_feed.etag:
|
||||||
|
if resp.headers['ETag'] == gtfs_feed.etag:
|
||||||
|
if verbosity >= 1:
|
||||||
|
self.stdout.write(self.style.WARNING(f"Database is already up-to-date for {gtfs_feed}."))
|
||||||
|
continue
|
||||||
|
if 'Last-Modified' in resp.headers and gtfs_feed.last_modified:
|
||||||
|
last_modified = resp.headers['Last-Modified']
|
||||||
|
last_modified = datetime.strptime(last_modified, "%a, %d %b %Y %H:%M:%S %Z") \
|
||||||
|
.replace(tzinfo=ZoneInfo(last_modified.split(' ')[-1]))
|
||||||
|
if last_modified <= gtfs_feed.last_modified:
|
||||||
|
if verbosity >= 1:
|
||||||
|
self.stdout.write(self.style.WARNING(f"Database is already up-to-date for {gtfs_feed}."))
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.stdout.write(f"Downloading GTFS feed for {gtfs_feed}...")
|
||||||
|
resp = requests.get(gtfs_feed.feed_url, allow_redirects=True, stream=True)
|
||||||
|
with ZipFile(BytesIO(resp.content)) as zipfile:
|
||||||
def read_file(filename):
|
def read_file(filename):
|
||||||
lines = zipfile.read(filename).decode().replace('\ufeff', '').splitlines()
|
lines = zipfile.read(filename).decode().replace('\ufeff', '').splitlines()
|
||||||
return [line.strip() for line in lines]
|
return [line.strip() for line in lines]
|
||||||
|
@ -67,23 +58,25 @@ class Command(BaseCommand):
|
||||||
agencies = []
|
agencies = []
|
||||||
for agency_dict in csv.DictReader(read_file("agency.txt")):
|
for agency_dict in csv.DictReader(read_file("agency.txt")):
|
||||||
agency_dict: dict
|
agency_dict: dict
|
||||||
if transport_type == "ES" \
|
# if gtfs_code == "FR-EUROSTAR" \
|
||||||
and agency_dict['agency_id'] != 'ES' and agency_dict['agency_id'] != 'ER':
|
# and agency_dict['agency_id'] != 'ES' and agency_dict['agency_id'] != 'ER':
|
||||||
continue
|
# continue
|
||||||
agency = Agency(
|
agency = Agency(
|
||||||
id=agency_dict['agency_id'],
|
id=f"{gtfs_code}-{agency_dict['agency_id']}",
|
||||||
name=agency_dict['agency_name'],
|
name=agency_dict['agency_name'],
|
||||||
url=agency_dict['agency_url'],
|
url=agency_dict['agency_url'],
|
||||||
timezone=agency_dict['agency_timezone'],
|
timezone=agency_dict['agency_timezone'],
|
||||||
lang=agency_dict.get('agency_lang', "fr"),
|
lang=agency_dict.get('agency_lang', "fr"),
|
||||||
phone=agency_dict.get('agency_phone', ""),
|
phone=agency_dict.get('agency_phone', ""),
|
||||||
email=agency_dict.get('agency_email', ""),
|
email=agency_dict.get('agency_email', ""),
|
||||||
|
gtfs_feed=gtfs_feed,
|
||||||
)
|
)
|
||||||
agencies.append(agency)
|
agencies.append(agency)
|
||||||
if agencies and not dry_run:
|
if agencies and not dry_run:
|
||||||
Agency.objects.bulk_create(agencies,
|
Agency.objects.bulk_create(agencies,
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
update_fields=['name', 'url', 'timezone', 'lang', 'phone', 'email'],
|
update_fields=['name', 'url', 'timezone', 'lang', 'phone', 'email',
|
||||||
|
'gtfs_feed'],
|
||||||
unique_fields=['id'])
|
unique_fields=['id'])
|
||||||
agencies.clear()
|
agencies.clear()
|
||||||
|
|
||||||
|
@ -91,8 +84,10 @@ class Command(BaseCommand):
|
||||||
for stop_dict in csv.DictReader(read_file("stops.txt")):
|
for stop_dict in csv.DictReader(read_file("stops.txt")):
|
||||||
stop_dict: dict
|
stop_dict: dict
|
||||||
stop_id = stop_dict['stop_id']
|
stop_id = stop_dict['stop_id']
|
||||||
if transport_type in ["ES", "TI", "RENFE"]:
|
stop_id = f"{gtfs_code}-{stop_id}"
|
||||||
stop_id = f"{transport_type}-{stop_id}"
|
|
||||||
|
parent_station_id = stop_dict.get('parent_station', None)
|
||||||
|
parent_station_id = f"{gtfs_code}-{parent_station_id}" if parent_station_id else None
|
||||||
|
|
||||||
stop = Stop(
|
stop = Stop(
|
||||||
id=stop_id,
|
id=stop_id,
|
||||||
|
@ -102,13 +97,13 @@ class Command(BaseCommand):
|
||||||
lon=stop_dict['stop_lon'],
|
lon=stop_dict['stop_lon'],
|
||||||
zone_id=stop_dict.get('zone_id', ""),
|
zone_id=stop_dict.get('zone_id', ""),
|
||||||
url=stop_dict.get('stop_url', ""),
|
url=stop_dict.get('stop_url', ""),
|
||||||
location_type=stop_dict.get('location_type', 1) or 1,
|
location_type=stop_dict.get('location_type', 0) or 0,
|
||||||
parent_station_id=stop_dict.get('parent_station', None) or None,
|
parent_station_id=parent_station_id,
|
||||||
timezone=stop_dict.get('stop_timezone', ""),
|
timezone=stop_dict.get('stop_timezone', ""),
|
||||||
wheelchair_boarding=stop_dict.get('wheelchair_boarding', 0),
|
wheelchair_boarding=stop_dict.get('wheelchair_boarding', 0),
|
||||||
level_id=stop_dict.get('level_id', ""),
|
level_id=stop_dict.get('level_id', ""),
|
||||||
platform_code=stop_dict.get('platform_code', ""),
|
platform_code=stop_dict.get('platform_code', ""),
|
||||||
transport_type=transport_type,
|
gtfs_feed=gtfs_feed,
|
||||||
)
|
)
|
||||||
stops.append(stop)
|
stops.append(stop)
|
||||||
|
|
||||||
|
@ -119,7 +114,7 @@ class Command(BaseCommand):
|
||||||
update_fields=['name', 'desc', 'lat', 'lon', 'zone_id', 'url',
|
update_fields=['name', 'desc', 'lat', 'lon', 'zone_id', 'url',
|
||||||
'location_type', 'parent_station_id', 'timezone',
|
'location_type', 'parent_station_id', 'timezone',
|
||||||
'wheelchair_boarding', 'level_id', 'platform_code',
|
'wheelchair_boarding', 'level_id', 'platform_code',
|
||||||
'transport_type'],
|
'gtfs_feed'],
|
||||||
unique_fields=['id'])
|
unique_fields=['id'])
|
||||||
stops.clear()
|
stops.clear()
|
||||||
|
|
||||||
|
@ -127,11 +122,10 @@ class Command(BaseCommand):
|
||||||
for route_dict in csv.DictReader(read_file("routes.txt")):
|
for route_dict in csv.DictReader(read_file("routes.txt")):
|
||||||
route_dict: dict
|
route_dict: dict
|
||||||
route_id = route_dict['route_id']
|
route_id = route_dict['route_id']
|
||||||
if transport_type == "TI":
|
route_id = f"{gtfs_code}-{route_id}"
|
||||||
route_id = f"{transport_type}-{route_id}"
|
|
||||||
route = Route(
|
route = Route(
|
||||||
id=route_id,
|
id=route_id,
|
||||||
agency_id=route_dict['agency_id'],
|
agency_id=f"{gtfs_code}-{route_dict['agency_id']}",
|
||||||
short_name=route_dict['route_short_name'],
|
short_name=route_dict['route_short_name'],
|
||||||
long_name=route_dict['route_long_name'],
|
long_name=route_dict['route_long_name'],
|
||||||
desc=route_dict.get('route_desc', ""),
|
desc=route_dict.get('route_desc', ""),
|
||||||
|
@ -139,7 +133,7 @@ class Command(BaseCommand):
|
||||||
url=route_dict.get('route_url', ""),
|
url=route_dict.get('route_url', ""),
|
||||||
color=route_dict.get('route_color', ""),
|
color=route_dict.get('route_color', ""),
|
||||||
text_color=route_dict.get('route_text_color', ""),
|
text_color=route_dict.get('route_text_color', ""),
|
||||||
transport_type=transport_type,
|
gtfs_feed=gtfs_feed,
|
||||||
)
|
)
|
||||||
routes.append(route)
|
routes.append(route)
|
||||||
|
|
||||||
|
@ -148,7 +142,7 @@ class Command(BaseCommand):
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
update_fields=['agency_id', 'short_name', 'long_name', 'desc',
|
update_fields=['agency_id', 'short_name', 'long_name', 'desc',
|
||||||
'type', 'url', 'color', 'text_color',
|
'type', 'url', 'color', 'text_color',
|
||||||
'transport_type'],
|
'gtfs_feed'],
|
||||||
unique_fields=['id'])
|
unique_fields=['id'])
|
||||||
routes.clear()
|
routes.clear()
|
||||||
if routes and not dry_run:
|
if routes and not dry_run:
|
||||||
|
@ -156,17 +150,17 @@ class Command(BaseCommand):
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
update_fields=['agency_id', 'short_name', 'long_name', 'desc',
|
update_fields=['agency_id', 'short_name', 'long_name', 'desc',
|
||||||
'type', 'url', 'color', 'text_color',
|
'type', 'url', 'color', 'text_color',
|
||||||
'transport_type'],
|
'gtfs_feed'],
|
||||||
unique_fields=['id'])
|
unique_fields=['id'])
|
||||||
routes.clear()
|
routes.clear()
|
||||||
|
|
||||||
Calendar.objects.filter(transport_type=transport_type).delete()
|
Calendar.objects.filter(gtfs_feed=gtfs_feed).delete()
|
||||||
calendars = {}
|
calendars = {}
|
||||||
if "calendar.txt" in zipfile.namelist():
|
if "calendar.txt" in zipfile.namelist():
|
||||||
for calendar_dict in csv.DictReader(read_file("calendar.txt")):
|
for calendar_dict in csv.DictReader(read_file("calendar.txt")):
|
||||||
calendar_dict: dict
|
calendar_dict: dict
|
||||||
calendar = Calendar(
|
calendar = Calendar(
|
||||||
id=f"{transport_type}-{calendar_dict['service_id']}",
|
id=f"{gtfs_code}-{calendar_dict['service_id']}",
|
||||||
monday=calendar_dict['monday'],
|
monday=calendar_dict['monday'],
|
||||||
tuesday=calendar_dict['tuesday'],
|
tuesday=calendar_dict['tuesday'],
|
||||||
wednesday=calendar_dict['wednesday'],
|
wednesday=calendar_dict['wednesday'],
|
||||||
|
@ -176,7 +170,7 @@ class Command(BaseCommand):
|
||||||
sunday=calendar_dict['sunday'],
|
sunday=calendar_dict['sunday'],
|
||||||
start_date=calendar_dict['start_date'],
|
start_date=calendar_dict['start_date'],
|
||||||
end_date=calendar_dict['end_date'],
|
end_date=calendar_dict['end_date'],
|
||||||
transport_type=transport_type,
|
gtfs_feed=gtfs_feed,
|
||||||
)
|
)
|
||||||
calendars[calendar.id] = calendar
|
calendars[calendar.id] = calendar
|
||||||
|
|
||||||
|
@ -185,14 +179,14 @@ class Command(BaseCommand):
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
update_fields=['monday', 'tuesday', 'wednesday', 'thursday',
|
update_fields=['monday', 'tuesday', 'wednesday', 'thursday',
|
||||||
'friday', 'saturday', 'sunday', 'start_date',
|
'friday', 'saturday', 'sunday', 'start_date',
|
||||||
'end_date', 'transport_type'],
|
'end_date', 'gtfs_feed'],
|
||||||
unique_fields=['id'])
|
unique_fields=['id'])
|
||||||
calendars.clear()
|
calendars.clear()
|
||||||
if calendars and not dry_run:
|
if calendars and not dry_run:
|
||||||
Calendar.objects.bulk_create(calendars.values(), update_conflicts=True,
|
Calendar.objects.bulk_create(calendars.values(), update_conflicts=True,
|
||||||
update_fields=['monday', 'tuesday', 'wednesday', 'thursday',
|
update_fields=['monday', 'tuesday', 'wednesday', 'thursday',
|
||||||
'friday', 'saturday', 'sunday', 'start_date',
|
'friday', 'saturday', 'sunday', 'start_date',
|
||||||
'end_date', 'transport_type'],
|
'end_date', 'gtfs_feed'],
|
||||||
unique_fields=['id'])
|
unique_fields=['id'])
|
||||||
calendars.clear()
|
calendars.clear()
|
||||||
|
|
||||||
|
@ -200,8 +194,8 @@ class Command(BaseCommand):
|
||||||
for calendar_date_dict in csv.DictReader(read_file("calendar_dates.txt")):
|
for calendar_date_dict in csv.DictReader(read_file("calendar_dates.txt")):
|
||||||
calendar_date_dict: dict
|
calendar_date_dict: dict
|
||||||
calendar_date = CalendarDate(
|
calendar_date = CalendarDate(
|
||||||
id=f"{transport_type}-{calendar_date_dict['service_id']}-{calendar_date_dict['date']}",
|
id=f"{gtfs_code}-{calendar_date_dict['service_id']}-{calendar_date_dict['date']}",
|
||||||
service_id=f"{transport_type}-{calendar_date_dict['service_id']}",
|
service_id=f"{gtfs_code}-{calendar_date_dict['service_id']}",
|
||||||
date=calendar_date_dict['date'],
|
date=calendar_date_dict['date'],
|
||||||
exception_type=calendar_date_dict['exception_type'],
|
exception_type=calendar_date_dict['exception_type'],
|
||||||
)
|
)
|
||||||
|
@ -209,7 +203,7 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
if calendar_date.service_id not in calendars:
|
if calendar_date.service_id not in calendars:
|
||||||
calendar = Calendar(
|
calendar = Calendar(
|
||||||
id=f"{transport_type}-{calendar_date_dict['service_id']}",
|
id=f"{gtfs_code}-{calendar_date_dict['service_id']}",
|
||||||
monday=False,
|
monday=False,
|
||||||
tuesday=False,
|
tuesday=False,
|
||||||
wednesday=False,
|
wednesday=False,
|
||||||
|
@ -219,11 +213,11 @@ class Command(BaseCommand):
|
||||||
sunday=False,
|
sunday=False,
|
||||||
start_date=calendar_date_dict['date'],
|
start_date=calendar_date_dict['date'],
|
||||||
end_date=calendar_date_dict['date'],
|
end_date=calendar_date_dict['date'],
|
||||||
transport_type=transport_type,
|
gtfs_feed=gtfs_feed,
|
||||||
)
|
)
|
||||||
calendars[calendar.id] = calendar
|
calendars[calendar.id] = calendar
|
||||||
else:
|
else:
|
||||||
calendar = calendars[f"{transport_type}-{calendar_date_dict['service_id']}"]
|
calendar = calendars[f"{gtfs_code}-{calendar_date_dict['service_id']}"]
|
||||||
if calendar.start_date > calendar_date.date:
|
if calendar.start_date > calendar_date.date:
|
||||||
calendar.start_date = calendar_date.date
|
calendar.start_date = calendar_date.date
|
||||||
if calendar.end_date < calendar_date.date:
|
if calendar.end_date < calendar_date.date:
|
||||||
|
@ -233,7 +227,7 @@ class Command(BaseCommand):
|
||||||
Calendar.objects.bulk_create(calendars.values(),
|
Calendar.objects.bulk_create(calendars.values(),
|
||||||
batch_size=bulk_size,
|
batch_size=bulk_size,
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
update_fields=['start_date', 'end_date'],
|
update_fields=['start_date', 'end_date', 'gtfs_feed'],
|
||||||
unique_fields=['id'])
|
unique_fields=['id'])
|
||||||
CalendarDate.objects.bulk_create(calendar_dates,
|
CalendarDate.objects.bulk_create(calendar_dates,
|
||||||
batch_size=bulk_size,
|
batch_size=bulk_size,
|
||||||
|
@ -248,22 +242,12 @@ class Command(BaseCommand):
|
||||||
trip_dict: dict
|
trip_dict: dict
|
||||||
trip_id = trip_dict['trip_id']
|
trip_id = trip_dict['trip_id']
|
||||||
route_id = trip_dict['route_id']
|
route_id = trip_dict['route_id']
|
||||||
if transport_type in ["TGV", "IC", "TER"]:
|
trip_id = f"{gtfs_code}-{trip_id}"
|
||||||
trip_id, last_update = trip_id.split(':', 1)
|
route_id = f"{gtfs_code}-{route_id}"
|
||||||
last_update = datetime.fromisoformat(last_update)
|
|
||||||
elif transport_type in ["ES", "RENFE"]:
|
|
||||||
trip_id = f"{transport_type}-{trip_id}"
|
|
||||||
last_update = None
|
|
||||||
elif transport_type == "TI":
|
|
||||||
trip_id = f"{transport_type}-{trip_id}"
|
|
||||||
route_id = f"{transport_type}-{route_id}"
|
|
||||||
last_update = None
|
|
||||||
else:
|
|
||||||
last_update = None
|
|
||||||
trip = Trip(
|
trip = Trip(
|
||||||
id=trip_id,
|
id=trip_id,
|
||||||
route_id=route_id,
|
route_id=route_id,
|
||||||
service_id=f"{transport_type}-{trip_dict['service_id']}",
|
service_id=f"{gtfs_code}-{trip_dict['service_id']}",
|
||||||
headsign=trip_dict.get('trip_headsign', ""),
|
headsign=trip_dict.get('trip_headsign', ""),
|
||||||
short_name=trip_dict.get('trip_short_name', ""),
|
short_name=trip_dict.get('trip_short_name', ""),
|
||||||
direction_id=trip_dict.get('direction_id', None) or None,
|
direction_id=trip_dict.get('direction_id', None) or None,
|
||||||
|
@ -271,7 +255,7 @@ class Command(BaseCommand):
|
||||||
shape_id=trip_dict.get('shape_id', ""),
|
shape_id=trip_dict.get('shape_id', ""),
|
||||||
wheelchair_accessible=trip_dict.get('wheelchair_accessible', None),
|
wheelchair_accessible=trip_dict.get('wheelchair_accessible', None),
|
||||||
bikes_allowed=trip_dict.get('bikes_allowed', None),
|
bikes_allowed=trip_dict.get('bikes_allowed', None),
|
||||||
last_update=last_update,
|
gtfs_feed=gtfs_feed,
|
||||||
)
|
)
|
||||||
trips.append(trip)
|
trips.append(trip)
|
||||||
|
|
||||||
|
@ -280,7 +264,7 @@ class Command(BaseCommand):
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
update_fields=['route_id', 'service_id', 'headsign', 'short_name',
|
update_fields=['route_id', 'service_id', 'headsign', 'short_name',
|
||||||
'direction_id', 'block_id', 'shape_id',
|
'direction_id', 'block_id', 'shape_id',
|
||||||
'wheelchair_accessible', 'bikes_allowed'],
|
'wheelchair_accessible', 'bikes_allowed', 'gtfs_feed'],
|
||||||
unique_fields=['id'])
|
unique_fields=['id'])
|
||||||
trips.clear()
|
trips.clear()
|
||||||
if trips and not dry_run:
|
if trips and not dry_run:
|
||||||
|
@ -288,7 +272,7 @@ class Command(BaseCommand):
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
update_fields=['route_id', 'service_id', 'headsign', 'short_name',
|
update_fields=['route_id', 'service_id', 'headsign', 'short_name',
|
||||||
'direction_id', 'block_id', 'shape_id',
|
'direction_id', 'block_id', 'shape_id',
|
||||||
'wheelchair_accessible', 'bikes_allowed'],
|
'wheelchair_accessible', 'bikes_allowed', 'gtfs_feed'],
|
||||||
unique_fields=['id'])
|
unique_fields=['id'])
|
||||||
trips.clear()
|
trips.clear()
|
||||||
|
|
||||||
|
@ -297,14 +281,10 @@ class Command(BaseCommand):
|
||||||
stop_time_dict: dict
|
stop_time_dict: dict
|
||||||
|
|
||||||
stop_id = stop_time_dict['stop_id']
|
stop_id = stop_time_dict['stop_id']
|
||||||
if transport_type in ["ES", "TI", "RENFE"]:
|
stop_id = f"{gtfs_code}-{stop_id}"
|
||||||
stop_id = f"{transport_type}-{stop_id}"
|
|
||||||
|
|
||||||
trip_id = stop_time_dict['trip_id']
|
trip_id = stop_time_dict['trip_id']
|
||||||
if transport_type in ["TGV", "IC", "TER"]:
|
trip_id = f"{gtfs_code}-{trip_id}"
|
||||||
trip_id = trip_id.split(':', 1)[0]
|
|
||||||
elif transport_type in ["ES", "TI", "RENFE"]:
|
|
||||||
trip_id = f"{transport_type}-{trip_id}"
|
|
||||||
|
|
||||||
arr_time = stop_time_dict['arrival_time']
|
arr_time = stop_time_dict['arrival_time']
|
||||||
arr_h, arr_m, arr_s = map(int, arr_time.split(':'))
|
arr_h, arr_m, arr_s = map(int, arr_time.split(':'))
|
||||||
|
@ -315,19 +295,16 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
pickup_type = stop_time_dict.get('pickup_type', 0)
|
pickup_type = stop_time_dict.get('pickup_type', 0)
|
||||||
drop_off_type = stop_time_dict.get('drop_off_type', 0)
|
drop_off_type = stop_time_dict.get('drop_off_type', 0)
|
||||||
if transport_type in ["ES", "RENFE", "OBB"]:
|
|
||||||
if stop_time_dict['stop_sequence'] == "1":
|
if stop_time_dict['stop_sequence'] == "1":
|
||||||
drop_off_type = 1
|
# First stop
|
||||||
|
drop_off_type = PickupType.NONE
|
||||||
elif arr_time == dep_time:
|
elif arr_time == dep_time:
|
||||||
pickup_type = 1
|
# Last stop
|
||||||
elif transport_type == "TI":
|
pickup_type = PickupType.NONE
|
||||||
if stop_time_dict['stop_sequence'] == "0":
|
|
||||||
drop_off_type = 1
|
|
||||||
elif arr_time == dep_time:
|
|
||||||
pickup_type = 1
|
|
||||||
|
|
||||||
st = StopTime(
|
st = StopTime(
|
||||||
id=f"{trip_id}-{stop_id}-{stop_time_dict['departure_time']}",
|
id=f"{gtfs_code}-{stop_time_dict['trip_id']}-{stop_time_dict['stop_id']}"
|
||||||
|
f"-{stop_time_dict['departure_time']}",
|
||||||
trip_id=trip_id,
|
trip_id=trip_id,
|
||||||
arrival_time=timedelta(seconds=arr_time),
|
arrival_time=timedelta(seconds=arr_time),
|
||||||
departure_time=timedelta(seconds=dep_time),
|
departure_time=timedelta(seconds=dep_time),
|
||||||
|
@ -363,14 +340,13 @@ class Command(BaseCommand):
|
||||||
transfer_dict: dict
|
transfer_dict: dict
|
||||||
from_stop_id = transfer_dict['from_stop_id']
|
from_stop_id = transfer_dict['from_stop_id']
|
||||||
to_stop_id = transfer_dict['to_stop_id']
|
to_stop_id = transfer_dict['to_stop_id']
|
||||||
if transport_type in ["ES", "RENFE", "OBB"]:
|
from_stop_id = f"{gtfs_code}-{from_stop_id}"
|
||||||
from_stop_id = f"{transport_type}-{from_stop_id}"
|
to_stop_id = f"{gtfs_code}-{to_stop_id}"
|
||||||
to_stop_id = f"{transport_type}-{to_stop_id}"
|
|
||||||
|
|
||||||
transfer = Transfer(
|
transfer = Transfer(
|
||||||
id=f"{from_stop_id}-{to_stop_id}",
|
id=f"{transfer_dict['from_stop_id']}-{transfer_dict['to_stop_id']}",
|
||||||
from_stop_id=transfer_dict['from_stop_id'],
|
from_stop_id=from_stop_id,
|
||||||
to_stop_id=transfer_dict['to_stop_id'],
|
to_stop_id=to_stop_id,
|
||||||
transfer_type=transfer_dict['transfer_type'],
|
transfer_type=transfer_dict['transfer_type'],
|
||||||
min_transfer_time=transfer_dict['min_transfer_time'],
|
min_transfer_time=transfer_dict['min_transfer_time'],
|
||||||
)
|
)
|
||||||
|
@ -395,6 +371,7 @@ class Command(BaseCommand):
|
||||||
feed_info_dict: dict
|
feed_info_dict: dict
|
||||||
FeedInfo.objects.update_or_create(
|
FeedInfo.objects.update_or_create(
|
||||||
publisher_name=feed_info_dict['feed_publisher_name'],
|
publisher_name=feed_info_dict['feed_publisher_name'],
|
||||||
|
gtfs_feed=gtfs_feed,
|
||||||
defaults=dict(
|
defaults=dict(
|
||||||
publisher_url=feed_info_dict['feed_publisher_url'],
|
publisher_url=feed_info_dict['feed_publisher_url'],
|
||||||
lang=feed_info_dict['feed_lang'],
|
lang=feed_info_dict['feed_lang'],
|
||||||
|
@ -403,3 +380,12 @@ class Command(BaseCommand):
|
||||||
version=feed_info_dict.get('feed_version', 1),
|
version=feed_info_dict.get('feed_version', 1),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if 'ETag' in resp.headers:
|
||||||
|
gtfs_feed.etag = resp.headers['ETag']
|
||||||
|
gtfs_feed.save()
|
||||||
|
if 'Last-Modified' in resp.headers:
|
||||||
|
last_modified = resp.headers['Last-Modified']
|
||||||
|
gtfs_feed.last_modified = datetime.strptime(last_modified, "%a, %d %b %Y %H:%M:%S %Z") \
|
||||||
|
.replace(tzinfo=ZoneInfo(last_modified.split(' ')[-1]))
|
||||||
|
gtfs_feed.save()
|
||||||
|
|
|
@ -5,8 +5,8 @@ import requests
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from sncfgtfs.gtfs_realtime_pb2 import FeedMessage
|
from sncfgtfs.gtfs_realtime_pb2 import FeedMessage, TripUpdate as GTFSTripUpdate
|
||||||
from sncfgtfs.models import Agency, Calendar, CalendarDate, ExceptionType, LocationType, PickupType, \
|
from sncfgtfs.models import Agency, Calendar, CalendarDate, ExceptionType, GTFSFeed, LocationType, PickupType, \
|
||||||
Route, RouteType, Stop, StopScheduleRelationship, StopTime, StopTimeUpdate, \
|
Route, RouteType, Stop, StopScheduleRelationship, StopTime, StopTimeUpdate, \
|
||||||
Trip, TripUpdate, TripScheduleRelationship
|
Trip, TripUpdate, TripScheduleRelationship
|
||||||
|
|
||||||
|
@ -14,34 +14,33 @@ from sncfgtfs.models import Agency, Calendar, CalendarDate, ExceptionType, Locat
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Update the SNCF GTFS Realtime database."
|
help = "Update the SNCF GTFS Realtime database."
|
||||||
|
|
||||||
GTFS_RT_FEEDS = {
|
|
||||||
"TGV": "https://proxy.transport.data.gouv.fr/resource/sncf-tgv-gtfs-rt-trip-updates",
|
|
||||||
"IC": "https://proxy.transport.data.gouv.fr/resource/sncf-ic-gtfs-rt-trip-updates",
|
|
||||||
"TER": "https://proxy.transport.data.gouv.fr/resource/sncf-ter-gtfs-rt-trip-updates",
|
|
||||||
"TI": "https://thello.axelor.com/public/gtfs/GTFS-RT.bin",
|
|
||||||
}
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('--debug', '-d', action='store_true', help="Activate debug mode")
|
parser.add_argument('--debug', '-d', action='store_true', help="Activate debug mode")
|
||||||
|
|
||||||
def handle(self, debug=False, *args, **options):
|
def handle(self, debug: bool = False, verbosity: int = 1, *args, **options):
|
||||||
for feed_type, feed_url in self.GTFS_RT_FEEDS.items():
|
for gtfs_feed in GTFSFeed.objects.all():
|
||||||
self.stdout.write(f"Updating {feed_type} feed...")
|
if not gtfs_feed.rt_feed_url:
|
||||||
|
if verbosity >= 2:
|
||||||
|
self.stdout.write(self.style.WARNING(f"No GTFS-RT feed found for {gtfs_feed}."))
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.stdout.write(f"Updating GTFS-RT feed for {gtfs_feed}…")
|
||||||
|
|
||||||
|
gtfs_code = gtfs_feed.code
|
||||||
feed_message = FeedMessage()
|
feed_message = FeedMessage()
|
||||||
feed_message.ParseFromString(requests.get(feed_url).content)
|
feed_message.ParseFromString(requests.get(gtfs_feed.rt_feed_url, allow_redirects=True).content)
|
||||||
|
|
||||||
stop_times_updates = []
|
stop_times_updates = []
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
with open(f'feed_message-{feed_type}.txt', 'w') as f:
|
with open(f'feed_message-{gtfs_code}.txt', 'w') as f:
|
||||||
f.write(str(feed_message))
|
f.write(str(feed_message))
|
||||||
|
|
||||||
for entity in feed_message.entity:
|
for entity in feed_message.entity:
|
||||||
if entity.HasField("trip_update"):
|
if entity.HasField("trip_update"):
|
||||||
trip_update = entity.trip_update
|
trip_update = entity.trip_update
|
||||||
trip_id = trip_update.trip.trip_id
|
trip_id = trip_update.trip.trip_id
|
||||||
if feed_type in ["TGV", "IC", "TER"]:
|
trip_id = f"{gtfs_code}-{trip_id}"
|
||||||
trip_id = trip_id.split(":", 1)[0]
|
|
||||||
|
|
||||||
start_date = date(year=int(trip_update.trip.start_date[:4]),
|
start_date = date(year=int(trip_update.trip.start_date[:4]),
|
||||||
month=int(trip_update.trip.start_date[4:6]),
|
month=int(trip_update.trip.start_date[4:6]),
|
||||||
|
@ -50,7 +49,7 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
if trip_update.trip.schedule_relationship == TripScheduleRelationship.ADDED:
|
if trip_update.trip.schedule_relationship == TripScheduleRelationship.ADDED:
|
||||||
# C'est un trajet nouveau. On crée le trajet associé.
|
# C'est un trajet nouveau. On crée le trajet associé.
|
||||||
self.create_trip(trip_update, trip_id, start_dt, feed_type)
|
self.create_trip(trip_update, trip_id, start_dt, gtfs_feed)
|
||||||
|
|
||||||
if not Trip.objects.filter(id=trip_id).exists():
|
if not Trip.objects.filter(id=trip_id).exists():
|
||||||
self.stdout.write(f"Trip {trip_id} does not exist in the GTFS feed.")
|
self.stdout.write(f"Trip {trip_id} does not exist in the GTFS feed.")
|
||||||
|
@ -68,22 +67,19 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
||||||
stop_id = stop_time_update.stop_id
|
stop_id = stop_time_update.stop_id
|
||||||
if stop_id.startswith('StopArea:'):
|
stop_id = f"{gtfs_code}-{stop_id}"
|
||||||
# On est dans le cadre d'une gare. On cherche le quai associé.
|
if StopTime.objects.filter(trip_id=trip_id, stop=stop_id).exists():
|
||||||
if StopTime.objects.filter(trip_id=trip_id, stop__parent_station_id=stop_id).exists():
|
st = StopTime.objects.filter(trip_id=trip_id, stop=stop_id)
|
||||||
# U
|
if st.count() > 1:
|
||||||
stop = StopTime.objects.get(trip_id=trip_id, stop__parent_station_id=stop_id).stop
|
st = st.get(stop_sequence=stop_sequence)
|
||||||
else:
|
else:
|
||||||
stops = [s for s in Stop.objects.filter(parent_station_id=stop_id).all()
|
st = st.first()
|
||||||
for s2 in StopTime.objects.filter(trip_id=trip_id).all()
|
else:
|
||||||
if s.stop_type in s2.stop.stop_type
|
# Stop is added
|
||||||
or s2.stop.stop_type in s.stop_type]
|
st = StopTime.objects.create(
|
||||||
stop = stops[0] if stops else Stop.objects.get(id=stop_id)
|
id=f"{trip_id}-{stop_time_update.stop_id}",
|
||||||
|
|
||||||
st, _created = StopTime.objects.update_or_create(
|
|
||||||
id=f"{trip_id}-{stop.id}",
|
|
||||||
trip_id=trip_id,
|
trip_id=trip_id,
|
||||||
stop_id=stop.id,
|
stop_id=stop_id,
|
||||||
defaults={
|
defaults={
|
||||||
"stop_sequence": stop_sequence,
|
"stop_sequence": stop_sequence,
|
||||||
"arrival_time": datetime.fromtimestamp(stop_time_update.arrival.time,
|
"arrival_time": datetime.fromtimestamp(stop_time_update.arrival.time,
|
||||||
|
@ -96,20 +92,13 @@ class Command(BaseCommand):
|
||||||
else PickupType.NONE),
|
else PickupType.NONE),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
elif stop_time_update.schedule_relationship == StopScheduleRelationship.SKIPPED:
|
|
||||||
st = StopTime.objects.get(Q(stop=stop_id) | Q(stop__parent_station_id=stop_id),
|
if stop_time_update.schedule_relationship == StopScheduleRelationship.SKIPPED:
|
||||||
trip_id=trip_id)
|
|
||||||
if st.pickup_type != PickupType.NONE or st.drop_off_type != PickupType.NONE:
|
if st.pickup_type != PickupType.NONE or st.drop_off_type != PickupType.NONE:
|
||||||
st.pickup_type = PickupType.NONE
|
st.pickup_type = PickupType.NONE
|
||||||
st.drop_off_type = PickupType.NONE
|
st.drop_off_type = PickupType.NONE
|
||||||
st.save()
|
st.save()
|
||||||
else:
|
|
||||||
qs = StopTime.objects.filter(Q(stop=stop_id) | Q(stop__parent_station_id=stop_id),
|
|
||||||
trip_id=trip_id)
|
|
||||||
if qs.count() == 1:
|
|
||||||
st = qs.first()
|
|
||||||
else:
|
|
||||||
st = qs.get(stop_sequence=stop_sequence)
|
|
||||||
if st.stop_sequence != stop_sequence:
|
if st.stop_sequence != stop_sequence:
|
||||||
st.stop_sequence = stop_sequence
|
st.stop_sequence = stop_sequence
|
||||||
st.save()
|
st.save()
|
||||||
|
@ -136,73 +125,22 @@ class Command(BaseCommand):
|
||||||
'departure_delay', 'departure_time'],
|
'departure_delay', 'departure_time'],
|
||||||
unique_fields=['trip_update', 'stop_time'])
|
unique_fields=['trip_update', 'stop_time'])
|
||||||
|
|
||||||
def create_trip(self, trip_update, trip_id, start_dt, feed_type):
|
def create_trip(self, trip_update: GTFSTripUpdate, trip_id: str, start_dt: datetime, gtfs_feed: GTFSFeed) -> None:
|
||||||
headsign = trip_id[5:-1]
|
headsign = trip_id[5:-1]
|
||||||
trip_qs = Trip.objects.all()
|
gtfs_code = gtfs_feed.code
|
||||||
trip_ids = trip_qs.values_list('id', flat=True)
|
|
||||||
|
|
||||||
first_stop_queryset = StopTime.objects.filter(
|
route, _created = Route.objects.get_or_create(
|
||||||
stop__parent_station_id=trip_update.stop_time_update[0].stop_id,
|
id=f"{gtfs_code}-ADDED-{headsign}",
|
||||||
).values('trip_id')
|
gtfs_feed=gtfs_feed,
|
||||||
last_stop_queryset = StopTime.objects.filter(
|
|
||||||
stop__parent_station_id=trip_update.stop_time_update[-1].stop_id,
|
|
||||||
).values('trip_id')
|
|
||||||
|
|
||||||
trip_ids = trip_ids.intersection(first_stop_queryset).intersection(last_stop_queryset)
|
|
||||||
# print(trip_id, trip_ids)
|
|
||||||
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
|
||||||
stop_id = stop_time_update.stop_id
|
|
||||||
st_queryset = StopTime.objects.filter(stop__parent_station_id=stop_id)
|
|
||||||
if stop_sequence == 0:
|
|
||||||
st_queryset = st_queryset.filter(stop_sequence=0)
|
|
||||||
# print(stop_sequence, Stop.objects.get(id=stop_id).name, stop_time_update)
|
|
||||||
# print(trip_ids)
|
|
||||||
# print(st_queryset.values('trip_id').all())
|
|
||||||
trip_ids_restrict = trip_ids.intersection(st_queryset.values('trip_id'))
|
|
||||||
if trip_ids_restrict:
|
|
||||||
trip_ids = trip_ids_restrict
|
|
||||||
else:
|
|
||||||
stop = Stop.objects.get(id=stop_id)
|
|
||||||
self.stdout.write(self.style.WARNING(f"Warning: No trip is found passing by stop "
|
|
||||||
f"{stop.name} ({stop_id})"))
|
|
||||||
trip_ids = set(trip_ids)
|
|
||||||
route_ids = set(Trip.objects.filter(id__in=trip_ids).values_list('route_id', flat=True))
|
|
||||||
self.stdout.write(f"{len(route_ids)} routes found on trip for new train {headsign}")
|
|
||||||
if not route_ids:
|
|
||||||
origin_id = trip_update.stop_time_update[0].stop_id
|
|
||||||
origin = Stop.objects.get(id=origin_id)
|
|
||||||
destination_id = trip_update.stop_time_update[-1].stop_id
|
|
||||||
destination = Stop.objects.get(id=destination_id)
|
|
||||||
trip_name = f"{origin.name} - {destination.name}"
|
|
||||||
trip_reverse_name = f"{destination.name} - {origin.name}"
|
|
||||||
route_qs = Route.objects.filter(long_name=trip_name, transport_type=feed_type)
|
|
||||||
route_reverse_qs = Route.objects.filter(long_name=trip_reverse_name,
|
|
||||||
transport_type=feed_type)
|
|
||||||
if route_qs.exists():
|
|
||||||
route_ids = set(route_qs.values_list('id', flat=True))
|
|
||||||
elif route_reverse_qs.exists():
|
|
||||||
route_ids = set(route_reverse_qs.values_list('id', flat=True))
|
|
||||||
else:
|
|
||||||
self.stdout.write(f"Route not found for trip {trip_id} ({trip_name}). Creating new one")
|
|
||||||
route = Route.objects.create(
|
|
||||||
id=f"CREATED-{trip_name}",
|
|
||||||
agency=Agency.objects.filter(routes__transport_type=feed_type).first(),
|
|
||||||
transport_type=feed_type,
|
|
||||||
type=RouteType.RAIL,
|
type=RouteType.RAIL,
|
||||||
short_name=trip_name,
|
short_name="ADDED",
|
||||||
long_name=trip_name,
|
long_name="ADDED ROUTE",
|
||||||
)
|
)
|
||||||
route_ids = {route.id}
|
|
||||||
self.stdout.write(f"Route {route.id} created for trip {trip_id} ({trip_name})")
|
|
||||||
elif len(route_ids) > 1:
|
|
||||||
self.stdout.write(f"Multiple routes found for trip {trip_id}.")
|
|
||||||
self.stdout.write(", ".join(route_ids))
|
|
||||||
route_id = route_ids.pop()
|
|
||||||
|
|
||||||
Calendar.objects.update_or_create(
|
Calendar.objects.update_or_create(
|
||||||
id=f"{feed_type}-new-{headsign}",
|
id=f"{gtfs_code}-ADDED-{headsign}",
|
||||||
defaults={
|
defaults={
|
||||||
"transport_type": feed_type,
|
"gtfs_feed": gtfs_feed,
|
||||||
"monday": False,
|
"monday": False,
|
||||||
"tuesday": False,
|
"tuesday": False,
|
||||||
"wednesday": False,
|
"wednesday": False,
|
||||||
|
@ -215,9 +153,9 @@ class Command(BaseCommand):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
CalendarDate.objects.update_or_create(
|
CalendarDate.objects.update_or_create(
|
||||||
id=f"{feed_type}-{headsign}-{trip_update.trip.start_date}",
|
id=f"{gtfs_code}-ADDED-{headsign}-{trip_update.trip.start_date}",
|
||||||
defaults={
|
defaults={
|
||||||
"service_id": f"{feed_type}-new-{headsign}",
|
"service_id": f"{gtfs_code}-ADDED-{headsign}",
|
||||||
"date": trip_update.trip.start_date,
|
"date": trip_update.trip.start_date,
|
||||||
"exception_type": ExceptionType.ADDED,
|
"exception_type": ExceptionType.ADDED,
|
||||||
}
|
}
|
||||||
|
@ -225,32 +163,17 @@ class Command(BaseCommand):
|
||||||
Trip.objects.update_or_create(
|
Trip.objects.update_or_create(
|
||||||
id=trip_id,
|
id=trip_id,
|
||||||
defaults={
|
defaults={
|
||||||
"route_id": route_id,
|
"route_id": route.id,
|
||||||
"service_id": f"{feed_type}-new-{headsign}",
|
"service_id": f"{gtfs_code}-ADDED-{headsign}",
|
||||||
"headsign": headsign,
|
"headsign": headsign,
|
||||||
"direction_id": trip_update.trip.direction_id,
|
"direction_id": trip_update.trip.direction_id,
|
||||||
|
"gtfs_feed": gtfs_feed,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sample_trip = Trip.objects.filter(id__in=trip_ids, route_id=route_id)
|
|
||||||
sample_trip = sample_trip.first() if sample_trip.exists() else None
|
|
||||||
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
||||||
stop_id = stop_time_update.stop_id
|
stop_id = stop_time_update.stop_id
|
||||||
stop = Stop.objects.get(id=stop_id)
|
stop_id = f"{gtfs_code}-{stop_id}"
|
||||||
if stop.location_type == LocationType.STATION:
|
|
||||||
if not StopTime.objects.filter(trip_id=trip_id).exists():
|
|
||||||
if sample_trip:
|
|
||||||
stop = StopTime.objects.get(trip_id=sample_trip.id,
|
|
||||||
stop__parent_station_id=stop_id).stop
|
|
||||||
elif StopTime.objects.filter(trip_id=trip_id, stop__parent_station_id=stop_id).exists():
|
|
||||||
stop = StopTime.objects.get(trip_id=trip_id, stop__parent_station_id=stop_id).stop
|
|
||||||
else:
|
|
||||||
stops = [s for s in Stop.objects.filter(parent_station_id=stop_id).all()
|
|
||||||
for s2 in StopTime.objects.filter(trip_id=trip_id).all()
|
|
||||||
if s.stop_type in s2.stop.stop_type
|
|
||||||
or s2.stop.stop_type in s.stop_type]
|
|
||||||
stop = stops[0] if stops else stop
|
|
||||||
stop_id = stop.id
|
|
||||||
|
|
||||||
arr_time = datetime.fromtimestamp(stop_time_update.arrival.time,
|
arr_time = datetime.fromtimestamp(stop_time_update.arrival.time,
|
||||||
tz=ZoneInfo("Europe/Paris")) - start_dt
|
tz=ZoneInfo("Europe/Paris")) - start_dt
|
||||||
|
@ -263,7 +186,7 @@ class Command(BaseCommand):
|
||||||
and stop_sequence < len(trip_update.stop_time_update) - 1 else PickupType.NONE
|
and stop_sequence < len(trip_update.stop_time_update) - 1 else PickupType.NONE
|
||||||
|
|
||||||
StopTime.objects.update_or_create(
|
StopTime.objects.update_or_create(
|
||||||
id=f"{trip_id}-{stop_id}",
|
id=f"{trip_id}-{stop_time_update.stop_id}",
|
||||||
trip_id=trip_id,
|
trip_id=trip_id,
|
||||||
defaults={
|
defaults={
|
||||||
"stop_id": stop_id,
|
"stop_id": stop_id,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,15 +2,58 @@ from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class TransportType(models.TextChoices):
|
class Country(models.TextChoices):
|
||||||
TGV = "TGV", _("TGV")
|
"""
|
||||||
TER = "TER", _("TER")
|
Country list by ISO 3166-1 alpha-2 code.
|
||||||
INTERCITES = "IC", _("Intercités")
|
Only countries that are member of the Council of Europe
|
||||||
TRANSILIEN = "TN", _("Transilien")
|
are listed for now.
|
||||||
EUROSTAR = "ES", _("Eurostar")
|
"""
|
||||||
TRENITALIA = "TI", _("Trenitalia")
|
ALBANIA = "AL", _("Albania")
|
||||||
RENFE = "RENFE", _("Renfe")
|
ANDORRA = "AD", _("Andorra")
|
||||||
OBB = "OBB", _("ÖBB")
|
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):
|
class LocationType(models.IntegerChoices):
|
||||||
|
@ -79,6 +122,66 @@ class StopScheduleRelationship(models.IntegerChoices):
|
||||||
UNSCHEDULED = 3, _("Unscheduled")
|
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):
|
class Agency(models.Model):
|
||||||
id = models.CharField(
|
id = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
|
@ -117,6 +220,12 @@ class Agency(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gtfs_feed = models.ForeignKey(
|
||||||
|
GTFSFeed,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("GTFS feed"),
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@ -124,6 +233,7 @@ class Agency(models.Model):
|
||||||
verbose_name = _("Agency")
|
verbose_name = _("Agency")
|
||||||
verbose_name_plural = _("Agencies")
|
verbose_name_plural = _("Agencies")
|
||||||
ordering = ("name",)
|
ordering = ("name",)
|
||||||
|
indexes = (models.Index(fields=['name']), models.Index(fields=['gtfs_feed']),)
|
||||||
|
|
||||||
|
|
||||||
class Stop(models.Model):
|
class Stop(models.Model):
|
||||||
|
@ -161,6 +271,7 @@ class Stop(models.Model):
|
||||||
zone_id = models.CharField(
|
zone_id = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("Zone ID"),
|
verbose_name=_("Zone ID"),
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
url = models.URLField(
|
url = models.URLField(
|
||||||
|
@ -209,10 +320,10 @@ class Stop(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
transport_type = models.CharField(
|
gtfs_feed = models.ForeignKey(
|
||||||
max_length=255,
|
GTFSFeed,
|
||||||
verbose_name=_("Transport type"),
|
on_delete=models.CASCADE,
|
||||||
choices=TransportType,
|
verbose_name=_("GTFS feed"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -227,6 +338,9 @@ class Stop(models.Model):
|
||||||
verbose_name = _("Stop")
|
verbose_name = _("Stop")
|
||||||
verbose_name_plural = _("Stops")
|
verbose_name_plural = _("Stops")
|
||||||
ordering = ("id",)
|
ordering = ("id",)
|
||||||
|
indexes = (models.Index(fields=['name']),
|
||||||
|
models.Index(fields=['code']),
|
||||||
|
models.Index(fields=['gtfs_feed']),)
|
||||||
|
|
||||||
|
|
||||||
class Route(models.Model):
|
class Route(models.Model):
|
||||||
|
@ -241,6 +355,9 @@ class Route(models.Model):
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_("Agency"),
|
verbose_name=_("Agency"),
|
||||||
related_name="routes",
|
related_name="routes",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
short_name = models.CharField(
|
short_name = models.CharField(
|
||||||
|
@ -251,6 +368,7 @@ class Route(models.Model):
|
||||||
long_name = models.CharField(
|
long_name = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("Route long name"),
|
verbose_name=_("Route long name"),
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
desc = models.CharField(
|
desc = models.CharField(
|
||||||
|
@ -281,19 +399,20 @@ class Route(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
transport_type = models.CharField(
|
gtfs_feed = models.ForeignKey(
|
||||||
max_length=255,
|
GTFSFeed,
|
||||||
verbose_name=_("Transport type"),
|
on_delete=models.CASCADE,
|
||||||
choices=TransportType,
|
verbose_name=_("GTFS feed"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.long_name}"
|
return self.long_name or self.short_name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Route")
|
verbose_name = _("Route")
|
||||||
verbose_name_plural = _("Routes")
|
verbose_name_plural = _("Routes")
|
||||||
ordering = ("id",)
|
ordering = ("id",)
|
||||||
|
indexes = (models.Index(fields=['gtfs_feed']),)
|
||||||
|
|
||||||
|
|
||||||
class Trip(models.Model):
|
class Trip(models.Model):
|
||||||
|
@ -361,21 +480,24 @@ class Trip(models.Model):
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
last_update = models.DateTimeField(
|
gtfs_feed = models.ForeignKey(
|
||||||
verbose_name=_("Last update"),
|
GTFSFeed,
|
||||||
null=True,
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("GTFS feed"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def origin(self):
|
def origin(self) -> Stop | None:
|
||||||
return self.stop_times.order_by('stop_sequence').first().stop
|
return self.stop_times.order_by('stop_sequence').first().stop if self.stop_times.exists() else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def destination(self):
|
def destination(self) -> Stop | None:
|
||||||
return self.stop_times.order_by('-stop_sequence').first().stop
|
return self.stop_times.order_by('-stop_sequence').first().stop if self.stop_times.exists() else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def departure_time(self):
|
def departure_time(self):
|
||||||
|
if not self.stop_times.exists():
|
||||||
|
return _("Unknown")
|
||||||
dep_time = self.stop_times.order_by('stop_sequence').first().departure_time
|
dep_time = self.stop_times.order_by('stop_sequence').first().departure_time
|
||||||
hours = int(dep_time.total_seconds() // 3600)
|
hours = int(dep_time.total_seconds() // 3600)
|
||||||
minutes = int((dep_time.total_seconds() % 3600) // 60)
|
minutes = int((dep_time.total_seconds() % 3600) // 60)
|
||||||
|
@ -383,6 +505,8 @@ class Trip(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arrival_time(self):
|
def arrival_time(self):
|
||||||
|
if not self.stop_times.exists():
|
||||||
|
return _("Unknown")
|
||||||
arr_time = self.stop_times.order_by('-stop_sequence').first().arrival_time
|
arr_time = self.stop_times.order_by('-stop_sequence').first().arrival_time
|
||||||
hours = int(arr_time.total_seconds() // 3600)
|
hours = int(arr_time.total_seconds() // 3600)
|
||||||
minutes = int((arr_time.total_seconds() % 3600) // 60)
|
minutes = int((arr_time.total_seconds() % 3600) // 60)
|
||||||
|
@ -390,14 +514,14 @@ class Trip(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def train_type(self):
|
def train_type(self):
|
||||||
if self.route.transport_type == TransportType.TRANSILIEN:
|
if self.gtfs_feed.code == "FR-IDF-TN":
|
||||||
return self.route.short_name
|
return self.route.short_name
|
||||||
else:
|
else:
|
||||||
return self.origin.stop_type
|
return self.origin.stop_type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def train_number(self):
|
def train_number(self):
|
||||||
if self.route.transport_type == TransportType.TRANSILIEN:
|
if self.gtfs_feed.code == "FR-IDF-TN":
|
||||||
return self.short_name
|
return self.short_name
|
||||||
else:
|
else:
|
||||||
return self.headsign
|
return self.headsign
|
||||||
|
@ -422,13 +546,23 @@ class Trip(models.Model):
|
||||||
return "404042"
|
return "404042"
|
||||||
return "000000"
|
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):
|
def __str__(self):
|
||||||
return f"{self.origin.name} {self.departure_time} → {self.destination.name} {self.arrival_time}" \
|
return self.origin_destination
|
||||||
f" - {self.service_id}"
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Trip")
|
verbose_name = _("Trip")
|
||||||
verbose_name_plural = _("Trips")
|
verbose_name_plural = _("Trips")
|
||||||
|
indexes = (models.Index(fields=['route']), models.Index(fields=['gtfs_feed']),)
|
||||||
|
|
||||||
|
|
||||||
class StopTime(models.Model):
|
class StopTime(models.Model):
|
||||||
|
@ -510,6 +644,7 @@ class StopTime(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Stop time")
|
verbose_name = _("Stop time")
|
||||||
verbose_name_plural = _("Stop times")
|
verbose_name_plural = _("Stop times")
|
||||||
|
indexes = (models.Index(fields=['stop']), models.Index(fields=['trip']),)
|
||||||
|
|
||||||
|
|
||||||
class Calendar(models.Model):
|
class Calendar(models.Model):
|
||||||
|
@ -555,10 +690,10 @@ class Calendar(models.Model):
|
||||||
verbose_name=_("End date"),
|
verbose_name=_("End date"),
|
||||||
)
|
)
|
||||||
|
|
||||||
transport_type = models.CharField(
|
gtfs_feed = models.ForeignKey(
|
||||||
max_length=255,
|
GTFSFeed,
|
||||||
verbose_name=_("Transport type"),
|
on_delete=models.CASCADE,
|
||||||
choices=TransportType,
|
verbose_name=_("GTFS feed"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -568,6 +703,7 @@ class Calendar(models.Model):
|
||||||
verbose_name = _("Calendar")
|
verbose_name = _("Calendar")
|
||||||
verbose_name_plural = _("Calendars")
|
verbose_name_plural = _("Calendars")
|
||||||
ordering = ("id",)
|
ordering = ("id",)
|
||||||
|
indexes = (models.Index(fields=['gtfs_feed']),)
|
||||||
|
|
||||||
|
|
||||||
class CalendarDate(models.Model):
|
class CalendarDate(models.Model):
|
||||||
|
@ -600,6 +736,7 @@ class CalendarDate(models.Model):
|
||||||
verbose_name = _("Calendar date")
|
verbose_name = _("Calendar date")
|
||||||
verbose_name_plural = _("Calendar dates")
|
verbose_name_plural = _("Calendar dates")
|
||||||
ordering = ("id",)
|
ordering = ("id",)
|
||||||
|
indexes = (models.Index(fields=['service']), models.Index(fields=['date']),)
|
||||||
|
|
||||||
|
|
||||||
class Transfer(models.Model):
|
class Transfer(models.Model):
|
||||||
|
@ -668,10 +805,17 @@ class FeedInfo(models.Model):
|
||||||
verbose_name=_("Feed version"),
|
verbose_name=_("Feed version"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gtfs_feed = models.ForeignKey(
|
||||||
|
GTFSFeed,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("GTFS feed"),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Feed info")
|
verbose_name = _("Feed info")
|
||||||
verbose_name_plural = _("Feed infos")
|
verbose_name_plural = _("Feed infos")
|
||||||
ordering = ("publisher_name",)
|
ordering = ("publisher_name",)
|
||||||
|
indexes = (models.Index(fields=['gtfs_feed']),)
|
||||||
|
|
||||||
|
|
||||||
class TripUpdate(models.Model):
|
class TripUpdate(models.Model):
|
||||||
|
@ -705,6 +849,7 @@ class TripUpdate(models.Model):
|
||||||
verbose_name_plural = _("Trip updates")
|
verbose_name_plural = _("Trip updates")
|
||||||
ordering = ("start_date", "trip",)
|
ordering = ("start_date", "trip",)
|
||||||
unique_together = ("trip", "start_date", "start_time",)
|
unique_together = ("trip", "start_date", "start_time",)
|
||||||
|
indexes = (models.Index(fields=['trip']),)
|
||||||
|
|
||||||
|
|
||||||
class StopTimeUpdate(models.Model):
|
class StopTimeUpdate(models.Model):
|
||||||
|
@ -753,3 +898,4 @@ class StopTimeUpdate(models.Model):
|
||||||
verbose_name_plural = _("Stop time updates")
|
verbose_name_plural = _("Stop time updates")
|
||||||
ordering = ("trip_update", "stop_time",)
|
ordering = ("trip_update", "stop_time",)
|
||||||
unique_together = ("trip_update", "stop_time",)
|
unique_together = ("trip_update", "stop_time",)
|
||||||
|
indexes = (models.Index(fields=['trip_update']), models.Index(fields=['stop_time']),)
|
||||||
|
|
Loading…
Reference in New Issue