Compare commits

..

3 Commits

24 changed files with 1690 additions and 165 deletions

View File

@ -18,7 +18,7 @@ function App() {
element: <Home />,
},
{
path: "/station/:theme/:stopId",
path: "/station/:theme/:stationSlug",
element: <Station />
}
])

View File

@ -1,7 +1,7 @@
import {Autocomplete, TextField} from "@mui/material";
import {useRef, useState} from "react";
function AutocompleteStop(params) {
function AutocompleteStation(params) {
const [options, setOptions] = useState([])
const previousController = useRef()
@ -17,7 +17,7 @@ function AutocompleteStop(params) {
const controller = new AbortController()
const signal = controller.signal
previousController.current = controller
fetch("/api/gtfs/stop/?location_type=1&search=" + value, {signal})
fetch("/api/core/station/?search=" + value, {signal})
.then(response => response.json())
.then(data => data.results)
.then(setOptions)
@ -40,24 +40,7 @@ function AutocompleteStop(params) {
}
function getOptionGroup(option) {
switch (option.gtfs_feed) {
case "FR-SNCF-TGV":
case "FR-SNCF-IC":
case "FR-SNCF-TER":
return "TGV/TER/Intercités"
case "FR-IDF-TN":
return "Transilien"
case "FR-EUROSTAR":
return "Eurostar"
case "IT-FRA-TI":
return "Trenitalia France"
case "ES-RENFE":
return "RENFE"
case "AT-OBB":
return "ÖBB"
default:
return option.gtfs_feed
}
return option.country
}
export default AutocompleteStop;
export default AutocompleteStation;

View File

@ -1,11 +1,11 @@
import AutocompleteStop from "./AutocompleteStop"
import AutocompleteStation from "./AutocompleteStation"
import {useNavigate} from "react-router-dom"
function Home() {
const navigate = useNavigate()
function onStationSelected(event, stop) {
navigate(`/station/sncf/${stop.id}/`)
function onStationSelected(event, station) {
navigate(`/station/sncf/${station.slug}/`)
}
return <>
@ -13,7 +13,7 @@ function Home() {
<h2>
Choisissez une gare dont vous désirez connaître le tableau des prochains départs et arrivées :
</h2>
<AutocompleteStop onChange={onStationSelected} />
<AutocompleteStation onChange={onStationSelected} />
</>
}

View File

@ -5,14 +5,14 @@ import {Box, Button, FormLabel} from "@mui/material";
import {DatePicker, TimePicker} from "@mui/x-date-pickers";
import dayjs from "dayjs";
import {useQuery, useQueryClient} from "@tanstack/react-query";
import AutocompleteStop from "./AutocompleteStop";
import AutocompleteStation from "./AutocompleteStation";
function DateTimeSelector({stop, date, time}) {
function DateTimeSelector({station, date, time}) {
const navigate = useNavigate()
function onStationSelected(event, stop) {
if (stop !== null)
navigate(`/station/sncf/${stop.id}/`)
function onStationSelected(event, station) {
if (station !== null)
navigate(`/station/sncf/${station.slug}/`)
}
return <>
@ -20,7 +20,7 @@ function DateTimeSelector({stop, date, time}) {
<FormLabel>
Changer la gare recherchée :
</FormLabel>
<AutocompleteStop onChange={onStationSelected} />
<AutocompleteStation onChange={onStationSelected} />
<FormLabel>
Modifier la date et l'heure de recherche :
</FormLabel>
@ -32,7 +32,7 @@ function DateTimeSelector({stop, date, time}) {
}
function Station() {
let {stopId, theme} = useParams()
let {theme, stationSlug} = useParams()
let [searchParams, _setSearchParams] = useSearchParams()
const now = new Date()
let dateNow = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
@ -41,13 +41,13 @@ function Station() {
let [time, setTime] = useState(searchParams.get('time') || timeNow)
useQueryClient()
const stopQuery = useQuery({
queryKey: ['stop', stopId],
queryFn: () => fetch(`/api/gtfs/stop/${stopId}/`)
const stationQuery = useQuery({
queryKey: ['station', stationSlug],
queryFn: () => fetch(`/api/core/station/${stationSlug}/`)
.then(response => response.json()),
enabled: !!stopId,
enabled: !!stationSlug,
})
const stop = stopQuery.data ?? {name: "Chargement…"}
const station = stationQuery.data ?? {name: "Chargement…"}
if (time === timeNow) {
setInterval(() => {
@ -62,13 +62,13 @@ function Station() {
return (
<div className="Station">
<header className="App-header">
<h1>Horaires en gare de {stop.name}</h1>
<h1>Horaires en gare de {station.name}</h1>
</header>
<main>
<DateTimeSelector stop={stop} date={date} time={time} />
<TrainsTable stop={stop} date={date} time={time} tableType="departures" />
<TrainsTable stop={stop} date={date} time={time} tableType="arrivals" />
<DateTimeSelector station={station} date={date} time={time} />
<TrainsTable station={station} date={date} time={time} tableType="departures" />
<TrainsTable station={station} date={date} time={time} tableType="arrivals" />
</main>
</div>
)

View File

@ -26,12 +26,12 @@ const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({
},
}));
function TrainsTable({stop, date, time, tableType}) {
function TrainsTable({station, date, time, tableType}) {
return <>
<TableContainer>
<Table>
<TrainsTableHeader tableType={tableType} />
<TrainsTableBody stop={stop} date={date} time={time} tableType={tableType} />
<TrainsTableBody station={station} date={date} time={time} tableType={tableType} />
</Table>
</TableContainer>
</>
@ -49,7 +49,7 @@ function TrainsTableHeader({tableType}) {
</>
}
function TrainsTableBody({stop, date, time, tableType}) {
function TrainsTableBody({station, date, time, tableType}) {
const filterTime = useCallback((train) => {
if (tableType === "departures")
return `${train.departure_date}T${train.departure_time_24h}` >= `${date}T${time}`
@ -58,16 +58,16 @@ function TrainsTableBody({stop, date, time, tableType}) {
}, [date, time, tableType])
const updateTrains = useCallback(() => {
return fetch(`/api/station/next_${tableType}/?stop_id=${stop.id}&date=${date}&time=${time}&offset=${0}&limit=${20}`)
return fetch(`/api/station/next_${tableType}/?station_slug=${station.slug}&date=${date}&time=${time}&offset=${0}&limit=${20}`)
.then(response => response.json())
.then(data => data.results)
.then(data => [...data])
}, [stop.id, date, time, tableType])
}, [station.id, date, time, tableType])
const trainsQuery = useQuery({
queryKey: ['trains', stop.id, tableType],
queryKey: ['trains', station.id, tableType],
queryFn: updateTrains,
enabled: !!stop.id,
enabled: !!station.id,
})
const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data])
@ -114,7 +114,7 @@ function TrainRow({train, tableType, date, time}) {
const stopTimesQuery = useQuery({
queryKey: ['stop_times', trip.id],
queryFn: () => fetch(`/api/gtfs/stop_time/?trip=${trip.id}&order=stop_sequence&limit=1000`)
queryFn: () => fetch(`/api/gtfs/stop_time/?${new URLSearchParams({trip: trip.id, order: 'stop_sequence', limit: 1000})}`)
.then(response => response.json())
.then(data => data.results),
enabled: !!trip.id,

View File

@ -1,9 +1,18 @@
from rest_framework import serializers
from trainvel.core.models import Station
from trainvel.gtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
Transfer, FeedInfo, TripUpdate, StopTimeUpdate
class StationSerializer(serializers.ModelSerializer):
class Meta:
model = Station
lookup_field = 'slug'
fields = ('id', 'slug', 'name', 'uic', 'uic8_sncf', 'latitude', 'longitude', 'country',
'country_hint', 'main_station_hint',)
class AgencySerializer(serializers.ModelSerializer):
class Meta:
model = Agency

View File

@ -10,13 +10,28 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from trainvel.api.serializers import AgencySerializer, StopSerializer, RouteSerializer, TripSerializer, \
StopTimeSerializer, CalendarSerializer, CalendarDateSerializer, TransferSerializer, \
FeedInfoSerializer, TripUpdateSerializer, StopTimeUpdateSerializer
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, StopTimeUpdate, \
Transfer, Trip, TripUpdate
FeedInfoSerializer, TripUpdateSerializer, StopTimeUpdateSerializer, StationSerializer
from trainvel.core.models import Station
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, \
StopTimeUpdate, \
Transfer, Trip, TripUpdate, PickupType
CACHE_CONTROL = cache_control(max_age=7200)
CACHE_CONTROL = cache_control(max_age=30)
LAST_MODIFIED = last_modified(lambda *args, **kwargs: GTFSFeed.objects.order_by('-last_modified').first().last_modified)
LOOKUP_VALUE_REGEX = r"[\w.: |-]+"
LOOKUP_VALUE_REGEX = r"[\w.: |+-]+"
class StationViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Station.objects.filter(is_suggestable=True)
serializer_class = StationSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = '__all__'
search_fields = ['name', 'slug',
'info_de', 'info_en', 'info_es', 'info_fr', 'info_it', 'info_nb', 'info_nl', 'info_cs',
'info_da', 'info_hu', 'info_ja', 'info_ko', 'info_pl', 'info_pt', 'info_ru', 'info_sv',
'info_tr', 'info_zh', ]
lookup_field = 'slug'
lookup_value_regex = LOOKUP_VALUE_REGEX
@method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED])
@ -135,8 +150,7 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
def get_queryset(self):
now = datetime.now()
stop_id = self.request.query_params.get('stop_id', None)
stop_name = self.request.query_params.get('stop_name', None)
station_slug = self.request.query_params.get('station_slug', None)
query_date = date.fromisoformat(self.request.query_params.get('date', now.date().isoformat()))
query_time = self.request.query_params.get('time', now.time().isoformat(timespec='seconds'))
query_time = timedelta(seconds=int(query_time[:2]) * 3600
@ -148,16 +162,10 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
tomorrow = query_date + timedelta(days=1)
stop_filter = Q(stop__location_type=0)
if stop_id:
stop = Stop.objects.get(id=stop_id)
stops = Stop.objects.filter(Q(id=stop_id)
| Q(parent_station=stop_id))
if stop.location_type == 0 and stop.parent_station_id is not None:
stops |= Stop.objects.filter(parent_station=stop.parent_station_id)
stop_filter = Q(stop__in=stops.values_list('id', flat=True))
elif stop_name:
stops = Stop.objects.filter(name__iexact=stop_name).values_list('id', flat=True)
stop_filter = Q(stop__in=stops)
if station_slug:
station = Station.objects.get(is_suggestable=True, slug=station_slug)
near_stops = station.get_near_stops()
stop_filter = Q(stop_id__in=near_stops.values_list('id', flat=True))
def calendar_filter(d: date):
return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1)
@ -189,7 +197,7 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
qs_today = StopTime.objects.filter(stop_filter) \
.annotate(departure_time_real=departure_time_real(query_date)) \
.filter(departure_time_real__gte=query_time) \
.filter(Q(pickup_type=0) | canceled_filter(query_date)) \
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(query_date)) \
.filter(calendar_filter(query_date)) \
.annotate(departure_date=Value(query_date)) \
.annotate(departure_time_24h=F('departure_time'))
@ -221,8 +229,7 @@ class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet):
def get_queryset(self):
now = datetime.now()
stop_id = self.request.query_params.get('stop_id', None)
stop_name = self.request.query_params.get('stop_name', None)
station_slug = self.request.query_params.get('station_slug', None)
query_date = date.fromisoformat(self.request.query_params.get('date', now.date().isoformat()))
query_time = self.request.query_params.get('time', now.time().isoformat(timespec='seconds'))
query_time = timedelta(seconds=int(query_time[:2]) * 3600
@ -235,16 +242,10 @@ class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet):
tomorrow = query_date + timedelta(days=1)
stop_filter = Q(stop__location_type=0)
if stop_id:
stop = Stop.objects.get(id=stop_id)
stops = Stop.objects.filter(Q(id=stop_id)
| Q(parent_station=stop_id))
if stop.location_type == 0 and stop.parent_station_id is not None:
stops |= Stop.objects.filter(parent_station=stop.parent_station_id)
stop_filter = Q(stop__in=stops.values_list('id', flat=True))
elif stop_name:
stops = Stop.objects.filter(name__iexact=stop_name).values_list('id', flat=True)
stop_filter = Q(stop__in=stops)
if station_slug:
station = Station.objects.get(is_suggestable=True, slug=station_slug)
near_stops = station.get_near_stops()
stop_filter = Q(stop_id__in=near_stops.values_list('id', flat=True))
def calendar_filter(d: date):
return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1)

View File

23
trainvel/core/admin.py Normal file
View File

@ -0,0 +1,23 @@
from django.contrib import admin
from trainvel.core.models import Station
class StationInline(admin.TabularInline):
model = Station
extra = 0
autocomplete_fields = ('parent_station', 'same_as',)
show_change_link = True
ordering = ('name',)
readonly_fields = ('id',)
fk_name = 'parent_station'
@admin.register(Station)
class StationAdmin(admin.ModelAdmin):
list_display = ('name', 'country', 'uic', 'latitude', 'longitude',)
list_filter = ('country', 'is_city', 'is_main_station', 'is_airport', 'is_suggestable',
'country_hint', 'main_station_hint',)
search_fields = ('name', 'slug',)
autocomplete_fields = ('parent_station', 'same_as',)
inlines = [StationInline]

8
trainvel/core/apps.py Normal file
View File

@ -0,0 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class CoreConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "trainvel.core"
verbose_name = _("Trainvel - Core")

View File

@ -0,0 +1,325 @@
msgid ""
msgstr ""
"Project-Id-Version: 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-09 22:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello <ynerant@emy.lu>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: trainvel/core/apps.py:8
msgid "Trainvel - Core"
msgstr "Trainvel - Cœur"
#: trainvel/core/models.py:15
msgid "name"
msgstr "nom"
#: trainvel/core/models.py:20
msgid "slug"
msgstr "slug"
#: trainvel/core/models.py:24
msgid "UIC"
msgstr "UIC"
#: trainvel/core/models.py:31
msgid "UIC8 SNCF"
msgstr "UIC8 SNCF"
#: trainvel/core/models.py:38
msgid "latitude"
msgstr "latitude"
#: trainvel/core/models.py:45
msgid "longitude"
msgstr "longitude"
#: trainvel/core/models.py:54
msgid "parent station"
msgstr "gare parente"
#: trainvel/core/models.py:63
msgid "country"
msgstr "pays"
#: trainvel/core/models.py:69
msgid "timezone"
msgstr "fuseau horaire"
#: trainvel/core/models.py:73
msgid "is city"
msgstr "est une ville"
#: trainvel/core/models.py:77
msgid "is main station"
msgstr "est une gare principale"
#: trainvel/core/models.py:81
msgid "is airport"
msgstr "est un aéroport"
#: trainvel/core/models.py:85
msgid "is suggestable"
msgstr "est suggérable"
#: trainvel/core/models.py:89
msgid "country hint"
msgstr "indice de pays"
#: trainvel/core/models.py:93
msgid "main station hint"
msgstr "indice de gare principale"
#: trainvel/core/models.py:98
msgid "SNCF ID"
msgstr "ID SNCF"
#: trainvel/core/models.py:106
msgid "SNCF TVS ID"
msgstr "ID SNCF TVS"
#: trainvel/core/models.py:113
msgid "SNCF is enabled"
msgstr "SNCF est activé"
#: trainvel/core/models.py:118
msgid "Entur ID"
msgstr "ID Entur"
#: trainvel/core/models.py:125
msgid "Entur is enabled"
msgstr "Entur est activé"
#: trainvel/core/models.py:129
msgid "DB ID"
msgstr "ID DB"
#: trainvel/core/models.py:136
msgid "DB is enabled"
msgstr "DB est activé"
#: trainvel/core/models.py:141
msgid "Busbud ID"
msgstr "ID Busbud"
#: trainvel/core/models.py:148
msgid "Busbud is enabled"
msgstr "Busbud est activé"
#: trainvel/core/models.py:153
msgid "distribusion ID"
msgstr "ID distribusion"
#: trainvel/core/models.py:160
msgid "distribusion is enabled"
msgstr "distribusion est activé"
#: trainvel/core/models.py:164
msgid "Flixbus ID"
msgstr "ID Flixbus"
#: trainvel/core/models.py:171
msgid "Flixbus is enabled"
msgstr "Flixbus est activé"
#: trainvel/core/models.py:175
msgid "CFF ID"
msgstr "ID CFF"
#: trainvel/core/models.py:182
msgid "CFF is enabled"
msgstr "CFF est activé"
#: trainvel/core/models.py:187
msgid "Leo Express ID"
msgstr "ID Leo Express"
#: trainvel/core/models.py:194
msgid "Leo Express is enabled"
msgstr "Leo Express est activé"
#: trainvel/core/models.py:198
msgid "ÖBB ID"
msgstr "ID ÖBB"
#: trainvel/core/models.py:205
msgid "ÖBB is enabled"
msgstr "ÖBB est activé"
#: trainvel/core/models.py:210
msgid "Ouigo ID"
msgstr "ID Ouigo"
#: trainvel/core/models.py:217
msgid "Ouigo is enabled"
msgstr "Ouigo est activé"
#: trainvel/core/models.py:221
msgid "Trenitalia ID"
msgstr "ID Trenitalia"
#: trainvel/core/models.py:228
msgid "Trenitalia is enabled"
msgstr "Trenitalia est activé"
#: trainvel/core/models.py:233
msgid "Trenitalia RTVT ID"
msgstr "ID Trenitalia RTVT"
#: trainvel/core/models.py:241
msgid "Trenord ID"
msgstr "ID Trenord"
#: trainvel/core/models.py:249
msgid "NTV RTIV ID"
msgstr "ID NTV RTIV"
#: trainvel/core/models.py:257
msgid "NTV ID"
msgstr "ID NTV"
#: trainvel/core/models.py:264
msgid "NTV is enabled"
msgstr "NTV est activé"
#: trainvel/core/models.py:269
msgid "HKX ID"
msgstr "ID HKX"
#: trainvel/core/models.py:276
msgid "HKX is enabled"
msgstr "HKX est activé"
#: trainvel/core/models.py:280
msgid "Renfe ID"
msgstr "ID Renfe"
#: trainvel/core/models.py:287
msgid "Renfe is enabled"
msgstr "Renfe est activé"
#: trainvel/core/models.py:292
msgid "ATOC ID"
msgstr "ID ATOC"
#: trainvel/core/models.py:299
msgid "ATOC is enabled"
msgstr "ATOC est activé"
#: trainvel/core/models.py:304
msgid "Benerail ID"
msgstr "ID Benerail"
#: trainvel/core/models.py:311
msgid "Benerail is enabled"
msgstr "Benerail est activé"
#: trainvel/core/models.py:316
msgid "Westbahn ID"
msgstr "ID Westbahn"
#: trainvel/core/models.py:323
msgid "Westbahn is enabled"
msgstr "Westbahn est activé"
#: trainvel/core/models.py:327
msgid "SNCF self-service machine"
msgstr "Automate self-service SNCF"
#: trainvel/core/models.py:333
msgid "same as"
msgstr "identique à"
#: trainvel/core/models.py:342
msgid "info (DE)"
msgstr "info (DE)"
#: trainvel/core/models.py:350
msgid "info (EN)"
msgstr "info (EN)"
#: trainvel/core/models.py:358
msgid "info (ES)"
msgstr "info (ES)"
#: trainvel/core/models.py:366
msgid "info (FR)"
msgstr "info (FR)"
#: trainvel/core/models.py:374
msgid "info (IT)"
msgstr "info (IT)"
#: trainvel/core/models.py:382
msgid "info (NB)"
msgstr "info (NB)"
#: trainvel/core/models.py:390
msgid "info (NL)"
msgstr "info (NL)"
#: trainvel/core/models.py:398
msgid "info (CS)"
msgstr "info (CS)"
#: trainvel/core/models.py:406
msgid "info (DA)"
msgstr "info (DA)"
#: trainvel/core/models.py:414
msgid "info (HU)"
msgstr "info (HU)"
#: trainvel/core/models.py:422
msgid "info (JA)"
msgstr "info (JA)"
#: trainvel/core/models.py:430
msgid "info (KO)"
msgstr "info (KO)"
#: trainvel/core/models.py:438
msgid "info (PL)"
msgstr "info (PL)"
#: trainvel/core/models.py:446
msgid "info (PT)"
msgstr "info (PT)"
#: trainvel/core/models.py:454
msgid "info (RU)"
msgstr "info (RU)"
#: trainvel/core/models.py:462
msgid "info (SV)"
msgstr "info (SV)"
#: trainvel/core/models.py:470
msgid "info (TR)"
msgstr "info (TR)"
#: trainvel/core/models.py:478
msgid "info (ZH)"
msgstr "info (ZH)"
#: trainvel/core/models.py:486
msgid "normalized code (Trainline)"
msgstr "code normalisé (Trainline)"
#: trainvel/core/models.py:491
msgid "IATA airport code"
msgstr "code aéroport IATA"
#: trainvel/core/models.py:501
msgid "station"
msgstr "gare"
#: trainvel/core/models.py:502
msgid "stations"
msgstr "gares"

View File

View File

@ -0,0 +1,33 @@
import csv
import requests
from django.core.management import BaseCommand
from tqdm import tqdm
from trainvel.core.models import Station
class Command(BaseCommand):
def handle(self, *args, **options):
def convert_value(value: str) -> str:
return True if value == 't' else False if value == 'f' else (value or None)
stations, stations_without_fk = [], []
STATIONS_URL = "https://raw.githubusercontent.com/trainline-eu/stations/master/stations.csv"
with requests.get(STATIONS_URL, stream=True) as resp:
for row in csv.DictReader(tqdm(resp.iter_lines(decode_unicode=True)), delimiter=';'):
row: dict
values = {k.replace(':', '_').replace('normalised_code', 'normalized_code_trainline')
.replace('same_as', 'same_as_id'): convert_value(v)
for k, v in row.items()}
values_without_fk = values.copy()
del values_without_fk['same_as_id']
del values_without_fk['parent_station_id']
stations.append(Station(**values))
stations_without_fk.append(Station(**values_without_fk))
Station.objects.bulk_create(stations_without_fk, update_conflicts=True, unique_fields=['id'],
update_fields=[k for k in values_without_fk.keys() if k != 'id'])
Station.objects.bulk_create(stations, update_conflicts=True, unique_fields=['id'],
update_fields=['same_as_id', 'parent_station_id'])

View File

@ -0,0 +1,603 @@
# Generated by Django 5.0.1 on 2024-05-09 22:15
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Station",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255, verbose_name="name")),
("slug", models.SlugField(max_length=255, verbose_name="slug")),
(
"uic",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="UIC"
),
),
(
"uic8_sncf",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="UIC8 SNCF"
),
),
(
"latitude",
models.FloatField(
blank=True, default=None, null=True, verbose_name="latitude"
),
),
(
"longitude",
models.FloatField(
blank=True, default=None, null=True, verbose_name="longitude"
),
),
(
"country",
models.CharField(
choices=[
("AL", "Albania"),
("AD", "Andorra"),
("AM", "Armenia"),
("AT", "Austria"),
("AZ", "Azerbaijan"),
("BE", "Belgium"),
("BA", "Bosnia and Herzegovina"),
("BG", "Bulgaria"),
("HR", "Croatia"),
("CY", "Cyprus"),
("CZ", "Czech Republic"),
("DK", "Denmark"),
("EE", "Estonia"),
("FI", "Finland"),
("FR", "France"),
("GE", "Georgia"),
("DE", "Germany"),
("GR", "Greece"),
("HU", "Hungary"),
("IS", "Iceland"),
("IE", "Ireland"),
("IT", "Italy"),
("LV", "Latvia"),
("LI", "Liechtenstein"),
("LT", "Lithuania"),
("LU", "Luxembourg"),
("MT", "Malta"),
("MD", "Moldova"),
("MC", "Monaco"),
("ME", "Montenegro"),
("NL", "Netherlands"),
("MK", "North Macedonia"),
("NO", "Norway"),
("PL", "Poland"),
("PT", "Portugal"),
("RO", "Romania"),
("SM", "San Marino"),
("RS", "Serbia"),
("SK", "Slovakia"),
("SI", "Slovenia"),
("ES", "Spain"),
("SE", "Sweden"),
("CH", "Switzerland"),
("TR", "Turkey"),
("GB", "United Kingdom"),
("UA", "Ukraine"),
],
max_length=255,
verbose_name="country",
),
),
(
"time_zone",
models.CharField(max_length=255, verbose_name="timezone"),
),
("is_city", models.BooleanField(verbose_name="is city")),
(
"is_main_station",
models.BooleanField(verbose_name="is main station"),
),
("is_airport", models.BooleanField(verbose_name="is airport")),
("is_suggestable", models.BooleanField(verbose_name="is suggestable")),
("country_hint", models.BooleanField(verbose_name="country hint")),
(
"main_station_hint",
models.BooleanField(verbose_name="main station hint"),
),
(
"sncf_id",
models.CharField(
blank=True,
default=None,
max_length=5,
null=True,
verbose_name="SNCF ID",
),
),
(
"sncf_tvs_id",
models.CharField(
blank=True,
default=None,
max_length=3,
null=True,
verbose_name="SNCF TVS ID",
),
),
(
"sncf_is_enabled",
models.BooleanField(verbose_name="SNCF is enabled"),
),
(
"entur_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Entur ID",
),
),
(
"entur_is_enabled",
models.BooleanField(verbose_name="Entur is enabled"),
),
(
"db_id",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="DB ID"
),
),
("db_is_enabled", models.BooleanField(verbose_name="DB is enabled")),
(
"busbud_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Busbud ID",
),
),
(
"busbud_is_enabled",
models.BooleanField(verbose_name="Busbud is enabled"),
),
(
"distribusion_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="distribusion ID",
),
),
(
"distribusion_is_enabled",
models.BooleanField(verbose_name="distribusion is enabled"),
),
(
"flixbus_id",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="Flixbus ID"
),
),
(
"flixbus_is_enabled",
models.BooleanField(verbose_name="Flixbus is enabled"),
),
(
"cff_id",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="CFF ID"
),
),
("cff_is_enabled", models.BooleanField(verbose_name="CFF is enabled")),
(
"leoexpress_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Leo Express ID",
),
),
(
"leoexpress_is_enabled",
models.BooleanField(verbose_name="Leo Express is enabled"),
),
(
"obb_id",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="ÖBB ID"
),
),
("obb_is_enabled", models.BooleanField(verbose_name="ÖBB is enabled")),
(
"ouigo_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Ouigo ID",
),
),
(
"ouigo_is_enabled",
models.BooleanField(verbose_name="Ouigo is enabled"),
),
(
"trenitalia_id",
models.IntegerField(
blank=True,
default=None,
null=True,
verbose_name="Trenitalia ID",
),
),
(
"trenitalia_is_enabled",
models.BooleanField(verbose_name="Trenitalia is enabled"),
),
(
"trenitalia_rtvt_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Trenitalia RTVT ID",
),
),
(
"trenord_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Trenord ID",
),
),
(
"ntv_rtiv_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="NTV RTIV ID",
),
),
(
"ntv_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="NTV ID",
),
),
("ntv_is_enabled", models.BooleanField(verbose_name="NTV is enabled")),
(
"hkx_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="HKX ID",
),
),
("hkx_is_enabled", models.BooleanField(verbose_name="HKX is enabled")),
(
"renfe_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Renfe ID",
),
),
(
"renfe_is_enabled",
models.BooleanField(verbose_name="Renfe is enabled"),
),
(
"atoc_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="ATOC ID",
),
),
(
"atoc_is_enabled",
models.BooleanField(verbose_name="ATOC is enabled"),
),
(
"benerail_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Benerail ID",
),
),
(
"benerail_is_enabled",
models.BooleanField(verbose_name="Benerail is enabled"),
),
(
"westbahn_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Westbahn ID",
),
),
(
"westbahn_is_enabled",
models.BooleanField(verbose_name="Westbahn is enabled"),
),
(
"sncf_self_service_machine",
models.BooleanField(verbose_name="SNCF self-service machine"),
),
(
"info_de",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (DE)",
),
),
(
"info_en",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (EN)",
),
),
(
"info_es",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (ES)",
),
),
(
"info_fr",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (FR)",
),
),
(
"info_it",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (IT)",
),
),
(
"info_nb",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (NB)",
),
),
(
"info_nl",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (NL)",
),
),
(
"info_cs",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (CS)",
),
),
(
"info_da",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (DA)",
),
),
(
"info_hu",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (HU)",
),
),
(
"info_ja",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (JA)",
),
),
(
"info_ko",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (KO)",
),
),
(
"info_pl",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (PL)",
),
),
(
"info_pt",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (PT)",
),
),
(
"info_ru",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (RU)",
),
),
(
"info_sv",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (SV)",
),
),
(
"info_tr",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (TR)",
),
),
(
"info_zh",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (ZH)",
),
),
(
"normalized_code_trainline",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="normalized code (Trainline)",
),
),
(
"iata_airport_code",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="IATA airport code",
),
),
(
"parent_station",
models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="children",
to="core.station",
verbose_name="parent station",
),
),
(
"same_as",
models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="same_as_other",
to="core.station",
verbose_name="same as",
),
),
],
options={
"verbose_name": "station",
"verbose_name_plural": "stations",
},
),
]

