diff options
Diffstat (limited to 'kitchen/views_htmx.py')
| -rw-r--r-- | kitchen/views_htmx.py | 100 |
1 files changed, 98 insertions, 2 deletions
diff --git a/kitchen/views_htmx.py b/kitchen/views_htmx.py index 7d011c9..a1e13f3 100644 --- a/kitchen/views_htmx.py +++ b/kitchen/views_htmx.py @@ -251,9 +251,65 @@ def pantry_delete(request, item_id): @csrf_exempt @require_POST +def pantry_move(request, item_id): + """Move item between fridge/freezer.""" + item = get_object_or_404(PantryItem, id=item_id) + target = request.POST.get("to", "fridge") + + if target == "freezer": + item.location = "freezer" + item.expiry_date = None + elif target == "fridge": + item.location = "fridge" + # Default +7 days when defrosting + if item.ingredient.shelf_life_days: + item.expiry_date = date.today() + timedelta(days=item.ingredient.shelf_life_days) + else: + item.expiry_date = date.today() + timedelta(days=7) + + item.save(update_fields=["location", "expiry_date"]) + ctx = _pantry_context() + return render(request, "kitchen/partials/pantry_table.html", ctx) + + +@csrf_exempt +@require_POST +def pantry_edit_expiry(request, item_id): + """Show inline expiry date editor.""" + item = get_object_or_404(PantryItem, id=item_id) + return render(request, "kitchen/partials/pantry_expiry_edit.html", {"item": item}) + + +@csrf_exempt +@require_POST +def pantry_save_expiry(request, item_id): + """Save edited expiry date.""" + item = get_object_or_404(PantryItem, id=item_id) + expiry = request.POST.get("expiry_date") + if expiry: + item.expiry_date = expiry + else: + item.expiry_date = None + item.save(update_fields=["expiry_date"]) + ctx = _pantry_context() + return render(request, "kitchen/partials/pantry_table.html", ctx) + + +def pantry_cancel_edit(request): + """Cancel expiry edit — just re-render the table.""" + ctx = _pantry_context() + return render(request, "kitchen/partials/pantry_table.html", ctx) + + +@csrf_exempt +@require_POST def shopping_generate(request): """Generate smart shopping list and return updated HTML.""" - from .views import _get_section, _find_ingredient as api_find, _is_known_staple + from .views import _get_section + + staple_count = 0 + expiring_count = 0 + recipe_gap_count = 0 suggestions = [] @@ -263,7 +319,9 @@ def shopping_generate(request): "ingredient": item.ingredient.name, "reason": "restock staple", "section": _get_section(item.ingredient), + "type": "staple", }) + staple_count += 1 # Expiring items cutoff = date.today() + timedelta(days=2) @@ -274,7 +332,34 @@ def shopping_generate(request): "ingredient": item.ingredient.name, "reason": f"expiring {item.expiry_date}", "section": _get_section(item.ingredient), + "type": "expiring", }) + expiring_count += 1 + + # Recipe gaps — check required slots with zero available options + for meta in MetaRecipe.objects.prefetch_related( + "slots__options__ingredient", "base_ingredients__ingredient" + ).all(): + for slot in meta.slots.all(): + if not slot.required: + continue + any_available = False + first_option = None + for opt in slot.options.all(): + if not first_option: + first_option = opt + available = _get_pantry_total(opt.ingredient_id) + if available >= opt.quantity_per_serving * 2: + any_available = True + break + if not any_available and first_option: + suggestions.append({ + "ingredient": first_option.ingredient.name, + "reason": f"for {meta.name} ({slot.name})", + "section": _get_section(first_option.ingredient), + "type": "recipe", + }) + recipe_gap_count += 1 # Dedupe and save seen = set() @@ -294,7 +379,18 @@ def shopping_generate(request): items = ShoppingListItem.objects.select_related("ingredient").all() for item in items: item.section = _get_section_for_item(item) - return render(request, "kitchen/partials/shopping_list.html", {"items": items}) + + summary = { + "total": len(set(s["ingredient"] for s in suggestions)), + "staples": staple_count, + "expiring": expiring_count, + "recipe_gaps": recipe_gap_count, + } + + return render(request, "kitchen/partials/shopping_list.html", { + "items": items, + "summary": summary, + }) @csrf_exempt |
