Simplify from claude
This commit is contained in:
+19
-51
@@ -1,3 +1,4 @@
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from datetime import date, timedelta
|
||||
|
||||
@@ -33,6 +34,11 @@ from .serializers import (
|
||||
CookLogSerializer,
|
||||
ShoppingListItemSerializer,
|
||||
)
|
||||
from .helpers import (
|
||||
find_ingredient as _find_ingredient,
|
||||
get_pantry_total as _get_pantry_total,
|
||||
get_section_key as _get_section,
|
||||
)
|
||||
|
||||
|
||||
# --- Standard CRUD ViewSets ---
|
||||
@@ -48,20 +54,16 @@ class IngredientViewSet(viewsets.ModelViewSet):
|
||||
queryset = Ingredient.objects.all()
|
||||
serializer_class = IngredientSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
search_fields = ["name", "aliases"]
|
||||
|
||||
|
||||
class PantryItemViewSet(viewsets.ModelViewSet):
|
||||
queryset = PantryItem.objects.select_related("ingredient").all()
|
||||
serializer_class = PantryItemSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
filterset_fields = ["location", "is_staple"]
|
||||
|
||||
@action(detail=False, methods=["get"])
|
||||
def expiring(self, request):
|
||||
"""Items expiring within 3 days."""
|
||||
from datetime import timedelta
|
||||
|
||||
cutoff = date.today() + timedelta(days=3)
|
||||
items = self.queryset.filter(
|
||||
expiry_date__isnull=False,
|
||||
@@ -127,7 +129,6 @@ class ShoppingListItemViewSet(viewsets.ModelViewSet):
|
||||
queryset = ShoppingListItem.objects.select_related("ingredient").all()
|
||||
serializer_class = ShoppingListItemSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
filterset_fields = ["checked"]
|
||||
|
||||
|
||||
# --- Create/Update Meta-Recipe (nested) ---
|
||||
@@ -307,20 +308,22 @@ def what_can_i_cook(request):
|
||||
servings = int(request.query_params.get("servings", 2))
|
||||
|
||||
# Build pantry lookup: ingredient_id -> list of {quantity, unit, location, expiry}
|
||||
today = date.today()
|
||||
pantry = {}
|
||||
for item in PantryItem.objects.select_related("ingredient").filter(quantity__gt=0):
|
||||
if item.ingredient_id not in pantry:
|
||||
pantry[item.ingredient_id] = []
|
||||
is_expired = item.expiry_date and item.expiry_date < today
|
||||
pantry[item.ingredient_id].append({
|
||||
"quantity": item.quantity,
|
||||
"unit": item.unit,
|
||||
"location": item.location,
|
||||
"expiry_date": item.expiry_date,
|
||||
"is_expired": item.expiry_date and item.expiry_date < date.today(),
|
||||
"is_expired": is_expired,
|
||||
"expiring_soon": (
|
||||
item.expiry_date
|
||||
and not (item.expiry_date < date.today())
|
||||
and (item.expiry_date - date.today()).days <= 2
|
||||
and not is_expired
|
||||
and (item.expiry_date - today).days <= 2
|
||||
),
|
||||
})
|
||||
|
||||
@@ -547,16 +550,18 @@ def log_cook(request):
|
||||
|
||||
|
||||
def _deduct_ingredient(ingredient, amount, unit):
|
||||
"""Deduct an amount from pantry, preferring fridge items first (use oldest first)."""
|
||||
"""Deduct an amount from pantry, using items with the earliest expiry first.
|
||||
|
||||
Ordering is purely by ``expiry_date`` across all locations — location is
|
||||
not part of the ordering.
|
||||
"""
|
||||
remaining = Decimal(str(amount))
|
||||
deducted = []
|
||||
|
||||
# Prefer fridge (expires first), then cupboard, then freezer
|
||||
# Earliest expiry first, regardless of location (fridge/cupboard/freezer).
|
||||
items = PantryItem.objects.filter(
|
||||
ingredient=ingredient, quantity__gt=0
|
||||
).order_by(
|
||||
# Fridge first, then cupboard, then freezer
|
||||
# Within same location, earliest expiry first
|
||||
"expiry_date",
|
||||
)
|
||||
|
||||
@@ -684,22 +689,6 @@ def bulk_pantry_add(request):
|
||||
return Response({"added": len(results), "results": results}, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
def _find_ingredient(name):
|
||||
"""Find ingredient by name or alias (case-insensitive)."""
|
||||
# Exact name match
|
||||
try:
|
||||
return Ingredient.objects.get(name__iexact=name)
|
||||
except Ingredient.DoesNotExist:
|
||||
pass
|
||||
|
||||
# Search aliases (JSONField contains)
|
||||
for ingredient in Ingredient.objects.all():
|
||||
if any(alias.lower() == name.lower() for alias in ingredient.aliases):
|
||||
return ingredient
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _is_known_staple(ingredient):
|
||||
"""Check if an ingredient should be marked as a staple."""
|
||||
staple_names = {"onions", "frozen chips", "salt", "black pepper", "olive oil", "paprika", "garlic powder"}
|
||||
@@ -842,28 +831,6 @@ def generate_shopping_list(request):
|
||||
})
|
||||
|
||||
|
||||
def _get_pantry_total(ingredient_id):
|
||||
"""Get total quantity of an ingredient across all pantry locations."""
|
||||
total = Decimal("0")
|
||||
for item in PantryItem.objects.filter(ingredient_id=ingredient_id, quantity__gt=0):
|
||||
total += item.quantity
|
||||
return total
|
||||
|
||||
|
||||
def _get_section(ingredient):
|
||||
"""Determine shopping section from ingredient tags."""
|
||||
tag_names = set(ingredient.tags.values_list("name", flat=True))
|
||||
if "protein" in tag_names:
|
||||
return "protein"
|
||||
if "veg" in tag_names:
|
||||
return "veg"
|
||||
if "carb" in tag_names:
|
||||
return "carbs"
|
||||
if "dairy" in tag_names:
|
||||
return "dairy"
|
||||
return "other"
|
||||
|
||||
|
||||
# --- Recipe URL Import ---
|
||||
|
||||
|
||||
@@ -881,6 +848,8 @@ def import_recipe_url(request):
|
||||
|
||||
Supports 100+ sites including BBC Good Food, Allrecipes, Jamie Oliver, etc.
|
||||
"""
|
||||
# Imported lazily so the optional recipe-scrapers/requests dependencies are
|
||||
# only required when this endpoint is actually used.
|
||||
from recipe_scrapers import scrape_html
|
||||
import requests as req
|
||||
|
||||
@@ -950,6 +919,5 @@ def _parse_servings(yields_str):
|
||||
"""Extract number from yields string like '4 servings'."""
|
||||
if not yields_str:
|
||||
return None
|
||||
import re
|
||||
match = re.search(r'(\d+)', str(yields_str))
|
||||
return int(match.group(1)) if match else None
|
||||
|
||||
Reference in New Issue
Block a user