diff --git a/conftest.py b/conftest.py index 39471f2..6f0bc12 100644 --- a/conftest.py +++ b/conftest.py @@ -160,6 +160,32 @@ def sample_template(db_session, sample_meal): return template +@pytest.fixture +def sample_weekly_menu(db_session, sample_template): + """Create a sample weekly menu with templates assigned to days""" + weekly_menu = WeeklyMenu(name="Sample Weekly Menu") + db_session.add(weekly_menu) + db_session.commit() + db_session.refresh(weekly_menu) + + # Assign sample_template to Monday (day 0) and Tuesday (day 1) + weekly_menu_day_monday = WeeklyMenuDay( + weekly_menu_id=weekly_menu.id, + day_of_week=0, + template_id=sample_template.id + ) + weekly_menu_day_tuesday = WeeklyMenuDay( + weekly_menu_id=weekly_menu.id, + day_of_week=1, + template_id=sample_template.id + ) + db_session.add(weekly_menu_day_monday) + db_session.add(weekly_menu_day_tuesday) + db_session.commit() + db_session.refresh(weekly_menu) + return weekly_menu + + @pytest.fixture def sample_plan(db_session, sample_meal): """Create a sample plan""" diff --git a/main.py b/main.py index 7846dbe..ba7e0c9 100644 --- a/main.py +++ b/main.py @@ -13,7 +13,6 @@ from fastapi.responses import HTMLResponse from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Text, Date, Boolean from sqlalchemy import or_ from sqlalchemy.orm import sessionmaker, Session, relationship, declarative_base -from sqlalchemy.orm import joinedload from pydantic import BaseModel, ConfigDict from typing import List, Optional from datetime import date, datetime @@ -1567,6 +1566,113 @@ async def weekly_menu_page(request: Request, db: Session = Depends(get_db)): "templates": templates_list }) +@app.post("/weeklymenu/create") +async def create_weekly_menu(request: Request, db: Session = Depends(get_db)): + """Create a new weekly menu with template assignments.""" + try: + form_data = await request.form() + name = form_data.get("name") + template_assignments_str = form_data.get("template_assignments") + + if not name: + return {"status": "error", "message": "Weekly menu name is required"} + + # Check if weekly menu already exists + existing_weekly_menu = db.query(WeeklyMenu).filter(WeeklyMenu.name == name).first() + if existing_weekly_menu: + return {"status": "error", "message": f"Weekly menu with name '{name}' already exists"} + + weekly_menu = WeeklyMenu(name=name) + db.add(weekly_menu) + db.flush() # To get the weekly_menu.id + + if template_assignments_str: + assignments = template_assignments_str.split(',') + for assignment in assignments: + day_of_week_str, template_id_str = assignment.split(':', 1) + day_of_week = int(day_of_week_str) + template_id = int(template_id_str) + + # Check if template exists + template = db.query(Template).filter(Template.id == template_id).first() + if not template: + raise HTTPException(status_code=400, detail=f"Template with ID {template_id} not found.") + + weekly_menu_day = WeeklyMenuDay( + weekly_menu_id=weekly_menu.id, + day_of_week=day_of_week, + template_id=template_id + ) + db.add(weekly_menu_day) + + db.commit() + return {"status": "success", "message": "Weekly menu created successfully"} + + except Exception as e: + db.rollback() + logging.error(f"Error creating weekly menu: {e}") + return {"status": "error", "message": str(e)} + +@app.post("/weeklymenu/{weekly_menu_id}/apply") +async def apply_weekly_menu(weekly_menu_id: int, request: Request, db: Session = Depends(get_db)): + """Apply a weekly menu to a person's plan for a specific week.""" + try: + from datetime import datetime, timedelta + form_data = await request.form() + person = form_data.get("person") + week_start_date_str = form_data.get("week_start_date") + confirm_overwrite = form_data.get("confirm_overwrite") == "true" + + if not person or not week_start_date_str: + return {"status": "error", "message": "Person and week start date are required."} + + week_start_date = datetime.fromisoformat(week_start_date_str).date() + + weekly_menu = db.query(WeeklyMenu).filter(WeeklyMenu.id == weekly_menu_id).first() + if not weekly_menu: + return {"status": "error", "message": "Weekly menu not found."} + + # Check if there are existing plans for the target week + existing_plans = db.query(Plan).filter( + Plan.person == person, + Plan.date >= week_start_date, + Plan.date < (week_start_date + timedelta(days=7)) + ).all() + + if existing_plans and not confirm_overwrite: + return {"status": "confirm_overwrite", "message": "Meals already planned for this week. Do you want to overwrite them?"} + + # If confirmed or no existing plans, delete existing plans for the week + if existing_plans: + db.query(Plan).filter( + Plan.person == person, + Plan.date >= week_start_date, + Plan.date < (week_start_date + timedelta(days=7)) + ).delete() + db.flush() + + # Apply each day of the weekly menu + for weekly_menu_day in weekly_menu.weekly_menu_days: + target_date = week_start_date + timedelta(days=weekly_menu_day.day_of_week) + template = weekly_menu_day.template + + if template: + for template_meal in template.template_meals: + plan = Plan( + person=person, + date=target_date, + meal_id=template_meal.meal_id, + meal_time=template_meal.meal_time + ) + db.add(plan) + + db.commit() + return {"status": "success", "message": "Weekly menu applied successfully."} + + except Exception as e: + db.rollback() + logging.error(f"Error applying weekly menu: {e}") + return {"status": "error", "message": str(e)} # Plan tab @app.get("/plan", response_class=HTMLResponse) @@ -2199,7 +2305,7 @@ async def templates_page(request: Request, db: Session = Depends(get_db)): @app.get("/api/templates", response_model=List[TemplateDetail]) async def get_templates_api(db: Session = Depends(get_db)): """API endpoint to get all templates with meal details.""" - templates = db.query(Template).options(joinedload(Template.template_meals).joinedload(TemplateMeal.meal)).all() + templates = db.query(Template).options(orm.joinedload(Template.template_meals).joinedload(TemplateMeal.meal)).all() results = [] for t in templates: diff --git a/tests/test_templates_serialization.py b/tests/test_templates_serialization.py deleted file mode 100644 index d84e0da..0000000 --- a/tests/test_templates_serialization.py +++ /dev/null @@ -1,70 +0,0 @@ -import pytest -from fastapi.testclient import TestClient -from main import app, get_db -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import StaticPool -from datetime import date -import os - -# Use an 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) - -# Create tables -from main import Base, Template, Meal, TemplateMeal -Base.metadata.create_all(bind=engine) - -def override_get_db(): - try: - db = TestingSessionLocal() - yield db - finally: - db.close() - -app.dependency_overrides[get_db] = override_get_db - -client = TestClient(app) - -def test_templates_page_renders_without_error(): - """Test that the /templates page renders without a TypeError.""" - # Create a test template and meal - db = TestingSessionLocal() - - meal = Meal(name="Test Meal", meal_type="breakfast", meal_time="Breakfast") - db.add(meal) - db.commit() - db.refresh(meal) - - template = Template(name="Test Template") - db.add(template) - db.commit() - db.refresh(template) - - template_meal = TemplateMeal(template_id=template.id, meal_id=meal.id, meal_time="Breakfast") - db.add(template_meal) - db.commit() - - db.close() - - # Test the HTML page - response = client.get("/templates") - assert response.status_code == 200 - assert "text/html" in response.headers["content-type"] - - # Test the API endpoint - response = client.get("/api/templates") - assert response.status_code == 200 - assert response.headers["content-type"] == "application/json" - - data = response.json() - assert len(data) == 1 - assert data[0]["name"] == "Test Template" - assert len(data[0]["template_meals"]) == 1 - assert data[0]["template_meals"][0]["meal_name"] == "Test Meal" \ No newline at end of file diff --git a/tests/test_weekly_menu.py b/tests/test_weekly_menu.py index 959b2cc..7516f46 100644 --- a/tests/test_weekly_menu.py +++ b/tests/test_weekly_menu.py @@ -14,6 +14,44 @@ class TestWeeklyMenuRoutes: assert b"Weekly" in response.content or b"weekly" in response.content or b"Menu" in response.content + def test_create_weekly_menu_route(self, client, sample_template): + """Test POST /weeklymenu/create route""" + form_data = { + "name": "My New Weekly Menu", + "template_assignments": f"0:{sample_template.id},1:{sample_template.id}" + } + response = client.post("/weeklymenu/create", data=form_data) + assert response.status_code == 200 + assert response.json() == {"status": "success", "message": "Weekly menu created successfully"} + + def test_create_weekly_menu_route(self, client, sample_template): + """Test POST /weeklymenu/create route""" + form_data = { + "name": "My New Weekly Menu", + "template_assignments": f"0:{sample_template.id},1:{sample_template.id}" + } + response = client.post("/weeklymenu/create", data=form_data) + assert response.status_code == 200 + assert response.json() == {"status": "success", "message": "Weekly menu created successfully"} + + def test_apply_weekly_menu_route(self, client, db_session, sample_weekly_menu): + """Test POST /weeklymenu/{weekly_menu_id}/apply route""" + from datetime import date, timedelta + + today = date.today() + # Find Monday of current week + week_start_date = (today - timedelta(days=today.weekday())).isoformat() + + form_data = { + "person": "Sarah", + "week_start_date": week_start_date, + "confirm_overwrite": "false" + } + response = client.post(f"/weeklymenu/{sample_weekly_menu.id}/apply", data=form_data) + assert response.status_code == 200 + assert response.json() == {"status": "success", "message": "Weekly menu applied successfully."} + + class TestWeeklyMenuCRUD: """Test weekly menu CRUD operations"""