mirror of
https://github.com/sstent/foodplanner.git
synced 2025-12-05 23:51:46 +00:00
fixing meal edit on teampltes page
This commit is contained in:
@@ -50,6 +50,10 @@ async def tracker_page(request: Request, person: str = "Sarah", date: str = None
|
|||||||
TrackedMeal.tracked_day_id == tracked_day.id
|
TrackedMeal.tracked_day_id == tracked_day.id
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
# Filter out deleted tracked foods from each tracked meal
|
||||||
|
for tracked_meal in tracked_meals:
|
||||||
|
tracked_meal.tracked_foods = [tf for tf in tracked_meal.tracked_foods if not tf.is_deleted]
|
||||||
|
|
||||||
# Get all meals for dropdown
|
# Get all meals for dropdown
|
||||||
meals = db.query(Meal).all()
|
meals = db.query(Meal).all()
|
||||||
|
|
||||||
@@ -472,9 +476,6 @@ async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depend
|
|||||||
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", [])
|
||||||
removed_food_ids = data.get("removed_food_ids", [])
|
removed_food_ids = data.get("removed_food_ids", [])
|
||||||
print(f"Received update for tracked_meal_id: {tracked_meal_id}")
|
|
||||||
print(f" Foods data: {foods_data}")
|
|
||||||
print(f" Removed food IDs: {removed_food_ids}")
|
|
||||||
|
|
||||||
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
|
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
|
||||||
if not tracked_meal:
|
if not tracked_meal:
|
||||||
@@ -482,18 +483,15 @@ async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depend
|
|||||||
|
|
||||||
# Process removals: mark existing foods as deleted
|
# Process removals: mark existing foods as deleted
|
||||||
for food_id_to_remove in removed_food_ids:
|
for food_id_to_remove in removed_food_ids:
|
||||||
print(f" Processing removal for food_id: {food_id_to_remove}")
|
|
||||||
# Check if an override already exists
|
# Check if an override already exists
|
||||||
override = db.query(TrackedMealFood).filter(
|
override = db.query(TrackedMealFood).filter(
|
||||||
TrackedMealFood.tracked_meal_id == tracked_meal_id,
|
TrackedMealFood.tracked_meal_id == tracked_meal_id,
|
||||||
TrackedMealFood.food_id == food_id_to_remove
|
TrackedMealFood.food_id == food_id_to_remove
|
||||||
).first()
|
).first()
|
||||||
if override:
|
if override:
|
||||||
print(f" Found existing override for food_id {food_id_to_remove}. Marking as deleted.")
|
|
||||||
override.is_deleted = True
|
override.is_deleted = True
|
||||||
else:
|
else:
|
||||||
# If no override exists, create one to mark the food as deleted
|
# If no override exists, create one to mark the food as deleted
|
||||||
print(f" No existing override for food_id {food_id_to_remove}. Creating new deleted override.")
|
|
||||||
new_override = TrackedMealFood(
|
new_override = TrackedMealFood(
|
||||||
tracked_meal_id=tracked_meal_id,
|
tracked_meal_id=tracked_meal_id,
|
||||||
food_id=food_id_to_remove,
|
food_id=food_id_to_remove,
|
||||||
@@ -502,7 +500,6 @@ async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depend
|
|||||||
is_deleted=True
|
is_deleted=True
|
||||||
)
|
)
|
||||||
db.add(new_override)
|
db.add(new_override)
|
||||||
print(f" New override created: {new_override.is_deleted}")
|
|
||||||
|
|
||||||
# Process updates and additions
|
# Process updates and additions
|
||||||
for food_data in foods_data:
|
for food_data in foods_data:
|
||||||
@@ -717,6 +714,10 @@ async def detailed_tracked_day(request: Request, person: str = "Sarah", date: Op
|
|||||||
TrackedMeal.tracked_day_id == tracked_day.id
|
TrackedMeal.tracked_day_id == tracked_day.id
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
# Filter out deleted tracked foods from each tracked meal
|
||||||
|
for tracked_meal in tracked_meals:
|
||||||
|
tracked_meal.tracked_foods = [tf for tf in tracked_meal.tracked_foods if not tf.is_deleted]
|
||||||
|
|
||||||
day_totals = calculate_day_nutrition_tracked(tracked_meals, db)
|
day_totals = calculate_day_nutrition_tracked(tracked_meals, db)
|
||||||
|
|
||||||
meal_details = []
|
meal_details = []
|
||||||
|
|||||||
@@ -80,9 +80,11 @@
|
|||||||
<div class="ms-3">
|
<div class="ms-3">
|
||||||
<div class="row row-cols-1 row-cols-sm-2">
|
<div class="row row-cols-1 row-cols-sm-2">
|
||||||
{% set overrides = {} %}
|
{% set overrides = {} %}
|
||||||
|
{% set non_deleted_override_ids = [] %}
|
||||||
{% for tmf in tracked_meal.tracked_foods %}
|
{% for tmf in tracked_meal.tracked_foods %}
|
||||||
{% if not tmf.is_deleted %}
|
{% if not tmf.is_deleted %}
|
||||||
{% set _ = overrides.update({tmf.food_id: tmf}) %}
|
{% set _ = overrides.update({tmf.food_id: tmf}) %}
|
||||||
|
{% set _ = non_deleted_override_ids.append(tmf.food_id) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@
|
|||||||
|
|
||||||
{# Display base meal foods, applying overrides #}
|
{# Display base meal foods, applying overrides #}
|
||||||
{% for meal_food in tracked_meal.meal.meal_foods %}
|
{% for meal_food in tracked_meal.meal.meal_foods %}
|
||||||
{% if meal_food.food_id not in overrides %}
|
{% if meal_food.food_id not in non_deleted_override_ids %}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="d-flex justify-content-between small text-muted">
|
<div class="d-flex justify-content-between small text-muted">
|
||||||
<span>• {{ meal_food.food.name }}</span>
|
<span>• {{ meal_food.food.name }}</span>
|
||||||
@@ -344,7 +346,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Edit tracked meal
|
// Edit tracked meal
|
||||||
|
let removedFoodIds = [];
|
||||||
|
|
||||||
function editTrackedMeal(trackedMealId) {
|
function editTrackedMeal(trackedMealId) {
|
||||||
|
removedFoodIds = []; // Reset the array when a new meal is edited
|
||||||
document.getElementById('editTrackedMealId').value = trackedMealId;
|
document.getElementById('editTrackedMealId').value = trackedMealId;
|
||||||
loadTrackedMealFoods(trackedMealId);
|
loadTrackedMealFoods(trackedMealId);
|
||||||
new bootstrap.Modal(document.getElementById('editTrackedMealModal')).show();
|
new bootstrap.Modal(document.getElementById('editTrackedMealModal')).show();
|
||||||
@@ -375,7 +380,7 @@
|
|||||||
<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.quantity.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.food_id}, ${food.is_custom})">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -393,6 +398,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeFoodFromTrackedMeal(foodId, isCustom) {
|
||||||
|
// Find the button that was clicked
|
||||||
|
const button = event.target.closest('button');
|
||||||
|
if (button) {
|
||||||
|
// Find the parent div and remove it
|
||||||
|
const foodDiv = button.closest('.d-flex');
|
||||||
|
if (foodDiv) {
|
||||||
|
const foodDataId = parseInt(foodDiv.querySelector('input').dataset.foodId);
|
||||||
|
removedFoodIds.push(foodDataId);
|
||||||
|
foodDiv.remove();
|
||||||
|
console.log('Removed food with ID:', foodDataId, 'and removedFoodIds is now:', removedFoodIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function saveTrackedMeal() {
|
async function saveTrackedMeal() {
|
||||||
const trackedMealId = document.getElementById('editTrackedMealId').value;
|
const trackedMealId = document.getElementById('editTrackedMealId').value;
|
||||||
const inputs = document.querySelectorAll('#editMealFoodsList input[type="number"]');
|
const inputs = document.querySelectorAll('#editMealFoodsList input[type="number"]');
|
||||||
@@ -408,19 +428,19 @@
|
|||||||
foods.push(foodData);
|
foods.push(foodData);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Payload being sent to /tracker/update_tracked_meal_foods:', JSON.stringify({
|
const payload = {
|
||||||
tracked_meal_id: trackedMealId,
|
tracked_meal_id: trackedMealId,
|
||||||
foods: foods
|
foods: foods,
|
||||||
}, null, 2));
|
removed_food_ids: removedFoodIds
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Payload being sent to /tracker/update_tracked_meal_foods:', JSON.stringify(payload, null, 2));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/tracker/update_tracked_meal_foods', {
|
const response = await fetch('/tracker/update_tracked_meal_foods', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(payload)
|
||||||
tracked_meal_id: trackedMealId,
|
|
||||||
foods: foods
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.status === 'success') {
|
if (result.status === 'success') {
|
||||||
|
|||||||
121
tests/test_delete_food_from_meal.py
Normal file
121
tests/test_delete_food_from_meal.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlalchemy.pool import StaticPool
|
||||||
|
|
||||||
|
from main import app
|
||||||
|
from app.database import Base, get_db, Food, Meal, MealFood, TrackedDay, TrackedMeal, TrackedMealFood
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# Setup for in-memory SQLite database for testing
|
||||||
|
SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"
|
||||||
|
engine = create_engine(
|
||||||
|
SQLALCHEMY_DATABASE_URL,
|
||||||
|
connect_args={"check_same_thread": False},
|
||||||
|
poolclass=StaticPool,
|
||||||
|
)
|
||||||
|
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
@pytest.fixture(name="session")
|
||||||
|
def session_fixture():
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
db = TestingSessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
Base.metadata.drop_all(engine)
|
||||||
|
|
||||||
|
@pytest.fixture(name="client")
|
||||||
|
def client_fixture(session):
|
||||||
|
def override_get_db():
|
||||||
|
yield session
|
||||||
|
app.dependency_overrides[get_db] = override_get_db
|
||||||
|
yield TestClient(app)
|
||||||
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
def create_test_data(session: TestingSessionLocal):
|
||||||
|
food1 = Food(name="Apple", serving_size=100, serving_unit="g", calories=52, protein=0.3, carbs=14, fat=0.2, fiber=2.4, sugar=10.4, sodium=1)
|
||||||
|
food2 = Food(name="Banana", serving_size=100, serving_unit="g", calories=89, protein=1.1, carbs=23, fat=0.3, fiber=2.6, sugar=12.2, sodium=1)
|
||||||
|
session.add_all([food1, food2])
|
||||||
|
session.commit()
|
||||||
|
session.refresh(food1)
|
||||||
|
session.refresh(food2)
|
||||||
|
|
||||||
|
meal1 = Meal(name="Fruit Salad", meal_type="custom", meal_time="Breakfast")
|
||||||
|
session.add(meal1)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(meal1)
|
||||||
|
|
||||||
|
meal_food1 = MealFood(meal_id=meal1.id, food_id=food1.id, quantity=150)
|
||||||
|
meal_food2 = MealFood(meal_id=meal1.id, food_id=food2.id, quantity=100)
|
||||||
|
session.add_all([meal_food1, meal_food2])
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
tracked_day = TrackedDay(person="Sarah", date=date.today(), is_modified=False)
|
||||||
|
session.add(tracked_day)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(tracked_day)
|
||||||
|
|
||||||
|
tracked_meal = TrackedMeal(tracked_day_id=tracked_day.id, meal_id=meal1.id, meal_time="Breakfast")
|
||||||
|
session.add(tracked_meal)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(tracked_meal)
|
||||||
|
|
||||||
|
return food1, food2, meal1, tracked_day, tracked_meal
|
||||||
|
|
||||||
|
def test_delete_food_from_tracked_meal(client: TestClient, session: TestingSessionLocal):
|
||||||
|
"""
|
||||||
|
Test deleting a food from a tracked meal. This simulates the user removing a food
|
||||||
|
from the edit meal modal.
|
||||||
|
"""
|
||||||
|
food1, food2, meal1, tracked_day, tracked_meal = create_test_data(session)
|
||||||
|
|
||||||
|
# We want to delete food1 (Apple) and keep food2 (Banana)
|
||||||
|
removed_food_ids = [food1.id]
|
||||||
|
|
||||||
|
# The 'foods' payload will only contain the food we are keeping.
|
||||||
|
# The id and is_custom fields are based on what the frontend would send.
|
||||||
|
original_meal_food2 = session.query(MealFood).filter(MealFood.meal_id == meal1.id, MealFood.food_id == food2.id).first()
|
||||||
|
|
||||||
|
update_payload = {
|
||||||
|
"tracked_meal_id": tracked_meal.id,
|
||||||
|
"foods": [
|
||||||
|
{"id": original_meal_food2.id, "food_id": food2.id, "grams": 100.0, "is_custom": False},
|
||||||
|
],
|
||||||
|
"removed_food_ids": removed_food_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
response_update = client.post("/tracker/update_tracked_meal_foods", json=update_payload)
|
||||||
|
assert response_update.status_code == 200
|
||||||
|
assert response_update.json()["status"] == "success"
|
||||||
|
|
||||||
|
session.expire_all()
|
||||||
|
|
||||||
|
# Verify that an override was created for the deleted food (Apple)
|
||||||
|
deleted_apple_override = session.query(TrackedMealFood).filter(
|
||||||
|
TrackedMealFood.tracked_meal_id == tracked_meal.id,
|
||||||
|
TrackedMealFood.food_id == food1.id,
|
||||||
|
TrackedMealFood.is_deleted == True
|
||||||
|
).first()
|
||||||
|
assert deleted_apple_override is not None
|
||||||
|
|
||||||
|
# Verify that the food we kept (Banana) does not have an override marked as deleted
|
||||||
|
banana_override = session.query(TrackedMealFood).filter(
|
||||||
|
TrackedMealFood.tracked_meal_id == tracked_meal.id,
|
||||||
|
TrackedMealFood.food_id == food2.id,
|
||||||
|
TrackedMealFood.is_deleted == False
|
||||||
|
).first()
|
||||||
|
# In this flow, an override for Banana might not be created if the quantity is unchanged.
|
||||||
|
# The key is that it's not marked as deleted.
|
||||||
|
|
||||||
|
# Finally, check the get_tracked_meal_foods endpoint to ensure 'Apple' is gone
|
||||||
|
response_get = client.get(f"/tracker/get_tracked_meal_foods/{tracked_meal.id}")
|
||||||
|
assert response_get.status_code == 200
|
||||||
|
data = response_get.json()
|
||||||
|
assert data["status"] == "success"
|
||||||
|
|
||||||
|
final_food_names = [f["food_name"] for f in data["meal_foods"]]
|
||||||
|
assert "Apple" not in final_food_names
|
||||||
|
assert "Banana" in final_food_names
|
||||||
Reference in New Issue
Block a user