View File

519
trainvel/core/models.py Normal file
View File

@ -0,0 +1,519 @@
from django.conf import settings
from django.db import models
from django.db.models import F, QuerySet
from django.db.models.functions import ACos, Sin, Radians, Cos
from django.utils.translation import gettext_lazy as _
from trainvel.gtfs.models import Country, Stop
class Station(models.Model):
"""
Describes the content of the stations CSV file generated by Trainline.
The CSV file can be found at https://raw.githubusercontent.com/trainline-eu/stations/master/stations.csv
"""
name = models.CharField(
max_length=255,
verbose_name=_("name"),
)
slug = models.SlugField(
max_length=255,
verbose_name=_("slug"),
)
uic = models.IntegerField(
verbose_name=_("UIC"),
blank=True,
null=True,
default=None,
)
uic8_sncf = models.IntegerField(
verbose_name=_("UIC8 SNCF"),
blank=True,
null=True,
default=None,
)
latitude = models.FloatField(
verbose_name=_("latitude"),
blank=True,
null=True,
default=None,
)
longitude = models.FloatField(
verbose_name=_("longitude"),
blank=True,
null=True,
default=None,
)
parent_station = models.ForeignKey(
"Station",
on_delete=models.CASCADE,
verbose_name=_("parent station"),
blank=True,
null=True,
default=None,
related_name="children",
)
country = models.CharField(
max_length=255,
verbose_name=_("country"),
choices=Country,
)
time_zone = models.CharField(
max_length=255,
verbose_name=_("timezone"),
)
is_city = models.BooleanField(
verbose_name=_("is city"),
)
is_main_station = models.BooleanField(
verbose_name=_("is main station"),
)
is_airport = models.BooleanField(
verbose_name=_("is airport"),
)
is_suggestable = models.BooleanField(
verbose_name=_("is suggestable"),
)
country_hint = models.BooleanField(
verbose_name=_("country hint"),
)
main_station_hint = models.BooleanField(
verbose_name=_("main station hint"),
)
sncf_id = models.CharField(
max_length=5,
verbose_name=_("SNCF ID"),
blank=True,
null=True,
default=None,
)
sncf_tvs_id = models.CharField(
max_length=3,
verbose_name=_("SNCF TVS ID"),
blank=True,
null=True,
default=None,
)
sncf_is_enabled = models.BooleanField(
verbose_name=_("SNCF is enabled"),
)
entur_id = models.CharField(
max_length=255,
verbose_name=_("Entur ID"),
blank=True,
null=True,
default=None,
)
entur_is_enabled = models.BooleanField(
verbose_name=_("Entur is enabled"),
)
db_id = models.IntegerField(
verbose_name=_("DB ID"),
blank=True,
null=True,
default=None,
)
db_is_enabled = models.BooleanField(
verbose_name=_("DB is enabled"),
)
busbud_id = models.CharField(
max_length=255,
verbose_name=_("Busbud ID"),
blank=True,
null=True,
default=None,
)
busbud_is_enabled = models.BooleanField(
verbose_name=_("Busbud is enabled"),
)
distribusion_id = models.CharField(
max_length=255,
verbose_name=_("distribusion ID"),
blank=True,
null=True,
default=None,
)
distribusion_is_enabled = models.BooleanField(
verbose_name=_("distribusion is enabled"),
)
flixbus_id = models.IntegerField(
verbose_name=_("Flixbus ID"),
blank=True,
null=True,
default=None,
)
flixbus_is_enabled = models.BooleanField(
verbose_name=_("Flixbus is enabled"),
)
cff_id = models.IntegerField(
verbose_name=_("CFF ID"),
blank=True,
null=True,
default=None,
)
cff_is_enabled = models.BooleanField(
verbose_name=_("CFF is enabled"),
)
leoexpress_id = models.CharField(
max_length=255,
verbose_name=_("Leo Express ID"),
blank=True,
null=True,
default=None,
)
leoexpress_is_enabled = models.BooleanField(
verbose_name=_("Leo Express is enabled"),
)
obb_id = models.IntegerField(
verbose_name=_("ÖBB ID"),
blank=True,
null=True,
default=None,
)
obb_is_enabled = models.BooleanField(
verbose_name=_("ÖBB is enabled"),
)
ouigo_id = models.CharField(
max_length=255,
verbose_name=_("Ouigo ID"),
blank=True,
null=True,
default=None,
)
ouigo_is_enabled = models.BooleanField(
verbose_name=_("Ouigo is enabled"),
)
trenitalia_id = models.IntegerField(
verbose_name=_("Trenitalia ID"),
blank=True,
null=True,
default=None,
)
trenitalia_is_enabled = models.BooleanField(
verbose_name=_("Trenitalia is enabled"),
)
trenitalia_rtvt_id = models.CharField(
max_length=255,
verbose_name=_("Trenitalia RTVT ID"),
blank=True,
null=True,
default=None,
)
trenord_id = models.CharField(
max_length=255,
verbose_name=_("Trenord ID"),
blank=True,
null=True,
default=None,
)
ntv_rtiv_id = models.CharField(
max_length=255,
verbose_name=_("NTV RTIV ID"),
blank=True,
null=True,
default=None,
)
ntv_id = models.CharField(
max_length=255,
verbose_name=_("NTV ID"),
blank=True,
null=True,
default=None,
)
ntv_is_enabled = models.BooleanField(
verbose_name=_("NTV is enabled"),
)
hkx_id = models.CharField(
max_length=255,
verbose_name=_("HKX ID"),
blank=True,
null=True,
default=None,
)
hkx_is_enabled = models.BooleanField(
verbose_name=_("HKX is enabled"),
)
renfe_id = models.CharField(
max_length=255,
verbose_name=_("Renfe ID"),
blank=True,
null=True,
default=None,
)
renfe_is_enabled = models.BooleanField(
verbose_name=_("Renfe is enabled"),
)
atoc_id = models.CharField(
max_length=255,
verbose_name=_("ATOC ID"),
blank=True,
null=True,
default=None,
)
atoc_is_enabled = models.BooleanField(
verbose_name=_("ATOC is enabled"),
)
benerail_id = models.CharField(
max_length=255,
verbose_name=_("Benerail ID"),
blank=True,
null=True,
default=None,
)
benerail_is_enabled = models.BooleanField(
verbose_name=_("Benerail is enabled"),
)
westbahn_id = models.CharField(
max_length=255,
verbose_name=_("Westbahn ID"),
blank=True,
null=True,
default=None,
)
westbahn_is_enabled = models.BooleanField(
verbose_name=_("Westbahn is enabled"),
)
sncf_self_service_machine = models.BooleanField(
verbose_name=_("SNCF self-service machine"),
)
same_as = models.ForeignKey(
"Station",
on_delete=models.CASCADE,
verbose_name=_("same as"),
blank=True,
null=True,
default=None,
related_name="same_as_other",
)
info_de = models.CharField(
max_length=255,
verbose_name=_("info (DE)"),
blank=True,
null=True,
default=None,
)
info_en = models.CharField(
max_length=255,
verbose_name=_("info (EN)"),
blank=True,
null=True,
default=None,
)
info_es = models.CharField(
max_length=255,
verbose_name=_("info (ES)"),
blank=True,
null=True,
default=None,
)
info_fr = models.CharField(
max_length=255,
verbose_name=_("info (FR)"),
blank=True,
null=True,
default=None,
)
info_it = models.CharField(
max_length=255,
verbose_name=_("info (IT)"),
blank=True,
null=True,
default=None,
)
info_nb = models.CharField(
max_length=255,
verbose_name=_("info (NB)"),
blank=True,
null=True,
default=None,
)
info_nl = models.CharField(
max_length=255,
verbose_name=_("info (NL)"),
blank=True,
null=True,
default=None,
)
info_cs = models.CharField(
max_length=255,
verbose_name=_("info (CS)"),
blank=True,
null=True,
default=None,
)
info_da = models.CharField(
max_length=255,
verbose_name=_("info (DA)"),
blank=True,
null=True,
default=None,
)
info_hu = models.CharField(
max_length=255,
verbose_name=_("info (HU)"),
blank=True,
null=True,
default=None,
)
info_ja = models.CharField(
max_length=255,
verbose_name=_("info (JA)"),
blank=True,
null=True,
default=None,
)
info_ko = models.CharField(
max_length=255,
verbose_name=_("info (KO)"),
blank=True,
null=True,
default=None,
)
info_pl = models.CharField(
max_length=255,
verbose_name=_("info (PL)"),
blank=True,
null=True,
default=None,
)
info_pt = models.CharField(
max_length=255,
verbose_name=_("info (PT)"),
blank=True,
null=True,
default=None,
)
info_ru = models.CharField(
max_length=255,
verbose_name=_("info (RU)"),
blank=True,
null=True,
default=None,
)
info_sv = models.CharField(
max_length=255,
verbose_name=_("info (SV)"),
blank=True,
null=True,
default=None,
)
info_tr = models.CharField(
max_length=255,
verbose_name=_("info (TR)"),
blank=True,
null=True,
default=None,
)
info_zh = models.CharField(
max_length=255,
verbose_name=_("info (ZH)"),
blank=True,
null=True,
default=None,
)
normalized_code_trainline = models.CharField(
max_length=255,
verbose_name=_("normalized code (Trainline)"),
blank=True,
null=True,
default=None,
)
iata_airport_code = models.CharField(
max_length=255,
verbose_name=_("IATA airport code"),
blank=True,
null=True,
default=None,
)
def get_near_stops(self, radius: float = settings.STATION_RADIUS) -> QuerySet[Stop]:
"""
Returns a queryset of all stops that are in a radius of radius meters around the station.
It calculates a distance from each stop to the station using spatial coordinates.
"""
return Stop.objects.annotate(distance=6371000 * ACos(
Sin(Radians(self.latitude)) * Sin(Radians(F('lat')))
+ Cos(Radians(self.latitude)) * Cos(Radians(F('lat'))) * Cos(Radians(F('lon')) - Radians(self.longitude))))\
.filter(distance__lte=radius)
def __str__(self):
return self.name
class Meta:
verbose_name = _("station")
verbose_name_plural = _("stations")

