From 9790ecf5f61f6d3289569ab6972b8ba9e31e3b48 Mon Sep 17 00:00:00 2001 From: april Date: Thu, 18 Jan 2024 13:08:23 -0600 Subject: [PATCH] Add MyFlightBook importer --- api/database/aircraft.py | 2 +- api/database/flights.py | 4 +- api/database/import_flights.py | 68 ++++++++++++++++++++++++++++++++++ api/routes/flights.py | 15 ++++++++ api/schemas/flight.py | 6 ++- 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 api/database/import_flights.py diff --git a/api/database/aircraft.py b/api/database/aircraft.py index 092e87b..78755b3 100644 --- a/api/database/aircraft.py +++ b/api/database/aircraft.py @@ -117,7 +117,7 @@ async def update_aircraft_field(field: str, value: Any, id: str) -> AircraftDisp if updated_aircraft is None: raise HTTPException(500, "Failed to update flight") - return AircraftDisplaySchema(**aircraft.model_dump()) + return AircraftDisplaySchema(**aircraft_display_helper(aircraft)) async def delete_aircraft(id: str) -> AircraftDisplaySchema: diff --git a/api/database/flights.py b/api/database/flights.py index 7582b91..27d779b 100644 --- a/api/database/flights.py +++ b/api/database/flights.py @@ -12,13 +12,11 @@ from schemas.aircraft import aircraft_class_dict, aircraft_category_dict from .aircraft import retrieve_aircraft_by_tail, update_aircraft_field from .db import flight_collection, aircraft_collection from schemas.flight import FlightConciseSchema, FlightDisplaySchema, FlightCreateSchema, flight_display_helper, \ - flight_add_helper, FlightPatchSchema + flight_add_helper, FlightPatchSchema, fs_keys from .img import delete_image logger = logging.getLogger("api") -fs_keys = list(FlightPatchSchema.__annotations__.keys()) + list(FlightDisplaySchema.__annotations__.keys()) - async def retrieve_flights(user: str = "", sort: str = "date", order: int = -1, filter: str = "", filter_val: str = "") -> list[FlightConciseSchema]: diff --git a/api/database/import_flights.py b/api/database/import_flights.py new file mode 100644 index 0000000..c515790 --- /dev/null +++ b/api/database/import_flights.py @@ -0,0 +1,68 @@ +import csv +from datetime import datetime + +from fastapi import UploadFile, HTTPException +from pydantic import ValidationError + +from database.flights import insert_flight +from schemas.flight import flight_add_helper, FlightCreateSchema, fs_keys, fs_types + +mfb_types = { + "Tail Number": "aircraft", + "Hold": "holds_instrument", + "Landings": "landings_day", + "FS Night Landings": "landings_night", + "X-Country": "time_xc", + "Night": "time_night", + "Simulated Instrument": "time_sim_instrument", + "Ground Simulator": "time_sim", + "Dual Received": "dual_recvd", + "SIC": "time_sic", + "PIC": "time_pic", + "Flying Time": "time_total", + "Hobbs Start": "hobbs_start", + "Hobbs End": "hobbs_end", + "Engine Start": "time_start", + "Engine End": "time_stop", + "Flight Start": "time_off", + "Flight End": "time_down", + "Comments": "comments", +} + + +async def import_from_csv_mfb(file: UploadFile, user: str): + content = await file.read() + decoded_content = content.decode("utf-8").splitlines() + decoded_content[0] = decoded_content[0].replace('\ufeff', '', 1) + reader = csv.DictReader(decoded_content) + flights = [] + for row in reader: + entry = {} + for label, value in dict(row).items(): + if len(value) and label in mfb_types: + entry[mfb_types[label]] = value + else: + if label == "Date": + entry["date"] = datetime.strptime(value, "%Y-%m-%d") + elif label == "Route": + r = str(value).split(" ") + l = len(r) + route = "" + start = "" + end = "" + if l == 1: + start = r[0] + elif l >= 2: + start = r[0] + end = r[-1] + route = " ".join(r[1:-1]) + entry["route"] = route + entry["waypoint_from"] = start + entry["waypoint_to"] = end + flights.append(entry) + # print(flights) + for entry in flights: + # try: + await insert_flight(FlightCreateSchema(**entry), user) + # except ValidationError as e: + # raise HTTPException(400, e.json()) diff --git a/api/routes/flights.py b/api/routes/flights.py index 330c39e..2d83d98 100644 --- a/api/routes/flights.py +++ b/api/routes/flights.py @@ -8,6 +8,7 @@ from app.deps import get_current_user, admin_required from database import flights as db from database.flights import update_flight_fields from database.img import upload_image +from database.import_flights import import_from_csv_mfb from schemas.flight import FlightConciseSchema, FlightDisplaySchema, FlightCreateSchema, FlightByDateSchema, \ FlightSchema @@ -221,3 +222,17 @@ async def delete_flight(flight_id: str, user: UserDisplaySchema = Depends(get_cu deleted = await db.delete_flight(flight_id) return deleted + + +@router.post('/import', summary="Import flights from given file") +async def import_flights(flights: UploadFile = File(...), type: str = "mfb", + user: UserDisplaySchema = Depends(get_current_user)): + """ + Import flights from a given file (csv). Note that all aircraft included must be created first + + :param flights: File of flights to import + :param type: Type of import (mfb: MyFlightBook) + :param user: Current user + :return: + """ + await import_from_csv_mfb(flights, user.id) diff --git a/api/schemas/flight.py b/api/schemas/flight.py index fd01315..5e7a111 100644 --- a/api/schemas/flight.py +++ b/api/schemas/flight.py @@ -1,5 +1,5 @@ import datetime -from typing import Optional, Dict, Union, List +from typing import Optional, Dict, Union, List, get_args from utils import to_objectid from pydantic import BaseModel @@ -123,6 +123,10 @@ class FlightConciseSchema(BaseModel): FlightByDateSchema = Dict[int, Union[Dict[int, 'FlightByDateSchema'], FlightConciseSchema]] +fs_keys = list(FlightPatchSchema.__annotations__.keys()) + list(FlightDisplaySchema.__annotations__.keys()) +fs_types = {label: get_args(type_)[0] if get_args(type_) else str(type_) for label, type_ in + FlightSchema.__annotations__.items() if len(get_args(type_)) > 0} + # HELPERS #