Compare commits
	
		
			3 Commits
		
	
	
		
			bc23d63c43
			...
			1c99c5ca47
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1c99c5ca47 | |||
| e3fd6a7f88 | |||
| 036e1604bd | 
							
								
								
									
										65
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,53 +1,26 @@ | ||||
| # Byte-compiled / optimized / DLL files | ||||
| dist | ||||
| build | ||||
| __pycache__ | ||||
| *.py[cod] | ||||
| *$py.class | ||||
| *.swp | ||||
| *.egg-info | ||||
| _build | ||||
| .tox | ||||
| .coverage | ||||
| coverage | ||||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||||
|  | ||||
| # Translations | ||||
| *.mo | ||||
| *.pot | ||||
|  | ||||
| # Jupyter Notebook | ||||
| .ipynb_checkpoints | ||||
|  | ||||
| # Spyder project settings | ||||
| .spyderproject | ||||
| .spyproject | ||||
|  | ||||
| # Rope project settings | ||||
| .ropeproject | ||||
|  | ||||
| # PyCharm project settings | ||||
| .idea | ||||
|  | ||||
| # VSCode project settings | ||||
| .vscode | ||||
|  | ||||
| # Local data | ||||
| secrets.py | ||||
| settings_local.py | ||||
| *.log | ||||
| *.txt | ||||
| media/ | ||||
| output/ | ||||
| /static/ | ||||
| /static_files/ | ||||
| # dependencies | ||||
| /node_modules | ||||
| /.pnp | ||||
| .pnp.js | ||||
|  | ||||
| # Virtualenv | ||||
| .env/ | ||||
| env/ | ||||
| .venv/ | ||||
| venv/ | ||||
| db.sqlite3 | ||||
| db.sqlite3-journal | ||||
| # testing | ||||
| /coverage | ||||
|  | ||||
| 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 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 'dayjs/locale/fr' | ||||
| import './App.css' | ||||
| import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; | ||||
| import {createSyncStoragePersister} from "@tanstack/query-sync-storage-persister"; | ||||
| import {persistQueryClient} from "@tanstack/react-query-persist-client"; | ||||
| import Home from "./Home"; | ||||
| import {QueryClient, QueryClientProvider} from "@tanstack/react-query" | ||||
| import Home from "./Home" | ||||
| import dayjs from "dayjs" | ||||
| 
 | ||||
| function App() { | ||||
|   const router = createBrowserRouter([ | ||||
| @@ -18,7 +17,7 @@ function App() { | ||||
|       element: <Home />, | ||||
|     }, | ||||
|     { | ||||
|       path: "/station/:theme/:stationSlug", | ||||
|       path: "/station/:theme/:stationId", | ||||
|       element: <Station /> | ||||
|     } | ||||
|   ]) | ||||
| @@ -54,14 +53,7 @@ function App() { | ||||
|     }, | ||||
|   }) | ||||
| 
 | ||||
|   const localStoragePersister = createSyncStoragePersister({ | ||||
|     storage: window.localStorage, | ||||
|   }) | ||||
| 
 | ||||
|   persistQueryClient({ | ||||
|     queryClient, | ||||
|     persister: localStoragePersister, | ||||
|   }) | ||||
|   dayjs.locale('fr') | ||||
| 
 | ||||
|   return <> | ||||
|     <ThemeProvider theme={theme}> | ||||
| @@ -1,9 +1,8 @@ | ||||
| import {Autocomplete, TextField} from "@mui/material"; | ||||
| import {useRef, useState} from "react"; | ||||
| import {useState} from "react"; | ||||
| 
 | ||||
| function AutocompleteStation(params) { | ||||
|   const [options, setOptions] = useState([]) | ||||
|   const previousController = useRef() | ||||
| 
 | ||||
|   function onInputChange(event, value) { | ||||
|     if (!value) { | ||||
| @@ -11,17 +10,9 @@ function AutocompleteStation(params) { | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     if (previousController.current) | ||||
|       previousController.current.abort() | ||||
| 
 | ||||
|     const controller = new AbortController() | ||||
|     const signal = controller.signal | ||||
|     previousController.current = controller | ||||
|     fetch("/api/core/station/?search=" + value, {signal}) | ||||
|     fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/geocode?language=fr&text=${value}`) | ||||
|       .then(response => response.json()) | ||||
|       .then(data => data.results) | ||||
|       .then(setOptions) | ||||
|       .catch() | ||||
|   } | ||||
| 
 | ||||
|   return <> | ||||
| @@ -29,7 +20,7 @@ function AutocompleteStation(params) { | ||||
|       id="stop" | ||||
|       options={options} | ||||
|       onInputChange={onInputChange} | ||||
|       filterOptions={(x) => x} | ||||
|       filterOptions={(x) => x.filter(stop => stop.type === "STOP").filter(stop => !stop.id.startsWith("node/"))} | ||||
|       getOptionKey={option => option.id} | ||||
|       getOptionLabel={option => option.name} | ||||
|       groupBy={option => getOptionGroup(option)} | ||||
| @@ -40,7 +31,7 @@ function AutocompleteStation(params) { | ||||
| } | ||||
| 
 | ||||
| function getOptionGroup(option) { | ||||
|   return option.country | ||||
|   return option.id.split('_')[0] | ||||
| } | ||||
| 
 | ||||
| export default AutocompleteStation; | ||||
| @@ -5,7 +5,7 @@ function Home() { | ||||
|   const navigate = useNavigate() | ||||
| 
 | ||||
|   function onStationSelected(event, station) { | ||||
|     navigate(`/station/sncf/${station.slug}/`) | ||||
|     navigate(`/station/sncf/${station.id}/`) | ||||
|   } | ||||
| 
 | ||||
|   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 './index.css'; | ||||
| 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() | ||||