3
trainvel/core/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
trainvel/core/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@ -88,5 +88,15 @@
"feed_url": "https://opentransportdata.swiss/fr/dataset/timetable-2024-gtfs2020/permalink",
"rt_feed_url": "https://api.opentransportdata.swiss/gtfsrt2020"
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "LU-ALL",
"fields": {
"name": "CFL",
"country": "LU",
"feed_url": "https://data.public.lu/fr/datasets/r/aab2922d-27ff-4e53-a789-d990cf1ceb1e",
"rt_feed_url": ""
}
}
]

View File

@ -155,7 +155,7 @@ class Command(BaseCommand):
unique_fields=['id'])
routes.clear()
# Calendar.objects.filter(gtfs_feed=gtfs_feed).delete()
Calendar.objects.filter(gtfs_feed=gtfs_feed).delete()
calendars = {}
if "calendar.txt" in zipfile.namelist():
for calendar_dict in csv.DictReader(tqdm(read_file("calendar.txt"), desc="Calendars")):
@ -294,14 +294,14 @@ class Command(BaseCommand):
dep_h, dep_m, dep_s = map(int, dep_time.split(':'))
dep_time = dep_h * 3600 + dep_m * 60 + dep_s
pickup_type = stop_time_dict.get('pickup_type', 0)
drop_off_type = stop_time_dict.get('drop_off_type', 0)
if stop_time_dict['stop_sequence'] == "1":
# First stop
drop_off_type = PickupType.NONE
elif arr_time == dep_time:
# Last stop
pickup_type = PickupType.NONE
pickup_type = stop_time_dict.get('pickup_type', PickupType.REGULAR)
drop_off_type = stop_time_dict.get('drop_off_type', PickupType.REGULAR)
# if stop_time_dict['stop_sequence'] == "1":
# # First stop
# drop_off_type = PickupType.NONE
# elif arr_time == dep_time:
# # Last stop
# pickup_type = PickupType.NONE
st = StopTime(
id=f"{gtfs_code}-{stop_time_dict['trip_id']}-{stop_time_dict['stop_id']}"
@ -349,7 +349,7 @@ class Command(BaseCommand):
from_stop_id=from_stop_id,
to_stop_id=to_stop_id,
transfer_type=transfer_dict['transfer_type'],
min_transfer_time=transfer_dict['min_transfer_time'],
min_transfer_time=transfer_dict.get('min_transfer_time', 0) or 0,
)
transfers.append(transfer)

