mirror of
https://github.com/sstent/foodplanner.git
synced 2025-12-06 08:01:47 +00:00
fixing tracker food edits
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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"""
|
||||||
|
|||||||
Reference in New Issue
Block a user