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:
jableader 2024-10-13 19:19:57 +11:00
parent 0fdd0d10c2
commit 78e94a3503
5 changed files with 141 additions and 252 deletions

83
main.py
View file

@ -246,60 +246,49 @@ async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db))
await conn.commit()
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}")
async def get_shopping_list(list_id: Union[int, str], 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'})
async def get_shopping_list(list_id: int, conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList:
return await shopping.load_shopping_list(conn, list_id)
@app.post("/api/shopping/current/purchased")
async def mark_purchased(conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> shopping.ShoppingList:
response = await shopping.mark_purchased(conn)
class ShoppingListPurchase(shopping.ShoppingList):
completed_requests: List[shopping.ShoppingListRequest] = []
# Its easier to make the next shopping list now, while we know calling it requires commit()
await shopping.current_shopping_list(conn)
@app.post("/api/shopping/")
async def purchase_ingredients(lst: ShoppingListPurchase, conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList:
await shopping.insert_shopping_list(conn, lst)
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)
await shopping.remove_request(conn, request)
await conn.commit()
return response
class FoundResult(BaseModel):
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()
return result
@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
return lst
@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]:
current = await shopping.current_shopping_list(conn)
return [r async for r in shopping.get_persons_requests(conn, current, person) if r.ingredient]
return [r async for r in shopping.get_current_requests(conn) if r.ingredient and r.person_id == person.id]
@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]:
current = await shopping.current_shopping_list(conn)
result = [r async for r in shopping.sync_persons_requested_ingredients(conn, current, person, requests) if r.ingredient]
result = [r async for r in shopping.sync_persons_requested_ingredients(conn, person, requests) if r.ingredient]
await conn.commit()
return result
@ -308,23 +297,21 @@ class MealIdWrapper(BaseModel):
@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:
current = await shopping.current_shopping_list(conn)
meal = await meals.find_meal_by_id(conn, r.meal_id)
if not meal:
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()
return response
@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:
current = await shopping.current_shopping_list(conn)
meal = await meals.find_meal_by_id(conn, meal_id)
if not meal:
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()
return {}

View file

@ -17,7 +17,7 @@ class MealRecipe(BaseModel):
recipe: Optional[Recipe] = None
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
suggested_date: datetime.datetime
consumed_date: Optional[datetime.datetime] = None
@ -37,7 +37,8 @@ async def create(conn):
id INTEGER PRIMARY KEY,
suggested_date TEXT,
consumed_date TEXT,
deleted_date TEXT DEFAULT NULL
deleted_date TEXT DEFAULT NULL,
purchase_date TEXT DEFAULT NULL
);''')
await conn.execute('''
@ -113,7 +114,7 @@ async def find_meal_by_id(conn, meal_id: int) -> Meal:
LIMIT 1
''', (meal_id,)) as 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_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
''', (start, end)) as 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 with conn.execute(f'''
@ -213,16 +214,13 @@ async def mark_consumed(conn, meal: Meal, date: datetime.datetime = datetime.dat
WHERE id = ?
''', (date, meal.id))
async def with_purchase_date(conn, meal: Meal) -> Meal:
async with conn.execute('''
SELECT purchased_date FROM ShoppingList
WHERE id = (
SELECT list_id FROM ShoppingListRequest
WHERE meal_id = ?
LIMIT 1
)
''', (meal.id,)) as cursor:
async for row in cursor:
meal.purchase_date = row[0]
return meal
async def mark_purchased(conn, meal: Meal) -> Meal:
meal.purchase_date = datetime.datetime.now()
await conn.execute('''
UPDATE Meal
SET purchase_date = ?
WHERE id = ?
''', (meal.purchase_date, meal.id))
return meal

View file

@ -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

View file

