First render
This commit is contained in:
parent
efd0dabc87
commit
ba6a859ec7
3 changed files with 65 additions and 28 deletions
48
main.py
48
main.py
|
|
@ -3,7 +3,7 @@ import products, recipes, db, meals, persons, ingredients, shopping
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import List, Annotated, Optional, Union
|
from typing import Dict, List, Annotated, Optional, Union
|
||||||
from fastapi import FastAPI, Depends, Query, Cookie
|
from fastapi import FastAPI, Depends, Query, Cookie
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
|
@ -252,17 +252,49 @@ async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db))
|
||||||
return meal
|
return meal
|
||||||
|
|
||||||
class CurrentShoppingList(BaseModel):
|
class CurrentShoppingList(BaseModel):
|
||||||
requests: List[shopping.ShoppingListItem]
|
outstanding_items: List[shopping.ShoppingListItem]
|
||||||
previously_purchased: List[shopping.ShoppingListItem] = []
|
requested_meals: List[shopping.ShoppingListItem]
|
||||||
other_shopping_lists: List[shopping.ShoppingList] = []
|
purchased_items: List[shopping.ShoppingListItem] = []
|
||||||
|
|
||||||
|
ingredients_lookup: Dict[int, ingredients.Ingredient] = {}
|
||||||
|
meals_lookup: Dict[int, meals.Meal] = {}
|
||||||
|
shopping_list_lookup: Dict[int, shopping.ShoppingList] = {}
|
||||||
|
recipes_lookup: Dict[int, recipes.Recipe] = {}
|
||||||
|
|
||||||
@app.get("/api/shopping/current")
|
@app.get("/api/shopping/current")
|
||||||
async def get_current_shopping_list(conn: sqlite3.Connection = Depends(get_db)) -> CurrentShoppingList:
|
async def get_current_shopping_list(conn: sqlite3.Connection = Depends(get_db)) -> CurrentShoppingList:
|
||||||
outstanding_requests, purchased_requests = await shopping.get_outstanding_requests(conn)
|
outstanding_requests, purchased_requests, meal_requests = await shopping.get_outstanding_requests(conn)
|
||||||
other_shopping_list_ids = {item.shopping_list for item in purchased_requests}
|
other_shopping_list_ids = {item.list_id for item in purchased_requests}
|
||||||
other_shopping_lists = [await shopping.load_shopping_list(conn, list_id) for list_id in other_shopping_list_ids if list_id is not None]
|
|
||||||
|
|
||||||
return CurrentShoppingList(requests=outstanding_requests, previously_purchased=purchased_requests, other_shopping_lists=other_shopping_lists)
|
shopping_list_lookup = { list_id: await shopping.load_shopping_list(conn, list_id) for list_id in other_shopping_list_ids }
|
||||||
|
meals_lookup = {}
|
||||||
|
ingredients_lookup = {}
|
||||||
|
recipes_lookup = {}
|
||||||
|
|
||||||
|
# Reduce the data structure to items and lookups
|
||||||
|
items = meal_requests + outstanding_requests + purchased_requests + [item for sl in shopping_list_lookup.values() for item in sl.items]
|
||||||
|
for item in items:
|
||||||
|
if item.meal and not item.meal.id in meals_lookup:
|
||||||
|
meals_lookup[item.meal.id] = item.meal
|
||||||
|
item.meal = None
|
||||||
|
|
||||||
|
if item.ingredient and not item.ingredient.id in ingredients_lookup:
|
||||||
|
ingredients_lookup[item.ingredient.id] = item.ingredient
|
||||||
|
item.ingredient = None
|
||||||
|
|
||||||
|
if item.recipe and not item.recipe.id in recipes_lookup:
|
||||||
|
recipes_lookup[item.recipe.id] = item.recipe
|
||||||
|
item.recipe = None
|
||||||
|
|
||||||
|
return CurrentShoppingList(
|
||||||
|
outstanding_items=outstanding_requests,
|
||||||
|
requested_meals=meal_requests,
|
||||||
|
purchased_items=purchased_requests,
|
||||||
|
meals_lookup=meals_lookup,
|
||||||
|
shopping_list_lookup=shopping_list_lookup,
|
||||||
|
ingredients_lookup=ingredients_lookup,
|
||||||
|
recipes_lookup=recipes_lookup
|
||||||
|
)
|
||||||
|
|
||||||
@app.get("/api/shopping/{list_id}")
|
@app.get("/api/shopping/{list_id}")
|
||||||
async def get_shopping_list(list_id: int, conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList:
|
async def get_shopping_list(list_id: int, conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList:
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,23 @@ async def get_persons_requests(conn, person_id: int) -> AsyncIterator[ShoppingLi
|
||||||
def flatten_items(items: Iterator[ShoppingListItem]) -> Iterator[ShoppingListItem]:
|
def flatten_items(items: Iterator[ShoppingListItem]) -> Iterator[ShoppingListItem]:
|
||||||
for item in items:
|
for item in items:
|
||||||
if item.meal:
|
if item.meal:
|
||||||
for ingredient in item.meal.ingredients + item.meal.extra_ingredients:
|
for mealRecipe in item.meal.recipes:
|
||||||
|
for ingredient in mealRecipe.recipe.ingredients:
|
||||||
|
yield ShoppingListItem(ingredient=ingredient, meal=item.meal, recipe=mealRecipe.recipe, person_id=item.person_id, created_date=item.created_date)
|
||||||
|
|
||||||
|
for ingredient in item.meal.extra_ingredients:
|
||||||
yield ShoppingListItem(ingredient=ingredient, meal=item.meal, person_id=item.person_id, created_date=item.created_date)
|
yield ShoppingListItem(ingredient=ingredient, meal=item.meal, person_id=item.person_id, created_date=item.created_date)
|
||||||
else:
|
else:
|
||||||
yield item
|
yield item
|
||||||
|
|
||||||
async def get_outstanding_requests(conn) -> Tuple[List[ShoppingListItem], List[ShoppingListItem]]:
|
async def get_outstanding_requests(conn) -> Tuple[List[ShoppingListItem], List[ShoppingListItem], List[ShoppingListItem]]:
|
||||||
current_requests = [r async for r in _find_items_by_list_id(conn, None)]
|
current_requests = [r async for r in _find_items_by_list_id(conn, None)]
|
||||||
|
meal_requests = [r for r in current_requests if r.meal_id is not None and r.meal_id > 0 and r.meal is not None]
|
||||||
meal_ids = {item.meal_id for item in current_requests if item.meal_id is not None and item.meal_id > 0}
|
meals = {r.meal_id: r.meal for r in meal_requests}
|
||||||
purchased_ingredients = {r.ingredient_id async for r in _get_purchased_ingredients(conn, meal_ids)}
|
purchased_ingredients = {r.ingredient_id async for r in _get_purchased_ingredients(conn, list(meals.keys()))}
|
||||||
|
|
||||||
flattened = flatten_items(current_requests)
|
flattened = flatten_items(current_requests)
|
||||||
outstanding_items = [r for r in flattened if r.ingredient_id not in purchased_ingredients]
|
outstanding_items = [r for r in flattened if r.ingredient_id not in purchased_ingredients]
|
||||||
purchased_items = [r for r in flattened if r.ingredient_id in purchased_ingredients]
|
purchased_items = [r for r in flattened if r.ingredient_id in purchased_ingredients]
|
||||||
|
|
||||||
return outstanding_items, purchased_items
|
return outstanding_items, purchased_items, meal_requests
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from common import BaseLinkedModel
|
from common import BaseLinkedModel
|
||||||
|
from recipes import Recipe
|
||||||
from meals import Meal, find_meal_by_id
|
from meals import Meal, find_meal_by_id
|
||||||
from ingredients import Ingredient, insert_ingredient
|
from ingredients import Ingredient, insert_ingredient
|
||||||
from persons import Person
|
from persons import Person
|
||||||
|
|
@ -19,6 +20,9 @@ class ShoppingListItem(BaseLinkedModel):
|
||||||
ingredient_id: Optional[int] = None
|
ingredient_id: Optional[int] = None
|
||||||
ingredient: Optional[Ingredient] = None
|
ingredient: Optional[Ingredient] = None
|
||||||
|
|
||||||
|
recipe_id: Optional[int] = None
|
||||||
|
recipe: Optional[Recipe] = None
|
||||||
|
|
||||||
meal_id: Optional[int] = None
|
meal_id: Optional[int] = None
|
||||||
meal: Optional[Meal] = None
|
meal: Optional[Meal] = None
|
||||||
|
|
||||||
|
|
@ -58,11 +62,13 @@ async def create(conn):
|
||||||
list_id INTEGER,
|
list_id INTEGER,
|
||||||
person_id INTEGER,
|
person_id INTEGER,
|
||||||
meal_id INTEGER,
|
meal_id INTEGER,
|
||||||
|
recipe_id INTEGER,
|
||||||
created_date DATETIME NOT NULL,
|
created_date DATETIME NOT NULL,
|
||||||
FOREIGN KEY(ingredient_id) REFERENCES Ingredient(id),
|
FOREIGN KEY(ingredient_id) REFERENCES Ingredient(id),
|
||||||
FOREIGN KEY(list_id) REFERENCES ShoppingList(id),
|
FOREIGN KEY(list_id) REFERENCES ShoppingList(id),
|
||||||
FOREIGN KEY(person_id) REFERENCES Person(id),
|
FOREIGN KEY(person_id) REFERENCES Person(id),
|
||||||
FOREIGN KEY(meal_id) REFERENCES Meal(id)
|
FOREIGN KEY(meal_id) REFERENCES Meal(id),
|
||||||
|
FOREIGN KEY(recipe_id) REFERENCES Recipe(id)
|
||||||
);''')
|
);''')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -110,7 +116,11 @@ async def purchase(conn, shopping_list: ShoppingList) -> None:
|
||||||
async with conn.execute('''
|
async with conn.execute('''
|
||||||
UPDATE ShoppingListItem
|
UPDATE ShoppingListItem
|
||||||
SET list_id = ?
|
SET list_id = ?
|
||||||
WHERE ingredient_id = ? AND person_id = ? AND meal_id IS NULL
|
WHERE ingredient_id = ?
|
||||||
|
AND list_id IS NULL
|
||||||
|
AND person_id = ?
|
||||||
|
AND meal_id IS NULL
|
||||||
|
AND recipe_id IS NULL
|
||||||
''', (shopping_list.id, item.ingredient_id, item.person_id)) as cursor:
|
''', (shopping_list.id, item.ingredient_id, item.person_id)) as cursor:
|
||||||
if cursor.rowcount == 0:
|
if cursor.rowcount == 0:
|
||||||
raise ValueError('Ingredient request must have a valid person id and ingredient id')
|
raise ValueError('Ingredient request must have a valid person id and ingredient id')
|
||||||
|
|
@ -121,22 +131,13 @@ async def purchase(conn, shopping_list: ShoppingList) -> None:
|
||||||
raise ValueError('Meal request must have a valid meal id')
|
raise ValueError('Meal request must have a valid meal id')
|
||||||
|
|
||||||
async with conn.execute('''
|
async with conn.execute('''
|
||||||
INSERT INTO ShoppingListItem (ingredient_id, list_id, person_id, meal_id, created_date)
|
INSERT INTO ShoppingListItem (ingredient_id, list_id, person_id, meal_id, recipe_id, created_date)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
''', (item.ingredient_id, shopping_list.id, item.person_id, item.meal_id, item.created_date.isoformat())) as cursor:
|
''', (item.ingredient_id, shopping_list.id, item.person_id, item.meal_id, item.recipe_id, item.created_date.isoformat())) as cursor:
|
||||||
item.id = cursor.lastrowid
|
item.id = cursor.lastrowid
|
||||||
|
|
||||||
# TODO: Calculate which meals have been fulfilled and update meal status
|
# TODO: Calculate which meals have been fulfilled and update meal status
|
||||||
|
|
||||||
async def requested_meal_ids(conn) -> AsyncIterator[ShoppingListItem]:
|
|
||||||
async with conn.execute(f'''
|
|
||||||
SELECT {ShoppingListItem.KEYS}
|
|
||||||
FROM ShoppingListItem
|
|
||||||
WHERE list_id IS NULL AND meal_id IS NOT NULL AND meal_id > 0
|
|
||||||
''') as cursor:
|
|
||||||
async for row in cursor:
|
|
||||||
yield ShoppingListItem(**{k:v for k,v in zip(ShoppingListItem.KEYS, row)})
|
|
||||||
|
|
||||||
async def request(conn, person: Person, ingredient: Optional[Ingredient] = None, meal: Optional[Meal] = None) -> ShoppingListItem:
|
async def request(conn, person: Person, ingredient: Optional[Ingredient] = None, meal: Optional[Meal] = None) -> ShoppingListItem:
|
||||||
if ingredient is not None and meal is not None:
|
if ingredient is not None and meal is not None:
|
||||||
raise ValueError('Cannot request both an ingredient and a meal')
|
raise ValueError('Cannot request both an ingredient and a meal')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue