Improve display of cabins list

This commit is contained in:
Edward Betts 2023-01-08 12:22:36 +00:00
parent 70db886a81
commit 944fe24662
5 changed files with 150 additions and 22 deletions

23
ferry/__init__.py Normal file
View file

@ -0,0 +1,23 @@
from dataclasses import dataclass
@dataclass
class Vehicle:
"""What type of vehicle is going on the ferry."""
type: str
registration: str
height: int
length: int
ports = {
"PORTSMOUTH": "GBPME",
"PLYMOUTH": "GBPLY",
"POOLE": "GBPOO",
"CAEN": "FROUI",
"CHERBOURG": "FRCER",
"ST MALO": "FRSML",
}
port_lookup = {code: name for name, code in ports.items()}

86
ferry/api.py Normal file
View file

@ -0,0 +1,86 @@
"""Interface with the Brittany Ferries API."""
from typing import Any, TypedDict
import requests
from . import Vehicle
api_root_url = "https://www.brittany-ferries.co.uk/api/ferry/v1/"
class VehicleDict(TypedDict):
"""Description of vechicle in the format expected by the API."""
type: str
registrations: list[str]
height: int
length: int
extras: dict[str, None]
def vehicle_dict(v: Vehicle) -> VehicleDict:
"""Return vehicle detail in the format for the Brittany Ferries API."""
return {
"type": v.type,
"registrations": [v.registration],
"height": v.height,
"length": v.length,
"extras": {"rearMountedBikeCarrier": None},
}
def get_prices(
departure_port: str,
arrival_port: str,
from_date: str,
to_date: str,
vehicle: Vehicle,
) -> dict[str, Any]:
"""Call Brittany Ferries API to get details of crossings."""
url = api_root_url + "crossing/prices"
post_data = {
"bookingReference": None,
"pets": {"smallDogs": 1, "largeDogs": 0, "cats": 0},
"passengers": {"adults": 2, "children": 0, "infants": 0},
"vehicle": vehicle_dict(vehicle),
"departurePort": departure_port,
"arrivalPort": arrival_port,
"disability": None,
"sponsor": None,
"fromDate": f"{from_date}T00:00:00",
"toDate": f"{to_date}T23:59:59",
}
r = requests.post(url, json=post_data)
data: dict[str, Any] = r.json()
return data
def get_accommodations(
departure_port: str,
arrival_port: str,
departure_date: str,
ticket_tier: str,
vehicle: Vehicle,
) -> dict[str, Any]:
"""Grab cabin details."""
url = api_root_url + "crossing/accommodations"
post_data = {
"bookingReference": None,
"departurePort": departure_port,
"arrivalPort": arrival_port,
"departureDate": departure_date,
"passengers": {"adults": 2, "children": 0, "infants": 0},
"disability": None,
"vehicle": vehicle_dict(vehicle),
"petCabinsNeeded": True,
"ticketTier": ticket_tier,
"pets": {"smallDogs": 1, "largeDogs": 0, "cats": 0},
"sponsor": None,
"offerType": "NONE",
}
json_data: dict[str, Any] = requests.post(url, json=post_data).json()
return json_data

17
ferry/read_config.py Normal file
View file

@ -0,0 +1,17 @@
import configparser
import os.path
from . import Vehicle
ferry_config = configparser.ConfigParser()
ferry_config.read(os.path.expanduser("~edward/config/brittany-ferries/config"))
def vehicle_from_config(config: configparser.ConfigParser) -> Vehicle:
"""Generate a vehicle object from config."""
return Vehicle(
type=config.get("vehicle", "type"),
registration=config.get("vehicle", "registration"),
height=config.getint("vehicle", "height"),
length=config.getint("vehicle", "length"),
)

41
main.py
View file