@ -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 persons import Person
from products import Product
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):
KEYS: ClassVar[List[str]] = ['id', 'ingredient_id', 'list_id', 'person_id', 'meal_id', 'created_date']
id: int = -1
list_id: int
list_id: Optional[int] = None
ingredient_id: Optional[int] = None
ingredient: Optional[Ingredient] = None
@ -25,7 +25,7 @@ class ShoppingListRequest(BaseModel):
created_date: datetime = datetime.now()
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
list_id: int
product_id: int
@ -34,14 +34,18 @@ class ShoppingListResult(BaseModel):
quantity: float
unit: str
created_date: datetime = datetime.now()
found_date: Optional[datetime] = None
from enum import Enum
class StoreEnum(str, Enum):
woolworths = 'woolworths'
coles = 'coles'
home = ''
class ShoppingList(BaseModel):
KEYS: ClassVar[List[str]] = ['id', 'created_date', 'purchased_date']
KEYS: ClassVar[List[str]] = ['id', 'created_date', 'store_name']
id: int = -1
created_date: datetime = datetime.now()
purchased_date: Optional[datetime] = None
store_name: StoreEnum = ''
requests: List[ShoppingListRequest] = []
results: List[ShoppingListResult] = []
@ -51,7 +55,7 @@ async def create(conn):
CREATE TABLE IF NOT EXISTS ShoppingList (
id INTEGER PRIMARY KEY,
created_date TEXT,
purchased_date TEXT
store_name TEXT,
);''')
await conn.execute('''
@ -75,8 +79,6 @@ async def create(conn):
list_id INTEGER,
quantity REAL,
unit TEXT,
created_date TEXT DEFAULT CURRENT_TIMESTAMP,
found_date TEXT,
FOREIGN KEY(product_id) REFERENCES Product(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 with conn.execute('''
INSERT INTO ShoppingList (created_date, purchased_date)
VALUES (CURRENT_TIMESTAMP, NULL)
''') as cursor:
INSERT INTO ShoppingList (created_date, store_name)
VALUES (CURRENT_TIMESTAMP, ?)
''', (shopping_list.store_name,)) as cursor:
shopping_list.id = cursor.lastrowid
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
async with conn.execute('''
INSERT INTO ShoppingListResult (product_id, list_id, quantity, unit, created_date, found_date)
VALUES (?, ?, ?, ?, ?, ?)
''', (item.product_id, item.list_id, item.quantity, item.unit, item.created_date, item.found_date)) as cursor:
INSERT INTO ShoppingListResult (product_id, list_id, quantity, unit)
VALUES (?, ?, ?, ?)
''', (item.product_id, item.list_id, item.quantity, item.unit)) as cursor:
item.id = cursor.lastrowid
async def find_request(conn, id: int) -> Optional[ShoppingListRequest]:
# Join Ingredient and Product to also load ingredient and product
product_keys = [f'product.{key}' for key in Product.KEYS]
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 def remove_request(conn, request: ShoppingListRequest) -> None:
if request.list_id != None:
raise ValueError('Request is already completed')
if request.meal and not request.meal_id:
raise ValueError('Meal request must have a meal id')
if request.meal_id != None:
await conn.execute('''
DELETE FROM ShoppingListRequest
WHERE meal_id = ? AND list_id IS NULL
''', (request.meal_id,))
async with conn.execute(f'''
SELECT {','.join(product_keys + ingredient_keys + person_keys + request_keys)}
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
elif request.person_id != None and request.ingredient_id != None:
await conn.execute('''
DELETE FROM ShoppingListRequest
WHERE person_id = ? AND ingredient_id = ? AND list_id IS NULL
''', (request.person_id, request.ingredient_id))
ingredient_keys = {k:v for k,v in zip(Ingredient.KEYS, row[len(Product.KEYS):len(Product.KEYS) + len(Ingredient.KEYS)])}
ingredient = Ingredient(**ingredient_keys, product=product) if ingredient_keys['id'] else None
else:
raise ValueError('Request is invalid')
person_keys = {k:v for k,v in zip(Person.KEYS, row[-len(Person.KEYS):])}
person = Person(**person_keys) if person_keys['id'] else None
request_keys = {k:v for k,v in zip(ShoppingListRequest.KEYS, row[len(Product.KEYS) + len(Ingredient.KEYS):-len(Person.KEYS)])}
request = ShoppingListRequest(**request_keys, ingredient=ingredient, person=person)
if request.meal_id is not None:
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]:
async def find_requests_by_list_id(conn, list_id: Optional[int]) -> AsyncIterator[ShoppingListRequest]:
# Join Ingredient and Product to also load ingredient and product
ingredient_keys = [f'ingredient.{key}' for key in Ingredient.KEYS]
product_keys = [f'product.{key}' for key in Product.KEYS]
request_keys = [f'shoppinglistrequest.{key}' for key in ShoppingListRequest.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)}
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 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:
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
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]
result_keys = [f'shoppinglistresult.{key}' for key in ShoppingListResult.KEYS]
@ -223,9 +219,9 @@ async def fill_related(conn, shopping_list: ShoppingList) -> ShoppingList:
async for request in find_requests_by_list_id(conn, shopping_list.id):
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)
async def load_shopping_list(conn, id: int) -> ShoppingList:
shopping_list = None
async with conn.execute(f'''
@ -242,160 +238,57 @@ async def load_shopping_list(conn, id: int) -> ShoppingList:
return shopping_list
async def _upcoming_meals(conn) -> AsyncIterator[Meal]:
start = datetime.now()
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:
async def request_ingredient(conn, person: Person, ingredient: Ingredient) -> ShoppingListRequest:
if ingredient.id >= 0:
raise ValueError('How did you get an existing 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('''
INSERT INTO ShoppingListRequest (ingredient_id, list_id, person_id, created_date)
VALUES (?, ?, ?, ?)
''', (request.ingredient_id, request.list_id, request.person_id, request.created_date)) as cursor:
INSERT INTO ShoppingListRequest (ingredient_id, person_id, created_date)
VALUES (?, ?, ?)
''', (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 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, list_id, person_id, created_date)
VALUES (?, ?, ?, ?)
''', (request.meal_id, request.list_id, request.person_id, request.created_date)) as cursor:
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 delete_requests(conn, shopping_list: ShoppingList, meal: Meal) -> None:
async def unrequest_meal(conn, meal: Meal) -> None:
await conn.execute('''
DELETE FROM ShoppingListRequest
WHERE list_id = ? AND meal_id = ?
''', (shopping_list.id, meal.id))
WHERE list_id IS NULL AND 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
await conn.execute('''
DELETE FROM ShoppingListRequest
WHERE list_id = ? AND person_id = ? AND ingredient_id IS NOT NULL
''', (shopping_list.id, person.id))
WHERE list_id IS NULL AND person_id = ? AND ingredient_id IS NOT NULL
''', (person.id,))
for ingredient in requests:
ingredient.id = 0
yield await request_ingredient(conn, shopping_list, 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)
ingredient.id = -1
yield await request_ingredient(conn, person, ingredient)
async def get_shopping_list_with_meal(conn, meal_id: int) -> AsyncIterator[ShoppingList]:
async with conn.execute('''
INSERT INTO ShoppingListResult (product_id, list_id, quantity, unit, created_date, found_date)
VALUES (?, ?, ?, ?, ?, ?)
''', (created.product_id, created.list_id, quantity, unit, created.created_date, created.found_date)) 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:
SELECT list_id FROM ShoppingListRequest
WHERE meal_id = ? AND list_id IS NOT NULL
''', (meal_id,)) as cursor:
async for row in cursor:
requests.append(row)
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
list_id = row[0]
yield await load_shopping_list(conn, list_id)

11
transform.sql Normal file
View 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;