fixing tracker food edits

This commit is contained in:
2025-10-01 08:28:48 -07:00
parent a2e5c91409
commit c31d97b527
3 changed files with 80 additions and 20 deletions

View File

@@ -367,19 +367,23 @@ async def tracker_reset_to_plan(request: Request, db: Session = Depends(get_db))
@router.get("/tracker/get_tracked_meal_foods/{tracked_meal_id}") @router.get("/tracker/get_tracked_meal_foods/{tracked_meal_id}")
async def get_tracked_meal_foods(tracked_meal_id: int, db: Session = Depends(get_db)): async def get_tracked_meal_foods(tracked_meal_id: int, db: Session = Depends(get_db)):
"""Get foods associated with a tracked meal""" """Get foods associated with a tracked meal"""
logging.info(f"DEBUG: get_tracked_meal_foods called for tracked_meal_id: {tracked_meal_id}")
try: try:
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first() tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
logging.info(f"DEBUG: Tracked meal found: {tracked_meal.id if tracked_meal else 'None'}")
if not tracked_meal: if not tracked_meal:
raise HTTPException(status_code=404, detail="Tracked meal not found") raise HTTPException(status_code=404, detail="Tracked meal not found")
# Load the associated Meal and its foods # Load the associated Meal and its foods
meal = db.query(Meal).options(joinedload(Meal.meal_foods).joinedload(MealFood.food)).filter(Meal.id == tracked_meal.meal_id).first() meal = db.query(Meal).options(joinedload(Meal.meal_foods).joinedload(MealFood.food)).filter(Meal.id == tracked_meal.meal_id).first()
logging.info(f"DEBUG: Associated meal found: {meal.id if meal else 'None'}")
if not meal: if not meal:
raise HTTPException(status_code=404, detail="Associated meal not found") raise HTTPException(status_code=404, detail="Associated meal not found")
# Load custom tracked foods for this tracked meal # Load custom tracked foods for this tracked meal
tracked_foods = db.query(TrackedMealFood).options(joinedload(TrackedMealFood.food)).filter(TrackedMealFood.tracked_meal_id == tracked_meal_id).all() tracked_foods = db.query(TrackedMealFood).options(joinedload(TrackedMealFood.food)).filter(TrackedMealFood.tracked_meal_id == tracked_meal_id).all()
logging.info(f"DEBUG: Found {len(tracked_foods)} custom tracked foods.")
# Combine foods from the base meal and custom tracked foods, handling overrides # Combine foods from the base meal and custom tracked foods, handling overrides
meal_foods_data = [] meal_foods_data = []
@@ -387,6 +391,7 @@ async def get_tracked_meal_foods(tracked_meal_id: int, db: Session = Depends(get
# Keep track of food_ids that have been overridden by TrackedMealFood entries # Keep track of food_ids that have been overridden by TrackedMealFood entries
# These should not be added from the base meal definition # These should not be added from the base meal definition
overridden_food_ids = {tf.food_id for tf in tracked_foods} overridden_food_ids = {tf.food_id for tf in tracked_foods}
logging.info(f"DEBUG: Overridden food IDs: {overridden_food_ids}")
for meal_food in meal.meal_foods: for meal_food in meal.meal_foods:
# Only add meal_food if it hasn't been overridden by a TrackedMealFood # Only add meal_food if it hasn't been overridden by a TrackedMealFood
@@ -401,6 +406,8 @@ async def get_tracked_meal_foods(tracked_meal_id: int, db: Session = Depends(get
"is_custom": False "is_custom": False
}) })
logging.info(f"DEBUG: Added {len(meal_foods_data)} meal foods (excluding overridden).")
for tracked_food in tracked_foods: for tracked_food in tracked_foods:
meal_foods_data.append({ meal_foods_data.append({
"id": tracked_food.id, "id": tracked_food.id,
@@ -411,6 +418,8 @@ async def get_tracked_meal_foods(tracked_meal_id: int, db: Session = Depends(get
"serving_size": tracked_food.food.serving_size, "serving_size": tracked_food.food.serving_size,
"is_custom": True "is_custom": True
}) })
logging.info(f"DEBUG: Added {len(tracked_foods)} custom tracked foods.")
logging.info(f"DEBUG: Total meal foods data items: {len(meal_foods_data)}")
return {"status": "success", "meal_foods": meal_foods_data} return {"status": "success", "meal_foods": meal_foods_data}
@@ -463,11 +472,14 @@ async def add_food_to_tracked_meal(data: dict = Body(...), db: Session = Depends
@router.post("/tracker/update_tracked_meal_foods") @router.post("/tracker/update_tracked_meal_foods")
async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depends(get_db)): async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depends(get_db)):
"""Update quantities of multiple foods in a tracked meal""" """Update quantities of multiple foods in a tracked meal"""
logging.info(f"DEBUG: update_tracked_meal_foods called for tracked_meal_id: {data.get('tracked_meal_id')}")
try: try:
tracked_meal_id = data.get("tracked_meal_id") tracked_meal_id = data.get("tracked_meal_id")
foods_data = data.get("foods", []) foods_data = data.get("foods", [])
logging.info(f"DEBUG: Foods data received: {foods_data}")
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first() tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
logging.info(f"DEBUG: Tracked meal found: {tracked_meal.id if tracked_meal else 'None'}")
if not tracked_meal: if not tracked_meal:
raise HTTPException(status_code=404, detail="Tracked meal not found") raise HTTPException(status_code=404, detail="Tracked meal not found")
@@ -476,6 +488,7 @@ async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depend
grams = float(food_data.get("quantity", 1.0)) # Assuming quantity is now grams grams = float(food_data.get("quantity", 1.0)) # Assuming quantity is now grams
is_custom = food_data.get("is_custom", False) is_custom = food_data.get("is_custom", False)
item_id = food_data.get("id") # This could be MealFood.id or TrackedMealFood.id item_id = food_data.get("id") # This could be MealFood.id or TrackedMealFood.id
logging.info(f"DEBUG: Processing food_id: {food_id}, quantity: {grams}, is_custom: {is_custom}, item_id: {item_id}")
quantity = grams quantity = grams
@@ -483,6 +496,7 @@ async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depend
tracked_food = db.query(TrackedMealFood).filter(TrackedMealFood.id == item_id).first() tracked_food = db.query(TrackedMealFood).filter(TrackedMealFood.id == item_id).first()
if tracked_food: if tracked_food:
tracked_food.quantity = quantity tracked_food.quantity = quantity
logging.info(f"DEBUG: Updated existing custom tracked food {item_id} to quantity {quantity}")
else: else:
# If it's a new custom food being added # If it's a new custom food being added
new_tracked_food = TrackedMealFood( new_tracked_food = TrackedMealFood(
@@ -491,6 +505,7 @@ async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depend
quantity=quantity quantity=quantity
) )
db.add(new_tracked_food) db.add(new_tracked_food)
logging.info(f"DEBUG: Added new custom tracked food for food_id {food_id} with quantity {quantity}")
else: else:
# This is a food from the original meal definition # This is a food from the original meal definition
# We need to check if it's already a TrackedMealFood (meaning it was overridden) # We need to check if it's already a TrackedMealFood (meaning it was overridden)
@@ -499,15 +514,18 @@ async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depend
TrackedMealFood.tracked_meal_id == tracked_meal.id, TrackedMealFood.tracked_meal_id == tracked_meal.id,
TrackedMealFood.food_id == food_id TrackedMealFood.food_id == food_id
).first() ).first()
logging.info(f"DEBUG: Checking for existing TrackedMealFood for food_id {food_id}: {existing_tracked_food.id if existing_tracked_food else 'None'}")
if existing_tracked_food: if existing_tracked_food:
existing_tracked_food.quantity = quantity existing_tracked_food.quantity = quantity
logging.info(f"DEBUG: Updated existing TrackedMealFood {existing_tracked_food.id} (override) to quantity {quantity}")
else: else:
# If it's not a TrackedMealFood, it must be a MealFood # If it's not a TrackedMealFood, it must be a MealFood
meal_food = db.query(MealFood).filter( meal_food = db.query(MealFood).filter(
MealFood.meal_id == tracked_meal.meal_id, MealFood.meal_id == tracked_meal.meal_id,
MealFood.food_id == food_id MealFood.food_id == food_id
).first() ).first()
logging.info(f"DEBUG: Checking for existing MealFood for food_id {food_id}: {meal_food.id if meal_food else 'None'}")
if meal_food: if meal_food:
# If quantity changed, convert to TrackedMealFood # If quantity changed, convert to TrackedMealFood
# NOTE: meal_food.quantity is already a multiplier, # NOTE: meal_food.quantity is already a multiplier,
@@ -522,6 +540,9 @@ async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depend
) )
db.add(new_tracked_food) db.add(new_tracked_food)
db.delete(meal_food) # Remove original MealFood db.delete(meal_food) # Remove original MealFood
logging.info(f"DEBUG: Converted MealFood {meal_food.id} to new TrackedMealFood for food_id {food_id} with quantity {quantity} and deleted original MealFood.")
else:
logging.info(f"DEBUG: MealFood {meal_food.id} quantity unchanged, no override needed.")
else: else:
# This case should ideally not happen if data is consistent, # This case should ideally not happen if data is consistent,
# but as a fallback, add as a new TrackedMealFood # but as a fallback, add as a new TrackedMealFood
@@ -531,6 +552,7 @@ async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depend
quantity=quantity quantity=quantity
) )
db.add(new_tracked_food) db.add(new_tracked_food)
logging.warning(f"DEBUG: Fallback: Added new TrackedMealFood for food_id {food_id} with quantity {quantity}. Original MealFood not found.")
# Mark the tracked day as modified # Mark the tracked day as modified
tracked_meal.tracked_day.is_modified = True tracked_meal.tracked_day.is_modified = True

