Compare commits
	
		
			3 Commits
		
	
	
		
			bc23d63c43
			...
			1c99c5ca47
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1c99c5ca47 | |||
| e3fd6a7f88 | |||
| 036e1604bd | 
							
								
								
									
										65
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,53 +1,26 @@ | |||||||
| # Byte-compiled / optimized / DLL files | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||||||
| dist |  | ||||||
| build |  | ||||||
| __pycache__ |  | ||||||
| *.py[cod] |  | ||||||
| *$py.class |  | ||||||
| *.swp |  | ||||||
| *.egg-info |  | ||||||
| _build |  | ||||||
| .tox |  | ||||||
| .coverage |  | ||||||
| coverage |  | ||||||
|  |  | ||||||
| # Translations |  | ||||||
| *.mo |  | ||||||
| *.pot |  | ||||||
|  |  | ||||||
| # Jupyter Notebook |  | ||||||
| .ipynb_checkpoints |  | ||||||
|  |  | ||||||
| # Spyder project settings |  | ||||||
| .spyderproject |  | ||||||
| .spyproject |  | ||||||
|  |  | ||||||
| # Rope project settings |  | ||||||
| .ropeproject |  | ||||||
|  |  | ||||||
| # PyCharm project settings |  | ||||||
| .idea | .idea | ||||||
|  |  | ||||||
| # VSCode project settings |  | ||||||
| .vscode | .vscode | ||||||
|  |  | ||||||
| # Local data | # dependencies | ||||||
| secrets.py | /node_modules | ||||||
| settings_local.py | /.pnp | ||||||
| *.log | .pnp.js | ||||||
| *.txt |  | ||||||
| media/ |  | ||||||
| output/ |  | ||||||
| /static/ |  | ||||||
| /static_files/ |  | ||||||
|  |  | ||||||
| # Virtualenv | # testing | ||||||
| .env/ | /coverage | ||||||
| env/ |  | ||||||
| .venv/ |  | ||||||
| venv/ |  | ||||||
| db.sqlite3 |  | ||||||
| db.sqlite3-journal |  | ||||||
|  |  | ||||||
| node_modules/ | # production | ||||||
|  | /build | ||||||
|  |  | ||||||
|  | # misc | ||||||
|  | .DS_Store | ||||||
|  | .env.local | ||||||
|  | .env.development.local | ||||||
|  | .env.test.local | ||||||
|  | .env.production.local | ||||||
|  |  | ||||||
|  | npm-debug.log* | ||||||
|  | yarn-debug.log* | ||||||
|  | yarn-error.log* | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								manage.py
									
									
									
									
									
								
							
							
						
						| @@ -1,22 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| """Django's command-line utility for administrative tasks.""" |  | ||||||
| import os |  | ||||||
| import sys |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def main(): |  | ||||||
|     """Run administrative tasks.""" |  | ||||||
|     os.environ.setdefault("DJANGO_SETTINGS_MODULE", "trainvel.settings") |  | ||||||
|     try: |  | ||||||
|         from django.core.management import execute_from_command_line |  | ||||||
|     except ImportError as exc: |  | ||||||
|         raise ImportError( |  | ||||||
|             "Couldn't import Django. Are you sure it's installed and " |  | ||||||
|             "available on your PYTHONPATH environment variable? Did you " |  | ||||||
|             "forget to activate a virtual environment?" |  | ||||||
|         ) from exc |  | ||||||
|     execute_from_command_line(sys.argv) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     main() |  | ||||||
| @@ -46,6 +46,5 @@ | |||||||
|       "last 1 firefox version", |       "last 1 firefox version", | ||||||
|       "last 1 safari version" |       "last 1 safari version" | ||||||
|     ] |     ] | ||||||
|   }, |   } | ||||||
|   "proxy": "http://localhost:8000" |  | ||||||
| } | } | ||||||
| Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB | 
| Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB | 
| Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB | 
| Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB | 
| Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB | 
| Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB | 
| Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB | 
| Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB | 
| Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB | 
| Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB | 
| Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										3
									
								
								public/robots.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | # https://www.robotstxt.org/robotstxt.html | ||||||
|  | User-agent: * | ||||||
|  | Disallow: | ||||||
| Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB | 
| Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB | 
| @@ -1,8 +0,0 @@ | |||||||
| Django>=5.0.4,<6.0 |  | ||||||
| django-cors-headers~=4.3.1 |  | ||||||
| django-extensions~=3.2.3 |  | ||||||
| django-filter~=24.2 |  | ||||||
| djangorestframework~=3.14.0 |  | ||||||
| protobuf~=5.26.1 |  | ||||||
| requests~=2.31.0 |  | ||||||
| tqdm~=4.66.4 |  | ||||||
| @@ -6,10 +6,9 @@ import {frFR, LocalizationProvider} from "@mui/x-date-pickers" | |||||||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs" | import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs" | ||||||
| import 'dayjs/locale/fr' | import 'dayjs/locale/fr' | ||||||
| import './App.css' | import './App.css' | ||||||
| import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; | import {QueryClient, QueryClientProvider} from "@tanstack/react-query" | ||||||
| import {createSyncStoragePersister} from "@tanstack/query-sync-storage-persister"; | import Home from "./Home" | ||||||
| import {persistQueryClient} from "@tanstack/react-query-persist-client"; | import dayjs from "dayjs" | ||||||
| import Home from "./Home"; |  | ||||||
| 
 | 
 | ||||||
