Squashed commit of the following:
commit 2cf565c8c42387cdc57fd7ec7922e6d9e173322c
Author: jableader <jacobdunk@gmail.com>
Date: Sun Oct 13 19:15:03 2024 +1100
Fixed bug when saving own ingredients
commit 3ff1204a2fe88da65864109218c0a6de2130f7cc
Author: jableader <jacobdunk@gmail.com>
Date: Sun Oct 13 18:02:04 2024 +1100
First successful save
commit 2620e2c899c145df1c4a941d1eaaef7b01087791
Author: jableader <jacobdunk@gmail.com>
Date: Fri Oct 4 17:53:21 2024 +1000
Change shopping list to be post-able
This commit is contained in:
parent
0fdd0d10c2
commit
78e94a3503
5 changed files with 141 additions and 252 deletions
77
main.py
77
main.py
|
|
@ -246,60 +246,49 @@ async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db))
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return meal
|
return meal
|
||||||
|
|
||||||
|
class CurrentShoppingList(BaseModel):
|
||||||
|
requests: List[shopping.ShoppingListRequest]
|
||||||
|
overlapping_previous_shops: List[shopping.ShoppingList]
|
||||||
|
|
||||||
|
@app.get("/api/shopping/current")
|
||||||
|
async def get_current_shopping_list(conn: sqlite3.Connection = Depends(get_db)) -> CurrentShoppingList:
|
||||||
|
overlapping = []
|
||||||
|
current_requests = [r async for r in shopping.get_current_requests(conn)]
|
||||||
|
|
||||||
|
for request in current_requests:
|
||||||
|
if request.meal_id:
|
||||||
|
overlapping.extend([r async for r in shopping.get_shopping_list_with_meal(conn, request.meal_id)])
|
||||||
|
|
||||||
|
return CurrentShoppingList(requests=current_requests, overlapping_previous_shops=overlapping)
|
||||||
|
|
||||||
@app.get("/api/shopping/{list_id}")
|
@app.get("/api/shopping/{list_id}")
|
||||||
async def get_shopping_list(list_id: Union[int, str], conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList:
|
async def get_shopping_list(list_id: int, conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList:
|
||||||
if list_id.lower() == 'current':
|
|
||||||
return await shopping.current_shopping_list(conn)
|
|
||||||
|
|
||||||
try:
|
|
||||||
list_id = int(list_id)
|
|
||||||
except ValueError:
|
|
||||||
return JSONResponse(status_code=400, content={'message': 'Invalid shopping list ID'})
|
|
||||||
|
|
||||||
return await shopping.load_shopping_list(conn, list_id)
|
return await shopping.load_shopping_list(conn, list_id)
|
||||||
|
|
||||||
@app.post("/api/shopping/current/purchased")
|
class ShoppingListPurchase(shopping.ShoppingList):
|
||||||
async def mark_purchased(conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> shopping.ShoppingList:
|
completed_requests: List[shopping.ShoppingListRequest] = []
|
||||||
response = await shopping.mark_purchased(conn)
|
|
||||||
|
|
||||||
# Its easier to make the next shopping list now, while we know calling it requires commit()
|
@app.post("/api/shopping/")
|
||||||
await shopping.current_shopping_list(conn)
|
async def purchase_ingredients(lst: ShoppingListPurchase, conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList:
|
||||||
await conn.commit()
|
await shopping.insert_shopping_list(conn, lst)
|
||||||
return response
|
for request in lst.completed_requests:
|
||||||
|
if request.meal_id:
|
||||||
|
meal = await meals.find_meal_by_id(conn, request.meal_id)
|
||||||
|
if meal:
|
||||||
|
await meals.mark_purchased(conn, meal)
|
||||||
|
|
||||||
class FoundResult(BaseModel):
|
await shopping.remove_request(conn, request)
|
||||||
created: List[shopping.ShoppingListResult] = []
|
|
||||||
removed: List[shopping.ShoppingListResult] = []
|
|
||||||
|
|
||||||
@app.post("/api/shopping/current/found")
|
|
||||||
async def mark_shopping_list(ingredients: List[ingredients.Ingredient], conn: sqlite3.Connection = Depends(get_db)) -> FoundResult:
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
result = FoundResult()
|
|
||||||
for ingredient in ingredients:
|
|
||||||
existing, created = await shopping.mark_found(conn, ingredient, now)
|
|
||||||
result.created.append(created)
|
|
||||||
|
|
||||||
if existing:
|
|
||||||
result.removed.append(existing)
|
|
||||||
|
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return result
|
return lst
|
||||||
|
|
||||||
@app.delete("/api/shopping/current/found/{product_id}")
|
|
||||||
async def unmark_shopping_list(product_id: int, conn: sqlite3.Connection = Depends(get_db)) -> List[shopping.ShoppingListResult]:
|
|
||||||
response = await shopping.unmark_found(conn, product_id)
|
|
||||||
await conn.commit()
|
|
||||||
return response
|
|
||||||
|
|
||||||
@app.get("/api/shopping/current/me/ingredients")
|
@app.get("/api/shopping/current/me/ingredients")
|
||||||
async def get_my_shopping_list(conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> List[shopping.ShoppingListRequest]:
|
async def get_my_shopping_list(conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> List[shopping.ShoppingListRequest]:
|
||||||
current = await shopping.current_shopping_list(conn)
|
return [r async for r in shopping.get_current_requests(conn) if r.ingredient and r.person_id == person.id]
|
||||||
return [r async for r in shopping.get_persons_requests(conn, current, person) if r.ingredient]
|
|
||||||
|
|
||||||
@app.post("/api/shopping/current/me/ingredients")
|
@app.post("/api/shopping/current/me/ingredients")
|
||||||
async def sync_my_shopping_list(requests: List[ingredients.Ingredient], conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> List[shopping.ShoppingListRequest]:
|
async def sync_my_shopping_list(requests: List[ingredients.Ingredient], conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> List[shopping.ShoppingListRequest]:
|
||||||
current = await shopping.current_shopping_list(conn)
|
result = [r async for r in shopping.sync_persons_requested_ingredients(conn, person, requests) if r.ingredient]
|
||||||
result = [r async for r in shopping.sync_persons_requested_ingredients(conn, current, person, requests) if r.ingredient]
|
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
@ -308,23 +297,21 @@ class MealIdWrapper(BaseModel):
|
||||||
|
|
||||||
@app.post("/api/shopping/current/meals/me")
|
@app.post("/api/shopping/current/meals/me")
|
||||||
async def request_meal(r: MealIdWrapper, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> shopping.ShoppingListRequest:
|
async def request_meal(r: MealIdWrapper, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> shopping.ShoppingListRequest:
|
||||||
current = await shopping.current_shopping_list(conn)
|
|
||||||
meal = await meals.find_meal_by_id(conn, r.meal_id)
|
meal = await meals.find_meal_by_id(conn, r.meal_id)
|
||||||
if not meal:
|
if not meal:
|
||||||
return JSONResponse(status_code=404, content={'message': 'Meal not found'})
|
return JSONResponse(status_code=404, content={'message': 'Meal not found'})
|
||||||
|
|
||||||
response = await shopping.request_meal(conn, current, person, meal)
|
response = await shopping.request_meal(conn, person, meal)
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@app.delete("/api/shopping/current/meals/{meal_id}")
|
@app.delete("/api/shopping/current/meals/{meal_id}")
|
||||||
async def unrequest_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> dict:
|
async def unrequest_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> dict:
|
||||||
current = await shopping.current_shopping_list(conn)
|
|
||||||
meal = await meals.find_meal_by_id(conn, meal_id)
|
meal = await meals.find_meal_by_id(conn, meal_id)
|
||||||
if not meal:
|
if not meal:
|
||||||
return JSONResponse(status_code=404, content={'message': 'Meal not found'})
|
return JSONResponse(status_code=404, content={'message': 'Meal not found'})
|
||||||
|
|
||||||
await shopping.delete_requests(conn, current, meal)
|
await shopping.unrequest_meal(conn, meal)
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
|
||||||
28
meals/db.py
28
meals/db.py
|
|
@ -17,7 +17,7 @@ class MealRecipe(BaseModel):
|
||||||
recipe: Optional[Recipe] = None
|
recipe: Optional[Recipe] = None
|
||||||
|
|
||||||
class Meal(BaseModel):
|
class Meal(BaseModel):
|
||||||
KEYS: ClassVar[List[str]] = ['id', 'suggested_date', 'consumed_date']
|
KEYS: ClassVar[List[str]] = ['id', 'suggested_date', 'consumed_date', 'purchase_date']
|
||||||
id: int = -1
|
id: int = -1
|
||||||
suggested_date: datetime.datetime
|
suggested_date: datetime.datetime
|
||||||
consumed_date: Optional[datetime.datetime] = None
|
consumed_date: Optional[datetime.datetime] = None
|
||||||
|
|
@ -37,7 +37,8 @@ async def create(conn):
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
suggested_date TEXT,
|
suggested_date TEXT,
|
||||||
consumed_date TEXT,
|
consumed_date TEXT,
|
||||||
deleted_date TEXT DEFAULT NULL
|
deleted_date TEXT DEFAULT NULL,
|
||||||
|
purchase_date TEXT DEFAULT NULL
|
||||||
);''')
|
);''')
|
||||||
|
|
||||||
await conn.execute('''
|
await conn.execute('''
|
||||||
|
|
@ -113,7 +114,7 @@ async def find_meal_by_id(conn, meal_id: int) -> Meal:
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
''', (meal_id,)) as cursor:
|
''', (meal_id,)) as cursor:
|
||||||
async for row in cursor:
|
async for row in cursor:
|
||||||
meal = await with_purchase_date(conn, Meal(**{k:v for k,v in zip(Meal.KEYS, row)}))
|
meal = Meal(**{k:v for k,v in zip(Meal.KEYS, row)})
|
||||||
|
|
||||||
await load_participants(conn, meal)
|
await load_participants(conn, meal)
|
||||||
await load_recipes(conn, meal)
|
await load_recipes(conn, meal)
|
||||||
|
|
@ -126,7 +127,7 @@ async def find_upcoming_meals_by_date_range(conn, start: datetime, end: datetime
|
||||||
WHERE suggested_date >= ? AND suggested_date <= ? AND consumed_date IS NULL AND deleted_date IS NULL
|
WHERE suggested_date >= ? AND suggested_date <= ? AND consumed_date IS NULL AND deleted_date IS NULL
|
||||||
''', (start, end)) as cursor:
|
''', (start, end)) as cursor:
|
||||||
async for row in cursor:
|
async for row in cursor:
|
||||||
yield await with_purchase_date(conn, Meal(**{k:v for k,v in zip(Meal.KEYS, row)}))
|
yield Meal(**{k:v for k,v in zip(Meal.KEYS, row)})
|
||||||
|
|
||||||
async def load_participants(conn, meal: Meal) -> None:
|
async def load_participants(conn, meal: Meal) -> None:
|
||||||
async with conn.execute(f'''
|
async with conn.execute(f'''
|
||||||
|
|
@ -213,16 +214,13 @@ async def mark_consumed(conn, meal: Meal, date: datetime.datetime = datetime.dat
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
''', (date, meal.id))
|
''', (date, meal.id))
|
||||||
|
|
||||||
async def with_purchase_date(conn, meal: Meal) -> Meal:
|
async def mark_purchased(conn, meal: Meal) -> Meal:
|
||||||
async with conn.execute('''
|
meal.purchase_date = datetime.datetime.now()
|
||||||
SELECT purchased_date FROM ShoppingList
|
|
||||||
WHERE id = (
|
await conn.execute('''
|
||||||
SELECT list_id FROM ShoppingListRequest
|
UPDATE Meal
|
||||||
WHERE meal_id = ?
|
SET purchase_date = ?
|
||||||
LIMIT 1
|
WHERE id = ?
|
||||||
)
|
''', (meal.purchase_date, meal.id))
|
||||||
''', (meal.id,)) as cursor:
|
|
||||||
async for row in cursor:
|
|
||||||
meal.purchase_date = row[0]
|
|
||||||
|
|
||||||
return meal
|
return meal
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
from shopping.db import ShoppingList, ShoppingListRequest, ShoppingListResult, current_shopping_list, mark_found, unmark_found, sync_persons_requested_ingredients, load_shopping_list, get_persons_requests, request_meal, delete_requests, mark_purchased
|
from shopping.db import ShoppingList, ShoppingListRequest, ShoppingListResult, sync_persons_requested_ingredients, load_shopping_list, get_current_requests, request_meal, unrequest_meal, insert_shopping_list, get_shopping_list_with_meal, remove_request
|
||||||
|
|
||||||
|
|
|
||||||
271
shopping/db.py
271
shopping/db.py
|
|
@ -1,17 +1,17 @@
|
||||||
from meals import Meal, find_upcoming_meals_by_date_range, 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
|
||||||
from products import Product
|
from products import Product
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import AsyncIterator, List, ClassVar, Optional, Tuple
|
from typing import AsyncIterator, List, ClassVar, Optional
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
|
|
||||||
class ShoppingListRequest(BaseModel):
|
class ShoppingListRequest(BaseModel):
|
||||||
KEYS: ClassVar[List[str]] = ['id', 'ingredient_id', 'list_id', 'person_id', 'meal_id', 'created_date']
|
KEYS: ClassVar[List[str]] = ['id', 'ingredient_id', 'list_id', 'person_id', 'meal_id', 'created_date']
|
||||||
id: int = -1
|
id: int = -1
|
||||||
list_id: int
|
list_id: Optional[int] = None
|
||||||
|
|
||||||
ingredient_id: Optional[int] = None
|
ingredient_id: Optional[int] = None
|
||||||
ingredient: Optional[Ingredient] = None
|
ingredient: Optional[Ingredient] = None
|
||||||
|
|
@ -25,7 +25,7 @@ class ShoppingListRequest(BaseModel):
|
||||||
created_date: datetime = datetime.now()
|
created_date: datetime = datetime.now()
|
||||||
|
|
||||||
class ShoppingListResult(BaseModel):
|
class ShoppingListResult(BaseModel):
|
||||||
KEYS: ClassVar[List[str]] = ['id', 'product_id', 'list_id', 'quantity', 'unit', 'created_date', 'found_date']
|
KEYS: ClassVar[List[str]] = ['id', 'product_id', 'list_id', 'quantity', 'unit' ]
|
||||||
id: int = -1
|
id: int = -1
|
||||||
list_id: int
|
list_id: int
|
||||||
product_id: int
|
product_id: int
|
||||||
|
|
@ -34,14 +34,18 @@ class ShoppingListResult(BaseModel):
|
||||||
quantity: float
|
quantity: float
|
||||||
unit: str
|
unit: str
|
||||||
|
|
||||||
created_date: datetime = datetime.now()
|
from enum import Enum
|
||||||
found_date: Optional[datetime] = None
|
|
||||||
|
class StoreEnum(str, Enum):
|
||||||
|
woolworths = 'woolworths'
|
||||||
|
coles = 'coles'
|
||||||
|
home = ''
|
||||||
|
|
||||||
class ShoppingList(BaseModel):
|
class ShoppingList(BaseModel):
|
||||||
KEYS: ClassVar[List[str]] = ['id', 'created_date', 'purchased_date']
|
KEYS: ClassVar[List[str]] = ['id', 'created_date', 'store_name']
|
||||||
id: int = -1
|
id: int = -1
|
||||||
created_date: datetime = datetime.now()
|
created_date: datetime = datetime.now()
|
||||||
purchased_date: Optional[datetime] = None
|
store_name: StoreEnum = ''
|
||||||
|
|
||||||
requests: List[ShoppingListRequest] = []
|
requests: List[ShoppingListRequest] = []
|
||||||
results: List[ShoppingListResult] = []
|
results: List[ShoppingListResult] = []
|
||||||
|
|
@ -51,7 +55,7 @@ async def create(conn):
|
||||||
CREATE TABLE IF NOT EXISTS ShoppingList (
|
CREATE TABLE IF NOT EXISTS ShoppingList (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
created_date TEXT,
|
created_date TEXT,
|
||||||
purchased_date TEXT
|
store_name TEXT,
|
||||||
);''')
|
);''')
|
||||||
|
|
||||||
await conn.execute('''
|
await conn.execute('''
|
||||||
|
|
@ -75,8 +79,6 @@ async def create(conn):
|
||||||
list_id INTEGER,
|
list_id INTEGER,
|
||||||
quantity REAL,
|
quantity REAL,
|
||||||
unit TEXT,
|
unit TEXT,
|
||||||
created_date TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
found_date TEXT,
|
|
||||||
FOREIGN KEY(product_id) REFERENCES Product(id),
|
FOREIGN KEY(product_id) REFERENCES Product(id),
|
||||||
FOREIGN KEY(list_id) REFERENCES ShoppingList(id)
|
FOREIGN KEY(list_id) REFERENCES ShoppingList(id)
|
||||||
);''')
|
);''')
|
||||||
|
|
@ -99,9 +101,9 @@ def validate_request(request: ShoppingListRequest) -> None:
|
||||||
|
|
||||||
async def insert_shopping_list(conn, shopping_list: ShoppingList):
|
async def insert_shopping_list(conn, shopping_list: ShoppingList):
|
||||||
async with conn.execute('''
|
async with conn.execute('''
|
||||||
INSERT INTO ShoppingList (created_date, purchased_date)
|
INSERT INTO ShoppingList (created_date, store_name)
|
||||||
VALUES (CURRENT_TIMESTAMP, NULL)
|
VALUES (CURRENT_TIMESTAMP, ?)
|
||||||
''') as cursor:
|
''', (shopping_list.store_name,)) as cursor:
|
||||||
shopping_list.id = cursor.lastrowid
|
shopping_list.id = cursor.lastrowid
|
||||||
|
|
||||||
for request in shopping_list.requests:
|
for request in shopping_list.requests:
|
||||||
|
|
@ -129,59 +131,53 @@ async def insert_shopping_list(conn, shopping_list: ShoppingList):
|
||||||
item.list_id = shopping_list.id
|
item.list_id = shopping_list.id
|
||||||
|
|
||||||
async with conn.execute('''
|
async with conn.execute('''
|
||||||
INSERT INTO ShoppingListResult (product_id, list_id, quantity, unit, created_date, found_date)
|
INSERT INTO ShoppingListResult (product_id, list_id, quantity, unit)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
''', (item.product_id, item.list_id, item.quantity, item.unit, item.created_date, item.found_date)) as cursor:
|
''', (item.product_id, item.list_id, item.quantity, item.unit)) as cursor:
|
||||||
item.id = cursor.lastrowid
|
item.id = cursor.lastrowid
|
||||||
|
|
||||||
async def find_request(conn, id: int) -> Optional[ShoppingListRequest]:
|
async def remove_request(conn, request: ShoppingListRequest) -> None:
|
||||||
# Join Ingredient and Product to also load ingredient and product
|
if request.list_id != None:
|
||||||
product_keys = [f'product.{key}' for key in Product.KEYS]
|
raise ValueError('Request is already completed')
|
||||||
ingredient_keys = [f'ingredient.{key}' for key in Ingredient.KEYS]
|
|
||||||
person_keys = [f'person.{key}' for key in Person.KEYS]
|
|
||||||
request_keys = [f'shoppinglistrequest.{key}' for key in ShoppingListRequest.KEYS]
|
|
||||||
|
|
||||||
async with conn.execute(f'''
|
if request.meal and not request.meal_id:
|
||||||
SELECT {','.join(product_keys + ingredient_keys + person_keys + request_keys)}
|
raise ValueError('Meal request must have a meal id')
|
||||||
FROM ShoppingListRequest
|
|
||||||
LEFT JOIN Ingredient ON ShoppingListRequest.ingredient_id = Ingredient.id
|
|
||||||
LEFT JOIN Product ON Ingredient.product_id = Product.id
|
|
||||||
LEFT JOIN Person ON ShoppingListRequest.person_id = Person.id
|
|
||||||
WHERE ShoppingListRequest.id = ?
|
|
||||||
''', (id,)) as cursor:
|
|
||||||
async for row in cursor:
|
|
||||||
product_keys = {k:v for k,v in zip(Product.KEYS, row[:len(Product.KEYS)])}
|
|
||||||
product = Product(**product_keys) if product_keys['id'] else None
|
|
||||||
|
|
||||||
ingredient_keys = {k:v for k,v in zip(Ingredient.KEYS, row[len(Product.KEYS):len(Product.KEYS) + len(Ingredient.KEYS)])}
|
if request.meal_id != None:
|
||||||
ingredient = Ingredient(**ingredient_keys, product=product) if ingredient_keys['id'] else None
|
await conn.execute('''
|
||||||
|
DELETE FROM ShoppingListRequest
|
||||||
|
WHERE meal_id = ? AND list_id IS NULL
|
||||||
|
''', (request.meal_id,))
|
||||||
|
|
||||||
person_keys = {k:v for k,v in zip(Person.KEYS, row[-len(Person.KEYS):])}
|
elif request.person_id != None and request.ingredient_id != None:
|
||||||
person = Person(**person_keys) if person_keys['id'] else None
|
await conn.execute('''
|
||||||
|
DELETE FROM ShoppingListRequest
|
||||||
|
WHERE person_id = ? AND ingredient_id = ? AND list_id IS NULL
|
||||||
|
''', (request.person_id, request.ingredient_id))
|
||||||
|
|
||||||
request_keys = {k:v for k,v in zip(ShoppingListRequest.KEYS, row[len(Product.KEYS) + len(Ingredient.KEYS):-len(Person.KEYS)])}
|
else:
|
||||||
request = ShoppingListRequest(**request_keys, ingredient=ingredient, person=person)
|
raise ValueError('Request is invalid')
|
||||||
|
|
||||||
if request.meal_id is not None:
|
async def find_requests_by_list_id(conn, list_id: Optional[int]) -> AsyncIterator[ShoppingListRequest]:
|
||||||
request.meal = await find_meal_by_id(conn, request.meal_id)
|
|
||||||
|
|
||||||
return request
|
|
||||||
|
|
||||||
async def find_requests_by_list_id(conn, list_id: int) -> AsyncIterator[ShoppingListRequest]:
|
|
||||||
# Join Ingredient and Product to also load ingredient and product
|
# Join Ingredient and Product to also load ingredient and product
|
||||||
ingredient_keys = [f'ingredient.{key}' for key in Ingredient.KEYS]
|
ingredient_keys = [f'ingredient.{key}' for key in Ingredient.KEYS]
|
||||||
product_keys = [f'product.{key}' for key in Product.KEYS]
|
product_keys = [f'product.{key}' for key in Product.KEYS]
|
||||||
request_keys = [f'shoppinglistrequest.{key}' for key in ShoppingListRequest.KEYS]
|
request_keys = [f'shoppinglistrequest.{key}' for key in ShoppingListRequest.KEYS]
|
||||||
person_keys = [f'person.{key}' for key in Person.KEYS]
|
person_keys = [f'person.{key}' for key in Person.KEYS]
|
||||||
|
|
||||||
cursor = await conn.execute(f'''
|
select = f'''
|
||||||
SELECT {','.join(ingredient_keys + product_keys + request_keys + person_keys)}
|
SELECT {','.join(ingredient_keys + product_keys + request_keys + person_keys)}
|
||||||
FROM ShoppingListRequest
|
FROM ShoppingListRequest
|
||||||
LEFT JOIN Ingredient ON ShoppingListRequest.ingredient_id = Ingredient.id
|
LEFT JOIN Ingredient ON ShoppingListRequest.ingredient_id = Ingredient.id
|
||||||
LEFT JOIN Product ON Ingredient.product_id = Product.id
|
LEFT JOIN Product ON Ingredient.product_id = Product.id
|
||||||
LEFT JOIN Person ON ShoppingListRequest.person_id = Person.id
|
LEFT JOIN Person ON ShoppingListRequest.person_id = Person.id
|
||||||
WHERE list_id = ?
|
'''
|
||||||
''', (list_id,))
|
|
||||||
|
where, params = ' WHERE list_id IS NULL', ()
|
||||||
|
if list_id is not None:
|
||||||
|
where, params = ' WHERE list_id = ?', (list_id,)
|
||||||
|
|
||||||
|
cursor = await conn.execute(select + where, params)
|
||||||
|
|
||||||
async for row in cursor:
|
async for row in cursor:
|
||||||
product_keys = {k:v for k,v in zip(Product.KEYS, row[len(Ingredient.KEYS):len(Ingredient.KEYS) + len(Product.KEYS)])}
|
product_keys = {k:v for k,v in zip(Product.KEYS, row[len(Ingredient.KEYS):len(Ingredient.KEYS) + len(Product.KEYS)])}
|
||||||
|
|
@ -201,7 +197,7 @@ async def find_requests_by_list_id(conn, list_id: int) -> AsyncIterator[Shopping
|
||||||
|
|
||||||
yield request
|
yield request
|
||||||
|
|
||||||
async def find_items_by_list_id(conn, list_id: int) -> AsyncIterator[ShoppingListResult]:
|
async def find_results_by_list_id(conn, list_id: int) -> AsyncIterator[ShoppingListResult]:
|
||||||
product_keys = [f'product.{key}' for key in Product.KEYS]
|
product_keys = [f'product.{key}' for key in Product.KEYS]
|
||||||
result_keys = [f'shoppinglistresult.{key}' for key in ShoppingListResult.KEYS]
|
result_keys = [f'shoppinglistresult.{key}' for key in ShoppingListResult.KEYS]
|
||||||
|
|
||||||
|
|
@ -223,7 +219,7 @@ async def fill_related(conn, shopping_list: ShoppingList) -> ShoppingList:
|
||||||
async for request in find_requests_by_list_id(conn, shopping_list.id):
|
async for request in find_requests_by_list_id(conn, shopping_list.id):
|
||||||
shopping_list.requests.append(request)
|
shopping_list.requests.append(request)
|
||||||
|
|
||||||
async for item in find_items_by_list_id(conn, shopping_list.id):
|
async for item in find_results_by_list_id(conn, shopping_list.id):
|
||||||
shopping_list.results.append(item)
|
shopping_list.results.append(item)
|
||||||
|
|
||||||
async def load_shopping_list(conn, id: int) -> ShoppingList:
|
async def load_shopping_list(conn, id: int) -> ShoppingList:
|
||||||
|
|
@ -242,160 +238,57 @@ async def load_shopping_list(conn, id: int) -> ShoppingList:
|
||||||
|
|
||||||
return shopping_list
|
return shopping_list
|
||||||
|
|
||||||
async def _upcoming_meals(conn) -> AsyncIterator[Meal]:
|
async def request_ingredient(conn, person: Person, ingredient: Ingredient) -> ShoppingListRequest:
|
||||||
start = datetime.now()
|
if ingredient.id >= 0:
|
||||||
end = start + timedelta(days=7)
|
|
||||||
async for meal in find_upcoming_meals_by_date_range(conn, start, end):
|
|
||||||
if meal.purchase_date is None:
|
|
||||||
yield meal
|
|
||||||
|
|
||||||
async def current_shopping_list(conn) -> ShoppingList:
|
|
||||||
shopping_list = None
|
|
||||||
async with conn.execute(f'''
|
|
||||||
SELECT {','.join(ShoppingList.KEYS)} FROM ShoppingList
|
|
||||||
WHERE purchased_date IS NULL
|
|
||||||
LIMIT 1
|
|
||||||
''') as cursor:
|
|
||||||
async for row in cursor:
|
|
||||||
shopping_list = ShoppingList(**{k:v for k,v in zip(ShoppingList.KEYS, row)})
|
|
||||||
await fill_related(conn, shopping_list)
|
|
||||||
break
|
|
||||||
|
|
||||||
if not shopping_list:
|
|
||||||
shopping_list = ShoppingList()
|
|
||||||
|
|
||||||
async for meal in _upcoming_meals(conn):
|
|
||||||
shopping_list.requests.append(ShoppingListRequest(list_id=shopping_list.id, meal_id=meal.id, meal=meal, created_date=datetime.now(),))
|
|
||||||
|
|
||||||
await insert_shopping_list(conn, shopping_list)
|
|
||||||
|
|
||||||
return shopping_list
|
|
||||||
|
|
||||||
async def find_existing_result(conn, product: Product, shopping_list: ShoppingList) -> ShoppingListResult:
|
|
||||||
async with conn.execute(f'''
|
|
||||||
SELECT {','.join(ShoppingListResult.KEYS)} FROM ShoppingListResult
|
|
||||||
WHERE product_id = ? AND list_id = ?
|
|
||||||
LIMIT 1
|
|
||||||
''', (product.id, shopping_list.id)) as cursor:
|
|
||||||
async for row in cursor:
|
|
||||||
return ShoppingListResult(**{k:v for k,v in zip(ShoppingListResult.KEYS, row)}, product=product)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_persons_requests(conn, shopping_list: ShoppingList, person: Person) -> AsyncIterator[ShoppingListRequest]:
|
|
||||||
async for request in find_requests_by_list_id(conn, shopping_list.id):
|
|
||||||
if request.person_id == person.id:
|
|
||||||
yield request
|
|
||||||
|
|
||||||
async def request_ingredient(conn, shopping_list: ShoppingList, person: Person, ingredient: Ingredient) -> ShoppingListRequest:
|
|
||||||
if ingredient.id:
|
|
||||||
raise ValueError('How did you get an existing ingredient?')
|
raise ValueError('How did you get an existing ingredient?')
|
||||||
|
|
||||||
await insert_ingredient(conn, ingredient)
|
await insert_ingredient(conn, ingredient)
|
||||||
|
|
||||||
request = ShoppingListRequest(ingredient_id=ingredient.id, ingredient=ingredient, list_id=shopping_list.id, person_id=person.id, created_date=datetime.now())
|
request = ShoppingListRequest(ingredient_id=ingredient.id, ingredient=ingredient, person_id=person.id, created_date=datetime.now())
|
||||||
async with conn.execute('''
|
async with conn.execute('''
|
||||||
INSERT INTO ShoppingListRequest (ingredient_id, list_id, person_id, created_date)
|
INSERT INTO ShoppingListRequest (ingredient_id, person_id, created_date)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
''', (request.ingredient_id, request.list_id, request.person_id, request.created_date)) as cursor:
|
''', (request.ingredient_id, request.person_id, request.created_date)) as cursor:
|
||||||
request.id = cursor.lastrowid
|
|
||||||
|
|
||||||
shopping_list.requests.append(request)
|
|
||||||
return request
|
|
||||||
|
|
||||||
async def request_meal(conn, shopping_list: ShoppingList, person: Person, meal: Meal) -> ShoppingListRequest:
|
|
||||||
request = ShoppingListRequest(meal_id=meal.id, meal=meal, list_id=shopping_list.id, person_id=person.id, created_date=datetime.now())
|
|
||||||
|
|
||||||
async with conn.execute('''
|
|
||||||
INSERT INTO ShoppingListRequest (meal_id, list_id, person_id, created_date)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
''', (request.meal_id, request.list_id, request.person_id, request.created_date)) as cursor:
|
|
||||||
request.id = cursor.lastrowid
|
request.id = cursor.lastrowid
|
||||||
|
|
||||||
return request
|
return request
|
||||||
|
|
||||||
async def delete_requests(conn, shopping_list: ShoppingList, meal: Meal) -> None:
|
async def request_meal(conn, person: Person, meal: Meal) -> ShoppingListRequest:
|
||||||
|
request = ShoppingListRequest(meal_id=meal.id, meal=meal, person_id=person.id, created_date=datetime.now())
|
||||||
|
|
||||||
|
async with conn.execute('''
|
||||||
|
INSERT INTO ShoppingListRequest (meal_id, person_id, created_date)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
''', (request.meal_id, request.person_id, request.created_date)) as cursor:
|
||||||
|
request.id = cursor.lastrowid
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
async def unrequest_meal(conn, meal: Meal) -> None:
|
||||||
await conn.execute('''
|
await conn.execute('''
|
||||||
DELETE FROM ShoppingListRequest
|
DELETE FROM ShoppingListRequest
|
||||||
WHERE list_id = ? AND meal_id = ?
|
WHERE list_id IS NULL AND meal_id = ?
|
||||||
''', (shopping_list.id, meal.id))
|
''', (meal.id,))
|
||||||
|
|
||||||
async def sync_persons_requested_ingredients(conn, shopping_list: ShoppingList, person: Person, requests: List[Ingredient]) -> AsyncIterator[ShoppingListRequest]:
|
def get_current_requests(conn) -> AsyncIterator[ShoppingListRequest]:
|
||||||
|
return find_requests_by_list_id(conn, None)
|
||||||
|
|
||||||
|
async def sync_persons_requested_ingredients(conn, person: Person, requests: List[Ingredient]) -> AsyncIterator[ShoppingListRequest]:
|
||||||
# Delete existing and insert all as new
|
# Delete existing and insert all as new
|
||||||
await conn.execute('''
|
await conn.execute('''
|
||||||
DELETE FROM ShoppingListRequest
|
DELETE FROM ShoppingListRequest
|
||||||
WHERE list_id = ? AND person_id = ? AND ingredient_id IS NOT NULL
|
WHERE list_id IS NULL AND person_id = ? AND ingredient_id IS NOT NULL
|
||||||
''', (shopping_list.id, person.id))
|
''', (person.id,))
|
||||||
|
|
||||||
for ingredient in requests:
|
for ingredient in requests:
|
||||||
ingredient.id = 0
|
ingredient.id = -1
|
||||||
yield await request_ingredient(conn, shopping_list, person, ingredient)
|
yield await request_ingredient(conn, person, ingredient)
|
||||||
|
|
||||||
async def find_existing_result(conn, shopping_list: ShoppingList, product: Product, unit: str) -> Optional[ShoppingListResult]:
|
|
||||||
product_keys = [f'product.{key}' for key in Product.KEYS]
|
|
||||||
result_keys = [f'shoppinglistresult.{key}' for key in ShoppingListResult.KEYS]
|
|
||||||
|
|
||||||
async with conn.execute(f'''
|
|
||||||
SELECT {','.join(product_keys + result_keys)}
|
|
||||||
FROM ShoppingListResult
|
|
||||||
LEFT JOIN Product ON ShoppingListResult.product_id = Product.id
|
|
||||||
WHERE ShoppingListResult.list_id = ? AND ShoppingListResult.product_id = ? AND ShoppingListResult.unit = ?
|
|
||||||
LIMIT 1
|
|
||||||
''', (shopping_list.id, product.id, unit)) as cursor:
|
|
||||||
async for row in cursor:
|
|
||||||
product_keys = {k:v for k,v in zip(Product.KEYS, row[:len(Product.KEYS)])}
|
|
||||||
product = Product(**product_keys) if product_keys['id'] else None
|
|
||||||
|
|
||||||
result_keys = {k:v for k,v in zip(ShoppingListResult.KEYS, row[len(Product.KEYS):])}
|
|
||||||
return ShoppingListResult(**result_keys, product=product)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def mark_found(conn, ingredient: Ingredient, date_found: datetime) -> Tuple[ShoppingListResult, ShoppingListResult]:
|
|
||||||
product, quantity, unit = ingredient.product, ingredient.quantity, ingredient.unit
|
|
||||||
shopping_list = await current_shopping_list(conn)
|
|
||||||
|
|
||||||
created = ShoppingListResult(product=product, product_id=product.id, list_id=shopping_list.id, quantity=quantity, unit=unit, found_date=date_found)
|
|
||||||
removed = await find_existing_result(conn, shopping_list, product, unit)
|
|
||||||
|
|
||||||
|
async def get_shopping_list_with_meal(conn, meal_id: int) -> AsyncIterator[ShoppingList]:
|
||||||
async with conn.execute('''
|
async with conn.execute('''
|
||||||
INSERT INTO ShoppingListResult (product_id, list_id, quantity, unit, created_date, found_date)
|
SELECT list_id FROM ShoppingListRequest
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
WHERE meal_id = ? AND list_id IS NOT NULL
|
||||||
''', (created.product_id, created.list_id, quantity, unit, created.created_date, created.found_date)) as cursor:
|
''', (meal_id,)) as cursor:
|
||||||
created.id = cursor.lastrowid
|
|
||||||
|
|
||||||
if removed:
|
|
||||||
shopping_list.results = [r for r in shopping_list.results if r.id != removed.id]
|
|
||||||
await conn.execute('''
|
|
||||||
DELETE FROM ShoppingListResult
|
|
||||||
WHERE id = ?
|
|
||||||
''', (removed.id,))
|
|
||||||
|
|
||||||
return removed, created
|
|
||||||
|
|
||||||
async def unmark_found(conn, product_id: int) -> List[ShoppingListRequest]:
|
|
||||||
shopping_list = await current_shopping_list(conn)
|
|
||||||
|
|
||||||
requests = []
|
|
||||||
async with conn.execute('''
|
|
||||||
DELETE FROM ShoppingListResult
|
|
||||||
WHERE list_id = ? AND product_id = ?
|
|
||||||
''', (shopping_list.id, product_id)) as cursor:
|
|
||||||
async for row in cursor:
|
async for row in cursor:
|
||||||
requests.append(row)
|
list_id = row[0]
|
||||||
|
yield await load_shopping_list(conn, list_id)
|
||||||
deleted = [r for r in shopping_list.results if r.product_id == product_id]
|
|
||||||
shopping_list.results = [r for r in shopping_list.results if r.product_id != product_id]
|
|
||||||
return deleted
|
|
||||||
|
|
||||||
async def mark_purchased(conn) -> ShoppingList:
|
|
||||||
shopping_list = await current_shopping_list(conn)
|
|
||||||
shopping_list.purchased_date = datetime.now()
|
|
||||||
|
|
||||||
await conn.execute('''
|
|
||||||
UPDATE ShoppingList
|
|
||||||
SET purchased_date = ?
|
|
||||||
WHERE id = ?
|
|
||||||
''', (shopping_list.purchased_date, shopping_list.id))
|
|
||||||
|
|
||||||
return shopping_list
|
|
||||||
11
transform.sql
Normal file
11
transform.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
alter table ShoppingList
|
||||||
|
add store_name text not null default '';
|
||||||
|
|
||||||
|
alter table ShoppingListResult
|
||||||
|
drop created_date;
|
||||||
|
|
||||||
|
alter table ShoppingListResult
|
||||||
|
drop found_date;
|
||||||
|
|
||||||
|
alter table Meal
|
||||||
|
add purchase_date TEXT DEFAULT NULL;
|
||||||
Loading…
Reference in a new issue