View File

@ -30,7 +30,7 @@ class Command(BaseCommand):
headers = {}
if gtfs_code == "CH-ALL":
headers["Authorization"] = settings.OPENTRANSPORTDATA_SWISS_TOKEN
resp = requests.get(gtfs_feed.rt_feed_url, allow_redirects=True)
resp = requests.get(gtfs_feed.rt_feed_url, allow_redirects=True, headers=headers)
feed_message = FeedMessage()
feed_message.ParseFromString(resp.content)
@ -41,6 +41,7 @@ class Command(BaseCommand):
f.write(str(feed_message))
for entity in feed_message.entity:
try:
if entity.HasField("trip_update"):
trip_update = entity.trip_update
trip_id = trip_update.trip.trip_id
@ -84,17 +85,15 @@ class Command(BaseCommand):
id=f"{trip_id}-{stop_time_update.stop_id}",
trip_id=trip_id,
stop_id=stop_id,
defaults={
"stop_sequence": stop_sequence,
"arrival_time": datetime.fromtimestamp(stop_time_update.arrival.time,
stop_sequence=stop_sequence,
arrival_time=datetime.fromtimestamp(stop_time_update.arrival.time,
tz=ZoneInfo("Europe/Paris")) - start_dt,
"departure_time": datetime.fromtimestamp(stop_time_update.departure.time,
departure_time=datetime.fromtimestamp(stop_time_update.departure.time,
tz=ZoneInfo("Europe/Paris")) - start_dt,
"pickup_type": (PickupType.REGULAR if stop_time_update.departure.time
pickup_type=(PickupType.REGULAR if stop_time_update.departure.time
else PickupType.NONE),
"drop_off_type": (PickupType.REGULAR if stop_time_update.arrival.time
drop_off_type=(PickupType.REGULAR if stop_time_update.arrival.time
else PickupType.NONE),
}
)
if stop_time_update.schedule_relationship == StopScheduleRelationship.SKIPPED:
@ -122,6 +121,8 @@ class Command(BaseCommand):
stop_times_updates.append(st_update)
else:
self.stdout.write(str(entity))
except Exception as e:
self.stderr.write(self.style.ERROR(f"Error while processing entity: {e}"))
StopTimeUpdate.objects.bulk_create(stop_times_updates,
update_conflicts=True,

View File

@ -45,6 +45,7 @@ INSTALLED_APPS = [
"rest_framework",
"trainvel.api",
"trainvel.core",
"trainvel.gtfs",
]
@ -150,6 +151,8 @@ REST_FRAMEWORK = {
'PAGE_SIZE': 20,
}
STATION_RADIUS = 300
OPENTRANSPORTDATA_SWISS_TOKEN = "CHANGE ME"

View File

@ -18,11 +18,12 @@ from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from trainvel.api.views import AgencyViewSet, StopViewSet, RouteViewSet, TripViewSet, StopTimeViewSet, \
from trainvel.api.views import AgencyViewSet, StopViewSet, RouteViewSet, StationViewSet, TripViewSet, StopTimeViewSet, \
CalendarViewSet, CalendarDateViewSet, TransferViewSet, FeedInfoViewSet, NextDeparturesViewSet, NextArrivalsViewSet, \
TripUpdateViewSet, StopTimeUpdateViewSet
router = routers.DefaultRouter()
router.register("core/station", StationViewSet)
router.register("gtfs/agency", AgencyViewSet)
router.register("gtfs/stop", StopViewSet)
router.register("gtfs/route", RouteViewSet)