| function App() { | function App() { | ||||||
|   const router = createBrowserRouter([ |   const router = createBrowserRouter([ | ||||||
| @@ -18,7 +17,7 @@ function App() { | |||||||
|       element: <Home />, |       element: <Home />, | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       path: "/station/:theme/:stationSlug", |       path: "/station/:theme/:stationId", | ||||||
|       element: <Station /> |       element: <Station /> | ||||||
|     } |     } | ||||||
|   ]) |   ]) | ||||||
| @@ -54,14 +53,7 @@ function App() { | |||||||
|     }, |     }, | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   const localStoragePersister = createSyncStoragePersister({ |   dayjs.locale('fr') | ||||||
|     storage: window.localStorage, |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   persistQueryClient({ |  | ||||||
|     queryClient, |  | ||||||
|     persister: localStoragePersister, |  | ||||||
|   }) |  | ||||||
| 
 | 
 | ||||||
|   return <> |   return <> | ||||||
|     <ThemeProvider theme={theme}> |     <ThemeProvider theme={theme}> | ||||||
| @@ -1,9 +1,8 @@ | |||||||
| import {Autocomplete, TextField} from "@mui/material"; | import {Autocomplete, TextField} from "@mui/material"; | ||||||
| import {useRef, useState} from "react"; | import {useState} from "react"; | ||||||
| 
 | 
 | ||||||
| function AutocompleteStation(params) { | function AutocompleteStation(params) { | ||||||
|   const [options, setOptions] = useState([]) |   const [options, setOptions] = useState([]) | ||||||
|   const previousController = useRef() |  | ||||||
| 
 | 
 | ||||||
|   function onInputChange(event, value) { |   function onInputChange(event, value) { | ||||||
|     if (!value) { |     if (!value) { | ||||||
| @@ -11,17 +10,9 @@ function AutocompleteStation(params) { | |||||||
|       return |       return | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (previousController.current) |     fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/geocode?language=fr&text=${value}`) | ||||||
|       previousController.current.abort() |  | ||||||
| 
 |  | ||||||
|     const controller = new AbortController() |  | ||||||
|     const signal = controller.signal |  | ||||||
|     previousController.current = controller |  | ||||||
|     fetch("/api/core/station/?search=" + value, {signal}) |  | ||||||
|       .then(response => response.json()) |       .then(response => response.json()) | ||||||
|       .then(data => data.results) |  | ||||||
|       .then(setOptions) |       .then(setOptions) | ||||||
|       .catch() |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return <> |   return <> | ||||||
| @@ -29,7 +20,7 @@ function AutocompleteStation(params) { | |||||||
|       id="stop" |       id="stop" | ||||||
|       options={options} |       options={options} | ||||||
|       onInputChange={onInputChange} |       onInputChange={onInputChange} | ||||||
|       filterOptions={(x) => x} |       filterOptions={(x) => x.filter(stop => stop.type === "STOP").filter(stop => !stop.id.startsWith("node/"))} | ||||||
|       getOptionKey={option => option.id} |       getOptionKey={option => option.id} | ||||||
|       getOptionLabel={option => option.name} |       getOptionLabel={option => option.name} | ||||||
|       groupBy={option => getOptionGroup(option)} |       groupBy={option => getOptionGroup(option)} | ||||||
| @@ -40,7 +31,7 @@ function AutocompleteStation(params) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getOptionGroup(option) { | function getOptionGroup(option) { | ||||||
|   return option.country |   return option.id.split('_')[0] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default AutocompleteStation; | export default AutocompleteStation; | ||||||
| @@ -5,7 +5,7 @@ function Home() { | |||||||
|   const navigate = useNavigate() |   const navigate = useNavigate() | ||||||
| 
 | 
 | ||||||
|   function onStationSelected(event, station) { |   function onStationSelected(event, station) { | ||||||
|     navigate(`/station/sncf/${station.slug}/`) |     navigate(`/station/sncf/${station.id}/`) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return <> |   return <> | ||||||
							
								
								
									
										72
									
								
								src/Station.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,72 @@ | |||||||
|  | import {useNavigate, useParams, useSearchParams} from "react-router-dom" | ||||||
|  | import TrainsTable from "./TrainsTable" | ||||||
|  | import TripsFilter from "./TripsFilter" | ||||||
|  | import {useState} from "react" | ||||||
|  | import {Box, Button, FormLabel} from "@mui/material" | ||||||
|  | import {DateTimePicker} from "@mui/x-date-pickers" | ||||||
|  | import dayjs from "dayjs" | ||||||
|  | import {useQuery, useQueryClient} from "@tanstack/react-query" | ||||||
|  | import AutocompleteStation from "./AutocompleteStation" | ||||||
|  |  | ||||||
|  | function DateTimeSelector({datetime, setDatetime}) { | ||||||
|  |   const navigate = useNavigate() | ||||||
|  |  | ||||||
|  |   function onStationSelected(event, station) { | ||||||
|  |     if (station !== null) | ||||||
|  |       navigate(`/station/sncf/${station.id}/`) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return <> | ||||||
|  |     <Box component="form" display="flex" alignItems="center" sx={{'& .MuiTextField-root': { m: 1, width: '25ch' },}}> | ||||||
|  |         <FormLabel> | ||||||
|  |           Changer la gare recherchée : | ||||||
|  |         </FormLabel> | ||||||
|  |         <AutocompleteStation onChange={onStationSelected} /> | ||||||
|  |         <FormLabel> | ||||||
|  |           Modifier la date et l'heure de recherche : | ||||||
|  |         </FormLabel> | ||||||
|  |         <DateTimePicker name="date" label="Date" onChange={setDatetime} value={datetime} /> | ||||||
|  |         <Button type="submit">Rechercher</Button> | ||||||
|  |     </Box> | ||||||
|  |   </> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function Station() { | ||||||
|  |   // eslint-disable-next-line no-unused-vars | ||||||
|  |   let {theme, stationId} = useParams() | ||||||
|  |   // eslint-disable-next-line no-unused-vars | ||||||
|  |   let [searchParams, setSearchParams] = useSearchParams() | ||||||
|  |   const [datetime, setDatetime] = useState(dayjs()) | ||||||
|  |  | ||||||
|  |   useQueryClient() | ||||||
|  |   const stationQuery = useQuery({ | ||||||
|  |     queryKey: ['station', stationId], | ||||||
|  |     queryFn: () => fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/stoptimes?stopId=${stationId}&n=1`) | ||||||
|  |       .then(response => response.json()), | ||||||
|  |     enabled: !!stationId, | ||||||
|  |   }) | ||||||
|  |   const station = stationQuery.data?.stopTimes[0].place ?? {name: "Chargement…"} | ||||||
|  |  | ||||||
|  |   if (searchParams.get("time") === undefined) { | ||||||
|  |     setInterval(() => { | ||||||
|  |       setDatetime(dayjs()) | ||||||
|  |     }, 5000) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <div className="Station"> | ||||||
|  |       <header className="App-header"> | ||||||
|  |         <h1>Horaires en gare de {station.name}</h1> | ||||||
|  |       </header> | ||||||
|  |  | ||||||
|  |       <main> | ||||||
|  |         <DateTimeSelector datetime={datetime} setDatetime={setDatetime} /> | ||||||
|  |         <TripsFilter /> | ||||||
|  |         <TrainsTable station={station} datetime={datetime} tableType="departures" /> | ||||||
|  |         <TrainsTable station={station} datetime={datetime} tableType="arrivals" /> | ||||||
|  |       </main> | ||||||
|  |     </div> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default Station; | ||||||
							
								
								
									
										310
									
								
								src/TrainsTable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,310 @@ | |||||||
|  | import { | ||||||
|  |   Box, | ||||||
|  |   styled, | ||||||
|  |   Table, | ||||||
|  |   TableBody, | ||||||
|  |   TableCell, | ||||||
|  |   TableContainer, | ||||||
|  |   TableHead, | ||||||
|  |   TableRow, | ||||||
|  |   Typography | ||||||
|  | } from "@mui/material" | ||||||
|  | import {CSSTransition, TransitionGroup} from 'react-transition-group' | ||||||
|  | import {useQuery} from "@tanstack/react-query" | ||||||
|  | import {useCallback, useEffect, useMemo, useRef} from "react" | ||||||
|  | import dayjs from "dayjs" | ||||||
|  |  | ||||||
|  | const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({ | ||||||
|  |   'tbody &:nth-of-type(odd)': { | ||||||
|  |     backgroundColor: theme.palette.sncf[tabletype].light, | ||||||
|  |   }, | ||||||
|  |   'th, &:nth-of-type(even)': { | ||||||
|  |     backgroundColor: theme.palette.sncf[tabletype].dark, | ||||||
|  |   }, | ||||||
|  |   // hide last border | ||||||
|  |   '&:last-child td, &:last-child th': { | ||||||
|  |     border: 0, | ||||||
|  |   }, | ||||||
|  | })); | ||||||
|  |  | ||||||
|  | function TrainsTable({station, datetime, tableType}) { | ||||||
|  |   return <> | ||||||
|  |     <TableContainer> | ||||||
|  |       <Table> | ||||||
|  |         <TrainsTableHeader tableType={tableType} /> | ||||||
|  |         <TrainsTableBody station={station} datetime={datetime} tableType={tableType} /> | ||||||
|  |       </Table> | ||||||
|  |     </TableContainer> | ||||||
|  |   </> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function TrainsTableHeader({tableType}) { | ||||||
|  |   return <> | ||||||
|  |     <TableHead> | ||||||
|  |       <StyledTableRow tabletype={tableType}> | ||||||
|  |         <TableCell colSpan="2" fontSize={16} fontWeight="bold">Train</TableCell> | ||||||
|  |         <TableCell fontSize={16} fontWeight="bold">Heure</TableCell> | ||||||
|  |         <TableCell fontSize={16} fontWeight="bold">Destination</TableCell> | ||||||
|  |       </StyledTableRow> | ||||||
|  |     </TableHead> | ||||||
|  |   </> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function TrainsTableBody({station, datetime, tableType}) { | ||||||
|  |   const filterTime = useCallback((train) => { | ||||||
|  |     if (tableType === "departures") | ||||||
|  |       return dayjs(train.place.departure) >= datetime | ||||||
|  |     else | ||||||
|  |       return dayjs(train.place.arrival) >= datetime | ||||||
|  |   }, [datetime, tableType]) | ||||||
|  |  | ||||||
|  |   const updateTrains = useCallback(() => { | ||||||
|  |     const query_params = new URLSearchParams({ | ||||||
|  |       stopId: station.stopId, | ||||||
|  |       arriveBy: tableType === "arrivals", | ||||||
|  |       time: datetime.format(), | ||||||
|  |       n: 20, | ||||||
|  |     }).toString() | ||||||
|  |     return fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/stoptimes?${query_params}`) | ||||||
|  |         .then(response => response.json()) | ||||||
|  |         .then(data => data.stopTimes) | ||||||
|  |         .then(data => [...data]) | ||||||
|  |   }, [station.stopId, datetime, tableType]) | ||||||
|  |  | ||||||
|  |   const trainsQuery = useQuery({ | ||||||
|  |     queryKey: ['trains', station.stopId, tableType], | ||||||
|  |     queryFn: updateTrains, | ||||||
|  |     enabled: !!station.stopId, | ||||||
|  |   }) | ||||||
|  |   const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data]) | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     let validTrains = trains?.filter(filterTime) ?? [] | ||||||
|  |     if (trains?.length > 0 && validTrains.length < trains?.length) | ||||||
|  |       trainsQuery.refetch().then() | ||||||
|  |   }, [trains, filterTime, trainsQuery]) | ||||||
|  |  | ||||||
|  |   const nullRef = useRef(null) | ||||||
|  |   let table_rows = trains.map((train) => <CSSTransition key={train.id} timeout={500} classNames="shrink" nodeRef={nullRef}> | ||||||
|  |       <TrainRow train={train} tableType={tableType} /> | ||||||
|  |     </CSSTransition>) | ||||||
|  |  | ||||||
|  |   return <> | ||||||
|  |     <TableBody> | ||||||
|  |       <TransitionGroup component={null}> | ||||||
|  |         {table_rows} | ||||||
|  |       </TransitionGroup> | ||||||
|  |     </TableBody> | ||||||
|  |   </> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function TrainRow({train, tableType}) { | ||||||
|  |   const tripQuery = useQuery({ | ||||||
|  |     queryKey: ['tripId', train.tripId], | ||||||
|  |     queryFn: () => fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/trip?${new URLSearchParams({tripId: train.tripId})}`) | ||||||
|  |         .then(response => response.json()), | ||||||
|  |     enabled: !!train.tripId, | ||||||
|  |   }) | ||||||
|  |   const trip = tripQuery.data ?? {} | ||||||
|  |   const leg = trip.legs ? trip.legs[0] : null | ||||||
|  |  | ||||||
|  |   const trainType = getTrainType(train) | ||||||
|  |   const backgroundColor = getBackgroundColor(train) | ||||||
|  |   const textColor = getTextColor(train) | ||||||
|  |   const trainTypeDisplay = getTrainTypeDisplay(trainType) | ||||||
|  |  | ||||||
|  |   const stops = useMemo(() => leg ? [leg.from, ...leg.intermediateStops, leg.to] : [], [leg]) | ||||||
|  |   const stopIndex = useMemo(() => { | ||||||
|  |     if (stops.length === 0 || train.place.stopId === undefined) | ||||||
|  |       return -1 | ||||||
|  |     for (let i = 0; i < stops.length; i++) { | ||||||
|  |       const index = tableType === "departures" ? i : stops.length - 1 - i | ||||||
|  |       const stop = stops[index] | ||||||
|  |       let timeCond = tableType === "departures" ? stop.scheduledDeparture === train.place.scheduledDeparture | ||||||
|  |         : stop.scheduledArrival === train.place.scheduledArrival | ||||||
|  |       if (stop.stopId === train.place.stopId && timeCond) | ||||||
|  |         return index | ||||||
|  |     } | ||||||
|  |   }, [stops, train, tableType]) | ||||||
|  |   const nextStops = tableType === "departures" ? stops.slice(stopIndex + 1) : stops.slice(0, stopIndex) | ||||||
|  |  | ||||||
|  |   let headline = nextStops[tableType === "departures" ? nextStops.length - 1 : 0] ?? {name: "Chargement…"} | ||||||
|  |  | ||||||
|  |   const canceled = false  // TODO Implémenter l'annulation | ||||||
|  |   const [delayed, prettyDelay] = getPrettyDelay(train, tableType) | ||||||
|  |  | ||||||
|  |   let stopsNames = nextStops.map(stopTime => stopTime?.name ?? "").join(" > ") ?? "" | ||||||
|  |  | ||||||
|  |   return <> | ||||||
|  |     <StyledTableRow tabletype={tableType}> | ||||||
|  |       <TableCell> | ||||||
|  |         <div> | ||||||
|  |           <Box display="flex" | ||||||
|  |                justifyContent="center" | ||||||
|  |                alignItems="center" | ||||||
|  |                textAlign="center" | ||||||
|  |                width="4em" | ||||||
|  |                height="4em" | ||||||
|  |                borderRadius="15%" | ||||||
|  |                fontWeight="bold" | ||||||
|  |                backgroundColor={backgroundColor} | ||||||
|  |                color={textColor}> | ||||||
|  |             {trainTypeDisplay} | ||||||
|  |           </Box> | ||||||
|  |         </div> | ||||||
|  |       </TableCell> | ||||||
|  |       <TableCell> | ||||||
|  |         <Box display="flex" alignItems="center" justifyContent="center" textAlign="center"> | ||||||
|  |           <div> | ||||||
|  |             <div>{train.routeShortName}</div> | ||||||
|  |             <div>{train.headsign}</div> | ||||||
|  |           </div> | ||||||
|  |         </Box> | ||||||
|  |       </TableCell> | ||||||
|  |       <TableCell> | ||||||
|  |         <Box display="flex" alignItems="center" justifyContent="center"> | ||||||
|  |           <Box> | ||||||
|  |             <Box fontWeight="bold" color="#FFED02" fontSize={24}> | ||||||
|  |               {getDisplayTime(train, tableType)} | ||||||
|  |             </Box> | ||||||
|  |             <Box color={delayed ? "#e86d2b" : "white"} | ||||||
|  |                  fontWeight={delayed ? "bold" : ""}> | ||||||
|  |               {prettyDelay} | ||||||
|  |             </Box> | ||||||
|  |           </Box> | ||||||
|  |         </Box> | ||||||
|  |       </TableCell> | ||||||
|  |       <TableCell> | ||||||
|  |         <Box style={{textDecoration: canceled ? 'line-through': ''}}> | ||||||
|  |           <Typography fontSize={24} fontWeight="bold" data-stop-id={headline.stopId}>{headline.name}</Typography> | ||||||
|  |           <span className="stops">{stopsNames}</span> | ||||||
|  |         </Box> | ||||||
|  |       </TableCell> | ||||||
|  |     </StyledTableRow> | ||||||
|  |   </> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getTrainType(train) { | ||||||
|  |   if (train.place.stopId === undefined) | ||||||
|  |     return "" | ||||||
|  |   switch (train.place.stopId.split('_')[0]) { | ||||||
|  |     case "FR-SNCF-TGV": | ||||||
|  |     case "FR-SNCF-IC": | ||||||
|  |     case "FR-SNCF-TER": | ||||||
|  |       let trainType = train.place.stopId.split("StopPoint:OCE")[1].split("-")[0] | ||||||
|  |       switch (trainType) { | ||||||
|  |         case "Train TER": | ||||||
|  |           return "TER" | ||||||
|  |         case "INTERCITES": | ||||||
|  |           return "INTER-CITÉS" | ||||||
|  |         case "INTERCITES de nuit": | ||||||
|  |           return "INTER-CITÉS de nuit" | ||||||
|  |         default: | ||||||
|  |           return trainType | ||||||
|  |       } | ||||||
|  |     case "FR-IDF-IDFM": | ||||||
|  |     case "FR-GES-CTS": | ||||||
|  |       return "A" | ||||||
|  |     case "FR-EUROSTAR": | ||||||
|  |       return "Eurostar" | ||||||
|  |     case "IT-FRA-TI": | ||||||
|  |       return "Trenitalia France" | ||||||
|  |     case "ES-RENFE": | ||||||
|  |       return "RENFE" | ||||||
|  |     case "AT-OBB": | ||||||
|  |       if (train.routeShortName?.startsWith("NJ")) | ||||||
|  |         return "NJ" | ||||||
|  |       return "ÖBB" | ||||||
|  |     case "CH-ALL": | ||||||
|  |       return "A" | ||||||
|  |     default: | ||||||
|  |       return train.routeShortName?.split(" ")[0] | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getTrainTypeDisplay(trainType) { | ||||||
|  |   switch (trainType) { | ||||||
|  |     case "TGV INOUI": | ||||||
|  |       return <img src="/tgv_inoui.svg" alt="TGV INOUI" width="80%" /> | ||||||
|  |     case "OUIGO": | ||||||
|  |       return <img src="/ouigo.svg" alt="OUIGO" width="80%" /> | ||||||
|  |     case "ICE": | ||||||
|  |       return <img src="/ice.svg" alt="ICE" width="80%" /> | ||||||
|  |     case "Lyria": | ||||||
|  |       return <img src="/lyria.svg" alt="Lyria" width="80%" /> | ||||||
|  |     case "TER": | ||||||
|  |       return <img src="/ter.svg" alt="TER" width="80%" /> | ||||||
|  |     case "Car TER": | ||||||
|  |       return <div><img src="/bus.svg" alt="Car" width="40%" /> | ||||||
|  |         <br/> | ||||||
|  |         <img src="/ter.svg" alt="TER" width="40%" /></div> | ||||||
|  |     case "Eurostar": | ||||||
|  |       return <img src="/eurostar_mini.svg" alt="Eurostar" width="80%" /> | ||||||
|  |     case "Trenitalia": | ||||||
|  |     case "Trenitalia France": | ||||||
|  |       return <img src="/trenitalia.svg" alt="Frecciarossa" width="80%" /> | ||||||
|  |     case "RENFE": | ||||||
|  |       return <img src="/renfe.svg" alt="RENFE" width="80%" /> | ||||||
|  |     case "NJ": | ||||||
|  |       return <img src="/nightjet.svg" alt="NightJet" width="80%" /> | ||||||
|  |     default: | ||||||
|  |       return trainType | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getBackgroundColor(train) { | ||||||
|  |   let trainType = getTrainType(train) | ||||||
|  |   switch (trainType) { | ||||||
|  |     case "OUIGO": | ||||||
|  |       return "#0096CA" | ||||||
|  |     case "Eurostar": | ||||||
|  |       return "#00286A" | ||||||
|  |     case "NJ": | ||||||
|  |       return "#272759" | ||||||
|  |     default: | ||||||
|  |       if (train.routeColor) | ||||||
|  |         return `#${train.routeColor}` | ||||||
|  |       return "#FFFFFF" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getTextColor(train) { | ||||||
|  |   if (train.routeTextColor) | ||||||
|  |     return `#${train.routeTextColor}` | ||||||
|  |   else { | ||||||
|  |     let trainType = getTrainType(train) | ||||||
|  |     switch (trainType) { | ||||||
|  |       case "OUIGO": | ||||||
|  |         return "#FFFFFF" | ||||||
|  |       case "TGV INOUI": | ||||||
|  |         return "#9B2743" | ||||||
|  |       case "ICE": | ||||||
|  |         return "#B4B4B4" | ||||||
|  |       case "INTER-CITÉS": | ||||||
|  |       case "INTER-CITÉS de nuit": | ||||||
|  |         return "#404042" | ||||||
|  |       default: | ||||||
|  |         return "#000000" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getDisplayTime(train, tableType) { | ||||||
|  |   dayjs.locale('fr') | ||||||
|  |   let time = tableType === "departures" ? train.place.scheduledDeparture : train.place.scheduledArrival | ||||||
|  |   return dayjs(time).format('LT') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getPrettyDelay(train, tableType) { | ||||||
|  |   if (train === undefined || !train.realTime) { | ||||||
|  |     return [false, ""] | ||||||
|  |   } | ||||||
|  |   const [scheduled, projected] = tableType === "departures" ? [train.place.scheduledDeparture, train.place.departure] | ||||||
|  |     : [train.place.scheduledArrival, train.place.arrival] | ||||||
|  |   const delay_minutes = dayjs(projected).diff(dayjs(scheduled), "minute") | ||||||
|  |   if (delay_minutes === 0) | ||||||
|  |     return [false, "À l'heure"] | ||||||
|  |   return [true, `+${delay_minutes} min`] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default TrainsTable; | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import React, {useMemo} from 'react'; | import React from 'react'; | ||||||
| import ReactDOM from 'react-dom/client'; | import ReactDOM from 'react-dom/client'; | ||||||
| import './index.css'; | import './index.css'; | ||||||
| import reportWebVitals from './reportWebVitals'; | import reportWebVitals from './reportWebVitals'; | ||||||
| Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										23
									
								
								trainvel-front/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,23 +0,0 @@ | |||||||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. |  | ||||||
|  |  | ||||||
| # dependencies |  | ||||||
| /node_modules |  | ||||||
| /.pnp |  | ||||||
| .pnp.js |  | ||||||
|  |  | ||||||
| # testing |  | ||||||
| /coverage |  | ||||||
|  |  | ||||||
| # production |  | ||||||
| /build |  | ||||||
|  |  | ||||||
| # misc |  | ||||||
| .DS_Store |  | ||||||
| .env.local |  | ||||||
| .env.development.local |  | ||||||
| .env.test.local |  | ||||||
| .env.production.local |  | ||||||
|  |  | ||||||
| npm-debug.log* |  | ||||||
| yarn-debug.log* |  | ||||||
| yarn-error.log* |  | ||||||
| @@ -1,79 +0,0 @@ | |||||||
| import {useNavigate, useParams, useSearchParams} from "react-router-dom" |  | ||||||
| import TrainsTable from "./TrainsTable" |  | ||||||
| import TripsFilter from "./TripsFilter" |  | ||||||
| import {useState} from "react"; |  | ||||||
| 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 AutocompleteStation from "./AutocompleteStation"; |  | ||||||
|  |  | ||||||
| function DateTimeSelector({station, date, time}) { |  | ||||||
|   const navigate = useNavigate() |  | ||||||
|  |  | ||||||
|   function onStationSelected(event, station) { |  | ||||||
|     if (station !== null) |  | ||||||
|       navigate(`/station/sncf/${station.slug}/`) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return <> |  | ||||||
|     <Box component="form" display="flex" alignItems="center" sx={{'& .MuiTextField-root': { m: 1, width: '25ch' },}}> |  | ||||||
|         <FormLabel> |  | ||||||
|           Changer la gare recherchée : |  | ||||||
|         </FormLabel> |  | ||||||
|         <AutocompleteStation onChange={onStationSelected} /> |  | ||||||
|         <FormLabel> |  | ||||||
|           Modifier la date et l'heure de recherche : |  | ||||||
|         </FormLabel> |  | ||||||
|         <DatePicker name="date" label="Date" format="YYYY-MM-DD" defaultValue={dayjs(`${date}`)} /> |  | ||||||
|         <TimePicker name="time" label="Heure" format="HH:mm" defaultValue={dayjs(`${date} ${time}`)} /> |  | ||||||
|         <Button type="submit">Rechercher</Button> |  | ||||||
|     </Box> |  | ||||||
|   </> |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function Station() { |  | ||||||
|   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')}` |  | ||||||
|   let timeNow = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}` |  | ||||||
|   let [date, setDate] = useState(searchParams.get('date') || dateNow) |  | ||||||
|   let [time, setTime] = useState(searchParams.get('time') || timeNow) |  | ||||||
|  |  | ||||||
|   useQueryClient() |  | ||||||
|   const stationQuery = useQuery({ |  | ||||||
|     queryKey: ['station', stationSlug], |  | ||||||
|     queryFn: () => fetch(`/api/core/station/${stationSlug}/`) |  | ||||||
|       .then(response => response.json()), |  | ||||||
|     enabled: !!stationSlug, |  | ||||||
|   }) |  | ||||||
|   const station = stationQuery.data ?? {name: "Chargement…"} |  | ||||||
|  |  | ||||||
|   if (time === timeNow) { |  | ||||||
|     setInterval(() => { |  | ||||||
|       const now = new Date() |  | ||||||
|       let dateNow = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` |  | ||||||
|       let timeNow = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}` |  | ||||||
|       setDate(dateNow) |  | ||||||
|       setTime(timeNow) |  | ||||||
|     }, 5000) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <div className="Station"> |  | ||||||
|       <header className="App-header"> |  | ||||||
|         <h1>Horaires en gare de {station.name}</h1> |  | ||||||
|       </header> |  | ||||||
|  |  | ||||||
|       <main> |  | ||||||
|         <DateTimeSelector station={station} date={date} time={time} /> |  | ||||||
|         <TripsFilter /> |  | ||||||
|         <TrainsTable station={station} date={date} time={time} tableType="departures" /> |  | ||||||
|         <TrainsTable station={station} date={date} time={time} tableType="arrivals" /> |  | ||||||
|       </main> |  | ||||||
|     </div> |  | ||||||
|   ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export default Station; |  | ||||||
| @@ -1,364 +0,0 @@ | |||||||
| import { |  | ||||||
|   Box, |  | ||||||
|   styled, |  | ||||||
|   Table, |  | ||||||
|   TableBody, |  | ||||||
|   TableCell, |  | ||||||
|   TableContainer, |  | ||||||
|   TableHead, |  | ||||||
|   TableRow, |  | ||||||
|   Typography |  | ||||||
| } from "@mui/material" |  | ||||||
| import {CSSTransition, TransitionGroup} from 'react-transition-group' |  | ||||||
| import {useQueries, useQuery} from "@tanstack/react-query"; |  | ||||||
| import {useCallback, useEffect, useMemo, useRef, useState} from "react"; |  | ||||||
|  |  | ||||||
| const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({ |  | ||||||
|   'tbody &:nth-of-type(odd)': { |  | ||||||
|     backgroundColor: theme.palette.sncf[tabletype].light, |  | ||||||
|   }, |  | ||||||
|   'th, &:nth-of-type(even)': { |  | ||||||
|     backgroundColor: theme.palette.sncf[tabletype].dark, |  | ||||||
|   }, |  | ||||||
|   // hide last border |  | ||||||
|   '&:last-child td, &:last-child th': { |  | ||||||
|     border: 0, |  | ||||||
|   }, |  | ||||||
| })); |  | ||||||
|  |  | ||||||
| function TrainsTable({station, date, time, tableType}) { |  | ||||||
|   return <> |  | ||||||
|     <TableContainer> |  | ||||||
|       <Table> |  | ||||||
|         <TrainsTableHeader tableType={tableType} /> |  | ||||||
|         <TrainsTableBody station={station} date={date} time={time} tableType={tableType} /> |  | ||||||
|       </Table> |  | ||||||
|     </TableContainer> |  | ||||||
|   </> |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function TrainsTableHeader({tableType}) { |  | ||||||
|   return <> |  | ||||||
|     <TableHead> |  | ||||||
|       <StyledTableRow tabletype={tableType}> |  | ||||||
|         <TableCell colSpan="2" fontSize={16} fontWeight="bold">Train</TableCell> |  | ||||||
|         <TableCell fontSize={16} fontWeight="bold">Heure</TableCell> |  | ||||||
|         <TableCell fontSize={16} fontWeight="bold">Destination</TableCell> |  | ||||||
|       </StyledTableRow> |  | ||||||
|     </TableHead> |  | ||||||
|   </> |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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}` |  | ||||||
|     else |  | ||||||
|       return `${train.arrival_date}T${train.arrival_time_24h}` >= `${date}T${time}` |  | ||||||
|   }, [date, time, tableType]) |  | ||||||
|  |  | ||||||
|   const updateTrains = useCallback(() => { |  | ||||||
|     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]) |  | ||||||
|   }, [station.id, date, time, tableType]) |  | ||||||
|  |  | ||||||
|   const trainsQuery = useQuery({ |  | ||||||
|     queryKey: ['trains', station.id, tableType], |  | ||||||
|     queryFn: updateTrains, |  | ||||||
|     enabled: !!station.id, |  | ||||||
|   }) |  | ||||||
|   const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data]) |  | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     let validTrains = trains?.filter(filterTime) ?? [] |  | ||||||
|     if (trains?.length > 0 && validTrains.length < trains?.length) |  | ||||||
|       trainsQuery.refetch().then() |  | ||||||
|   }, [trains, filterTime, trainsQuery]) |  | ||||||
|  |  | ||||||
|   const nullRef = useRef(null) |  | ||||||
|   let table_rows = trains.map((train) => <CSSTransition key={train.id} timeout={500} classNames="shrink" nodeRef={nullRef}> |  | ||||||
|       <TrainRow train={train} tableType={tableType} date={date} time={time} /> |  | ||||||
|     </CSSTransition>) |  | ||||||
|  |  | ||||||
|   return <> |  | ||||||
|     <TableBody> |  | ||||||
|       <TransitionGroup component={null}> |  | ||||||
|         {table_rows} |  | ||||||
|       </TransitionGroup> |  | ||||||
|     </TableBody> |  | ||||||
|   </> |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function TrainRow({train, tableType, date, time}) { |  | ||||||
|   const tripQuery = useQuery({ |  | ||||||
|     queryKey: ['trip', train.trip], |  | ||||||
|     queryFn: () => fetch(`/api/gtfs/trip/${train.trip}/`) |  | ||||||
|         .then(response => response.json()), |  | ||||||
|     enabled: !!train.trip, |  | ||||||
|   }) |  | ||||||
|   const trip = tripQuery.data ?? {} |  | ||||||
|  |  | ||||||
|   const routeQuery = useQuery({ |  | ||||||
|     queryKey: ['route', trip.route], |  | ||||||
|     queryFn: () => fetch(`/api/gtfs/route/${trip.route}/`) |  | ||||||
|         .then(response => response.json()), |  | ||||||
|     enabled: !!trip.route, |  | ||||||
|   }) |  | ||||||
|   const route = routeQuery.data ?? {} |  | ||||||
|   const trainType = getTrainType(train, trip, route) |  | ||||||
|   const backgroundColor = getBackgroundColor(train, trip, route) |  | ||||||
|   const textColor = getTextColor(train, trip, route) |  | ||||||
|   const trainTypeDisplay = getTrainTypeDisplay(trainType) |  | ||||||
|  |  | ||||||
|   const stopTimesQuery = useQuery({ |  | ||||||
|     queryKey: ['stop_times', trip.id], |  | ||||||
|     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, |  | ||||||
|   }) |  | ||||||
|   const stopTimes = stopTimesQuery.data ?? [] |  | ||||||
|   const stopIds = stopTimes.map(stop_time => stop_time.stop) |  | ||||||
|  |  | ||||||
|   const stopQueries = useQueries({ |  | ||||||
|     queries: stopIds.map(stopId => ({ |  | ||||||
|       queryKey: ['stop', stopId], |  | ||||||
|       queryFn: () => fetch(`/api/gtfs/stop/${stopId}/`) |  | ||||||
|         .then(response => response.json()), |  | ||||||
|       enabled: !!stopId, |  | ||||||
|     })), |  | ||||||
|   }) |  | ||||||
|   const stops = stopTimes.map(((stopTime, i) => ({...stopTime, stop: stopQueries[i]?.data ?? {"name": "…"}}))) ?? [] |  | ||||||
|  |  | ||||||
|   let headline = stops[tableType === "departures" ? stops.length - 1 : 0]?.stop ?? {name: "Chargement…"} |  | ||||||
|  |  | ||||||
|   const realtimeTripQuery = useQuery({ |  | ||||||
|     queryKey: ['realtimeTrip', trip.id, date, time], |  | ||||||
|     queryFn: () => fetch(`/api/gtfs-rt/trip_update/${trip.id}/`) |  | ||||||
|         .then(response => response.json()), |  | ||||||
|     enabled: !!trip.id, |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   const [realtimeTripData, setRealtimeTripData] = useState({}) |  | ||||||
|   useEffect(() => { |  | ||||||
|     if (realtimeTripQuery.data) |  | ||||||
|       setRealtimeTripData(realtimeTripQuery.data) |  | ||||||
|   }, [realtimeTripQuery.data]) |  | ||||||
|   const tripScheduleRelationship = realtimeTripData.schedule_relationship ?? 0 |  | ||||||
|  |  | ||||||
|   const realtimeQuery = useQuery({ |  | ||||||
|     queryKey: ['realtime', train.id, date, time], |  | ||||||
|     queryFn: () => fetch(`/api/gtfs-rt/stop_time_update/${train.id}/`) |  | ||||||
|         .then(response => response.json()), |  | ||||||
|     enabled: !!train.id, |  | ||||||
|   }) |  | ||||||
|   const [realtimeData, setRealtimeData] = useState({}) |  | ||||||
|   useEffect(() => { |  | ||||||
|     if (realtimeQuery.data) |  | ||||||
|       setRealtimeData(realtimeQuery.data) |  | ||||||
|   }, [realtimeQuery.data]) |  | ||||||
|   const stopScheduleRelationship = realtimeData.schedule_relationship ?? 0 |  | ||||||
|  |  | ||||||
|   const canceled = tripScheduleRelationship === 3 || stopScheduleRelationship === 1 |  | ||||||
|  |  | ||||||
|   const delay = tableType === "departures" ? realtimeData.departure_delay : realtimeData.arrival_delay |  | ||||||
|   const prettyDelay = delay && !canceled ? getPrettyDelay(delay) : "" |  | ||||||
|   const [prettyScheduleRelationship, scheduleRelationshipColor] = getPrettyScheduleRelationship(tripScheduleRelationship, stopScheduleRelationship) |  | ||||||
|  |  | ||||||
|   let stopsFilter |  | ||||||
|   if (canceled) |  | ||||||
|     stopsFilter = (stop_time) => true |  | ||||||
|   else if (tableType === "departures") |  | ||||||
|     stopsFilter = (stop_time) => stop_time.stop_sequence > train.stop_sequence && stop_time.drop_off_type === 0 |  | ||||||
|   else |  | ||||||
|     stopsFilter = (stop_time) => stop_time.stop_sequence < train.stop_sequence && stop_time.pickup_type === 0 |  | ||||||
|   let stopsNames = stops.filter(stopsFilter).map(stopTime => stopTime?.stop.name ?? "").join(" > ") ?? "" |  | ||||||
|  |  | ||||||
|   return <> |  | ||||||
|     <StyledTableRow tabletype={tableType}> |  | ||||||
|       <TableCell> |  | ||||||
|         <div> |  | ||||||
|           <Box display="flex" |  | ||||||
|                justifyContent="center" |  | ||||||
|                alignItems="center" |  | ||||||
|                textAlign="center" |  | ||||||
|                width="4em" |  | ||||||
|                height="4em" |  | ||||||
|                borderRadius="15%" |  | ||||||
|                fontWeight="bold" |  | ||||||
|                backgroundColor={backgroundColor} |  | ||||||
|                color={textColor}> |  | ||||||
|             {trainTypeDisplay} |  | ||||||
|           </Box> |  | ||||||
|         </div> |  | ||||||
|       </TableCell> |  | ||||||
|       <TableCell> |  | ||||||
|         <Box display="flex" alignItems="center" justifyContent="center" textAlign="center"> |  | ||||||
|           <div> |  | ||||||
|             <div>{trip.short_name}</div> |  | ||||||
|             <div>{trip.headsign}</div> |  | ||||||
|           </div> |  | ||||||
|         </Box> |  | ||||||
|       </TableCell> |  | ||||||
|       <TableCell> |  | ||||||
|         <Box display="flex" alignItems="center" justifyContent="center"> |  | ||||||
|           <Box> |  | ||||||
|             <Box fontWeight="bold" color="#FFED02" fontSize={24}> |  | ||||||
|               {getDisplayTime(train, tableType)} |  | ||||||
|             </Box> |  | ||||||
|             <Box color={delay && delay !== "00:00:00" ? "#e86d2b" : "white"} |  | ||||||
|                  fontWeight={delay && delay !== "00:00:00" ? "bold" : ""}> |  | ||||||
|               {prettyDelay} |  | ||||||
|             </Box> |  | ||||||
|             <Box color={scheduleRelationshipColor} fontWeight="bold"> |  | ||||||
|               {prettyScheduleRelationship} |  | ||||||
|             </Box> |  | ||||||
|           </Box> |  | ||||||
|         </Box> |  | ||||||
|       </TableCell> |  | ||||||
|       <TableCell> |  | ||||||
|         <Box style={{textDecoration: canceled ? 'line-through': ''}}> |  | ||||||
|           <Typography fontSize={24} fontWeight="bold" data-stop-id={headline.id}>{headline.name}</Typography> |  | ||||||
|           <span className="stops">{stopsNames}</span> |  | ||||||
|         </Box> |  | ||||||
|       </TableCell> |  | ||||||
|     </StyledTableRow> |  | ||||||
|   </> |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getTrainType(train, trip, route) { |  | ||||||
|   switch (route.gtfs_feed) { |  | ||||||
|     case "FR-SNCF-TGV": |  | ||||||
|     case "FR-SNCF-IC": |  | ||||||
|     case "FR-SNCF-TER": |  | ||||||
|       let trainType = train.stop.split("StopPoint:OCE")[1].split("-")[0] |  | ||||||
|       switch (trainType) { |  | ||||||
|         case "Train TER": |  | ||||||
|           return "TER" |  | ||||||
|         case "INTERCITES": |  | ||||||
|           return "INTER-CITÉS" |  | ||||||
|         case "INTERCITES de nuit": |  | ||||||
|           return "INTER-CITÉS de nuit" |  | ||||||
|         default: |  | ||||||
|           return trainType |  | ||||||
|       } |  | ||||||
|     case "FR-IDF-IDFM": |  | ||||||
|     case "FR-GES-CTS": |  | ||||||
|       return route.short_name |  | ||||||
|     case "FR-EUROSTAR": |  | ||||||
|       return "Eurostar" |  | ||||||
|     case "IT-FRA-TI": |  | ||||||
|       return "Trenitalia France" |  | ||||||
|     case "ES-RENFE": |  | ||||||
|       return "RENFE" |  | ||||||
|     case "AT-OBB": |  | ||||||
|       if (trip.short_name?.startsWith("NJ")) |  | ||||||
|         return "NJ" |  | ||||||
|       return "ÖBB" |  | ||||||
|     case "CH-ALL": |  | ||||||
|       return route.desc |  | ||||||
|     default: |  | ||||||
|       return trip.short_name?.split(" ")[0] |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getTrainTypeDisplay(trainType) { |  | ||||||
|   switch (trainType) { |  | ||||||
|     case "TGV INOUI": |  | ||||||
|       return <img src="/tgv_inoui.svg" alt="TGV INOUI" width="80%" /> |  | ||||||
|     case "OUIGO": |  | ||||||
|       return <img src="/ouigo.svg" alt="OUIGO" width="80%" /> |  | ||||||
|     case "ICE": |  | ||||||
|       return <img src="/ice.svg" alt="ICE" width="80%" /> |  | ||||||
|     case "Lyria": |  | ||||||
|       return <img src="/lyria.svg" alt="Lyria" width="80%" /> |  | ||||||
|     case "TER": |  | ||||||
|       return <img src="/ter.svg" alt="TER" width="80%" /> |  | ||||||
|     case "Car TER": |  | ||||||
|       return <div><img src="/bus.svg" alt="Car" width="40%" /> |  | ||||||
|         <br/> |  | ||||||
|         <img src="/ter.svg" alt="TER" width="40%" /></div> |  | ||||||
|     case "Eurostar": |  | ||||||
|       return <img src="/eurostar_mini.svg" alt="Eurostar" width="80%" /> |  | ||||||
|     case "Trenitalia": |  | ||||||
|     case "Trenitalia France": |  | ||||||
|       return <img src="/trenitalia.svg" alt="Frecciarossa" width="80%" /> |  | ||||||
|     case "RENFE": |  | ||||||
|       return <img src="/renfe.svg" alt="RENFE" width="80%" /> |  | ||||||
|     case "NJ": |  | ||||||
|       return <img src="/nightjet.svg" alt="NightJet" width="80%" /> |  | ||||||
|     default: |  | ||||||
|       return trainType |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getBackgroundColor(train, trip, route) { |  | ||||||
|   let trainType = getTrainType(train, trip, route) |  | ||||||
|   switch (trainType) { |  | ||||||
|     case "OUIGO": |  | ||||||
|       return "#0096CA" |  | ||||||
|     case "Eurostar": |  | ||||||
|       return "#00286A" |  | ||||||
|     case "NJ": |  | ||||||
|       return "#272759" |  | ||||||
|     default: |  | ||||||
|       if (route.color) |  | ||||||
|         return `#${route.color}` |  | ||||||
|       return "#FFFFFF" |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getTextColor(train, trip, route) { |  | ||||||
|   if (route.text_color) |  | ||||||
|     return `#${route.text_color}` |  | ||||||
|   else { |  | ||||||
|     let trainType = getTrainType(train, trip, route) |  | ||||||
|     switch (trainType) { |  | ||||||
|       case "OUIGO": |  | ||||||
|         return "#FFFFFF" |  | ||||||
|       case "TGV INOUI": |  | ||||||
|         return "#9B2743" |  | ||||||
|       case "ICE": |  | ||||||
|         return "#B4B4B4" |  | ||||||
|       case "INTER-CITÉS": |  | ||||||
|       case "INTER-CITÉS de nuit": |  | ||||||
|         return "#404042" |  | ||||||
|       default: |  | ||||||
|         return "#000000" |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getDisplayTime(train, tableType) { |  | ||||||
|   let time = tableType === "departures" ? train.departure_time : train.arrival_time |  | ||||||
|   let day_split = time.split(' ') |  | ||||||
|   return day_split[day_split.length - 1].substring(0, 5) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getPrettyDelay(delay) { |  | ||||||
|   let delay_split = delay.split(':') |  | ||||||
|   let hours = parseInt(delay_split[0]) |  | ||||||
|   let minutes = parseInt(delay_split[1]) |  | ||||||
|   let full_minutes = hours * 60 + minutes |  | ||||||
|   return full_minutes ? `+${full_minutes} min` : "À l'heure" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getPrettyScheduleRelationship(tripScheduledRelationship, stopScheduledRelationship) { |  | ||||||
|   switch (tripScheduledRelationship) { |  | ||||||
|     case 1: |  | ||||||
|       return ["Ajouté", "#3ebb18"] |  | ||||||
|     case 3: |  | ||||||
|       return ["Supprimé", "#ff8701"] |  | ||||||
|     default: |  | ||||||
|       switch (stopScheduledRelationship) { |  | ||||||
|         case 1: |  | ||||||
|           return ["Supprimé", "#ff8701"] |  | ||||||
|         default: |  | ||||||
|           return ["", ""] |  | ||||||
|       } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export default TrainsTable; |  | ||||||
| @@ -1,6 +0,0 @@ | |||||||
| from django.apps import AppConfig |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ApiConfig(AppConfig): |  | ||||||
|     default_auto_field = "django.db.models.BigAutoField" |  | ||||||
|     name = "trainvel.api" |  | ||||||
| @@ -1,91 +0,0 @@ | |||||||
| from rest_framework import serializers |  | ||||||
|  |  | ||||||
| from trainvel.core.models import Station |  | ||||||
| from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, \ |  | ||||||
|     Stop, StopTime, StopTimeUpdate, Transfer, Trip, TripUpdate |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 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 GTFSFeedSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = GTFSFeed |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AgencySerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = Agency |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StopSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = Stop |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RouteSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = Route |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TripSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = Trip |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StopTimeSerializer(serializers.ModelSerializer): |  | ||||||
|     arrival_date = serializers.DateField(required=False) |  | ||||||
|     departure_date = serializers.DateField(required=False) |  | ||||||
|     arrival_time_24h = serializers.DurationField(required=False) |  | ||||||
|     departure_time_24h = serializers.DurationField(required=False) |  | ||||||
|     departure_time_real = serializers.CharField(required=False) |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|         model = StopTime |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CalendarSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = Calendar |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CalendarDateSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = CalendarDate |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TransferSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = Transfer |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FeedInfoSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = FeedInfo |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TripUpdateSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = TripUpdate |  | ||||||
|         fields = '__all__' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StopTimeUpdateSerializer(serializers.ModelSerializer): |  | ||||||
|     class Meta: |  | ||||||
|         model = StopTimeUpdate |  | ||||||
|         fields = '__all__' |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| from django.test import TestCase |  | ||||||
|  |  | ||||||
| # Create your tests here. |  | ||||||
| @@ -1,357 +0,0 @@ | |||||||
| from datetime import datetime, timedelta, date |  | ||||||
|  |  | ||||||
| from django.db.models import Exists, Case, F, Min, OuterRef, Q, Value, When |  | ||||||
| from django.utils.decorators import method_decorator |  | ||||||
| from django.views.decorators.cache import cache_control |  | ||||||
| from django.views.decorators.http import last_modified |  | ||||||
| from django_filters.rest_framework import DjangoFilterBackend |  | ||||||
| from rest_framework import viewsets |  | ||||||
| from rest_framework.filters import OrderingFilter, SearchFilter |  | ||||||
|  |  | ||||||
| from trainvel.api.serializers import AgencySerializer, CalendarDateSerializer, CalendarSerializer, \ |  | ||||||
|     FeedInfoSerializer, GTFSFeedSerializer, RouteSerializer, StationSerializer, StopSerializer, StopTimeSerializer, \ |  | ||||||
|     StopTimeUpdateSerializer, TransferSerializer, TripSerializer, TripUpdateSerializer |  | ||||||
| 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=30) |  | ||||||
| LAST_MODIFIED = last_modified(lambda *args, **kwargs: GTFSFeed.objects.order_by('-last_modified').first().last_modified) |  | ||||||
| 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]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class GTFSFeedViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = GTFSFeed.objects.all() |  | ||||||
|     serializer_class = GTFSFeedSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class AgencyViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = Agency.objects.all() |  | ||||||
|     serializer_class = AgencySerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class StopViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = Stop.objects.all() |  | ||||||
|     serializer_class = StopSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend, SearchFilter] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     search_fields = ['name',] |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class RouteViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = Route.objects.all() |  | ||||||
|     serializer_class = RouteSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class TripViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = Trip.objects.all() |  | ||||||
|     serializer_class = TripSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class StopTimeViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = StopTime.objects.order_by('id').all() |  | ||||||
|     serializer_class = StopTimeSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend, OrderingFilter] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     ordering_fields = ['arrival_time', 'departure_time', 'stop_sequence', ] |  | ||||||
|     ordering = ['stop_sequence', ] |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class CalendarViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = Calendar.objects.all() |  | ||||||
|     serializer_class = CalendarSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class CalendarDateViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = CalendarDate.objects.all() |  | ||||||
|     serializer_class = CalendarDateSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class TransferViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = Transfer.objects.all() |  | ||||||
|     serializer_class = TransferSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| @method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED]) |  | ||||||
| class FeedInfoViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = FeedInfo.objects.all() |  | ||||||
|     serializer_class = FeedInfoSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TripUpdateViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = TripUpdate.objects.all() |  | ||||||
|     serializer_class = TripUpdateSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StopTimeUpdateViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = StopTimeUpdate.objects.all() |  | ||||||
|     serializer_class = StopTimeUpdateSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|     filterset_fields = '__all__' |  | ||||||
|     lookup_value_regex = LOOKUP_VALUE_REGEX |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = StopTime.objects.none() |  | ||||||
|     serializer_class = StopTimeSerializer |  | ||||||
|  |  | ||||||
|     def get_queryset(self): |  | ||||||
|         now = datetime.now() |  | ||||||
|  |  | ||||||
|         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 |  | ||||||
|                                        + int(query_time[3:5]) * 60 |  | ||||||
|                                        + (int(query_time[6:]) if len(query_time) > 6 else 0)) |  | ||||||
|  |  | ||||||
|         yesterday = query_date - timedelta(days=1) |  | ||||||
|         time_yesterday = query_time + timedelta(days=1) |  | ||||||
|         tomorrow = query_date + timedelta(days=1) |  | ||||||
|  |  | ||||||
|         stop_filter = Q(stop__location_type=0) |  | ||||||
|         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)) |  | ||||||
|  |  | ||||||
|         excluded_agencies = ~Q(trip__route__gtfs_feed__excluded_agencies=F('trip__route__agency_id')) |  | ||||||
|  |  | ||||||
|         not_last_stop = ~Q(stop_sequence=StopTime.objects.filter(trip_id=OuterRef('trip_id')) |  | ||||||
|                            .filter(pickup_type=PickupType.REGULAR) |  | ||||||
|                            .order_by('-stop_sequence')[:1].values_list('stop_sequence')) |  | ||||||
|  |  | ||||||
|         trip_filter = Q() |  | ||||||
|         if self.request.query_params.get('route_name', None): |  | ||||||
|             trip_filter &= Q(trip__route_name__in=self.request.query_params.get('route_name').split(',')) |  | ||||||
|         if self.request.query_params.get('transport_type', None): |  | ||||||
|             trip_filter &= Q(trip__route__type__in=self.request.query_params.get('transport_type').split(',')) |  | ||||||
|         if self.request.query_params.get('long_distance', None) is not None: |  | ||||||
|             long_distance = str(self.request.query_params.get('long_distance')) == 'true' |  | ||||||
|             trip_filter &= Q(trip__long_distance=long_distance) |  | ||||||
|  |  | ||||||
|         def calendar_filter(d: date): |  | ||||||
|             return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1) |  | ||||||
|                      .values_list('service_id')) \ |  | ||||||
|                 | Q(trip__service_id__in=Calendar.objects.filter( |  | ||||||
|                     start_date__lte=d, |  | ||||||
|                     end_date__gte=d, |  | ||||||
|                     **{f"{d:%A}".lower(): True}) |  | ||||||
|                     .filter(~Q(id__in=CalendarDate.objects.filter(date=d, exception_type=2) |  | ||||||
|                                .values_list('service_id', flat=True))) |  | ||||||
|                     .values_list('id')) |  | ||||||
|  |  | ||||||
|         def stop_time_update_qs(d: date): |  | ||||||
|             return StopTimeUpdate.objects.filter(trip_update__start_date=d) \ |  | ||||||
|                 .exclude(departure_time=datetime.fromtimestamp(0)).filter(stop_time_id=OuterRef('pk')) |  | ||||||
|  |  | ||||||
|         def departure_time_real(d: date): |  | ||||||
|             return Case( |  | ||||||
|                 When( |  | ||||||
|                     condition=Exists(stop_time_update_qs(d)), |  | ||||||
|                     then=F('departure_time') + stop_time_update_qs(d).values('departure_delay'), |  | ||||||
|                 ), |  | ||||||
|                 default=F('departure_time'), |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         def canceled_filter(d: date): |  | ||||||
|             return Exists(stop_time_update_qs(d).filter(Q(schedule_relationship=1) | Q(schedule_relationship=3))) |  | ||||||
|  |  | ||||||
|         qs_today = StopTime.objects.filter(stop_filter) \ |  | ||||||
|             .filter(excluded_agencies) \ |  | ||||||
|             .filter(not_last_stop) \ |  | ||||||
|             .filter(trip_filter) \ |  | ||||||
|             .annotate(departure_time_real=departure_time_real(query_date)) \ |  | ||||||
|             .filter(departure_time_real__gte=query_time) \ |  | ||||||
|             .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')) |  | ||||||
|  |  | ||||||
|         qs_yesterday = StopTime.objects.filter(stop_filter) \ |  | ||||||
|             .filter(excluded_agencies) \ |  | ||||||
|             .filter(not_last_stop) \ |  | ||||||
|             .filter(trip_filter) \ |  | ||||||
|             .annotate(departure_time_real=departure_time_real(query_date)) \ |  | ||||||
|             .filter(departure_time_real__gte=time_yesterday) \ |  | ||||||
|             .filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(yesterday)) \ |  | ||||||
|             .filter(calendar_filter(yesterday)) \ |  | ||||||
|             .annotate(departure_date=Value(yesterday)) \ |  | ||||||
|             .annotate(departure_time_24h=F('departure_time') - timedelta(days=1)) |  | ||||||
|  |  | ||||||
|         qs_tomorrow = StopTime.objects.filter(stop_filter) \ |  | ||||||
|             .filter(excluded_agencies) \ |  | ||||||
|             .filter(not_last_stop) \ |  | ||||||
|             .filter(trip_filter) \ |  | ||||||
|             .annotate(departure_time_real=departure_time_real(query_date)) \ |  | ||||||
|             .filter(departure_time_real__gte=timedelta(0)) \ |  | ||||||
|             .filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(tomorrow)) \ |  | ||||||
|             .filter(calendar_filter(tomorrow)) \ |  | ||||||
|             .annotate(departure_date=Value(tomorrow)) \ |  | ||||||
|             .annotate(departure_time_24h=F('departure_time') + timedelta(days=1)) |  | ||||||
|  |  | ||||||
|         return qs_today.union(qs_yesterday).union(qs_tomorrow).order_by("departure_time_24h").all() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet): |  | ||||||
|     queryset = StopTime.objects.none() |  | ||||||
|     serializer_class = StopTimeSerializer |  | ||||||
|     filter_backends = [DjangoFilterBackend] |  | ||||||
|  |  | ||||||
|     def get_queryset(self): |  | ||||||
|         now = datetime.now() |  | ||||||
|  |  | ||||||
|         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 |  | ||||||
|                                        + int(query_time[3:5]) * 60 |  | ||||||
|                                        + (int(query_time[6:]) if len(query_time) > 6 else 0)) |  | ||||||
|         query_time -= timedelta(minutes=5)  # Keep the last trains of the 5 previous minutes |  | ||||||
|  |  | ||||||
|         yesterday = query_date - timedelta(days=1) |  | ||||||
|         time_yesterday = query_time + timedelta(days=1) |  | ||||||
|         tomorrow = query_date + timedelta(days=1) |  | ||||||
|  |  | ||||||
|         stop_filter = Q(stop__location_type=0) |  | ||||||
|         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)) |  | ||||||
|  |  | ||||||
|         excluded_agencies = ~Q(trip__route__gtfs_feed__excluded_agencies=F('trip__route__agency_id')) |  | ||||||
|  |  | ||||||
|         not_first_stop = ~Q(stop_sequence=StopTime.objects.filter(trip_id=OuterRef('trip_id')) |  | ||||||
|                             .filter(drop_off_type=PickupType.REGULAR) |  | ||||||
|                             .order_by('stop_sequence')[:1].values_list('stop_sequence')) |  | ||||||
|  |  | ||||||
|         trip_filter = Q() |  | ||||||
|         if self.request.query_params.get('route_name', None): |  | ||||||
|             trip_filter &= Q(trip__route_name__in=self.request.query_params.get('route_name').split(',')) |  | ||||||
|         if self.request.query_params.get('transport_type', None): |  | ||||||
|             trip_filter &= Q(trip__route__type__in=self.request.query_params.get('transport_type').split(',')) |  | ||||||
|         if self.request.query_params.get('long_distance', None) is not None: |  | ||||||
|             long_distance = str(self.request.query_params.get('long_distance')) == 'true' |  | ||||||
|             trip_filter &= Q(trip__long_distance=long_distance) |  | ||||||
|  |  | ||||||
|         def calendar_filter(d: date): |  | ||||||
|             return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1) |  | ||||||
|                      .values_list('service_id')) \ |  | ||||||
|                 | Q(trip__service_id__in=Calendar.objects.filter( |  | ||||||
|                     start_date__lte=d, |  | ||||||
|                     end_date__gte=d, |  | ||||||
|                     **{f"{d:%A}".lower(): True}) |  | ||||||
|                     .filter(~Q(id__in=CalendarDate.objects.filter(date=d, exception_type=2) |  | ||||||
|                                .values_list('service_id', flat=True))) |  | ||||||
|                     .values_list('id')) |  | ||||||
|  |  | ||||||
|         def stop_time_update_qs(d: date): |  | ||||||
|             return StopTimeUpdate.objects.filter(trip_update__start_date=d) \ |  | ||||||
|                 .exclude(arrival_time=datetime.fromtimestamp(0)).filter(stop_time_id=OuterRef('pk')) |  | ||||||
|  |  | ||||||
|         def arrival_time_real(d: date): |  | ||||||
|             return Case( |  | ||||||
|                 When( |  | ||||||
|                     condition=Exists(stop_time_update_qs(d)), |  | ||||||
|                     then=F('arrival_time') + stop_time_update_qs(d).values('arrival_delay'), |  | ||||||
|                 ), |  | ||||||
|                 default=F('arrival_time'), |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         def canceled_filter(d: date): |  | ||||||
|             return Exists(stop_time_update_qs(d).filter(Q(schedule_relationship=1) | Q(schedule_relationship=3))) |  | ||||||
|  |  | ||||||
|         qs_today = StopTime.objects.filter(stop_filter) \ |  | ||||||
|             .filter(excluded_agencies) \ |  | ||||||
|             .filter(not_first_stop) \ |  | ||||||
|             .filter(trip_filter) \ |  | ||||||
|             .annotate(arrival_time_real=arrival_time_real(query_date)) \ |  | ||||||
|             .filter(arrival_time_real__gte=query_time) \ |  | ||||||
|             .filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(query_date)) \ |  | ||||||
|             .filter(calendar_filter(query_date)) \ |  | ||||||
|             .annotate(arrival_date=Value(query_date)) \ |  | ||||||
|             .annotate(arrival_time_24h=F('arrival_time')) |  | ||||||
|  |  | ||||||
|         qs_yesterday = StopTime.objects.filter(stop_filter) \ |  | ||||||
|             .filter(excluded_agencies) \ |  | ||||||
|             .filter(not_first_stop) \ |  | ||||||
|             .filter(trip_filter) \ |  | ||||||
|             .annotate(arrival_time_real=arrival_time_real(yesterday)) \ |  | ||||||
|             .filter(arrival_time_real__gte=time_yesterday) \ |  | ||||||
|             .filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(yesterday)) \ |  | ||||||
|             .filter(calendar_filter(yesterday)) \ |  | ||||||
|             .annotate(arrival_date=Value(yesterday)) \ |  | ||||||
|             .annotate(arrival_time_24h=F('arrival_time') - timedelta(days=1)) |  | ||||||
|  |  | ||||||
|         qs_tomorrow = StopTime.objects.filter(stop_filter) \ |  | ||||||
|             .filter(excluded_agencies) \ |  | ||||||
|             .filter(not_first_stop) \ |  | ||||||
|             .filter(trip_filter) \ |  | ||||||
|             .annotate(arrival_time_real=arrival_time_real(tomorrow)) \ |  | ||||||
|             .filter(arrival_time_real__gte=timedelta(0)) \ |  | ||||||
|             .filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(tomorrow)) \ |  | ||||||
|             .filter(calendar_filter(tomorrow)) \ |  | ||||||
|             .annotate(arrival_date=Value(tomorrow)) \ |  | ||||||
|             .annotate(arrival_time_24h=F('arrival_time') + timedelta(days=1)) |  | ||||||
|  |  | ||||||
|         return qs_today.union(qs_yesterday).union(qs_tomorrow).order_by("arrival_time_24h").all() |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| """ |  | ||||||
| ASGI config for trainvel project. |  | ||||||
|  |  | ||||||
| It exposes the ASGI callable as a module-level variable named ``application``. |  | ||||||
|  |  | ||||||
| For more information on this file, see |  | ||||||
| https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import os |  | ||||||
|  |  | ||||||
| from django.core.asgi import get_asgi_application |  | ||||||
|  |  | ||||||
| os.environ.setdefault("DJANGO_SETTINGS_MODULE", "trainvel.settings") |  | ||||||
|  |  | ||||||
| application = get_asgi_application() |  | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| 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] |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| 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") |  | ||||||
| @@ -1,325 +0,0 @@ | |||||||
| 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" |  | ||||||
| @@ -1,37 +0,0 @@ | |||||||
| import csv |  | ||||||
| from time import time |  | ||||||
|  |  | ||||||
| import requests |  | ||||||
| from django.core.management import BaseCommand |  | ||||||
| from django.db import connection, transaction |  | ||||||
| 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_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()} |  | ||||||
|                 stations.append(values) |  | ||||||
|  |  | ||||||
|         Station.objects.all().delete() |  | ||||||
|         print("Deleted all stations.") |  | ||||||
|         with connection.cursor() as cursor: |  | ||||||
|             cursor.execute("BEGIN;") |  | ||||||
|             keys = ", ".join(stations[0].keys()) |  | ||||||
|             cursor.executemany( |  | ||||||
|                 f"INSERT INTO core_station ({keys}) VALUES ({', '.join(['%s'] * len(values))});", |  | ||||||
|                 [list(station.values()) for station in stations], |  | ||||||
|             ) |  | ||||||
|             cursor.execute("COMMIT;") |  | ||||||
| @@ -1,603 +0,0 @@ | |||||||
| # 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=16, |  | ||||||
|                         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", |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @@ -1,40 +0,0 @@ | |||||||
| # Generated by Django 5.0.6 on 2024-05-12 11:09 |  | ||||||
|  |  | ||||||
| import django.db.models.deletion |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ("core", "0001_initial"), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name="station", |  | ||||||
|             name="parent_station", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 blank=True, |  | ||||||
|                 default=None, |  | ||||||
|                 null=True, |  | ||||||
|                 on_delete=django.db.models.deletion.SET_NULL, |  | ||||||
|                 related_name="children", |  | ||||||
|                 to="core.station", |  | ||||||
|                 verbose_name="parent station", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name="station", |  | ||||||
|             name="same_as", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 blank=True, |  | ||||||
|                 default=None, |  | ||||||
|                 null=True, |  | ||||||
|                 on_delete=django.db.models.deletion.SET_NULL, |  | ||||||
|                 related_name="same_as_other", |  | ||||||
|                 to="core.station", |  | ||||||
|                 verbose_name="same as", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @@ -1,519 +0,0 @@ | |||||||
| 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.SET_NULL, |  | ||||||
|         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=16, |  | ||||||
|         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.SET_NULL, |  | ||||||
|         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") |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| from django.test import TestCase |  | ||||||
|  |  | ||||||
| # Create your tests here. |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| from django.shortcuts import render |  | ||||||
|  |  | ||||||
| # Create your views here. |  | ||||||
| @@ -1,166 +0,0 @@ | |||||||
| from django.contrib import admin |  | ||||||
| from django.forms import BaseInlineFormSet |  | ||||||
|  |  | ||||||
| from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, \ |  | ||||||
|     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): |  | ||||||
|     model = CalendarDate |  | ||||||
|     extra = 0 |  | ||||||
|     formset = LimitModelFormset |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TripInline(admin.TabularInline): |  | ||||||
|     model = Trip |  | ||||||
|     extra = 0 |  | ||||||
|     formset = LimitModelFormset |  | ||||||
|     autocomplete_fields = ('route', 'service',) |  | ||||||
|     show_change_link = True |  | ||||||
|     ordering = ('service',) |  | ||||||
|     readonly_fields = ('gtfs_feed',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StopTimeInline(admin.TabularInline): |  | ||||||
|     model = StopTime |  | ||||||
|     extra = 0 |  | ||||||
|     formset = LimitModelFormset |  | ||||||
|     autocomplete_fields = ('stop',) |  | ||||||
|     readonly_fields = ('id',) |  | ||||||
|     show_change_link = True |  | ||||||
|     ordering = ('stop_sequence',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TripUpdateInline(admin.StackedInline): |  | ||||||
|     model = TripUpdate |  | ||||||
|     extra = 0 |  | ||||||
|     formset = LimitModelFormset |  | ||||||
|     autocomplete_fields = ('trip',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StopTimeUpdateInline(admin.StackedInline): |  | ||||||
|     model = StopTimeUpdate |  | ||||||
|     extra = 0 |  | ||||||
|     formset = LimitModelFormset |  | ||||||
|     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',) |  | ||||||
|     autocomplete_fields = ('excluded_agencies',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(Agency) |  | ||||||
| class AgencyAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('name', 'id', 'url', 'timezone', 'gtfs_feed',) |  | ||||||
|     list_filter = ('gtfs_feed', 'timezone',) |  | ||||||
|     search_fields = ('name',) |  | ||||||
|     autocomplete_fields = ('gtfs_feed',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(Stop) |  | ||||||
| class StopAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('name', 'id', 'lat', 'lon', 'location_type',) |  | ||||||
|     list_filter = ('location_type', 'gtfs_feed',) |  | ||||||
|     search_fields = ('name', 'id',) |  | ||||||
|     ordering = ('name',) |  | ||||||
|     autocomplete_fields = ('parent_station', 'gtfs_feed',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(Route) |  | ||||||
| class RouteAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('__str__', 'id', 'type', 'gtfs_feed',) |  | ||||||
|     list_filter = ('gtfs_feed', 'type', 'agency',) |  | ||||||
|     search_fields = ('long_name', 'short_name', 'id',) |  | ||||||
|     ordering = ('long_name',) |  | ||||||
|     autocomplete_fields = ('agency', 'gtfs_feed',) |  | ||||||
|     inlines = (TripInline,) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(Trip) |  | ||||||
| class TripAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('id', 'origin_destination', 'route', 'service', 'headsign', 'direction_id',) |  | ||||||
|     list_filter = ('direction_id', 'route__gtfs_feed',) |  | ||||||
|     search_fields = ('id', 'route__id', 'route__long_name', 'service__id', 'headsign',) |  | ||||||
|     ordering = ('route', 'service',) |  | ||||||
|     autocomplete_fields = ('route', 'service', 'gtfs_feed',) |  | ||||||
|     inlines = (StopTimeInline, TripUpdateInline,) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(StopTime) |  | ||||||
| class StopTimeAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('trip', 'stop', 'arrival_time', 'departure_time', |  | ||||||
|                     'stop_sequence', 'pickup_type', 'drop_off_type',) |  | ||||||
|     list_filter = ('pickup_type', 'drop_off_type', 'trip__route__gtfs_feed',) |  | ||||||
|     search_fields = ('trip__id', 'stop__name', 'arrival_time', 'departure_time',) |  | ||||||
|     ordering = ('trip', 'stop_sequence',) |  | ||||||
|     autocomplete_fields = ('trip', 'stop',) |  | ||||||
|     readonly_fields = ('id',) |  | ||||||
|     inlines = (StopTimeUpdateInline,) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(Calendar) |  | ||||||
| class CalendarAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('id', 'gtfs_feed', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', |  | ||||||
|                     'saturday', 'sunday', 'start_date', 'end_date',) |  | ||||||
|     list_filter = ('gtfs_feed', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', |  | ||||||
|                    'start_date', 'end_date',) |  | ||||||
|     search_fields = ('id', 'start_date', 'end_date',) |  | ||||||
|     autocomplete_fields = ('gtfs_feed',) |  | ||||||
|     ordering = ('gtfs_feed', 'id',) |  | ||||||
|     inlines = (CalendarDateInline, TripInline,) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(CalendarDate) |  | ||||||
| class CalendarDateAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('id', 'service_id', 'date', 'exception_type',) |  | ||||||
|     list_filter = ('exception_type', 'date', 'service__gtfs_feed',) |  | ||||||
|     search_fields = ('id', 'date',) |  | ||||||
|     ordering = ('date', 'service_id',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(Transfer) |  | ||||||
| class TransferAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('from_stop', 'to_stop', 'transfer_type', 'min_transfer_time',) |  | ||||||
|     list_filter = ('transfer_type',) |  | ||||||
|     search_fields = ('from_stop__name', 'to_stop__name',) |  | ||||||
|     autocomplete_fields = ('from_stop', 'to_stop',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(FeedInfo) |  | ||||||
| class FeedInfoAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('publisher_name', 'publisher_url', 'lang', 'start_date', |  | ||||||
|                     'end_date', 'version',) |  | ||||||
|     search_fields = ('publisher_name', 'publisher_url', 'lang', 'start_date', |  | ||||||
|                      'end_date', 'version',) |  | ||||||
|     autocomplete_fields = ('gtfs_feed',) |  | ||||||
|     ordering = ('publisher_name',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(StopTimeUpdate) |  | ||||||
| class StopTimeUpdateAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('trip_update', 'stop_time', 'arrival_delay', 'arrival_time', |  | ||||||
|                     'departure_delay', 'departure_time', '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',) |  | ||||||
|     ordering = ('trip_update', 'stop_time',) |  | ||||||
|     autocomplete_fields = ('trip_update', 'stop_time',) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admin.register(TripUpdate) |  | ||||||
| class TripUpdateAdmin(admin.ModelAdmin): |  | ||||||
|     list_display = ('trip_id', 'start_date', 'start_time',) |  | ||||||
|     list_filter = ('start_date', 'schedule_relationship',) |  | ||||||
|     search_fields = ('trip__id', 'start_date', 'start_time',) |  | ||||||
|     ordering = ('trip_id', 'start_date', 'start_time',) |  | ||||||
|     autocomplete_fields = ('trip',) |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| from django.apps import AppConfig |  | ||||||
| from django.db.models.signals import pre_save |  | ||||||
| from django.utils.translation import gettext_lazy as _ |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TrainvelGTFSConfig(AppConfig): |  | ||||||
|     default_auto_field = "django.db.models.BigAutoField" |  | ||||||
|     name = "trainvel.gtfs" |  | ||||||
|     verbose_name = _("Trainvel - GTFS") |  | ||||||
|  |  | ||||||
|     def ready(self): |  | ||||||
|         from trainvel.gtfs import signals |  | ||||||
|  |  | ||||||
|         pre_save.connect(signals.keep_gtfs_feed_modification_date, sender="gtfs.GTFSFeed") |  | ||||||
| @@ -1,180 +0,0 @@ | |||||||
| [ |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.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", |  | ||||||
|             "categorize_routes": false, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\s]+)", |  | ||||||
|             "route_type_regex": "SNCF-&stop:FR-SNCF-TGV-StopPoint:OCE([\\w\\s]+)-\\d+", |  | ||||||
|             "trip_number_regex": "trip_headsign:(\\d+)", |  | ||||||
|             "long_distance_regex": "", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.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", |  | ||||||
|             "categorize_routes": false, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\s]+)", |  | ||||||
|             "route_type_regex": "SNCF-&stop:FR-SNCF-IC-StopPoint:OCE([\\w\\s]+)-\\d+", |  | ||||||
|             "trip_number_regex": "trip_headsign:(\\d+)", |  | ||||||
|             "long_distance_regex": "", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.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", |  | ||||||
|             "categorize_routes": false, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\s]+)", |  | ||||||
|             "route_type_regex": "SNCF-&stop:FR-SNCF-TER-StopPoint:OCE([\\w\\s]+)-\\d+", |  | ||||||
|             "trip_number_regex": "trip_headsign:(\\d+)", |  | ||||||
|             "long_distance_regex": "^$", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.gtfsfeed", |  | ||||||
|         "pk": "FR-IDF-IDFM", |  | ||||||
|         "fields": { |  | ||||||
|             "name": "Île-de-France Mobilités", |  | ||||||
|             "country": "FR", |  | ||||||
|             "feed_url": "https://eu.ftp.opendatasoft.com/stif/GTFS/IDFM-gtfs.zip", |  | ||||||
|             "rt_feed_url": "", |  | ||||||
|             "categorize_routes": true, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w]+)", |  | ||||||
|             "route_type_regex": "IDFM", |  | ||||||
|             "trip_number_regex": "trip_short_name:([\\d\\w-]*)§", |  | ||||||
|             "long_distance_regex": "^$", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.gtfsfeed", |  | ||||||
|         "pk": "FR-GES-CTS", |  | ||||||
|         "fields": { |  | ||||||
|             "name": "Compagnie des Transports Strasbourgeois (CTS)", |  | ||||||
|             "country": "FR", |  | ||||||
|             "feed_url": "https://opendata.cts-strasbourg.eu/google_transit.zip", |  | ||||||
|             "rt_feed_url": "", |  | ||||||
|             "categorize_routes": true, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\d]+)", |  | ||||||
|             "route_type_regex": "CTS", |  | ||||||
|             "trip_number_regex": "", |  | ||||||
|             "long_distance_regex": "^$", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.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": "", |  | ||||||
|             "categorize_routes": false, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\s]+)", |  | ||||||
|             "route_type_regex": "Eurostar", |  | ||||||
|             "trip_number_regex": "trip_short_name:(\\d+)", |  | ||||||
|             "long_distance_regex": "", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.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", |  | ||||||
|             "categorize_routes": false, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\s/-]+)", |  | ||||||
|             "route_type_regex": "Trenitalia France", |  | ||||||
|             "trip_number_regex": "trip_id:IT-FRA-TI-(\\d+)", |  | ||||||
|             "long_distance_regex": "", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.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": "", |  | ||||||
|             "categorize_routes": true, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\s]+)", |  | ||||||
|             "route_type_regex": "RENFE-&route_short_name:([\\w\\s]+)", |  | ||||||
|             "trip_number_regex": "trip_short_name:(\\d+)", |  | ||||||
|             "long_distance_regex": "", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.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": "", |  | ||||||
|             "categorize_routes": true, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\s]+)", |  | ||||||
|             "route_type_regex": "ÖBB-&trip_short_name:(\\w+)\\s", |  | ||||||
|             "trip_number_regex": "trip_short_name:\\w+\\s(\\d+)", |  | ||||||
|             "long_distance_regex": "", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "gtfs.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", |  | ||||||
|             "categorize_routes": true, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\s]*)", |  | ||||||
|             "route_type_regex": "CH-&route_desc:([\\w\\s]*)", |  | ||||||
|             "trip_number_regex": "trip_short_name:(\\d+)", |  | ||||||
|             "long_distance_regex": "", |  | ||||||
|             "excluded_agencies": [ |  | ||||||
|                 "CH-ALL-87_LEX" |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "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": "", |  | ||||||
|             "categorize_routes": true, |  | ||||||
|             "route_name_regex": "route_short_name:([\\w\\s]+)", |  | ||||||
|             "route_type_regex": "CFL-&route_short_name:([\\w\\s]+)", |  | ||||||
|             "trip_number_regex": "trip_short_name:(\\d+)", |  | ||||||
|             "long_distance_regex": "route_type:3", |  | ||||||
|             "excluded_agencies": [] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| ] |  | ||||||
| @@ -1,436 +0,0 @@ | |||||||
| from google.protobuf.internal import containers as _containers |  | ||||||
| from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper |  | ||||||
| from google.protobuf.internal import python_message as _python_message |  | ||||||
| from google.protobuf import descriptor as _descriptor |  | ||||||
| from google.protobuf import message as _message |  | ||||||
| from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union |  | ||||||
|  |  | ||||||
| DESCRIPTOR: _descriptor.FileDescriptor |  | ||||||
|  |  | ||||||
| class FeedMessage(_message.Message): |  | ||||||
|     __slots__ = ("header", "entity") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     HEADER_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     ENTITY_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     header: FeedHeader |  | ||||||
|     entity: _containers.RepeatedCompositeFieldContainer[FeedEntity] |  | ||||||
|     def __init__(self, header: _Optional[_Union[FeedHeader, _Mapping]] = ..., entity: _Optional[_Iterable[_Union[FeedEntity, _Mapping]]] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class FeedHeader(_message.Message): |  | ||||||
|     __slots__ = ("gtfs_realtime_version", "incrementality", "timestamp") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     class Incrementality(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|         __slots__ = () |  | ||||||
|         FULL_DATASET: _ClassVar[FeedHeader.Incrementality] |  | ||||||
|         DIFFERENTIAL: _ClassVar[FeedHeader.Incrementality] |  | ||||||
|     FULL_DATASET: FeedHeader.Incrementality |  | ||||||
|     DIFFERENTIAL: FeedHeader.Incrementality |  | ||||||
|     GTFS_REALTIME_VERSION_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     INCREMENTALITY_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     TIMESTAMP_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     gtfs_realtime_version: str |  | ||||||
|     incrementality: FeedHeader.Incrementality |  | ||||||
|     timestamp: int |  | ||||||
|     def __init__(self, gtfs_realtime_version: _Optional[str] = ..., incrementality: _Optional[_Union[FeedHeader.Incrementality, str]] = ..., timestamp: _Optional[int] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class FeedEntity(_message.Message): |  | ||||||
|     __slots__ = ("id", "is_deleted", "trip_update", "vehicle", "alert", "shape") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     IS_DELETED_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     TRIP_UPDATE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     VEHICLE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     ALERT_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     SHAPE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     id: str |  | ||||||
|     is_deleted: bool |  | ||||||
|     trip_update: TripUpdate |  | ||||||
|     vehicle: VehiclePosition |  | ||||||
|     alert: Alert |  | ||||||
|     shape: Shape |  | ||||||
|     def __init__(self, id: _Optional[str] = ..., is_deleted: bool = ..., trip_update: _Optional[_Union[TripUpdate, _Mapping]] = ..., vehicle: _Optional[_Union[VehiclePosition, _Mapping]] = ..., alert: _Optional[_Union[Alert, _Mapping]] = ..., shape: _Optional[_Union[Shape, _Mapping]] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class TripUpdate(_message.Message): |  | ||||||
|     __slots__ = ("trip", "vehicle", "stop_time_update", "timestamp", "delay", "trip_properties") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     class StopTimeEvent(_message.Message): |  | ||||||
|         __slots__ = ("delay", "time", "uncertainty") |  | ||||||
|         Extensions: _python_message._ExtensionDict |  | ||||||
|         DELAY_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         TIME_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         UNCERTAINTY_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         delay: int |  | ||||||
|         time: int |  | ||||||
|         uncertainty: int |  | ||||||
|         def __init__(self, delay: _Optional[int] = ..., time: _Optional[int] = ..., uncertainty: _Optional[int] = ...) -> None: ... |  | ||||||
|     class StopTimeUpdate(_message.Message): |  | ||||||
|         __slots__ = ("stop_sequence", "stop_id", "arrival", "departure", "departure_occupancy_status", "schedule_relationship", "stop_time_properties") |  | ||||||
|         Extensions: _python_message._ExtensionDict |  | ||||||
|         class ScheduleRelationship(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|             __slots__ = () |  | ||||||
|             SCHEDULED: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship] |  | ||||||
|             SKIPPED: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship] |  | ||||||
|             NO_DATA: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship] |  | ||||||
|             UNSCHEDULED: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship] |  | ||||||
|         SCHEDULED: TripUpdate.StopTimeUpdate.ScheduleRelationship |  | ||||||
|         SKIPPED: TripUpdate.StopTimeUpdate.ScheduleRelationship |  | ||||||
|         NO_DATA: TripUpdate.StopTimeUpdate.ScheduleRelationship |  | ||||||
|         UNSCHEDULED: TripUpdate.StopTimeUpdate.ScheduleRelationship |  | ||||||
|         class StopTimeProperties(_message.Message): |  | ||||||
|             __slots__ = ("assigned_stop_id",) |  | ||||||
|             Extensions: _python_message._ExtensionDict |  | ||||||
|             ASSIGNED_STOP_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|             assigned_stop_id: str |  | ||||||
|             def __init__(self, assigned_stop_id: _Optional[str] = ...) -> None: ... |  | ||||||
|         STOP_SEQUENCE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         STOP_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         ARRIVAL_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         DEPARTURE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         DEPARTURE_OCCUPANCY_STATUS_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         SCHEDULE_RELATIONSHIP_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         STOP_TIME_PROPERTIES_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         stop_sequence: int |  | ||||||
|         stop_id: str |  | ||||||
|         arrival: TripUpdate.StopTimeEvent |  | ||||||
|         departure: TripUpdate.StopTimeEvent |  | ||||||
|         departure_occupancy_status: VehiclePosition.OccupancyStatus |  | ||||||
|         schedule_relationship: TripUpdate.StopTimeUpdate.ScheduleRelationship |  | ||||||
|         stop_time_properties: TripUpdate.StopTimeUpdate.StopTimeProperties |  | ||||||
|         def __init__(self, stop_sequence: _Optional[int] = ..., stop_id: _Optional[str] = ..., arrival: _Optional[_Union[TripUpdate.StopTimeEvent, _Mapping]] = ..., departure: _Optional[_Union[TripUpdate.StopTimeEvent, _Mapping]] = ..., departure_occupancy_status: _Optional[_Union[VehiclePosition.OccupancyStatus, str]] = ..., schedule_relationship: _Optional[_Union[TripUpdate.StopTimeUpdate.ScheduleRelationship, str]] = ..., stop_time_properties: _Optional[_Union[TripUpdate.StopTimeUpdate.StopTimeProperties, _Mapping]] = ...) -> None: ... |  | ||||||
|     class TripProperties(_message.Message): |  | ||||||
|         __slots__ = ("trip_id", "start_date", "start_time", "shape_id") |  | ||||||
|         Extensions: _python_message._ExtensionDict |  | ||||||
|         TRIP_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         START_DATE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         START_TIME_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         SHAPE_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         trip_id: str |  | ||||||
|         start_date: str |  | ||||||
|         start_time: str |  | ||||||
|         shape_id: str |  | ||||||
|         def __init__(self, trip_id: _Optional[str] = ..., start_date: _Optional[str] = ..., start_time: _Optional[str] = ..., shape_id: _Optional[str] = ...) -> None: ... |  | ||||||
|     TRIP_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     VEHICLE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     STOP_TIME_UPDATE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     TIMESTAMP_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     DELAY_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     TRIP_PROPERTIES_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     trip: TripDescriptor |  | ||||||
|     vehicle: VehicleDescriptor |  | ||||||
|     stop_time_update: _containers.RepeatedCompositeFieldContainer[TripUpdate.StopTimeUpdate] |  | ||||||
|     timestamp: int |  | ||||||
|     delay: int |  | ||||||
|     trip_properties: TripUpdate.TripProperties |  | ||||||
|     def __init__(self, trip: _Optional[_Union[TripDescriptor, _Mapping]] = ..., vehicle: _Optional[_Union[VehicleDescriptor, _Mapping]] = ..., stop_time_update: _Optional[_Iterable[_Union[TripUpdate.StopTimeUpdate, _Mapping]]] = ..., timestamp: _Optional[int] = ..., delay: _Optional[int] = ..., trip_properties: _Optional[_Union[TripUpdate.TripProperties, _Mapping]] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class VehiclePosition(_message.Message): |  | ||||||
|     __slots__ = ("trip", "vehicle", "position", "current_stop_sequence", "stop_id", "current_status", "timestamp", "congestion_level", "occupancy_status", "occupancy_percentage", "multi_carriage_details") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     class VehicleStopStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|         __slots__ = () |  | ||||||
|         INCOMING_AT: _ClassVar[VehiclePosition.VehicleStopStatus] |  | ||||||
|         STOPPED_AT: _ClassVar[VehiclePosition.VehicleStopStatus] |  | ||||||
|         IN_TRANSIT_TO: _ClassVar[VehiclePosition.VehicleStopStatus] |  | ||||||
|     INCOMING_AT: VehiclePosition.VehicleStopStatus |  | ||||||
|     STOPPED_AT: VehiclePosition.VehicleStopStatus |  | ||||||
|     IN_TRANSIT_TO: VehiclePosition.VehicleStopStatus |  | ||||||
|     class CongestionLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|         __slots__ = () |  | ||||||
|         UNKNOWN_CONGESTION_LEVEL: _ClassVar[VehiclePosition.CongestionLevel] |  | ||||||
|         RUNNING_SMOOTHLY: _ClassVar[VehiclePosition.CongestionLevel] |  | ||||||
|         STOP_AND_GO: _ClassVar[VehiclePosition.CongestionLevel] |  | ||||||
|         CONGESTION: _ClassVar[VehiclePosition.CongestionLevel] |  | ||||||
|         SEVERE_CONGESTION: _ClassVar[VehiclePosition.CongestionLevel] |  | ||||||
|     UNKNOWN_CONGESTION_LEVEL: VehiclePosition.CongestionLevel |  | ||||||
|     RUNNING_SMOOTHLY: VehiclePosition.CongestionLevel |  | ||||||
|     STOP_AND_GO: VehiclePosition.CongestionLevel |  | ||||||
|     CONGESTION: VehiclePosition.CongestionLevel |  | ||||||
|     SEVERE_CONGESTION: VehiclePosition.CongestionLevel |  | ||||||
|     class OccupancyStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|         __slots__ = () |  | ||||||
|         EMPTY: _ClassVar[VehiclePosition.OccupancyStatus] |  | ||||||
|         MANY_SEATS_AVAILABLE: _ClassVar[VehiclePosition.OccupancyStatus] |  | ||||||
|         FEW_SEATS_AVAILABLE: _ClassVar[VehiclePosition.OccupancyStatus] |  | ||||||
|         STANDING_ROOM_ONLY: _ClassVar[VehiclePosition.OccupancyStatus] |  | ||||||
|         CRUSHED_STANDING_ROOM_ONLY: _ClassVar[VehiclePosition.OccupancyStatus] |  | ||||||
|         FULL: _ClassVar[VehiclePosition.OccupancyStatus] |  | ||||||
|         NOT_ACCEPTING_PASSENGERS: _ClassVar[VehiclePosition.OccupancyStatus] |  | ||||||
|         NO_DATA_AVAILABLE: _ClassVar[VehiclePosition.OccupancyStatus] |  | ||||||
|         NOT_BOARDABLE: _ClassVar[VehiclePosition.OccupancyStatus] |  | ||||||
|     EMPTY: VehiclePosition.OccupancyStatus |  | ||||||
|     MANY_SEATS_AVAILABLE: VehiclePosition.OccupancyStatus |  | ||||||
|     FEW_SEATS_AVAILABLE: VehiclePosition.OccupancyStatus |  | ||||||
|     STANDING_ROOM_ONLY: VehiclePosition.OccupancyStatus |  | ||||||
|     CRUSHED_STANDING_ROOM_ONLY: VehiclePosition.OccupancyStatus |  | ||||||
|     FULL: VehiclePosition.OccupancyStatus |  | ||||||
|     NOT_ACCEPTING_PASSENGERS: VehiclePosition.OccupancyStatus |  | ||||||
|     NO_DATA_AVAILABLE: VehiclePosition.OccupancyStatus |  | ||||||
|     NOT_BOARDABLE: VehiclePosition.OccupancyStatus |  | ||||||
|     class CarriageDetails(_message.Message): |  | ||||||
|         __slots__ = ("id", "label", "occupancy_status", "occupancy_percentage", "carriage_sequence") |  | ||||||
|         Extensions: _python_message._ExtensionDict |  | ||||||
|         ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         LABEL_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         OCCUPANCY_STATUS_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         OCCUPANCY_PERCENTAGE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         CARRIAGE_SEQUENCE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         id: str |  | ||||||
|         label: str |  | ||||||
|         occupancy_status: VehiclePosition.OccupancyStatus |  | ||||||
|         occupancy_percentage: int |  | ||||||
|         carriage_sequence: int |  | ||||||
|         def __init__(self, id: _Optional[str] = ..., label: _Optional[str] = ..., occupancy_status: _Optional[_Union[VehiclePosition.OccupancyStatus, str]] = ..., occupancy_percentage: _Optional[int] = ..., carriage_sequence: _Optional[int] = ...) -> None: ... |  | ||||||
|     TRIP_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     VEHICLE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     POSITION_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     CURRENT_STOP_SEQUENCE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     STOP_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     CURRENT_STATUS_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     TIMESTAMP_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     CONGESTION_LEVEL_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     OCCUPANCY_STATUS_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     OCCUPANCY_PERCENTAGE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     MULTI_CARRIAGE_DETAILS_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     trip: TripDescriptor |  | ||||||
|     vehicle: VehicleDescriptor |  | ||||||
|     position: Position |  | ||||||
|     current_stop_sequence: int |  | ||||||
|     stop_id: str |  | ||||||
|     current_status: VehiclePosition.VehicleStopStatus |  | ||||||
|     timestamp: int |  | ||||||
|     congestion_level: VehiclePosition.CongestionLevel |  | ||||||
|     occupancy_status: VehiclePosition.OccupancyStatus |  | ||||||
|     occupancy_percentage: int |  | ||||||
|     multi_carriage_details: _containers.RepeatedCompositeFieldContainer[VehiclePosition.CarriageDetails] |  | ||||||
|     def __init__(self, trip: _Optional[_Union[TripDescriptor, _Mapping]] = ..., vehicle: _Optional[_Union[VehicleDescriptor, _Mapping]] = ..., position: _Optional[_Union[Position, _Mapping]] = ..., current_stop_sequence: _Optional[int] = ..., stop_id: _Optional[str] = ..., current_status: _Optional[_Union[VehiclePosition.VehicleStopStatus, str]] = ..., timestamp: _Optional[int] = ..., congestion_level: _Optional[_Union[VehiclePosition.CongestionLevel, str]] = ..., occupancy_status: _Optional[_Union[VehiclePosition.OccupancyStatus, str]] = ..., occupancy_percentage: _Optional[int] = ..., multi_carriage_details: _Optional[_Iterable[_Union[VehiclePosition.CarriageDetails, _Mapping]]] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class Alert(_message.Message): |  | ||||||
|     __slots__ = ("active_period", "informed_entity", "cause", "effect", "url", "header_text", "description_text", "tts_header_text", "tts_description_text", "severity_level", "image", "image_alternative_text", "cause_detail", "effect_detail") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     class Cause(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|         __slots__ = () |  | ||||||
|         UNKNOWN_CAUSE: _ClassVar[Alert.Cause] |  | ||||||
|         OTHER_CAUSE: _ClassVar[Alert.Cause] |  | ||||||
|         TECHNICAL_PROBLEM: _ClassVar[Alert.Cause] |  | ||||||
|         STRIKE: _ClassVar[Alert.Cause] |  | ||||||
|         DEMONSTRATION: _ClassVar[Alert.Cause] |  | ||||||
|         ACCIDENT: _ClassVar[Alert.Cause] |  | ||||||
|         HOLIDAY: _ClassVar[Alert.Cause] |  | ||||||
|         WEATHER: _ClassVar[Alert.Cause] |  | ||||||
|         MAINTENANCE: _ClassVar[Alert.Cause] |  | ||||||
|         CONSTRUCTION: _ClassVar[Alert.Cause] |  | ||||||
|         POLICE_ACTIVITY: _ClassVar[Alert.Cause] |  | ||||||
|         MEDICAL_EMERGENCY: _ClassVar[Alert.Cause] |  | ||||||
|     UNKNOWN_CAUSE: Alert.Cause |  | ||||||
|     OTHER_CAUSE: Alert.Cause |  | ||||||
|     TECHNICAL_PROBLEM: Alert.Cause |  | ||||||
|     STRIKE: Alert.Cause |  | ||||||
|     DEMONSTRATION: Alert.Cause |  | ||||||
|     ACCIDENT: Alert.Cause |  | ||||||
|     HOLIDAY: Alert.Cause |  | ||||||
|     WEATHER: Alert.Cause |  | ||||||
|     MAINTENANCE: Alert.Cause |  | ||||||
|     CONSTRUCTION: Alert.Cause |  | ||||||
|     POLICE_ACTIVITY: Alert.Cause |  | ||||||
|     MEDICAL_EMERGENCY: Alert.Cause |  | ||||||
|     class Effect(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|         __slots__ = () |  | ||||||
|         NO_SERVICE: _ClassVar[Alert.Effect] |  | ||||||
|         REDUCED_SERVICE: _ClassVar[Alert.Effect] |  | ||||||
|         SIGNIFICANT_DELAYS: _ClassVar[Alert.Effect] |  | ||||||
|         DETOUR: _ClassVar[Alert.Effect] |  | ||||||
|         ADDITIONAL_SERVICE: _ClassVar[Alert.Effect] |  | ||||||
|         MODIFIED_SERVICE: _ClassVar[Alert.Effect] |  | ||||||
|         OTHER_EFFECT: _ClassVar[Alert.Effect] |  | ||||||
|         UNKNOWN_EFFECT: _ClassVar[Alert.Effect] |  | ||||||
|         STOP_MOVED: _ClassVar[Alert.Effect] |  | ||||||
|         NO_EFFECT: _ClassVar[Alert.Effect] |  | ||||||
|         ACCESSIBILITY_ISSUE: _ClassVar[Alert.Effect] |  | ||||||
|     NO_SERVICE: Alert.Effect |  | ||||||
|     REDUCED_SERVICE: Alert.Effect |  | ||||||
|     SIGNIFICANT_DELAYS: Alert.Effect |  | ||||||
|     DETOUR: Alert.Effect |  | ||||||
|     ADDITIONAL_SERVICE: Alert.Effect |  | ||||||
|     MODIFIED_SERVICE: Alert.Effect |  | ||||||
|     OTHER_EFFECT: Alert.Effect |  | ||||||
|     UNKNOWN_EFFECT: Alert.Effect |  | ||||||
|     STOP_MOVED: Alert.Effect |  | ||||||
|     NO_EFFECT: Alert.Effect |  | ||||||
|     ACCESSIBILITY_ISSUE: Alert.Effect |  | ||||||
|     class SeverityLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|         __slots__ = () |  | ||||||
|         UNKNOWN_SEVERITY: _ClassVar[Alert.SeverityLevel] |  | ||||||
|         INFO: _ClassVar[Alert.SeverityLevel] |  | ||||||
|         WARNING: _ClassVar[Alert.SeverityLevel] |  | ||||||
|         SEVERE: _ClassVar[Alert.SeverityLevel] |  | ||||||
|     UNKNOWN_SEVERITY: Alert.SeverityLevel |  | ||||||
|     INFO: Alert.SeverityLevel |  | ||||||
|     WARNING: Alert.SeverityLevel |  | ||||||
|     SEVERE: Alert.SeverityLevel |  | ||||||
|     ACTIVE_PERIOD_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     INFORMED_ENTITY_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     CAUSE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     EFFECT_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     URL_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     HEADER_TEXT_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     DESCRIPTION_TEXT_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     TTS_HEADER_TEXT_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     TTS_DESCRIPTION_TEXT_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     SEVERITY_LEVEL_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     IMAGE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     IMAGE_ALTERNATIVE_TEXT_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     CAUSE_DETAIL_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     EFFECT_DETAIL_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     active_period: _containers.RepeatedCompositeFieldContainer[TimeRange] |  | ||||||
|     informed_entity: _containers.RepeatedCompositeFieldContainer[EntitySelector] |  | ||||||
|     cause: Alert.Cause |  | ||||||
|     effect: Alert.Effect |  | ||||||
|     url: TranslatedString |  | ||||||
|     header_text: TranslatedString |  | ||||||
|     description_text: TranslatedString |  | ||||||
|     tts_header_text: TranslatedString |  | ||||||
|     tts_description_text: TranslatedString |  | ||||||
|     severity_level: Alert.SeverityLevel |  | ||||||
|     image: TranslatedImage |  | ||||||
|     image_alternative_text: TranslatedString |  | ||||||
|     cause_detail: TranslatedString |  | ||||||
|     effect_detail: TranslatedString |  | ||||||
|     def __init__(self, active_period: _Optional[_Iterable[_Union[TimeRange, _Mapping]]] = ..., informed_entity: _Optional[_Iterable[_Union[EntitySelector, _Mapping]]] = ..., cause: _Optional[_Union[Alert.Cause, str]] = ..., effect: _Optional[_Union[Alert.Effect, str]] = ..., url: _Optional[_Union[TranslatedString, _Mapping]] = ..., header_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., description_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., tts_header_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., tts_description_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., severity_level: _Optional[_Union[Alert.SeverityLevel, str]] = ..., image: _Optional[_Union[TranslatedImage, _Mapping]] = ..., image_alternative_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., cause_detail: _Optional[_Union[TranslatedString, _Mapping]] = ..., effect_detail: _Optional[_Union[TranslatedString, _Mapping]] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class TimeRange(_message.Message): |  | ||||||
|     __slots__ = ("start", "end") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     START_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     END_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     start: int |  | ||||||
|     end: int |  | ||||||
|     def __init__(self, start: _Optional[int] = ..., end: _Optional[int] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class Position(_message.Message): |  | ||||||
|     __slots__ = ("latitude", "longitude", "bearing", "odometer", "speed") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     LATITUDE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     LONGITUDE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     BEARING_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     ODOMETER_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     SPEED_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     latitude: float |  | ||||||
|     longitude: float |  | ||||||
|     bearing: float |  | ||||||
|     odometer: float |  | ||||||
|     speed: float |  | ||||||
|     def __init__(self, latitude: _Optional[float] = ..., longitude: _Optional[float] = ..., bearing: _Optional[float] = ..., odometer: _Optional[float] = ..., speed: _Optional[float] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class TripDescriptor(_message.Message): |  | ||||||
|     __slots__ = ("trip_id", "route_id", "direction_id", "start_time", "start_date", "schedule_relationship") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     class ScheduleRelationship(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|         __slots__ = () |  | ||||||
|         SCHEDULED: _ClassVar[TripDescriptor.ScheduleRelationship] |  | ||||||
|         ADDED: _ClassVar[TripDescriptor.ScheduleRelationship] |  | ||||||
|         UNSCHEDULED: _ClassVar[TripDescriptor.ScheduleRelationship] |  | ||||||
|         CANCELED: _ClassVar[TripDescriptor.ScheduleRelationship] |  | ||||||
|         REPLACEMENT: _ClassVar[TripDescriptor.ScheduleRelationship] |  | ||||||
|         DUPLICATED: _ClassVar[TripDescriptor.ScheduleRelationship] |  | ||||||
|         DELETED: _ClassVar[TripDescriptor.ScheduleRelationship] |  | ||||||
|     SCHEDULED: TripDescriptor.ScheduleRelationship |  | ||||||
|     ADDED: TripDescriptor.ScheduleRelationship |  | ||||||
|     UNSCHEDULED: TripDescriptor.ScheduleRelationship |  | ||||||
|     CANCELED: TripDescriptor.ScheduleRelationship |  | ||||||
|     REPLACEMENT: TripDescriptor.ScheduleRelationship |  | ||||||
|     DUPLICATED: TripDescriptor.ScheduleRelationship |  | ||||||
|     DELETED: TripDescriptor.ScheduleRelationship |  | ||||||
|     TRIP_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     ROUTE_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     DIRECTION_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     START_TIME_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     START_DATE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     SCHEDULE_RELATIONSHIP_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     trip_id: str |  | ||||||
|     route_id: str |  | ||||||
|     direction_id: int |  | ||||||
|     start_time: str |  | ||||||
|     start_date: str |  | ||||||
|     schedule_relationship: TripDescriptor.ScheduleRelationship |  | ||||||
|     def __init__(self, trip_id: _Optional[str] = ..., route_id: _Optional[str] = ..., direction_id: _Optional[int] = ..., start_time: _Optional[str] = ..., start_date: _Optional[str] = ..., schedule_relationship: _Optional[_Union[TripDescriptor.ScheduleRelationship, str]] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class VehicleDescriptor(_message.Message): |  | ||||||
|     __slots__ = ("id", "label", "license_plate", "wheelchair_accessible") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     class WheelchairAccessible(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): |  | ||||||
|         __slots__ = () |  | ||||||
|         NO_VALUE: _ClassVar[VehicleDescriptor.WheelchairAccessible] |  | ||||||
|         UNKNOWN: _ClassVar[VehicleDescriptor.WheelchairAccessible] |  | ||||||
|         WHEELCHAIR_ACCESSIBLE: _ClassVar[VehicleDescriptor.WheelchairAccessible] |  | ||||||
|         WHEELCHAIR_INACCESSIBLE: _ClassVar[VehicleDescriptor.WheelchairAccessible] |  | ||||||
|     NO_VALUE: VehicleDescriptor.WheelchairAccessible |  | ||||||
|     UNKNOWN: VehicleDescriptor.WheelchairAccessible |  | ||||||
|     WHEELCHAIR_ACCESSIBLE: VehicleDescriptor.WheelchairAccessible |  | ||||||
|     WHEELCHAIR_INACCESSIBLE: VehicleDescriptor.WheelchairAccessible |  | ||||||
|     ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     LABEL_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     LICENSE_PLATE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     WHEELCHAIR_ACCESSIBLE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     id: str |  | ||||||
|     label: str |  | ||||||
|     license_plate: str |  | ||||||
|     wheelchair_accessible: VehicleDescriptor.WheelchairAccessible |  | ||||||
|     def __init__(self, id: _Optional[str] = ..., label: _Optional[str] = ..., license_plate: _Optional[str] = ..., wheelchair_accessible: _Optional[_Union[VehicleDescriptor.WheelchairAccessible, str]] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class EntitySelector(_message.Message): |  | ||||||
|     __slots__ = ("agency_id", "route_id", "route_type", "trip", "stop_id", "direction_id") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     AGENCY_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     ROUTE_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     ROUTE_TYPE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     TRIP_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     STOP_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     DIRECTION_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     agency_id: str |  | ||||||
|     route_id: str |  | ||||||
|     route_type: int |  | ||||||
|     trip: TripDescriptor |  | ||||||
|     stop_id: str |  | ||||||
|     direction_id: int |  | ||||||
|     def __init__(self, agency_id: _Optional[str] = ..., route_id: _Optional[str] = ..., route_type: _Optional[int] = ..., trip: _Optional[_Union[TripDescriptor, _Mapping]] = ..., stop_id: _Optional[str] = ..., direction_id: _Optional[int] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class TranslatedString(_message.Message): |  | ||||||
|     __slots__ = ("translation",) |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     class Translation(_message.Message): |  | ||||||
|         __slots__ = ("text", "language") |  | ||||||
|         Extensions: _python_message._ExtensionDict |  | ||||||
|         TEXT_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         LANGUAGE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         text: str |  | ||||||
|         language: str |  | ||||||
|         def __init__(self, text: _Optional[str] = ..., language: _Optional[str] = ...) -> None: ... |  | ||||||
|     TRANSLATION_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     translation: _containers.RepeatedCompositeFieldContainer[TranslatedString.Translation] |  | ||||||
|     def __init__(self, translation: _Optional[_Iterable[_Union[TranslatedString.Translation, _Mapping]]] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class TranslatedImage(_message.Message): |  | ||||||
|     __slots__ = ("localized_image",) |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     class LocalizedImage(_message.Message): |  | ||||||
|         __slots__ = ("url", "media_type", "language") |  | ||||||
|         Extensions: _python_message._ExtensionDict |  | ||||||
|         URL_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         MEDIA_TYPE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         LANGUAGE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|         url: str |  | ||||||
|         media_type: str |  | ||||||
|         language: str |  | ||||||
|         def __init__(self, url: _Optional[str] = ..., media_type: _Optional[str] = ..., language: _Optional[str] = ...) -> None: ... |  | ||||||
|     LOCALIZED_IMAGE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     localized_image: _containers.RepeatedCompositeFieldContainer[TranslatedImage.LocalizedImage] |  | ||||||
|     def __init__(self, localized_image: _Optional[_Iterable[_Union[TranslatedImage.LocalizedImage, _Mapping]]] = ...) -> None: ... |  | ||||||
|  |  | ||||||
| class Shape(_message.Message): |  | ||||||
|     __slots__ = ("shape_id", "encoded_polyline") |  | ||||||
|     Extensions: _python_message._ExtensionDict |  | ||||||
|     SHAPE_ID_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     ENCODED_POLYLINE_FIELD_NUMBER: _ClassVar[int] |  | ||||||
|     shape_id: str |  | ||||||
|     encoded_polyline: str |  | ||||||
|     def __init__(self, shape_id: _Optional[str] = ..., encoded_polyline: _Optional[str] = ...) -> None: ... |  | ||||||
| @@ -1,825 +0,0 @@ | |||||||
| 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/gtfs/apps.py:8 |  | ||||||
| msgid "Trainvel - GTFS" |  | ||||||
| msgstr "Trainvel - GTFS" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:11 |  | ||||||
| msgid "Albania" |  | ||||||
| msgstr "Albanie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:12 |  | ||||||
| msgid "Andorra" |  | ||||||
| msgstr "Andorre" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:13 |  | ||||||
| msgid "Armenia" |  | ||||||
| msgstr "Arménie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:14 |  | ||||||
| msgid "Austria" |  | ||||||
| msgstr "Autriche" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:15 |  | ||||||
| msgid "Azerbaijan" |  | ||||||
| msgstr "Azerbaijan" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:16 |  | ||||||
| msgid "Belgium" |  | ||||||
| msgstr "Belgique" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:17 |  | ||||||
| msgid "Bosnia and Herzegovina" |  | ||||||
| msgstr " Bosnie-Herzégovine" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:18 |  | ||||||
| msgid "Bulgaria" |  | ||||||
| msgstr "Bulgarie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:19 |  | ||||||
| msgid "Croatia" |  | ||||||
| msgstr "Croatie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:20 |  | ||||||
| msgid "Cyprus" |  | ||||||
| msgstr "Chypre" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:21 |  | ||||||
| msgid "Czech Republic" |  | ||||||
| msgstr "République Tchèque" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:22 |  | ||||||
| msgid "Denmark" |  | ||||||
| msgstr "Danemark" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:23 |  | ||||||
| msgid "Estonia" |  | ||||||
| msgstr "Estonie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:24 |  | ||||||
| msgid "Finland" |  | ||||||
| msgstr "Finlande" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:25 |  | ||||||
| msgid "France" |  | ||||||
| msgstr "France" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:26 |  | ||||||
| msgid "Georgia" |  | ||||||
| msgstr "Géorgie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:27 |  | ||||||
| msgid "Germany" |  | ||||||
| msgstr "Allemagne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:28 |  | ||||||
| msgid "Greece" |  | ||||||
| msgstr "Grèce" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:29 |  | ||||||
| msgid "Hungary" |  | ||||||
| msgstr "Hongrie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:30 |  | ||||||
| msgid "Iceland" |  | ||||||
| msgstr "Islande" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:31 |  | ||||||
| msgid "Ireland" |  | ||||||
| msgstr "Irlande" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:32 |  | ||||||
| msgid "Italy" |  | ||||||
| msgstr "Italie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:33 |  | ||||||
| msgid "Latvia" |  | ||||||
| msgstr "Lettonie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:34 |  | ||||||
| msgid "Liechtenstein" |  | ||||||
| msgstr "Liechtenstein" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:35 |  | ||||||
| msgid "Lithuania" |  | ||||||
| msgstr "Lituanie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:36 |  | ||||||
| msgid "Luxembourg" |  | ||||||
| msgstr "Luxembourg" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:37 |  | ||||||
| msgid "Malta" |  | ||||||
| msgstr "Malte" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:38 |  | ||||||
| msgid "Moldova" |  | ||||||
| msgstr "Moldavie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:39 |  | ||||||
| msgid "Monaco" |  | ||||||
| msgstr "Monaco" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:40 |  | ||||||
| msgid "Montenegro" |  | ||||||
| msgstr "Monténégro" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:41 |  | ||||||
| msgid "Netherlands" |  | ||||||
| msgstr "Pays-Bas" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:42 |  | ||||||
| msgid "North Macedonia" |  | ||||||
| msgstr "Macédoine du Nord" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:43 |  | ||||||
| msgid "Norway" |  | ||||||
| msgstr "Norvège" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:44 |  | ||||||
| msgid "Poland" |  | ||||||
| msgstr "Pologne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:45 |  | ||||||
| msgid "Portugal" |  | ||||||
| msgstr "Portugal" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:46 |  | ||||||
| msgid "Romania" |  | ||||||
| msgstr "Roumanie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:47 |  | ||||||
| msgid "San Marino" |  | ||||||
| msgstr "Saint-Marin" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:48 |  | ||||||
| msgid "Serbia" |  | ||||||
| msgstr "Serbie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:49 |  | ||||||
| msgid "Slovakia" |  | ||||||
| msgstr "Slovaquie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:50 |  | ||||||
| msgid "Slovenia" |  | ||||||
| msgstr "Slovénie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:51 |  | ||||||
| msgid "Spain" |  | ||||||
| msgstr "Espagne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:52 |  | ||||||
| msgid "Sweden" |  | ||||||
| msgstr "Suède" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:53 |  | ||||||
| msgid "Switzerland" |  | ||||||
| msgstr "Suisse" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:54 |  | ||||||
| msgid "Turkey" |  | ||||||
| msgstr "Turquie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:55 |  | ||||||
| msgid "United Kingdom" |  | ||||||
| msgstr "Royaume-Uni" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:56 |  | ||||||
| msgid "Ukraine" |  | ||||||
| msgstr "Ukraine" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:60 |  | ||||||
| msgid "Stop/platform" |  | ||||||
| msgstr "Arrêt / quai" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:61 |  | ||||||
| msgid "Station" |  | ||||||
| msgstr "Gare" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:62 |  | ||||||
| msgid "Entrance/exit" |  | ||||||
| msgstr "Entrée / sortie" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:63 |  | ||||||
| msgid "Generic node" |  | ||||||
| msgstr "Nœud générique" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:64 |  | ||||||
| msgid "Boarding area" |  | ||||||
| msgstr "Zone d'embarquement" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:68 |  | ||||||
| msgid "No information" |  | ||||||
| msgstr "Pas d'information" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:69 |  | ||||||
| msgid "Possible" |  | ||||||
| msgstr "Possible" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:70 trainvel/gtfs/models.py:100 |  | ||||||
| msgid "Not possible" |  | ||||||
| msgstr "Impossible" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:74 |  | ||||||
| msgid "Regular" |  | ||||||
| msgstr "Régulier" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:75 |  | ||||||
| msgid "None" |  | ||||||
| msgstr "Aucun" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:76 |  | ||||||
| msgid "Must phone agency" |  | ||||||
| msgstr "Doit téléphoner à l'agence" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:77 |  | ||||||
| msgid "Must coordinate with driver" |  | ||||||
| msgstr "Doit se coordonner avec læ conducteurice" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:81 |  | ||||||
| msgid "Tram" |  | ||||||
| msgstr "Tram" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:82 |  | ||||||
| msgid "Metro" |  | ||||||
| msgstr "Métro" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:83 |  | ||||||
| msgid "Rail" |  | ||||||
| msgstr "Rail" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:84 |  | ||||||
| msgid "Bus" |  | ||||||
| msgstr "Bus" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:85 |  | ||||||
| msgid "Ferry" |  | ||||||
| msgstr "Ferry" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:86 |  | ||||||
| msgid "Cable car" |  | ||||||
| msgstr "Câble" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:87 |  | ||||||
| msgid "Gondola" |  | ||||||
| msgstr "Gondole" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:88 |  | ||||||
| msgid "Funicular" |  | ||||||
| msgstr "Funiculaire" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:92 |  | ||||||
| msgid "Outbound" |  | ||||||
| msgstr "Vers l'extérieur" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:93 |  | ||||||
| msgid "Inbound" |  | ||||||
| msgstr "Vers l'intérieur" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:97 |  | ||||||
| msgid "Recommended" |  | ||||||
| msgstr "Recommandé" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:98 |  | ||||||
| msgid "Timed" |  | ||||||
| msgstr "Correspondance programmée" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:99 |  | ||||||
| msgid "Minimum time" |  | ||||||
| msgstr "Temps de correspondance minimum requis" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:104 trainvel/gtfs/models.py:110 |  | ||||||
| msgid "Added" |  | ||||||
| msgstr "Ajouté" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:105 |  | ||||||
| msgid "Removed" |  | ||||||
| msgstr "Supprimé" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:109 trainvel/gtfs/models.py:119 |  | ||||||
| msgid "Scheduled" |  | ||||||
| msgstr "Planifié" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:111 trainvel/gtfs/models.py:122 |  | ||||||
| msgid "Unscheduled" |  | ||||||
| msgstr "Non planifié" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:112 |  | ||||||
| msgid "Canceled" |  | ||||||
| msgstr "Annulé" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:113 |  | ||||||
| msgid "Replacement" |  | ||||||
| msgstr "Remplacé" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:114 |  | ||||||
| msgid "Duplicated" |  | ||||||
| msgstr "Dupliqué" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:115 |  | ||||||
| msgid "Deleted" |  | ||||||
| msgstr "Supprimé" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:120 |  | ||||||
| msgid "Skipped" |  | ||||||
| msgstr "Sauté" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:121 |  | ||||||
| msgid "No data" |  | ||||||
| msgstr "Pas de données" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:129 |  | ||||||
| msgid "code" |  | ||||||
| msgstr "code" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:130 |  | ||||||
| msgid "Unique code of the feed." |  | ||||||
| msgstr "Code unique du flux." |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:135 |  | ||||||
| msgid "name" |  | ||||||
| msgstr "nom" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:137 |  | ||||||
| msgid "Full name that describes the feed." |  | ||||||
| msgstr "Nom complet qui décrit le flux." |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:142 |  | ||||||
| msgid "country" |  | ||||||
| msgstr "pays" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:147 |  | ||||||
| msgid "feed URL" |  | ||||||
| msgstr "URL du flux" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/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." |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:153 |  | ||||||
| msgid "realtime feed URL" |  | ||||||
| msgstr "URL du flux temps réel" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/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." |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:161 |  | ||||||
| msgid "last modified date" |  | ||||||
| msgstr "Date de dernière modification" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:168 |  | ||||||
| msgid "ETag" |  | ||||||
| msgstr "ETag" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/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." |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:179 trainvel/gtfs/models.py:226 |  | ||||||
| #: trainvel/gtfs/models.py:326 trainvel/gtfs/models.py:405 |  | ||||||
| #: trainvel/gtfs/models.py:486 trainvel/gtfs/models.py:696 |  | ||||||
| #: trainvel/gtfs/models.py:811 |  | ||||||
| msgid "GTFS feed" |  | ||||||
| msgstr "flux GTFS" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:180 |  | ||||||
| msgid "GTFS feeds" |  | ||||||
| msgstr "flux GTFS" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:189 |  | ||||||
| msgid "Agency ID" |  | ||||||
| msgstr "ID de l'agence" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:194 |  | ||||||
| msgid "Agency name" |  | ||||||
| msgstr "Nom de l'agence" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:198 |  | ||||||
| msgid "Agency URL" |  | ||||||
| msgstr "URL de l'agence" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:203 |  | ||||||
| msgid "Agency timezone" |  | ||||||
| msgstr "Fuseau horaire de l'agence" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:208 |  | ||||||
| msgid "Agency language" |  | ||||||
| msgstr "Langue de l'agence" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:214 |  | ||||||
| msgid "Agency phone" |  | ||||||
| msgstr "Téléphone de l'agence" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:219 |  | ||||||
| msgid "Agency email" |  | ||||||
| msgstr "Adresse email de l'agence" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:233 trainvel/gtfs/models.py:356 |  | ||||||
| msgid "Agency" |  | ||||||
| msgstr "Agence" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:234 |  | ||||||
| msgid "Agencies" |  | ||||||
| msgstr "Agences" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:243 trainvel/gtfs/models.py:593 |  | ||||||
| msgid "Stop ID" |  | ||||||
| msgstr "ID de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:248 |  | ||||||
| msgid "Stop code" |  | ||||||
| msgstr "Code de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:254 |  | ||||||
| msgid "Stop name" |  | ||||||
| msgstr "Nom de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:259 |  | ||||||
| msgid "Stop description" |  | ||||||
| msgstr "Description de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:264 |  | ||||||
| msgid "Stop longitude" |  | ||||||
| msgstr "Longitude de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:268 |  | ||||||
| msgid "Stop latitude" |  | ||||||
| msgstr "Latitude de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:273 |  | ||||||
| msgid "Zone ID" |  | ||||||
| msgstr "ID de la zone" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:278 |  | ||||||
| msgid "Stop URL" |  | ||||||
| msgstr "URL de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:283 |  | ||||||
| msgid "Location type" |  | ||||||
| msgstr "Type de localisation" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:292 |  | ||||||
| msgid "Parent station" |  | ||||||
| msgstr "Gare parente" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:300 |  | ||||||
| msgid "Stop timezone" |  | ||||||
| msgstr "Fuseau horaire de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:306 |  | ||||||
| msgid "Level ID" |  | ||||||
| msgstr "ID du niveau" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:311 |  | ||||||
| msgid "Wheelchair boarding" |  | ||||||
| msgstr "Embarquement en fauteuil roulant" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:319 |  | ||||||
| msgid "Platform code" |  | ||||||
| msgstr "Code du quai" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:338 |  | ||||||
| msgid "Stop" |  | ||||||
| msgstr "Arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:339 |  | ||||||
| msgid "Stops" |  | ||||||
| msgstr "Arrêts" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:350 trainvel/gtfs/models.py:572 |  | ||||||
| #: trainvel/gtfs/models.py:713 trainvel/gtfs/models.py:746 |  | ||||||
| msgid "ID" |  | ||||||
| msgstr "Identifiant" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:365 |  | ||||||
| msgid "Route short name" |  | ||||||
| msgstr "Nom court de la ligne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:370 |  | ||||||
| msgid "Route long name" |  | ||||||
| msgstr "Nom long de la ligne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:376 |  | ||||||
| msgid "Route description" |  | ||||||
| msgstr "Description de la ligne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:381 |  | ||||||
| msgid "Route type" |  | ||||||
| msgstr "Type de ligne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:386 |  | ||||||
| msgid "Route URL" |  | ||||||
| msgstr "URL de la ligne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:392 |  | ||||||
| msgid "Route color" |  | ||||||
| msgstr "Couleur de la ligne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:398 |  | ||||||
| msgid "Route text color" |  | ||||||
| msgstr "Couleur du texte de la ligne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:412 trainvel/gtfs/models.py:428 |  | ||||||
| msgid "Route" |  | ||||||
| msgstr "Ligne" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:413 |  | ||||||
| msgid "Routes" |  | ||||||
| msgstr "Lignes" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:422 |  | ||||||
| msgid "Trip ID" |  | ||||||
| msgstr "ID du trajet" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:435 trainvel/gtfs/models.py:719 |  | ||||||
| msgid "Service" |  | ||||||
| msgstr "Service" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:441 |  | ||||||
| msgid "Trip headsign" |  | ||||||
| msgstr "Destination du trajet" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:447 |  | ||||||
| msgid "Trip short name" |  | ||||||
| msgstr "Nom court du trajet" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:452 |  | ||||||
| msgid "Direction" |  | ||||||
| msgstr "Direction" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:459 |  | ||||||
| msgid "Block ID" |  | ||||||
| msgstr "ID du bloc" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:465 |  | ||||||
| msgid "Shape ID" |  | ||||||
| msgstr "ID de la forme" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:470 |  | ||||||
| msgid "Wheelchair accessible" |  | ||||||
| msgstr "Accessible en fauteuil roulant" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:477 |  | ||||||
| msgid "Bikes allowed" |  | ||||||
| msgstr "Vélos autorisés" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:500 trainvel/gtfs/models.py:509 |  | ||||||
| #: trainvel/gtfs/models.py:552 trainvel/gtfs/models.py:554 |  | ||||||
| msgid "Unknown" |  | ||||||
| msgstr "Inconnu" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:557 |  | ||||||
| msgid "Origin → Destination" |  | ||||||
| msgstr "Origine → Destination" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:563 trainvel/gtfs/models.py:578 |  | ||||||
| #: trainvel/gtfs/models.py:825 |  | ||||||
| msgid "Trip" |  | ||||||
| msgstr "Trajet" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:564 |  | ||||||
| msgid "Trips" |  | ||||||
| msgstr "Trajets" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:583 trainvel/gtfs/models.py:876 |  | ||||||
| msgid "Arrival time" |  | ||||||
| msgstr "Heure d'arrivée" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:587 trainvel/gtfs/models.py:884 |  | ||||||
| msgid "Departure time" |  | ||||||
| msgstr "Heure de départ" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:598 |  | ||||||
| msgid "Stop sequence" |  | ||||||
| msgstr "Séquence de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:603 |  | ||||||
| msgid "Stop headsign" |  | ||||||
| msgstr "Destination de l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:608 |  | ||||||
| msgid "Pickup type" |  | ||||||
| msgstr "Type de prise en charge" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:615 |  | ||||||
| msgid "Drop off type" |  | ||||||
| msgstr "Type de dépose" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:622 |  | ||||||
| msgid "Timepoint" |  | ||||||
| msgstr "Ponctualité" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:645 trainvel/gtfs/models.py:866 |  | ||||||
| msgid "Stop time" |  | ||||||
| msgstr "Heure d'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:646 |  | ||||||
| msgid "Stop times" |  | ||||||
| msgstr "Heures d'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:654 |  | ||||||
| msgid "Service ID" |  | ||||||
| msgstr "ID du service" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:658 |  | ||||||
| msgid "Monday" |  | ||||||
| msgstr "Lundi" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:662 |  | ||||||
| msgid "Tuesday" |  | ||||||
| msgstr "Mardi" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:666 |  | ||||||
| msgid "Wednesday" |  | ||||||
| msgstr "Mercredi" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:670 |  | ||||||
| msgid "Thursday" |  | ||||||
| msgstr "Jeudi" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:674 |  | ||||||
| msgid "Friday" |  | ||||||
| msgstr "Vendredi" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:678 |  | ||||||
| msgid "Saturday" |  | ||||||
| msgstr "Samedi" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:682 |  | ||||||
| msgid "Sunday" |  | ||||||
| msgstr "Dimanche" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:686 trainvel/gtfs/models.py:831 |  | ||||||
| msgid "Start date" |  | ||||||
| msgstr "Date de début" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:690 |  | ||||||
| msgid "End date" |  | ||||||
| msgstr "Date de fin" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:703 |  | ||||||
| msgid "Calendar" |  | ||||||
| msgstr "Calendrier" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:704 |  | ||||||
| msgid "Calendars" |  | ||||||
| msgstr "Calendriers" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:724 |  | ||||||
| msgid "Date" |  | ||||||
| msgstr "Date" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:728 |  | ||||||
| msgid "Exception type" |  | ||||||
| msgstr "Type d'exception" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:736 |  | ||||||
| msgid "Calendar date" |  | ||||||
| msgstr "Date du calendrier" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:737 |  | ||||||
| msgid "Calendar dates" |  | ||||||
| msgstr "Dates du calendrier" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:752 |  | ||||||
| msgid "From stop" |  | ||||||
| msgstr "Depuis l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:759 |  | ||||||
| msgid "To stop" |  | ||||||
| msgstr "Jusqu'à l'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:764 |  | ||||||
| msgid "Transfer type" |  | ||||||
| msgstr "Type de correspondance" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:770 |  | ||||||
| msgid "Minimum transfer time" |  | ||||||
| msgstr "Temps de correspondance minimum" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:775 |  | ||||||
| msgid "Transfer" |  | ||||||
| msgstr "Correspondance" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:776 |  | ||||||
| msgid "Transfers" |  | ||||||
| msgstr "Correspondances" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:783 |  | ||||||
| msgid "Feed publisher name" |  | ||||||
| msgstr "Nom de l'éditeur du flux" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:787 |  | ||||||
| msgid "Feed publisher URL" |  | ||||||
| msgstr "URL de l'éditeur du flux" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:792 |  | ||||||
| msgid "Feed language" |  | ||||||
| msgstr "Langue du flux" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:796 |  | ||||||
| msgid "Feed start date" |  | ||||||
| msgstr "Date de début du flux" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:800 |  | ||||||
| msgid "Feed end date" |  | ||||||
| msgstr "Date de fin du flux" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:805 |  | ||||||
| msgid "Feed version" |  | ||||||
| msgstr "Version du flux" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:815 |  | ||||||
| msgid "Feed info" |  | ||||||
| msgstr "Information du flux" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:816 |  | ||||||
| msgid "Feed infos" |  | ||||||
| msgstr "Informations du flux" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:835 |  | ||||||
| msgid "Start time" |  | ||||||
| msgstr "Heure de début" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:839 trainvel/gtfs/models.py:888 |  | ||||||
| msgid "Schedule relationship" |  | ||||||
| msgstr "Relation de la planification" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:848 trainvel/gtfs/models.py:859 |  | ||||||
| msgid "Trip update" |  | ||||||
| msgstr "Mise à jour du trajet" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:849 |  | ||||||
| msgid "Trip updates" |  | ||||||
| msgstr "Mises à jour des trajets" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:872 |  | ||||||
| msgid "Arrival delay" |  | ||||||
| msgstr "Retard à l'arrivée" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:880 |  | ||||||
| msgid "Departure delay" |  | ||||||
| msgstr "Retard au départ" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:897 |  | ||||||
| msgid "Stop time update" |  | ||||||
| msgstr "Mise à jour du temps d'arrêt" |  | ||||||
|  |  | ||||||
| #: trainvel/gtfs/models.py:898 |  | ||||||
| msgid "Stop time updates" |  | ||||||
| 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" |  | ||||||
| @@ -1,20 +0,0 @@ | |||||||
| from django.core.management import BaseCommand |  | ||||||
| from django.db import transaction |  | ||||||
|  |  | ||||||
| from tqdm import tqdm |  | ||||||
| from trainvel.gtfs.models import Trip |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): |  | ||||||
|     def add_arguments(self, parser): |  | ||||||
|         parser.add_argument('--feed', type=str, help='GTFS Feed code') |  | ||||||
|  |  | ||||||
|     def handle(self, *args, **options): |  | ||||||
|         trips = Trip.objects.filter(route_name="").prefetch_related('gtfs_feed', 'route') |  | ||||||
|         if options['feed']: |  | ||||||
|             trips = trips.filter(gtfs_feed__code=options['feed']) |  | ||||||
|  |  | ||||||
|         with transaction.atomic(): |  | ||||||
|             for trip in tqdm(trips.all()): |  | ||||||
|                 trip.augment_data() |  | ||||||
|                 trip.save() |  | ||||||
| @@ -1,430 +0,0 @@ | |||||||
| import csv |  | ||||||
| import os.path |  | ||||||
| import tempfile |  | ||||||
| from datetime import datetime, timedelta |  | ||||||
| from time import time |  | ||||||
| from zipfile import ZipFile |  | ||||||
| from zoneinfo import ZoneInfo |  | ||||||
|  |  | ||||||
| import requests |  | ||||||
| from django.core.management import BaseCommand |  | ||||||
| from django.db import transaction |  | ||||||
| from django.db.models import Q |  | ||||||
| from tqdm import tqdm |  | ||||||
|  |  | ||||||
| from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, \ |  | ||||||
|     Transfer, Trip, PickupType, TripUpdate |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): |  | ||||||
|     help = "Update the Trainvel GTFS database." |  | ||||||
|  |  | ||||||
|     def add_arguments(self, parser): |  | ||||||
|         parser.add_argument('--debug', '-d', action='store_true', help="Activate debug mode") |  | ||||||
|         parser.add_argument('--bulk_size', '-b', type=int, default=2000, help="Number of objects to create in bulk.") |  | ||||||
|         parser.add_argument('--dry-run', action='store_true', |  | ||||||
|                             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.") |  | ||||||
|  |  | ||||||
|     def handle(self, debug: bool = False, bulk_size: int = 100, dry_run: bool = False, force: bool = False, |  | ||||||
|                verbosity: int = 1, *args, **options): |  | ||||||
|         if dry_run: |  | ||||||
|             self.stdout.write(self.style.WARNING("Dry run mode activated.")) |  | ||||||
|  |  | ||||||
|         self.stdout.write("Updating database...") |  | ||||||
|  |  | ||||||
|         for gtfs_feed in GTFSFeed.objects.all(): |  | ||||||
|             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 tempfile.TemporaryFile(suffix=".zip") as file: |  | ||||||
|                 for chunk in resp.iter_content(chunk_size=128): |  | ||||||
|                     file.write(chunk) |  | ||||||
|                 file.seek(0) |  | ||||||
|  |  | ||||||
|                 with tempfile.TemporaryDirectory() as tmp_dir: |  | ||||||
|                     with ZipFile(file) as zipfile: |  | ||||||
|                         zipfile.extractall(tmp_dir) |  | ||||||
|  |  | ||||||
|                     self.parse_gtfs(tmp_dir, gtfs_feed, bulk_size, dry_run, verbosity) |  | ||||||
|  |  | ||||||
|             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() |  | ||||||
|  |  | ||||||
|     def parse_gtfs(self, zip_dir: str, gtfs_feed: GTFSFeed, bulk_size: int, dry_run: bool, verbosity: int): |  | ||||||
|         gtfs_code = gtfs_feed.code |  | ||||||
|  |  | ||||||
|         def read_csv(filename): |  | ||||||
|             with open(os.path.join(zip_dir, filename), 'r') as f: |  | ||||||
|                 reader = csv.DictReader(f) |  | ||||||
|                 reader.fieldnames = [field.replace('\ufeff', '').strip() |  | ||||||
|                                      for field in reader.fieldnames] |  | ||||||
|                 iterator = tqdm(reader, desc=filename, unit=' rows') if verbosity >= 2 else reader |  | ||||||
|                 for row in iterator: |  | ||||||
|                     yield {k.strip(): v.strip() for k, v in row.items()} |  | ||||||
|  |  | ||||||
|         agencies = [] |  | ||||||
|         for agency_dict in read_csv("agency.txt"): |  | ||||||
|             agency_dict: dict |  | ||||||
|             agency = Agency( |  | ||||||
|                 id=f"{gtfs_code}-{agency_dict['agency_id']}", |  | ||||||
|                 name=agency_dict['agency_name'], |  | ||||||
|                 url=agency_dict['agency_url'], |  | ||||||
|                 timezone=agency_dict['agency_timezone'], |  | ||||||
|                 lang=agency_dict.get('agency_lang', "fr"), |  | ||||||
|                 phone=agency_dict.get('agency_phone', ""), |  | ||||||
|                 email=agency_dict.get('agency_email', ""), |  | ||||||
|                 gtfs_feed_id=gtfs_code, |  | ||||||
|             ) |  | ||||||
|             agencies.append(agency) |  | ||||||
|         if agencies and not dry_run: |  | ||||||
|             Agency.objects.bulk_create(agencies, |  | ||||||
|                                        update_conflicts=True, |  | ||||||
|                                        update_fields=['name', 'url', 'timezone', 'lang', 'phone', 'email', |  | ||||||
|                                                       'gtfs_feed'], |  | ||||||
|                                        unique_fields=['id']) |  | ||||||
|             agencies.clear() |  | ||||||
|  |  | ||||||
|         stops = [] |  | ||||||
|         for stop_dict in read_csv("stops.txt"): |  | ||||||
|             stop_dict: dict |  | ||||||
|             stop_id = stop_dict['stop_id'] |  | ||||||
|             stop_id = f"{gtfs_code}-{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( |  | ||||||
|                 id=stop_id, |  | ||||||
|                 name=stop_dict['stop_name'], |  | ||||||
|                 desc=stop_dict.get('stop_desc', ""), |  | ||||||
|                 lat=stop_dict['stop_lat'], |  | ||||||
|                 lon=stop_dict['stop_lon'], |  | ||||||
|                 zone_id=stop_dict.get('zone_id', ""), |  | ||||||
|                 url=stop_dict.get('stop_url', ""), |  | ||||||
|                 location_type=stop_dict.get('location_type', 0) or 0, |  | ||||||
|                 parent_station_id=parent_station_id, |  | ||||||
|                 timezone=stop_dict.get('stop_timezone', ""), |  | ||||||
|                 wheelchair_boarding=stop_dict.get('wheelchair_boarding', 0), |  | ||||||
|                 level_id=stop_dict.get('level_id', ""), |  | ||||||
|                 platform_code=stop_dict.get('platform_code', ""), |  | ||||||
|                 gtfs_feed_id=gtfs_code, |  | ||||||
|             ) |  | ||||||
|             stops.append(stop) |  | ||||||
|  |  | ||||||
|         if stops and not dry_run: |  | ||||||
|             Stop.objects.bulk_create(stops, |  | ||||||
|                                      batch_size=bulk_size, |  | ||||||
|                                      update_conflicts=True, |  | ||||||
|                                      update_fields=['name', 'desc', 'lat', 'lon', 'zone_id', 'url', |  | ||||||
|                                                     'location_type', 'parent_station_id', 'timezone', |  | ||||||
|                                                     'wheelchair_boarding', 'level_id', 'platform_code', |  | ||||||
|                                                     'gtfs_feed'], |  | ||||||
|                                      unique_fields=['id']) |  | ||||||
|             stops.clear() |  | ||||||
|  |  | ||||||
|         routes = [] |  | ||||||
|         for route_dict in read_csv("routes.txt"): |  | ||||||
|             route_dict: dict |  | ||||||
|             route_id = route_dict['route_id'] |  | ||||||
|             route_id = f"{gtfs_code}-{route_id}" |  | ||||||
|             # Agency is optional there is only one |  | ||||||
|             agency_id = route_dict.get('agency_id', "") or Agency.objects.get(gtfs_feed_id=gtfs_code) |  | ||||||
|             route = Route( |  | ||||||
|                 id=route_id, |  | ||||||
|                 agency_id=f"{gtfs_code}-{agency_id}", |  | ||||||
|                 short_name=route_dict['route_short_name'], |  | ||||||
|                 long_name=route_dict['route_long_name'], |  | ||||||
|                 desc=route_dict.get('route_desc', ""), |  | ||||||
|                 type=route_dict['route_type'], |  | ||||||
|                 url=route_dict.get('route_url', ""), |  | ||||||
|                 color=route_dict.get('route_color', ""), |  | ||||||
|                 text_color=route_dict.get('route_text_color', ""), |  | ||||||
|                 gtfs_feed_id=gtfs_code, |  | ||||||
|             ) |  | ||||||
|             routes.append(route) |  | ||||||
|  |  | ||||||
|             if len(routes) >= bulk_size and not dry_run: |  | ||||||
|                 Route.objects.bulk_create(routes, |  | ||||||
|                                           update_conflicts=True, |  | ||||||
|                                           update_fields=['agency_id', 'short_name', 'long_name', 'desc', |  | ||||||
|                                                          'type', 'url', 'color', 'text_color', |  | ||||||
|                                                          'gtfs_feed'], |  | ||||||
|                                           unique_fields=['id']) |  | ||||||
|                 routes.clear() |  | ||||||
|         if routes and not dry_run: |  | ||||||
|             Route.objects.bulk_create(routes, |  | ||||||
|                                       update_conflicts=True, |  | ||||||
|                                       update_fields=['agency_id', 'short_name', 'long_name', 'desc', |  | ||||||
|                                                      'type', 'url', 'color', 'text_color', |  | ||||||
|                                                      'gtfs_feed'], |  | ||||||
|                                       unique_fields=['id']) |  | ||||||
|             routes.clear() |  | ||||||
|  |  | ||||||
|         start_time = 0 |  | ||||||
|         if verbosity >= 1: |  | ||||||
|             self.stdout.write("Deleting old calendars, trips and stop times…") |  | ||||||
|             start_time = time() |  | ||||||
|  |  | ||||||
|         TripUpdate.objects.filter(trip__gtfs_feed_id=gtfs_code).delete() |  | ||||||
|         StopTime.objects.filter(trip__gtfs_feed_id=gtfs_code)._raw_delete(StopTime.objects.db) |  | ||||||
|         Trip.objects.filter(gtfs_feed_id=gtfs_code)._raw_delete(Trip.objects.db) |  | ||||||
|         Calendar.objects.filter(gtfs_feed_id=gtfs_code).delete() |  | ||||||
|  |  | ||||||
|         if verbosity >= 1: |  | ||||||
|             end = time() |  | ||||||
|             self.stdout.write(f"Done in {end - start_time:.2f} s") |  | ||||||
|  |  | ||||||
|         calendars = {} |  | ||||||
|         if os.path.exists(os.path.join(zip_dir, "calendar.txt")): |  | ||||||
|             for calendar_dict in read_csv("calendar.txt"): |  | ||||||
|                 calendar_dict: dict |  | ||||||
|                 calendar = Calendar( |  | ||||||
|                     id=f"{gtfs_code}-{calendar_dict['service_id']}", |  | ||||||
|                     monday=calendar_dict['monday'], |  | ||||||
|                     tuesday=calendar_dict['tuesday'], |  | ||||||
|                     wednesday=calendar_dict['wednesday'], |  | ||||||
|                     thursday=calendar_dict['thursday'], |  | ||||||
|                     friday=calendar_dict['friday'], |  | ||||||
|                     saturday=calendar_dict['saturday'], |  | ||||||
|                     sunday=calendar_dict['sunday'], |  | ||||||
|                     start_date=calendar_dict['start_date'], |  | ||||||
|                     end_date=calendar_dict['end_date'], |  | ||||||
|                     gtfs_feed_id=gtfs_code, |  | ||||||
|                 ) |  | ||||||
|                 calendars[calendar.id] = calendar |  | ||||||
|  |  | ||||||
|                 if len(calendars) >= bulk_size and not dry_run: |  | ||||||
|                     Calendar.objects.bulk_create(calendars.values(), batch_size=bulk_size) |  | ||||||
|                     calendars.clear() |  | ||||||
|  |  | ||||||
|             if calendars and not dry_run: |  | ||||||
|                 Calendar.objects.bulk_create(calendars.values(), batch_size=bulk_size) |  | ||||||
|                 calendars.clear() |  | ||||||
|  |  | ||||||
|         calendar_dates = [] |  | ||||||
|         all_calendars = {calendar.id: calendar for calendar in Calendar.objects.filter(gtfs_feed_id=gtfs_code)} |  | ||||||
|         new_calendars = {} |  | ||||||
|         with transaction.atomic(): |  | ||||||
|             for calendar_date_dict in read_csv("calendar_dates.txt"): |  | ||||||
|                 calendar_date_dict: dict |  | ||||||
|                 service_id = f"{gtfs_code}-{calendar_date_dict['service_id']}" |  | ||||||
|                 date = datetime.fromisoformat(calendar_date_dict['date']).date() |  | ||||||
|  |  | ||||||
|                 calendar_date = CalendarDate( |  | ||||||
|                     id=f"{gtfs_code}-{calendar_date_dict['service_id']}-{calendar_date_dict['date']}", |  | ||||||
|                     service_id=service_id, |  | ||||||
|                     date=calendar_date_dict['date'], |  | ||||||
|                     exception_type=calendar_date_dict['exception_type'], |  | ||||||
|                 ) |  | ||||||
|                 calendar_dates.append(calendar_date) |  | ||||||
|  |  | ||||||
|                 if service_id not in all_calendars: |  | ||||||
|                     calendar = Calendar( |  | ||||||
|                         id=service_id, |  | ||||||
|                         monday=False, |  | ||||||
|                         tuesday=False, |  | ||||||
|                         wednesday=False, |  | ||||||
|                         thursday=False, |  | ||||||
|                         friday=False, |  | ||||||
|                         saturday=False, |  | ||||||
|                         sunday=False, |  | ||||||
|                         start_date=date, |  | ||||||
|                         end_date=date, |  | ||||||
|                         gtfs_feed_id=gtfs_code, |  | ||||||
|                     ) |  | ||||||
|                     all_calendars[service_id] = calendar |  | ||||||
|                     new_calendars[service_id] = calendar |  | ||||||
|                 else: |  | ||||||
|                     calendar = all_calendars[service_id] |  | ||||||
|                     if calendar.start_date > date: |  | ||||||
|                         calendar.start_date = date |  | ||||||
|                     if calendar.end_date < date: |  | ||||||
|                         calendar.end_date = date |  | ||||||
|  |  | ||||||
|                 if len(calendar_dates) >= bulk_size and not dry_run: |  | ||||||
|                     CalendarDate.objects.bulk_create(calendar_dates, batch_size=bulk_size) |  | ||||||
|                     calendar_dates.clear() |  | ||||||
|  |  | ||||||
|             if (calendar_dates or new_calendars) and not dry_run: |  | ||||||
|                 Calendar.objects.bulk_create(new_calendars.values(), batch_size=bulk_size) |  | ||||||
|                 CalendarDate.objects.bulk_create(calendar_dates, batch_size=bulk_size) |  | ||||||
|                 new_calendars.clear() |  | ||||||
|                 calendar_dates.clear() |  | ||||||
|  |  | ||||||
|         trips = [] |  | ||||||
|         # start_time = time() |  | ||||||
|         for trip_dict in read_csv("trips.txt"): |  | ||||||
|             trip_dict: dict |  | ||||||
|             trip_id = trip_dict['trip_id'] |  | ||||||
|             route_id = trip_dict['route_id'] |  | ||||||
|             trip_id = f"{gtfs_code}-{trip_id}" |  | ||||||
|             route_id = f"{gtfs_code}-{route_id}" |  | ||||||
|             trip = Trip( |  | ||||||
|                 id=trip_id, |  | ||||||
|                 route_id=route_id, |  | ||||||
|                 service_id=f"{gtfs_code}-{trip_dict['service_id']}", |  | ||||||
|                 headsign=trip_dict.get('trip_headsign', ""), |  | ||||||
|                 short_name=trip_dict.get('trip_short_name', ""), |  | ||||||
|                 direction_id=trip_dict.get('direction_id', None) or None, |  | ||||||
|                 block_id=trip_dict.get('block_id', ""), |  | ||||||
|                 shape_id=trip_dict.get('shape_id', ""), |  | ||||||
|                 wheelchair_accessible=trip_dict.get('wheelchair_accessible', None), |  | ||||||
|                 bikes_allowed=trip_dict.get('bikes_allowed', None), |  | ||||||
|                 gtfs_feed_id=gtfs_code, |  | ||||||
|             ) |  | ||||||
|             trips.append(trip) |  | ||||||
|  |  | ||||||
|             if len(trips) >= bulk_size and not dry_run: |  | ||||||
|                 # now = time() |  | ||||||
|                 # print(f"Elapsed time: {now - start_time:.3f}s, " |  | ||||||
|                 #       f"{1000 * (now - start_time) / len(trips):.2f}ms per iteration") |  | ||||||
|                 # start_time = now |  | ||||||
|                 Trip.objects.bulk_create(trips) |  | ||||||
|                 # now = time() |  | ||||||
|                 # print(f"Elapsed time: {now - start_time:.3f}s to save") |  | ||||||
|                 # start_time = now |  | ||||||
|                 trips.clear() |  | ||||||
|         if trips and not dry_run: |  | ||||||
|             Trip.objects.bulk_create(trips) |  | ||||||
|             trips.clear() |  | ||||||
|  |  | ||||||
|         stop_times = [] |  | ||||||
|         # start_time = time() |  | ||||||
|         for stop_time_dict in read_csv("stop_times.txt"): |  | ||||||
|             stop_time_dict: dict |  | ||||||
|  |  | ||||||
|             stop_id = stop_time_dict['stop_id'] |  | ||||||
|             stop_id = f"{gtfs_code}-{stop_id}" |  | ||||||
|  |  | ||||||
|             trip_id = stop_time_dict['trip_id'] |  | ||||||
|             trip_id = f"{gtfs_code}-{trip_id}" |  | ||||||
|  |  | ||||||
|             arr_time = stop_time_dict['arrival_time'] |  | ||||||
|             arr_h, arr_m, arr_s = map(int, arr_time.split(':')) |  | ||||||
|             arr_time = arr_h * 3600 + arr_m * 60 + arr_s |  | ||||||
|             dep_time = stop_time_dict['departure_time'] |  | ||||||
|             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', 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_sequence']}", |  | ||||||
|                 trip_id=trip_id, |  | ||||||
|                 arrival_time=timedelta(seconds=arr_time), |  | ||||||
|                 departure_time=timedelta(seconds=dep_time), |  | ||||||
|                 stop_id=stop_id, |  | ||||||
|                 stop_sequence=stop_time_dict['stop_sequence'], |  | ||||||
|                 stop_headsign=stop_time_dict.get('stop_headsign', ""), |  | ||||||
|                 pickup_type=pickup_type, |  | ||||||
|                 drop_off_type=drop_off_type, |  | ||||||
|                 timepoint=stop_time_dict.get('timepoint', None), |  | ||||||
|             ) |  | ||||||
|             stop_times.append(st) |  | ||||||
|  |  | ||||||
|             if len(stop_times) >= bulk_size and not dry_run: |  | ||||||
|                 # now = time() |  | ||||||
|                 # print(f"Elapsed time: {now - start_time:.3f}s, " |  | ||||||
|                 #       f"{1000 * (now - start_time) / len(stop_times):.2f}ms per iteration") |  | ||||||
|                 # start_time = now |  | ||||||
|                 StopTime.objects.bulk_create(stop_times) |  | ||||||
|                 # now = time() |  | ||||||
|                 # print(f"Elapsed time: {now - start_time:.3f}s to save") |  | ||||||
|                 # start_time = now |  | ||||||
|                 stop_times.clear() |  | ||||||
|  |  | ||||||
|         if stop_times and not dry_run: |  | ||||||
|             StopTime.objects.bulk_create(stop_times) |  | ||||||
|             stop_times.clear() |  | ||||||
|  |  | ||||||
|         if os.path.exists(os.path.join(zip_dir, "transfers.txt")): |  | ||||||
|             Transfer.objects.filter(Q(from_stop__gtfs_feed_id=gtfs_code) | Q(to_stop__gtfs_feed_id=gtfs_code)).delete() |  | ||||||
|             transfers = [] |  | ||||||
|             for transfer_dict in read_csv("transfers.txt"): |  | ||||||
|                 transfer_dict: dict |  | ||||||
|                 from_stop_id = transfer_dict['from_stop_id'] |  | ||||||
|                 to_stop_id = transfer_dict['to_stop_id'] |  | ||||||
|                 from_stop_id = f"{gtfs_code}-{from_stop_id}" |  | ||||||
|                 to_stop_id = f"{gtfs_code}-{to_stop_id}" |  | ||||||
|                 from_route_id = transfer_dict.get('from_route_id', None) |  | ||||||
|                 from_route_id = f"{gtfs_code}-{from_route_id}" if from_route_id else None |  | ||||||
|                 to_route_id = transfer_dict.get('to_route_id', None) |  | ||||||
|                 to_route_id = f"{gtfs_code}-{to_route_id}" if to_route_id else None |  | ||||||
|                 from_trip_id = transfer_dict.get('from_trip_id', None) |  | ||||||
|                 from_trip_id = f"{gtfs_code}-{from_trip_id}" if from_trip_id else None |  | ||||||
|                 to_trip_id = transfer_dict.get('to_trip_id', None) |  | ||||||
|                 to_trip_id = f"{gtfs_code}-{to_trip_id}" if to_trip_id else None |  | ||||||
|  |  | ||||||
|                 transfer_id = f"{gtfs_code}-{transfer_dict['from_stop_id']}-{transfer_dict['to_stop_id']}" |  | ||||||
|                 if from_route_id and to_route_id: |  | ||||||
|                     transfer_id += f"-{from_route_id}-{to_route_id}" |  | ||||||
|                 if from_trip_id and to_trip_id: |  | ||||||
|                     transfer_id += f"-{from_trip_id}-{to_trip_id}" |  | ||||||
|                 transfer_id += f"-{transfer_dict['transfer_type']}" |  | ||||||
|  |  | ||||||
|                 transfer = Transfer( |  | ||||||
|                     id=transfer_id, |  | ||||||
|                     from_stop_id=from_stop_id, |  | ||||||
|                     to_stop_id=to_stop_id, |  | ||||||
|                     from_route_id=from_route_id, |  | ||||||
|                     to_route_id=to_route_id, |  | ||||||
|                     from_trip_id=from_trip_id, |  | ||||||
|                     to_trip_id=to_trip_id, |  | ||||||
|                     transfer_type=transfer_dict['transfer_type'], |  | ||||||
|                     min_transfer_time=transfer_dict.get('min_transfer_time', 0) or 0, |  | ||||||
|                 ) |  | ||||||
|                 transfers.append(transfer) |  | ||||||
|  |  | ||||||
|                 if len(transfers) >= bulk_size and not dry_run: |  | ||||||
|                     Transfer.objects.bulk_create(transfers) |  | ||||||
|                     transfers.clear() |  | ||||||
|  |  | ||||||
|             if transfers and not dry_run: |  | ||||||
|                 Transfer.objects.bulk_create(transfers) |  | ||||||
|                 transfers.clear() |  | ||||||
|  |  | ||||||
|         if os.path.exists(os.path.join(zip_dir, "feed_info.txt")) and not dry_run: |  | ||||||
|             for feed_info_dict in read_csv("feed_info.txt"): |  | ||||||
|                 feed_info_dict: dict |  | ||||||
|                 FeedInfo.objects.update_or_create( |  | ||||||
|                     publisher_name=feed_info_dict['feed_publisher_name'], |  | ||||||
|                     gtfs_feed_id=gtfs_code, |  | ||||||
|                     defaults=dict( |  | ||||||
|                         publisher_url=feed_info_dict['feed_publisher_url'], |  | ||||||
|                         lang=feed_info_dict['feed_lang'], |  | ||||||
|                         start_date=feed_info_dict.get('feed_start_date', datetime.now().date()), |  | ||||||
|                         end_date=feed_info_dict.get('feed_end_date', datetime.now().date()), |  | ||||||
|                         version=feed_info_dict.get('feed_version', 1), |  | ||||||
|                     ) |  | ||||||
|                 ) |  | ||||||
| @@ -1,204 +0,0 @@ | |||||||
| from datetime import timedelta, datetime, date, time |  | ||||||
| from zoneinfo import ZoneInfo |  | ||||||
|  |  | ||||||
| import requests |  | ||||||
| from django.core.management import BaseCommand |  | ||||||
|  |  | ||||||
| from trainvel import settings |  | ||||||
| from trainvel.gtfs.gtfs_realtime_pb2 import FeedMessage, TripUpdate as GTFSTripUpdate |  | ||||||
| from trainvel.gtfs.models import Calendar, CalendarDate, ExceptionType, GTFSFeed, PickupType, \ |  | ||||||
|     Route, RouteType, StopScheduleRelationship, StopTime, StopTimeUpdate, \ |  | ||||||
|     Trip, TripUpdate, TripScheduleRelationship |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): |  | ||||||
|     help = "Update the Trainvel GTFS Realtime database." |  | ||||||
|  |  | ||||||
|     def add_arguments(self, parser): |  | ||||||
|         parser.add_argument('--debug', '-d', action='store_true', help="Activate debug mode") |  | ||||||
|  |  | ||||||
|     def handle(self, debug: bool = False, verbosity: int = 1, *args, **options): |  | ||||||
|         for gtfs_feed in GTFSFeed.objects.all(): |  | ||||||
|             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 |  | ||||||
|             headers = {} |  | ||||||
|             if gtfs_code == "CH-ALL": |  | ||||||
|                 headers["Authorization"] = settings.OPENTRANSPORTDATA_SWISS_TOKEN |  | ||||||
|             resp = requests.get(gtfs_feed.rt_feed_url, allow_redirects=True, headers=headers) |  | ||||||
|             feed_message = FeedMessage() |  | ||||||
|             feed_message.ParseFromString(resp.content) |  | ||||||
|  |  | ||||||
|             stop_times_updates = [] |  | ||||||
|  |  | ||||||
|             if debug: |  | ||||||
|                 with open(f'feed_message-{gtfs_code}.txt', 'w') as f: |  | ||||||
|                     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 |  | ||||||
|                         trip_id = f"{gtfs_code}-{trip_id}" |  | ||||||
|  |  | ||||||
|                         start_date = date(year=int(trip_update.trip.start_date[:4]), |  | ||||||
|                                           month=int(trip_update.trip.start_date[4:6]), |  | ||||||
|                                           day=int(trip_update.trip.start_date[6:])) |  | ||||||
|                         start_dt = datetime.combine(start_date, time(0), tzinfo=ZoneInfo("Europe/Paris")) |  | ||||||
|  |  | ||||||
|                         if trip_update.trip.schedule_relationship == TripScheduleRelationship.ADDED: |  | ||||||
|                             # C'est un trajet nouveau. On crée le trajet associé. |  | ||||||
|                             self.create_trip(trip_update, trip_id, start_dt, gtfs_feed) |  | ||||||
|  |  | ||||||
|                         if not Trip.objects.filter(id=trip_id).exists(): |  | ||||||
|                             self.stdout.write(f"Trip {trip_id} does not exist in the GTFS feed.") |  | ||||||
|                             continue |  | ||||||
|  |  | ||||||
|                         # Création du TripUpdate |  | ||||||
|                         tu, _created = TripUpdate.objects.update_or_create( |  | ||||||
|                             trip_id=trip_id, |  | ||||||
|                             start_date=trip_update.trip.start_date, |  | ||||||
|                             start_time=trip_update.trip.start_time, |  | ||||||
|                             defaults=dict( |  | ||||||
|                                 schedule_relationship=trip_update.trip.schedule_relationship, |  | ||||||
|                             ) |  | ||||||
|                         ) |  | ||||||
|  |  | ||||||
|                         for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update): |  | ||||||
|                             stop_id = stop_time_update.stop_id |  | ||||||
|                             stop_id = f"{gtfs_code}-{stop_id}" |  | ||||||
|                             if StopTime.objects.filter(trip_id=trip_id, stop=stop_id).exists(): |  | ||||||
|                                 st = StopTime.objects.filter(trip_id=trip_id, stop=stop_id) |  | ||||||
|                                 if st.count() > 1: |  | ||||||
|                                     st = st.get(stop_sequence=stop_sequence) |  | ||||||
|                                 else: |  | ||||||
|                                     st = st.first() |  | ||||||
|                             else: |  | ||||||
|                                 # Stop is added |  | ||||||
|                                 st = StopTime.objects.create( |  | ||||||
|                                     id=f"{trip_id}-{stop_time_update.stop_id}", |  | ||||||
|                                     trip_id=trip_id, |  | ||||||
|                                     stop_id=stop_id, |  | ||||||
|                                     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, |  | ||||||
|                                                                           tz=ZoneInfo("Europe/Paris")) - start_dt, |  | ||||||
|                                     pickup_type=(PickupType.REGULAR if stop_time_update.departure.time |  | ||||||
|                                                  else PickupType.NONE), |  | ||||||
|                                     drop_off_type=(PickupType.REGULAR if stop_time_update.arrival.time |  | ||||||
|                                                    else PickupType.NONE), |  | ||||||
|                                 ) |  | ||||||
|  |  | ||||||
|                             if stop_time_update.schedule_relationship == StopScheduleRelationship.SKIPPED: |  | ||||||
|                                 if st.pickup_type != PickupType.NONE or st.drop_off_type != PickupType.NONE: |  | ||||||
|                                     st.pickup_type = PickupType.NONE |  | ||||||
|                                     st.drop_off_type = PickupType.NONE |  | ||||||
|                                     st.save() |  | ||||||
|  |  | ||||||
|                             if st.stop_sequence != stop_sequence: |  | ||||||
|                                 st.stop_sequence = stop_sequence |  | ||||||
|                                 st.save() |  | ||||||
|  |  | ||||||
|                             st_update = StopTimeUpdate( |  | ||||||
|                                 trip_update=tu, |  | ||||||
|                                 stop_time=st, |  | ||||||
|                                 arrival_delay=timedelta(seconds=stop_time_update.arrival.delay), |  | ||||||
|                                 arrival_time=datetime.fromtimestamp(stop_time_update.arrival.time, |  | ||||||
|                                                                     tz=ZoneInfo("Europe/Paris")), |  | ||||||
|                                 departure_delay=timedelta(seconds=stop_time_update.departure.delay), |  | ||||||
|                                 departure_time=datetime.fromtimestamp(stop_time_update.departure.time, |  | ||||||
|                                                                       tz=ZoneInfo("Europe/Paris")), |  | ||||||
|                                 schedule_relationship=stop_time_update.schedule_relationship |  | ||||||
|                                                       or StopScheduleRelationship.SCHEDULED, |  | ||||||
|                             ) |  | ||||||
|                             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, |  | ||||||
|                                                update_fields=['arrival_delay', 'arrival_time', |  | ||||||
|                                                               'departure_delay', 'departure_time'], |  | ||||||
|                                                unique_fields=['trip_update', 'stop_time']) |  | ||||||
|  |  | ||||||
|     def create_trip(self, trip_update: GTFSTripUpdate, trip_id: str, start_dt: datetime, gtfs_feed: GTFSFeed) -> None: |  | ||||||
|         headsign = trip_id[5:-1] |  | ||||||
|         gtfs_code = gtfs_feed.code |  | ||||||
|  |  | ||||||
|         route, _created = Route.objects.get_or_create( |  | ||||||
|             id=f"{gtfs_code}-ADDED-{headsign}", |  | ||||||
|             gtfs_feed=gtfs_feed, |  | ||||||
|             type=RouteType.RAIL, |  | ||||||
|             short_name="ADDED", |  | ||||||
|             long_name="ADDED ROUTE", |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         Calendar.objects.update_or_create( |  | ||||||
|             id=f"{gtfs_code}-ADDED-{headsign}", |  | ||||||
|             defaults={ |  | ||||||
|                 "gtfs_feed": gtfs_feed, |  | ||||||
|                 "monday": False, |  | ||||||
|                 "tuesday": False, |  | ||||||
|                 "wednesday": False, |  | ||||||
|                 "thursday": False, |  | ||||||
|                 "friday": False, |  | ||||||
|                 "saturday": False, |  | ||||||
|                 "sunday": False, |  | ||||||
|                 "start_date": start_dt.date(), |  | ||||||
|                 "end_date": start_dt.date(), |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|         CalendarDate.objects.update_or_create( |  | ||||||
|             id=f"{gtfs_code}-ADDED-{headsign}-{trip_update.trip.start_date}", |  | ||||||
|             defaults={ |  | ||||||
|                 "service_id": f"{gtfs_code}-ADDED-{headsign}", |  | ||||||
|                 "date": trip_update.trip.start_date, |  | ||||||
|                 "exception_type": ExceptionType.ADDED, |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|         Trip.objects.update_or_create( |  | ||||||
|             id=trip_id, |  | ||||||
|             defaults={ |  | ||||||
|                 "route_id": route.id, |  | ||||||
|                 "service_id": f"{gtfs_code}-ADDED-{headsign}", |  | ||||||
|                 "headsign": headsign, |  | ||||||
|                 "direction_id": trip_update.trip.direction_id, |  | ||||||
|                 "gtfs_feed": gtfs_feed, |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update): |  | ||||||
|             stop_id = stop_time_update.stop_id |  | ||||||
|             stop_id = f"{gtfs_code}-{stop_id}" |  | ||||||
|  |  | ||||||
|             arr_time = datetime.fromtimestamp(stop_time_update.arrival.time, |  | ||||||
|                                               tz=ZoneInfo("Europe/Paris")) - start_dt |  | ||||||
|             dep_time = datetime.fromtimestamp(stop_time_update.departure.time, |  | ||||||
|                                               tz=ZoneInfo("Europe/Paris")) - start_dt |  | ||||||
|  |  | ||||||
|             pickup_type = PickupType.REGULAR if stop_time_update.departure.time and stop_sequence > 0 \ |  | ||||||
|                 else PickupType.NONE |  | ||||||
|             drop_off_type = PickupType.REGULAR if stop_time_update.arrival.time \ |  | ||||||
|                 and stop_sequence < len(trip_update.stop_time_update) - 1 else PickupType.NONE |  | ||||||
|  |  | ||||||
|             StopTime.objects.update_or_create( |  | ||||||
|                 id=f"{gtfs_code}-{trip_id}-{stop_time_update.stop_sequence}", |  | ||||||
|                 trip_id=trip_id, |  | ||||||
|                 defaults={ |  | ||||||
|                     "stop_id": stop_id, |  | ||||||
|                     "stop_sequence": stop_sequence, |  | ||||||
|                     "arrival_time": arr_time, |  | ||||||
|                     "departure_time": dep_time, |  | ||||||
|                     "pickup_type": pickup_type, |  | ||||||
|                     "drop_off_type": drop_off_type, |  | ||||||
|                 } |  | ||||||
|             ) |  | ||||||
| @@ -1,911 +0,0 @@ | |||||||
| # Generated by Django 5.0.1 on 2024-05-09 17:34 |  | ||||||
|  |  | ||||||
| import django.db.models.deletion |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     initial = True |  | ||||||
|  |  | ||||||
|     dependencies = [] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="StopTime", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         max_length=255, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="ID", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("arrival_time", models.DurationField(verbose_name="Arrival time")), |  | ||||||
|                 ("departure_time", models.DurationField(verbose_name="Departure time")), |  | ||||||
|                 ("stop_sequence", models.IntegerField(verbose_name="Stop sequence")), |  | ||||||
|                 ( |  | ||||||
|                     "stop_headsign", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Stop headsign" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "pickup_type", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "Regular"), |  | ||||||
|                             (1, "None"), |  | ||||||
|                             (2, "Must phone agency"), |  | ||||||
|                             (3, "Must coordinate with driver"), |  | ||||||
|                         ], |  | ||||||
|                         default=0, |  | ||||||
|                         null=True, |  | ||||||
|                         verbose_name="Pickup type", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "drop_off_type", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "Regular"), |  | ||||||
|                             (1, "None"), |  | ||||||
|                             (2, "Must phone agency"), |  | ||||||
|                             (3, "Must coordinate with driver"), |  | ||||||
|                         ], |  | ||||||
|                         default=0, |  | ||||||
|                         null=True, |  | ||||||
|                         verbose_name="Drop off type", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "timepoint", |  | ||||||
|                     models.BooleanField( |  | ||||||
|                         default=True, null=True, verbose_name="Timepoint" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Stop time", |  | ||||||
|                 "verbose_name_plural": "Stop times", |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="Trip", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         max_length=255, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="Trip ID", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "headsign", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Trip headsign" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "short_name", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Trip short name" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "direction_id", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[(0, "Outbound"), (1, "Inbound")], |  | ||||||
|                         null=True, |  | ||||||
|                         verbose_name="Direction", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "block_id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Block ID" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "shape_id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Shape ID" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "wheelchair_accessible", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "No information"), |  | ||||||
|                             (1, "Possible"), |  | ||||||
|                             (2, "Not possible"), |  | ||||||
|                         ], |  | ||||||
|                         default=0, |  | ||||||
|                         null=True, |  | ||||||
|                         verbose_name="Wheelchair accessible", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "bikes_allowed", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "No information"), |  | ||||||
|                             (1, "Possible"), |  | ||||||
|                             (2, "Not possible"), |  | ||||||
|                         ], |  | ||||||
|                         default=0, |  | ||||||
|                         null=True, |  | ||||||
|                         verbose_name="Bikes allowed", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Trip", |  | ||||||
|                 "verbose_name_plural": "Trips", |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="GTFSFeed", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "code", |  | ||||||
|                     models.CharField( |  | ||||||
|                         help_text="Unique code of the feed.", |  | ||||||
|                         max_length=64, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="code", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "name", |  | ||||||
|                     models.CharField( |  | ||||||
|                         help_text="Full name that describes the feed.", |  | ||||||
|                         max_length=255, |  | ||||||
|                         unique=True, |  | ||||||
|                         verbose_name="name", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "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=2, |  | ||||||
|                         verbose_name="country", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "feed_url", |  | ||||||
|                     models.URLField( |  | ||||||
|                         help_text="URL to download the GTFS feed. Must point to a ZIP archive. See https://gtfs.org/schedule/ for more information.", |  | ||||||
|                         verbose_name="feed URL", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "rt_feed_url", |  | ||||||
|                     models.URLField( |  | ||||||
|                         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.", |  | ||||||
|                         verbose_name="realtime feed URL", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "last_modified", |  | ||||||
|                     models.DateTimeField( |  | ||||||
|                         default=None, null=True, verbose_name="last modified date" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "etag", |  | ||||||
|                     models.CharField( |  | ||||||
|                         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.", |  | ||||||
|                         max_length=255, |  | ||||||
|                         verbose_name="ETag", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "GTFS feed", |  | ||||||
|                 "verbose_name_plural": "GTFS feeds", |  | ||||||
|                 "ordering": ("country", "name"), |  | ||||||
|                 "indexes": [ |  | ||||||
|                     models.Index(fields=["name"], name="gtfs_gtfsfe_name_aabd02_idx") |  | ||||||
|                 ], |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="Calendar", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         max_length=255, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="Service ID", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("monday", models.BooleanField(verbose_name="Monday")), |  | ||||||
|                 ("tuesday", models.BooleanField(verbose_name="Tuesday")), |  | ||||||
|                 ("wednesday", models.BooleanField(verbose_name="Wednesday")), |  | ||||||
|                 ("thursday", models.BooleanField(verbose_name="Thursday")), |  | ||||||
|                 ("friday", models.BooleanField(verbose_name="Friday")), |  | ||||||
|                 ("saturday", models.BooleanField(verbose_name="Saturday")), |  | ||||||
|                 ("sunday", models.BooleanField(verbose_name="Sunday")), |  | ||||||
|                 ("start_date", models.DateField(verbose_name="Start date")), |  | ||||||
|                 ("end_date", models.DateField(verbose_name="End date")), |  | ||||||
|                 ( |  | ||||||
|                     "gtfs_feed", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         to="gtfs.gtfsfeed", |  | ||||||
|                         verbose_name="GTFS feed", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Calendar", |  | ||||||
|                 "verbose_name_plural": "Calendars", |  | ||||||
|                 "ordering": ("id",), |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="Agency", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         max_length=255, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="Agency ID", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("name", models.CharField(max_length=255, verbose_name="Agency name")), |  | ||||||
|                 ("url", models.URLField(verbose_name="Agency URL")), |  | ||||||
|                 ( |  | ||||||
|                     "timezone", |  | ||||||
|                     models.CharField(max_length=255, verbose_name="Agency timezone"), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "lang", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Agency language" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "phone", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Agency phone" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "email", |  | ||||||
|                     models.EmailField( |  | ||||||
|                         blank=True, max_length=254, verbose_name="Agency email" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "gtfs_feed", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         to="gtfs.gtfsfeed", |  | ||||||
|                         verbose_name="GTFS feed", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Agency", |  | ||||||
|                 "verbose_name_plural": "Agencies", |  | ||||||
|                 "ordering": ("name",), |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="Route", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         max_length=255, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="ID", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "short_name", |  | ||||||
|                     models.CharField(max_length=255, verbose_name="Route short name"), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "long_name", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Route long name" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "desc", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Route description" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "type", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "Tram"), |  | ||||||
|                             (1, "Metro"), |  | ||||||
|                             (2, "Rail"), |  | ||||||
|                             (3, "Bus"), |  | ||||||
|                             (4, "Ferry"), |  | ||||||
|                             (5, "Cable car"), |  | ||||||
|                             (6, "Gondola"), |  | ||||||
|                             (7, "Funicular"), |  | ||||||
|                         ], |  | ||||||
|                         verbose_name="Route type", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("url", models.URLField(blank=True, verbose_name="Route URL")), |  | ||||||
|                 ( |  | ||||||
|                     "color", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Route color" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "text_color", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Route text color" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "agency", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         blank=True, |  | ||||||
|                         default=None, |  | ||||||
|                         null=True, |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         related_name="routes", |  | ||||||
|                         to="gtfs.agency", |  | ||||||
|                         verbose_name="Agency", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "gtfs_feed", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         to="gtfs.gtfsfeed", |  | ||||||
|                         verbose_name="GTFS feed", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Route", |  | ||||||
|                 "verbose_name_plural": "Routes", |  | ||||||
|                 "ordering": ("id",), |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="Stop", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         max_length=255, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="Stop ID", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "code", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Stop code" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("name", models.CharField(max_length=255, verbose_name="Stop name")), |  | ||||||
|                 ( |  | ||||||
|                     "desc", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Stop description" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("lon", models.FloatField(verbose_name="Stop longitude")), |  | ||||||
|                 ("lat", models.FloatField(verbose_name="Stop latitude")), |  | ||||||
|                 ( |  | ||||||
|                     "zone_id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Zone ID" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("url", models.URLField(blank=True, verbose_name="Stop URL")), |  | ||||||
|                 ( |  | ||||||
|                     "location_type", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         blank=True, |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "Stop/platform"), |  | ||||||
|                             (1, "Station"), |  | ||||||
|                             (2, "Entrance/exit"), |  | ||||||
|                             (3, "Generic node"), |  | ||||||
|                             (4, "Boarding area"), |  | ||||||
|                         ], |  | ||||||
|                         default=0, |  | ||||||
|                         verbose_name="Location type", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "timezone", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Stop timezone" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "level_id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Level ID" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "wheelchair_boarding", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         blank=True, |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "No information"), |  | ||||||
|                             (1, "Possible"), |  | ||||||
|                             (2, "Not possible"), |  | ||||||
|                         ], |  | ||||||
|                         default=0, |  | ||||||
|                         verbose_name="Wheelchair boarding", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "platform_code", |  | ||||||
|                     models.CharField( |  | ||||||
|                         blank=True, max_length=255, verbose_name="Platform code" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "gtfs_feed", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         to="gtfs.gtfsfeed", |  | ||||||
|                         verbose_name="GTFS feed", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "parent_station", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         blank=True, |  | ||||||
|                         null=True, |  | ||||||
|                         on_delete=django.db.models.deletion.PROTECT, |  | ||||||
|                         related_name="children", |  | ||||||
|                         to="gtfs.stop", |  | ||||||
|                         verbose_name="Parent station", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Stop", |  | ||||||
|                 "verbose_name_plural": "Stops", |  | ||||||
|                 "ordering": ("id",), |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="StopTimeUpdate", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "stop_time", |  | ||||||
|                     models.OneToOneField( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         related_name="update", |  | ||||||
|                         serialize=False, |  | ||||||
|                         to="gtfs.stoptime", |  | ||||||
|                         verbose_name="Stop time", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("arrival_delay", models.DurationField(verbose_name="Arrival delay")), |  | ||||||
|                 ("arrival_time", models.DateTimeField(verbose_name="Arrival time")), |  | ||||||
|                 ( |  | ||||||
|                     "departure_delay", |  | ||||||
|                     models.DurationField(verbose_name="Departure delay"), |  | ||||||
|                 ), |  | ||||||
|                 ("departure_time", models.DateTimeField(verbose_name="Departure time")), |  | ||||||
|                 ( |  | ||||||
|                     "schedule_relationship", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "Scheduled"), |  | ||||||
|                             (1, "Skipped"), |  | ||||||
|                             (2, "No data"), |  | ||||||
|                             (3, "Unscheduled"), |  | ||||||
|                         ], |  | ||||||
|                         default=0, |  | ||||||
|                         verbose_name="Schedule relationship", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Stop time update", |  | ||||||
|                 "verbose_name_plural": "Stop time updates", |  | ||||||
|                 "ordering": ("trip_update", "stop_time"), |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="stoptime", |  | ||||||
|             name="stop", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 related_name="stop_times", |  | ||||||
|                 to="gtfs.stop", |  | ||||||
|                 verbose_name="Stop ID", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="Transfer", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         max_length=255, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="ID", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "transfer_type", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "Recommended"), |  | ||||||
|                             (1, "Timed"), |  | ||||||
|                             (2, "Minimum time"), |  | ||||||
|                             (3, "Not possible"), |  | ||||||
|                         ], |  | ||||||
|                         default=0, |  | ||||||
|                         verbose_name="Transfer type", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "min_transfer_time", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         blank=True, verbose_name="Minimum transfer time" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "from_stop", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         related_name="transfers_from", |  | ||||||
|                         to="gtfs.stop", |  | ||||||
|                         verbose_name="From stop", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "to_stop", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         related_name="transfers_to", |  | ||||||
|                         to="gtfs.stop", |  | ||||||
|                         verbose_name="To stop", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Transfer", |  | ||||||
|                 "verbose_name_plural": "Transfers", |  | ||||||
|                 "ordering": ("id",), |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="TripUpdate", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "trip", |  | ||||||
|                     models.OneToOneField( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         related_name="update", |  | ||||||
|                         serialize=False, |  | ||||||
|                         to="gtfs.trip", |  | ||||||
|                         verbose_name="Trip", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("start_date", models.DateField(verbose_name="Start date")), |  | ||||||
|                 ("start_time", models.TimeField(verbose_name="Start time")), |  | ||||||
|                 ( |  | ||||||
|                     "schedule_relationship", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[ |  | ||||||
|                             (0, "Scheduled"), |  | ||||||
|                             (1, "Added"), |  | ||||||
|                             (2, "Unscheduled"), |  | ||||||
|                             (3, "Canceled"), |  | ||||||
|                             (5, "Replacement"), |  | ||||||
|                             (6, "Duplicated"), |  | ||||||
|                             (7, "Deleted"), |  | ||||||
|                         ], |  | ||||||
|                         default=0, |  | ||||||
|                         verbose_name="Schedule relationship", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Trip update", |  | ||||||
|                 "verbose_name_plural": "Trip updates", |  | ||||||
|                 "ordering": ("start_date", "trip"), |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="trip", |  | ||||||
|             name="gtfs_feed", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 to="gtfs.gtfsfeed", |  | ||||||
|                 verbose_name="GTFS feed", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="trip", |  | ||||||
|             name="route", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 related_name="trips", |  | ||||||
|                 to="gtfs.route", |  | ||||||
|                 verbose_name="Route", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="trip", |  | ||||||
|             name="service", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 related_name="trips", |  | ||||||
|                 to="gtfs.calendar", |  | ||||||
|                 verbose_name="Service", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="stoptime", |  | ||||||
|             name="trip", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 related_name="stop_times", |  | ||||||
|                 to="gtfs.trip", |  | ||||||
|                 verbose_name="Trip", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="CalendarDate", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "id", |  | ||||||
|                     models.CharField( |  | ||||||
|                         max_length=255, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="ID", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("date", models.DateField(verbose_name="Date")), |  | ||||||
|                 ( |  | ||||||
|                     "exception_type", |  | ||||||
|                     models.IntegerField( |  | ||||||
|                         choices=[(1, "Added"), (2, "Removed")], |  | ||||||
|                         verbose_name="Exception type", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "service", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         related_name="dates", |  | ||||||
|                         to="gtfs.calendar", |  | ||||||
|                         verbose_name="Service", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Calendar date", |  | ||||||
|                 "verbose_name_plural": "Calendar dates", |  | ||||||
|                 "ordering": ("id",), |  | ||||||
|                 "indexes": [ |  | ||||||
|                     models.Index( |  | ||||||
|                         fields=["service"], name="gtfs_calend_service_211472_idx" |  | ||||||
|                     ), |  | ||||||
|                     models.Index(fields=["date"], name="gtfs_calend_date_e90040_idx"), |  | ||||||
|                 ], |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name="FeedInfo", |  | ||||||
|             fields=[ |  | ||||||
|                 ( |  | ||||||
|                     "id", |  | ||||||
|                     models.BigAutoField( |  | ||||||
|                         auto_created=True, |  | ||||||
|                         primary_key=True, |  | ||||||
|                         serialize=False, |  | ||||||
|                         verbose_name="ID", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "publisher_name", |  | ||||||
|                     models.CharField( |  | ||||||
|                         max_length=255, verbose_name="Feed publisher name" |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|                 ("publisher_url", models.URLField(verbose_name="Feed publisher URL")), |  | ||||||
|                 ( |  | ||||||
|                     "lang", |  | ||||||
|                     models.CharField(max_length=255, verbose_name="Feed language"), |  | ||||||
|                 ), |  | ||||||
|                 ("start_date", models.DateField(verbose_name="Feed start date")), |  | ||||||
|                 ("end_date", models.DateField(verbose_name="Feed end date")), |  | ||||||
|                 ( |  | ||||||
|                     "version", |  | ||||||
|                     models.CharField(max_length=255, verbose_name="Feed version"), |  | ||||||
|                 ), |  | ||||||
|                 ( |  | ||||||
|                     "gtfs_feed", |  | ||||||
|                     models.ForeignKey( |  | ||||||
|                         on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                         to="gtfs.gtfsfeed", |  | ||||||
|                         verbose_name="GTFS feed", |  | ||||||
|                     ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 "verbose_name": "Feed info", |  | ||||||
|                 "verbose_name_plural": "Feed infos", |  | ||||||
|                 "ordering": ("publisher_name",), |  | ||||||
|                 "indexes": [ |  | ||||||
|                     models.Index( |  | ||||||
|                         fields=["gtfs_feed"], name="gtfs_feedin_gtfs_fe_73554b_idx" |  | ||||||
|                     ) |  | ||||||
|                 ], |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="calendar", |  | ||||||
|             index=models.Index( |  | ||||||
|                 fields=["gtfs_feed"], name="gtfs_calend_gtfs_fe_ff03d1_idx" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="agency", |  | ||||||
|             index=models.Index(fields=["name"], name="gtfs_agency_name_a6dd2b_idx"), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="agency", |  | ||||||
|             index=models.Index( |  | ||||||
|                 fields=["gtfs_feed"], name="gtfs_agency_gtfs_fe_86414c_idx" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="route", |  | ||||||
|             index=models.Index( |  | ||||||
|                 fields=["gtfs_feed"], name="gtfs_route_gtfs_fe_c6ac59_idx" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="stop", |  | ||||||
|             index=models.Index(fields=["name"], name="gtfs_stop_name_1c87d7_idx"), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="stop", |  | ||||||
|             index=models.Index(fields=["code"], name="gtfs_stop_code_5f4ebc_idx"), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="stop", |  | ||||||
|             index=models.Index( |  | ||||||
|                 fields=["gtfs_feed"], name="gtfs_stop_gtfs_fe_0e17d6_idx" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="tripupdate", |  | ||||||
|             index=models.Index(fields=["trip"], name="gtfs_tripup_trip_id_b3ee0e_idx"), |  | ||||||
|         ), |  | ||||||
|         migrations.AlterUniqueTogether( |  | ||||||
|             name="tripupdate", |  | ||||||
|             unique_together={("trip", "start_date", "start_time")}, |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="stoptimeupdate", |  | ||||||
|             name="trip_update", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 related_name="stop_time_updates", |  | ||||||
|                 to="gtfs.tripupdate", |  | ||||||
|                 verbose_name="Trip update", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="trip", |  | ||||||
|             index=models.Index(fields=["route"], name="gtfs_trip_route_i_6d85d9_idx"), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="trip", |  | ||||||
|             index=models.Index( |  | ||||||
|                 fields=["gtfs_feed"], name="gtfs_trip_gtfs_fe_e63eac_idx" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="stoptime", |  | ||||||
|             index=models.Index(fields=["stop"], name="gtfs_stopti_stop_id_64a4e3_idx"), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="stoptime", |  | ||||||
|             index=models.Index(fields=["trip"], name="gtfs_stopti_trip_id_bec7fe_idx"), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="stoptimeupdate", |  | ||||||
|             index=models.Index( |  | ||||||
|                 fields=["trip_update"], name="gtfs_stopti_trip_up_ffe901_idx" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddIndex( |  | ||||||
|             model_name="stoptimeupdate", |  | ||||||
|             index=models.Index( |  | ||||||
|                 fields=["stop_time"], name="gtfs_stopti_stop_ti_4f2c63_idx" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AlterUniqueTogether( |  | ||||||
|             name="stoptimeupdate", |  | ||||||
|             unique_together={("trip_update", "stop_time")}, |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| # Generated by Django 5.0.6 on 2024-05-12 09:31 |  | ||||||
|  |  | ||||||
| import django.db.models.deletion |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ("gtfs", "0001_initial"), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name="stop", |  | ||||||
|             name="parent_station", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 blank=True, |  | ||||||
|                 null=True, |  | ||||||
|                 on_delete=django.db.models.deletion.SET_NULL, |  | ||||||
|                 related_name="children", |  | ||||||
|                 to="gtfs.stop", |  | ||||||
|                 verbose_name="Parent station", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @@ -1,72 +0,0 @@ | |||||||
| # Generated by Django 5.0.6 on 2024-05-12 17:18 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ("gtfs", "0002_alter_stop_parent_station"), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="gtfsfeed", |  | ||||||
|             name="categorize_routes", |  | ||||||
|             field=models.BooleanField( |  | ||||||
|                 default=False, |  | ||||||
|                 help_text="If checked, trips can be categorized by route type.", |  | ||||||
|                 verbose_name="categorize routes", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="gtfsfeed", |  | ||||||
|             name="excluded_agencies", |  | ||||||
|             field=models.ManyToManyField( |  | ||||||
|                 blank=True, |  | ||||||
|                 help_text="Agencies that are part of another feed and shouldn't be displayed with this feed.", |  | ||||||
|                 to="gtfs.agency", |  | ||||||
|                 verbose_name="excluded agencies", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="gtfsfeed", |  | ||||||
|             name="long_distance_regex", |  | ||||||
|             field=models.TextField( |  | ||||||
|                 blank=True, |  | ||||||
|                 default="", |  | ||||||
|                 help_text="Regular expression that filters long distance trips.", |  | ||||||
|                 verbose_name="long distance regex", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="gtfsfeed", |  | ||||||
|             name="route_name_regex", |  | ||||||
|             field=models.TextField( |  | ||||||
|                 blank=True, |  | ||||||
|                 default="route_short_name:([^§]+)", |  | ||||||
|                 help_text="Regular expression that catches the route name from a trip.", |  | ||||||
|                 verbose_name="route name regex", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="gtfsfeed", |  | ||||||
|             name="route_type_regex", |  | ||||||
|             field=models.TextField( |  | ||||||
|                 blank=True, |  | ||||||
|                 default="gtfs_feed:([^§]+)&-&route_type:([^§]+)", |  | ||||||
|                 help_text="Regular expression that catches the route type from a trip.", |  | ||||||
|                 verbose_name="route name regex", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="gtfsfeed", |  | ||||||
|             name="trip_number_regex", |  | ||||||
|             field=models.TextField( |  | ||||||
|                 blank=True, |  | ||||||
|                 default="trip_short_name:([^§]+)", |  | ||||||
|                 help_text="Regular expression that catches the trip number from a trip.", |  | ||||||
|                 verbose_name="route name regex", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @@ -1,39 +0,0 @@ | |||||||
| # Generated by Django 5.0.6 on 2024-05-12 18:33 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ("gtfs", "0003_gtfsfeed_categorize_routes_and_more"), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="trip", |  | ||||||
|             name="long_distance", |  | ||||||
|             field=models.BooleanField(default=True, verbose_name="Long distance trip"), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="trip", |  | ||||||
|             name="route_name", |  | ||||||
|             field=models.CharField( |  | ||||||
|                 blank=True, default="", max_length=255, verbose_name="Route name" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="trip", |  | ||||||
|             name="route_type", |  | ||||||
|             field=models.CharField( |  | ||||||
|                 blank=True, default="", max_length=255, verbose_name="Route type" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="trip", |  | ||||||
|             name="trip_number", |  | ||||||
|             field=models.CharField( |  | ||||||
|                 blank=True, default="", max_length=255, verbose_name="Trip number" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @@ -1,73 +0,0 @@ | |||||||
| # Generated by Django 5.0.6 on 2024-08-12 18:33 |  | ||||||
|  |  | ||||||
| import django.db.models.deletion |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ("gtfs", "0004_trip_long_distance_trip_route_name_trip_route_type_and_more"), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="transfer", |  | ||||||
|             name="from_route", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 blank=True, |  | ||||||
|                 default=None, |  | ||||||
|                 null=True, |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 related_name="transfers_from", |  | ||||||
|                 to="gtfs.route", |  | ||||||
|                 verbose_name="From route", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="transfer", |  | ||||||
|             name="from_trip", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 blank=True, |  | ||||||
|                 default=None, |  | ||||||
|                 null=True, |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 related_name="transfers_from", |  | ||||||
|                 to="gtfs.trip", |  | ||||||
|                 verbose_name="From trip", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="transfer", |  | ||||||
|             name="to_route", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 blank=True, |  | ||||||
|                 default=None, |  | ||||||
|                 null=True, |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 related_name="transfers_to", |  | ||||||
|                 to="gtfs.route", |  | ||||||
|                 verbose_name="To route", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name="transfer", |  | ||||||
|             name="to_trip", |  | ||||||
|             field=models.ForeignKey( |  | ||||||
|                 blank=True, |  | ||||||
|                 default=None, |  | ||||||
|                 null=True, |  | ||||||
|                 on_delete=django.db.models.deletion.CASCADE, |  | ||||||
|                 related_name="transfers_to", |  | ||||||
|                 to="gtfs.trip", |  | ||||||
|                 verbose_name="To trip", |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name="transfer", |  | ||||||
|             name="min_transfer_time", |  | ||||||
|             field=models.IntegerField( |  | ||||||
|                 blank=True, default=None, verbose_name="Minimum transfer time" |  | ||||||
|             ), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| from trainvel.gtfs.models import GTFSFeed |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def keep_gtfs_feed_modification_date(instance: GTFSFeed, raw: bool = True, **_kwargs): |  | ||||||
|     """ |  | ||||||
|     Keep the last_modified and etag fields from the existing GTFSFeed instance when saving a new one. |  | ||||||
|     """ |  | ||||||
|     if raw and GTFSFeed.objects.filter(pk=instance.pk).exists(): |  | ||||||
|         # If a GTFSFeed instance is being saved from a raw query, |  | ||||||
|         # we want to keep the last_modified and etag fields from the existing instance. |  | ||||||
|         # This is useful when loading initial data from a fixture, for example. |  | ||||||
|         old_instance = GTFSFeed.objects.get(pk=instance.pk) |  | ||||||
|         instance.last_modified = old_instance.last_modified |  | ||||||
|         instance.etag = old_instance.etag |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| from django.test import TestCase |  | ||||||
|  |  | ||||||
| # Create your tests here. |  | ||||||
| @@ -1,163 +0,0 @@ | |||||||
| """ |  | ||||||
| Django settings for trainvel project. |  | ||||||
|  |  | ||||||
| Generated by 'django-admin startproject' using Django 5.0.1. |  | ||||||
|  |  | ||||||
| For more information on this file, see |  | ||||||
| https://docs.djangoproject.com/en/5.0/topics/settings/ |  | ||||||
|  |  | ||||||
| For the full list of settings and their values, see |  | ||||||
| https://docs.djangoproject.com/en/5.0/ref/settings/ |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from pathlib import Path |  | ||||||
|  |  | ||||||
| from corsheaders.defaults import default_headers |  | ||||||
|  |  | ||||||
| # Build paths inside the project like this: BASE_DIR / 'subdir'. |  | ||||||
| BASE_DIR = Path(__file__).resolve().parent.parent |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Quick-start development settings - unsuitable for production |  | ||||||
| # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ |  | ||||||
|  |  | ||||||
| # SECURITY WARNING: keep the secret key used in production secret! |  | ||||||
| SECRET_KEY = "CHANGE ME" |  | ||||||
|  |  | ||||||
| # SECURITY WARNING: don't run with debug turned on in production! |  | ||||||
| DEBUG = True |  | ||||||
|  |  | ||||||
| ALLOWED_HOSTS = [] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Application definition |  | ||||||
|  |  | ||||||
| INSTALLED_APPS = [ |  | ||||||
|     "django.contrib.admin", |  | ||||||
|     "django.contrib.auth", |  | ||||||
|     "django.contrib.contenttypes", |  | ||||||
|     "django.contrib.sessions", |  | ||||||
|     "django.contrib.messages", |  | ||||||
|     "django.contrib.staticfiles", |  | ||||||
|  |  | ||||||
|     "corsheaders", |  | ||||||
|     "django_extensions", |  | ||||||
|     "django_filters", |  | ||||||
|     "rest_framework", |  | ||||||
|  |  | ||||||
|     "trainvel.api", |  | ||||||
|     "trainvel.core", |  | ||||||
|     "trainvel.gtfs", |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| MIDDLEWARE = [ |  | ||||||
|     "django.middleware.security.SecurityMiddleware", |  | ||||||
|     "django.contrib.sessions.middleware.SessionMiddleware", |  | ||||||
|     "corsheaders.middleware.CorsMiddleware", |  | ||||||
|     "django.middleware.common.CommonMiddleware", |  | ||||||
|     "django.middleware.csrf.CsrfViewMiddleware", |  | ||||||
|     "django.contrib.auth.middleware.AuthenticationMiddleware", |  | ||||||
|     "django.contrib.messages.middleware.MessageMiddleware", |  | ||||||
|     "django.middleware.clickjacking.XFrameOptionsMiddleware", |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| CORS_ALLOWED_ORIGINS = [ |  | ||||||
|     "http://localhost:3000", |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| CORS_ALLOW_HEADERS = ( |  | ||||||
|     *default_headers, |  | ||||||
|     "If-Modified-Since", |  | ||||||
|     'Cache-Control', |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| ROOT_URLCONF = "trainvel.urls" |  | ||||||
|  |  | ||||||
| TEMPLATES = [ |  | ||||||
|     { |  | ||||||
|         "BACKEND": "django.template.backends.django.DjangoTemplates", |  | ||||||
|         "DIRS": [], |  | ||||||
|         "APP_DIRS": True, |  | ||||||
|         "OPTIONS": { |  | ||||||
|             "context_processors": [ |  | ||||||
|                 "django.template.context_processors.debug", |  | ||||||
|                 "django.template.context_processors.request", |  | ||||||
|                 "django.contrib.auth.context_processors.auth", |  | ||||||
|                 "django.contrib.messages.context_processors.messages", |  | ||||||
|             ], |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| WSGI_APPLICATION = "trainvel.wsgi.application" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Database |  | ||||||
| # https://docs.djangoproject.com/en/5.0/ref/settings/#databases |  | ||||||
|  |  | ||||||
| DATABASES = { |  | ||||||
|     "default": { |  | ||||||
|         "ENGINE": "django.db.backends.sqlite3", |  | ||||||
|         "NAME": BASE_DIR / "db.sqlite3", |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Password validation |  | ||||||
| # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators |  | ||||||
|  |  | ||||||
| AUTH_PASSWORD_VALIDATORS = [ |  | ||||||
|     { |  | ||||||
|         "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", |  | ||||||
|     }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Internationalization |  | ||||||
| # https://docs.djangoproject.com/en/5.0/topics/i18n/ |  | ||||||
|  |  | ||||||
| LANGUAGE_CODE = "fr-fr" |  | ||||||
|  |  | ||||||
| TIME_ZONE = "Europe/Paris" |  | ||||||
|  |  | ||||||
| USE_I18N = True |  | ||||||
|  |  | ||||||
| USE_TZ = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Static files (CSS, JavaScript, Images) |  | ||||||
| # https://docs.djangoproject.com/en/5.0/howto/static-files/ |  | ||||||
|  |  | ||||||
| STATIC_URL = "api-static/" |  | ||||||
|  |  | ||||||
| STATIC_ROOT = BASE_DIR / "static_files" |  | ||||||
|  |  | ||||||
| # Default primary key field type |  | ||||||
| # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field |  | ||||||
|  |  | ||||||
| DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" |  | ||||||
|  |  | ||||||
| REST_FRAMEWORK = { |  | ||||||
|     'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], |  | ||||||
|     'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', |  | ||||||
|     'PAGE_SIZE': 20, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| STATION_RADIUS = 300 |  | ||||||
|  |  | ||||||
| OPENTRANSPORTDATA_SWISS_TOKEN = "CHANGE ME" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     from .settings_local import * |  | ||||||
| except ImportError: |  | ||||||
|     pass |  | ||||||
| @@ -1,22 +0,0 @@ | |||||||
| SECRET_KEY = "CHANGE ME" |  | ||||||
|  |  | ||||||
| DEBUG = False |  | ||||||
|  |  | ||||||
| ALLOWED_HOSTS = ['sncf.emy.lu'] |  | ||||||
|  |  | ||||||
| CORS_ALLOWED_ORIGINS = [ |  | ||||||
|     "https://sncf.emy.lu", |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| DATABASES = { |  | ||||||
|     "default": { |  | ||||||
|         "ENGINE": "django.db.backends.postgresql", |  | ||||||
|         "NAME": "trainvel", |  | ||||||
|         "USER": "trainvel", |  | ||||||
|         "PASSWORD": "CHANGE ME", |  | ||||||
|         "HOST": "localhost", |  | ||||||
|         "PORT": "5432", |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| OPENTRANSPORTDATA_SWISS_TOKEN = "CHANGE ME" |  | ||||||
| @@ -1,45 +0,0 @@ | |||||||
| """ |  | ||||||
| URL configuration for trainvel project. |  | ||||||
|  |  | ||||||
| The `urlpatterns` list routes URLs to views. For more information please see: |  | ||||||
|     https://docs.djangoproject.com/en/5.0/topics/http/urls/ |  | ||||||
| Examples: |  | ||||||
| Function views |  | ||||||
|     1. Add an import:  from my_app import views |  | ||||||
|     2. Add a URL to urlpatterns:  path('', views.home, name='home') |  | ||||||
| Class-based views |  | ||||||
|     1. Add an import:  from other_app.views import Home |  | ||||||
|     2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home') |  | ||||||
| Including another URLconf |  | ||||||
|     1. Import the include() function: from django.urls import include, path |  | ||||||
|     2. Add a URL to urlpatterns:  path('blog/', include('blog.urls')) |  | ||||||
| """ |  | ||||||
| from django.contrib import admin |  | ||||||
| from django.urls import path, include |  | ||||||
| from rest_framework import routers |  | ||||||
|  |  | ||||||
| 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) |  | ||||||
| router.register("gtfs/trip", TripViewSet) |  | ||||||
| router.register("gtfs/stop_time", StopTimeViewSet) |  | ||||||
| router.register("gtfs/calendar", CalendarViewSet) |  | ||||||
| router.register("gtfs/calendar_date", CalendarDateViewSet) |  | ||||||
| router.register("gtfs/transfer", TransferViewSet) |  | ||||||
| router.register("gtfs/feed_info", FeedInfoViewSet) |  | ||||||
| router.register("gtfs-rt/trip_update", TripUpdateViewSet) |  | ||||||
| router.register("gtfs-rt/stop_time_update", StopTimeUpdateViewSet) |  | ||||||
| router.register("station/next_departures", NextDeparturesViewSet, basename="next_departures") |  | ||||||
| router.register("station/next_arrivals", NextArrivalsViewSet, basename="next_arrivals") |  | ||||||
|  |  | ||||||
| urlpatterns = [ |  | ||||||
|     path("admin/", admin.site.urls, name="admin"), |  | ||||||
|     path("api/", include(router.urls)), |  | ||||||
|     path("api-auth/", include('rest_framework.urls', namespace='rest_framework')), |  | ||||||
| ] |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| """ |  | ||||||
| WSGI config for trainvel project. |  | ||||||
|  |  | ||||||
| It exposes the WSGI callable as a module-level variable named ``application``. |  | ||||||
|  |  | ||||||
| For more information on this file, see |  | ||||||
| https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import os |  | ||||||
|  |  | ||||||
| from django.core.wsgi import get_wsgi_application |  | ||||||
|  |  | ||||||
| os.environ.setdefault("DJANGO_SETTINGS_MODULE", "trainvel.settings") |  | ||||||
|  |  | ||||||
| application = get_wsgi_application() |  | ||||||