View File

@@ -184,9 +184,11 @@
// Add meal to specific time // Add meal to specific time
function addMealToTime(mealTime) { function addMealToTime(mealTime) {
console.log('addMealToTime called with:', mealTime);
document.getElementById('mealTimeDisplay').textContent = mealTime; document.getElementById('mealTimeDisplay').textContent = mealTime;
document.getElementById('addMealTime').value = mealTime; document.getElementById('addMealTime').value = mealTime;
new bootstrap.Modal(document.getElementById('addMealModal')).show(); new bootstrap.Modal(document.getElementById('addMealModal')).show();
console.log('addMealModal should be shown.');
} }
// Submit add meal form // Submit add meal form
@@ -327,10 +329,12 @@
// Load foods for editing // Load foods for editing
async function loadTrackedMealFoods(trackedMealId) { async function loadTrackedMealFoods(trackedMealId) {
console.log('loadTrackedMealFoods called with:', trackedMealId);
document.getElementById('tracked_meal_id_for_food').value = trackedMealId; document.getElementById('tracked_meal_id_for_food').value = trackedMealId;
try { try {
const response = await fetch(`/tracker/get_tracked_meal_foods/${trackedMealId}`); const response = await fetch(`/tracker/get_tracked_meal_foods/${trackedMealId}`);
const data = await response.json(); const data = await response.json();
console.log('Response from get_tracked_meal_foods:', data);
const container = document.getElementById('editMealFoodsList'); const container = document.getElementById('editMealFoodsList');
container.innerHTML = ''; container.innerHTML = '';
@@ -338,6 +342,7 @@
if (data.status === 'success') { if (data.status === 'success') {
if (data.meal_foods.length === 0) { if (data.meal_foods.length === 0) {
container.innerHTML = '<em>No foods added yet</em>'; container.innerHTML = '<em>No foods added yet</em>';
console.log('No foods found for tracked meal.');
} else { } else {
data.meal_foods.forEach(food => { data.meal_foods.forEach(food => {
const foodDiv = document.createElement('div'); const foodDiv = document.createElement('div');
@@ -345,7 +350,7 @@
foodDiv.innerHTML = ` foodDiv.innerHTML = `
<span>${food.food_name}</span> <span>${food.food_name}</span>
<div class="input-group w-50"> <div class="input-group w-50">
<input type="number" step="0.01" class="form-control form-control-sm" value="${food.grams.toFixed(2)}" data-food-id="${food.food_id}" data-item-id="${food.id}" data-is-custom="${food.is_custom}"> <input type="number" step="0.01" class="form-control form-control-sm" value="${food.quantity.toFixed(2)}" data-food-id="${food.food_id}" data-item-id="${food.id}" data-is-custom="${food.is_custom}">
<span class="input-group-text">g</span> <span class="input-group-text">g</span>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeFoodFromTrackedMeal(${food.id}, ${food.is_custom})"> <button type="button" class="btn btn-sm btn-outline-danger" onclick="removeFoodFromTrackedMeal(${food.id}, ${food.is_custom})">
<i class="bi bi-trash"></i> <i class="bi bi-trash"></i>
@@ -353,8 +358,11 @@
</div> </div>
`; `;
container.appendChild(foodDiv); container.appendChild(foodDiv);
console.log('Added food to modal:', food.food_name, 'Quantity:', food.quantity);
}); });
} }
} else {
console.error('Error status in response:', data.message);
} }
} catch (error) { } catch (error) {
console.error('Error loading tracked meal foods:', error); console.error('Error loading tracked meal foods:', error);

View File

@@ -87,45 +87,75 @@ def test_get_tracked_meal_foods_endpoint(client: TestClient, session: TestingSes
elif food_data["food_name"] == "Banana": elif food_data["food_name"] == "Banana":
assert food_data["quantity"] == 100.0 assert food_data["quantity"] == 100.0
def test_get_tracked_meal_foods_after_override(client: TestClient, session: TestingSessionLocal): def test_edit_tracked_meal_with_override_flow(client: TestClient, session: TestingSessionLocal):
""" """
Test retrieving foods for a tracked meal after one of its MealFoods has been overridden Test the full flow of editing a tracked meal, overriding a food, and then retrieving its foods.
and converted to a TrackedMealFood. This simulates the bug where the original MealFood This test aims to reproduce the "Error loading tracked meal foods" bug.
might be deleted, causing issues when fetching foods.
""" """
food1, food2, meal1, tracked_day, tracked_meal = create_test_data(session) food1, food2, meal1, tracked_day, tracked_meal = create_test_data(session)
# Manually create a TrackedMealFood to override food1 # 1. Simulate adding a meal (already done by create_test_data, so tracked_meal exists)
tracked_meal_food1 = TrackedMealFood(tracked_meal_id=tracked_meal.id, food_id=food1.id, quantity=175.0, is_override=True) # 2. Simulate updating a food in the tracked meal to create an override
session.add(tracked_meal_food1) # This will call /tracker/update_tracked_meal_foods
session.commit()
session.refresh(tracked_meal_food1) # Get the original MealFood for food1
original_meal_food1 = session.query(MealFood).filter(
# Manually delete the original MealFood for food1
meal_food_to_delete = session.query(MealFood).filter(
MealFood.meal_id == meal1.id, MealFood.meal_id == meal1.id,
MealFood.food_id == food1.id MealFood.food_id == food1.id
).first() ).first()
if meal_food_to_delete: assert original_meal_food1 is not None
session.delete(meal_food_to_delete)
session.commit() # Prepare update data: update food1 quantity (should create a TrackedMealFood and delete original MealFood)
updated_foods_data = [
{"id": original_meal_food1.id, "food_id": food1.id, "quantity": 175.0, "is_custom": False}, # Original MealFood, but quantity changed
{"id": None, "food_id": food2.id, "quantity": 100.0, "is_custom": False} # Unchanged original MealFood
]
response_update = client.post(
"/tracker/update_tracked_meal_foods",
json={
"tracked_meal_id": tracked_meal.id,
"foods": updated_foods_data
}
)
assert response_update.status_code == 200
assert response_update.json()["status"] == "success"
session.expire_all() # Ensure a fresh load from the database session.expire_all() # Ensure a fresh load from the database
# Now, try to get the tracked meal foods # Verify original MealFood for food1 is deleted
deleted_meal_food1 = session.query(MealFood).filter(MealFood.id == original_meal_food1.id).first()
assert deleted_meal_food1 is None
# Verify a TrackedMealFood for food1 now exists
overridden_tracked_food1 = session.query(TrackedMealFood).filter(
TrackedMealFood.tracked_meal_id == tracked_meal.id,
TrackedMealFood.food_id == food1.id
).first()
assert overridden_tracked_food1 is not None
assert overridden_tracked_food1.quantity == 175.0
# 3. Now, try to get the tracked meal foods again, which is where the bug occurs
# This will call /tracker/get_tracked_meal_foods
response_get = client.get(f"/tracker/get_tracked_meal_foods/{tracked_meal.id}") response_get = client.get(f"/tracker/get_tracked_meal_foods/{tracked_meal.id}")
assert response_get.status_code == 200 assert response_get.status_code == 200
data_get = response_get.json() data_get = response_get.json()
assert data_get["status"] == "success" assert data_get["status"] == "success"
assert len(data_get["meal_foods"]) == 2 assert len(data_get["meal_foods"]) == 2
# Verify the updated quantity for food1 and the existence of food2 # Verify the contents of the returned meal_foods
food_names = [f["food_name"] for f in data_get["meal_foods"]]
assert "Apple" in food_names
assert "Banana" in food_names
for food_data in data_get["meal_foods"]: for food_data in data_get["meal_foods"]:
if food_data["food_name"] == "Apple": if food_data["food_name"] == "Apple":
assert food_data["quantity"] == 175.0 assert food_data["quantity"] == 175.0
assert food_data["is_custom"] == True # It should now be a custom tracked food assert food_data["is_custom"] == True
elif food_data["food_name"] == "Banana": elif food_data["food_name"] == "Banana":
assert food_data["quantity"] == 100.0 assert food_data["quantity"] == 100.0
assert food_data["is_custom"] == False # This one should still be from the original meal definition assert food_data["is_custom"] == False
def test_update_tracked_meal_foods_endpoint(client: TestClient, session: TestingSessionLocal): def test_update_tracked_meal_foods_endpoint(client: TestClient, session: TestingSessionLocal):
"""Test updating quantities of foods in a tracked meal""" """Test updating quantities of foods in a tracked meal"""