@ -9,14 +9,14 @@ import re
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from typing import Any from typing import Any
import dateutil.parser
import flask import flask
import pytz import pytz
import routes
import werkzeug.exceptions import werkzeug.exceptions
from werkzeug.debug.tbtools import get_current_traceback from werkzeug.debug.tbtools import get_current_traceback
from werkzeug.wrappers import Response from werkzeug.wrappers import Response
from ferry import ports import ferry
from ferry.api import get_accommodations, get_prices from ferry.api import get_accommodations, get_prices
from ferry.read_config import ferry_config, vehicle_from_config from ferry.read_config import ferry_config, vehicle_from_config
@ -86,22 +86,19 @@ def show_route(
"""Page showing list of prices.""" """Page showing list of prices."""
prices = get_prices(departure_port, arrival_port) prices = get_prices(departure_port, arrival_port)
port_lookup = {code: name for name, code in ports.items()}
return flask.render_template( return flask.render_template(
"route.html", "route.html",
departure_port=port_lookup[departure_port], departure_port=ferry.port_lookup[departure_port],
arrival_port=port_lookup[arrival_port], arrival_port=ferry.port_lookup[arrival_port],
days=prices["crossings"], days=prices["crossings"],
parse_date=parse_date, parse_date=parse_date,
) )
@app.route("/") @app.route("/")
def start() -> Response: def start() -> Response | str:
"""Start page.""" """Start page."""
return flask.render_template("index.html") return flask.render_template("index.html")
return flask.redirect(flask.url_for("outbound_page"))
def cabins_url(dep, arr, crossing, ticket_tier): def cabins_url(dep, arr, crossing, ticket_tier):
@ -110,8 +107,8 @@ def cabins_url(dep, arr, crossing, ticket_tier):
return flask.url_for( return flask.url_for(
"cabins", "cabins",
departure_port=ports[dep], departure_port=ferry.ports[dep],
arrival_port=ports[arr], arrival_port=ferry.ports[arr],
departure_date=utc_dt.strftime("%Y-%m-%dT%H:%M:%S.000Z"), departure_date=utc_dt.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
ticket_tier=ticket_tier, ticket_tier=ticket_tier,
) )
@ -150,7 +147,13 @@ def get_prices_with_cache(
filename = cache_filename(params) filename = cache_filename(params)
all_data = [ all_data = [
(dep, arr, get_prices(ports[dep], ports[arr], start, end, vehicle)["crossings"]) (
dep,
arr,
get_prices(ferry.ports[dep], ferry.ports[arr], start, end, vehicle)[
"crossings"
],
)
for dep, arr in selection for dep, arr in selection
] ]
@ -180,7 +183,7 @@ def build_outbound(section: str) -> str:
"all_routes.html", "all_routes.html",
data=all_data, data=all_data,
days_until_start=get_days_until_start(), days_until_start=get_days_until_start(),
ports=ports, ports=ferry.ports,
parse_date=parse_date, parse_date=parse_date,
from_date=start, from_date=start,
to_date=end, to_date=end,
@ -232,7 +235,7 @@ def return_page() -> str:
return flask.render_template( return flask.render_template(
"all_routes.html", "all_routes.html",
data=all_data, data=all_data,
ports=ports, ports=ferry.ports,
days_until_start=get_days_until_start(), days_until_start=get_days_until_start(),
parse_date=parse_date, parse_date=parse_date,
from_date=start, from_date=start,
@ -286,22 +289,20 @@ def cabins(
if a["quantityAvailable"] > 0 and a["code"] != "RS" if a["quantityAvailable"] > 0 and a["code"] != "RS"
# and "Inside" not in a["description"] # and "Inside" not in a["description"]
] ]
dep = dateutil.parser.isoparse(departure_date)
return flask.render_template( return flask.render_template(
"cabins.html", "cabins.html",
port_lookup=ferry.port_lookup,
departure_port=departure_port, departure_port=departure_port,
arrival_port=arrival_port, arrival_port=arrival_port,
departure_date=departure_date, departure_date=dep,
ticket_tier=ticket_tier, ticket_tier=ticket_tier,
accommodations=accommodations, accommodations=accommodations,
pet_accommodations=cabin_data["petAccommodations"], pet_accommodations=cabin_data["petAccommodations"],
) )
@app.route("/routes")
def route_list() -> str:
"""List of routes."""
return flask.render_template("index.html", routes=routes, ports=ports)
if __name__ == "__main__": if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001) app.run(host="0.0.0.0", port=5001)

View file

@ -29,9 +29,10 @@ a:link {
<body> <body>
<div class="m-3"> <div class="m-3">
<h1>{{ departure_port }} to {{ arrival_port }}</h1> <h1>{{ port_lookup[departure_port].title() }}
to {{ port_lookup[arrival_port].title() }}</h1>
<p>{{ departure_date }} {{ ticket_tier }}</p> <p>{{ departure_date.strftime("%A, %d %B %Y %H:%M UTC") }} {{ ticket_tier }}</p>
<table class="table w-auto"> <table class="table w-auto">
<tr> <tr>