246 lines
13 KiB
Python
246 lines
13 KiB
Python
"""
|
|
Seed the database with initial data:
|
|
- Tags
|
|
- Ingredients (from current pantry + recipe knowledge)
|
|
- Current pantry items (migrated from Food/Pantry.md)
|
|
- Meta-recipes: Stir Fry and Traybake
|
|
"""
|
|
|
|
from datetime import date
|
|
from decimal import Decimal
|
|
|
|
from django.core.management.base import BaseCommand
|
|
|
|
from kitchen.models import (
|
|
Tag,
|
|
Ingredient,
|
|
PantryItem,
|
|
MetaRecipe,
|
|
Slot,
|
|
SlotOption,
|
|
MetaRecipeBase,
|
|
)
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Seed database with initial ingredients, pantry, and meta-recipes"
|
|
|
|
def handle(self, *args, **options):
|
|
self.stdout.write("Creating tags...")
|
|
tags = {}
|
|
for name in ["protein", "carb", "veg", "seasoning", "dairy", "sauce", "staple", "frozen"]:
|
|
tags[name], _ = Tag.objects.get_or_create(name=name)
|
|
|
|
self.stdout.write("Creating ingredients...")
|
|
ingredients = {}
|
|
|
|
def make_ingredient(name, unit, tag_names, shelf_life=None, aliases=None):
|
|
obj, _ = Ingredient.objects.get_or_create(
|
|
name=name,
|
|
defaults={
|
|
"default_unit": unit,
|
|
"shelf_life_days": shelf_life,
|
|
"aliases": aliases or [],
|
|
},
|
|
)
|
|
for t in tag_names:
|
|
obj.tags.add(tags[t])
|
|
ingredients[name] = obj
|
|
return obj
|
|
|
|
# Proteins
|
|
make_ingredient("chicken mini fillets", "items", ["protein"], shelf_life=2, aliases=["chicken fillets", "mini fillets"])
|
|
make_ingredient("chicken thighs", "items", ["protein"], shelf_life=2, aliases=["chicken thigh"])
|
|
make_ingredient("pork mince", "g", ["protein"], shelf_life=2, aliases=["pork mince 500g"])
|
|
make_ingredient("sausages", "items", ["protein"], shelf_life=5, aliases=["pork sausages"])
|
|
|
|
# Carbs
|
|
make_ingredient("egg noodles", "nests", ["carb"], aliases=["noodles", "egg noodle nests"])
|
|
make_ingredient("microwave rice", "packets", ["carb"], aliases=["rice"])
|
|
make_ingredient("pasta", "g", ["carb"], aliases=["dried pasta"])
|
|
make_ingredient("bread", "loaves", ["carb"], shelf_life=5)
|
|
make_ingredient("frozen chips", "bags", ["carb", "frozen", "staple"], aliases=["chips", "McCain chips"])
|
|
|
|
# Veg
|
|
make_ingredient("onions", "items", ["veg", "staple"], aliases=["onion"])
|
|
make_ingredient("garlic", "bulbs", ["veg"], shelf_life=14, aliases=["garlic bulb"])
|
|
make_ingredient("frozen stir fry veg", "bags", ["veg", "frozen"], aliases=["stir fry veg", "Birds Eye stir fry veg"])
|
|
make_ingredient("broccoli", "heads", ["veg"], shelf_life=5)
|
|
make_ingredient("peppers", "items", ["veg"], shelf_life=7, aliases=["pepper", "bell pepper"])
|
|
make_ingredient("potatoes", "items", ["veg"], shelf_life=14, aliases=["Cyprus potatoes"])
|
|
make_ingredient("chopped tomatoes", "tins", ["veg"], aliases=["tinned tomatoes"])
|
|
|
|
# Dairy / Other
|
|
make_ingredient("eggs", "items", ["protein"], shelf_life=14, aliases=["free range eggs"])
|
|
make_ingredient("milk", "bottles", ["dairy"], shelf_life=7)
|
|
make_ingredient("Lurpak spreadable", "tubs", ["dairy"], aliases=["butter", "Lurpak"])
|
|
|
|
# Seasonings & Sauces
|
|
make_ingredient("salt", "n/a", ["seasoning", "staple"])
|
|
make_ingredient("black pepper", "n/a", ["seasoning", "staple"])
|
|
make_ingredient("olive oil", "bottles", ["seasoning", "staple"])
|
|
make_ingredient("paprika", "n/a", ["seasoning", "staple"])
|
|
make_ingredient("garlic powder", "n/a", ["seasoning", "staple"])
|
|
make_ingredient("soy sauce", "bottles", ["sauce"], aliases=["soy"])
|
|
make_ingredient("Lao Gan Ma", "jars", ["sauce"], aliases=["chilli crisp"])
|
|
make_ingredient("peanut butter", "jars", ["staple"], aliases=["Whole Earth crunchy"])
|
|
|
|
# Other
|
|
make_ingredient("baked beans", "tins", ["staple"])
|
|
make_ingredient("freezer bags", "boxes", ["staple"], aliases=["Tesco tie handle"])
|
|
|
|
self.stdout.write(f" Created {len(ingredients)} ingredients")
|
|
|
|
# --- Pantry Items (from current Pantry.md as of 31 Mar 2026) ---
|
|
self.stdout.write("Creating pantry items...")
|
|
|
|
def add_pantry(name, qty, unit, location, expiry=None, is_staple=False, notes=""):
|
|
PantryItem.objects.get_or_create(
|
|
ingredient=ingredients[name],
|
|
defaults={
|
|
"quantity": Decimal(str(qty)),
|
|
"unit": unit,
|
|
"location": location,
|
|
"expiry_date": expiry,
|
|
"is_staple": is_staple,
|
|
"notes": notes,
|
|
},
|
|
)
|
|
|
|
# Fridge
|
|
add_pantry("onions", 2, "items", "fridge", is_staple=True)
|
|
add_pantry("milk", 1, "bottles", "fridge", expiry=date(2026, 4, 3))
|
|
add_pantry("egg noodles", 1, "nests", "fridge")
|
|
add_pantry("garlic", 1, "bulbs", "fridge", expiry=date(2026, 4, 2), notes="minus 2 cloves")
|
|
add_pantry("bread", 1, "loaves", "fridge", expiry=date(2026, 4, 1))
|
|
add_pantry("pasta", 4, "portions", "cupboard")
|
|
add_pantry("microwave rice", 2, "packets", "cupboard")
|
|
add_pantry("peanut butter", 1, "jars", "cupboard")
|
|
add_pantry("Lurpak spreadable", 1, "tubs", "fridge")
|
|
add_pantry("Lao Gan Ma", 1, "jars", "cupboard")
|
|
add_pantry("sausages", 3, "items", "fridge", expiry=date(2026, 3, 30), notes="Likely expired — sniff test")
|
|
|
|
# Freezer
|
|
add_pantry("sausages", 3, "items", "freezer", notes="From frozen pack")
|
|
add_pantry("frozen chips", 1, "bags", "freezer", is_staple=True)
|
|
add_pantry("bread", 1, "loaves", "freezer", notes="Sliced")
|
|
|
|
# Staples (seasonings — always in cupboard)
|
|
for name in ["salt", "black pepper", "olive oil", "paprika", "garlic powder"]:
|
|
add_pantry(name, 1, "n/a", "cupboard", is_staple=True)
|
|
|
|
# Out of stock (for reference)
|
|
add_pantry("eggs", 0, "items", "fridge", notes="OUT — restock")
|
|
add_pantry("baked beans", 1, "tins", "cupboard")
|
|
add_pantry("freezer bags", 1, "boxes", "cupboard")
|
|
|
|
self.stdout.write(" Pantry seeded")
|
|
|
|
# --- Meta-Recipes ---
|
|
self.stdout.write("Creating meta-recipes...")
|
|
|
|
# === STIR FRY ===
|
|
stir_fry, _ = MetaRecipe.objects.get_or_create(
|
|
name="Stir Fry",
|
|
defaults={
|
|
"method": (
|
|
"1. Defrost protein if frozen (cold water, ~30 mins for fillets)\n"
|
|
"2. Slice protein thin, fry in oil til cooked through\n"
|
|
"3. Dice onion + mince garlic, add to pan\n"
|
|
"4. Add veg (frozen goes straight in)\n"
|
|
"5. Boil carb separately if needed (noodles 4 mins, rice per packet)\n"
|
|
"6. Toss everything together with sauce\n"
|
|
"7. Optional: push to one side, crack egg in, scramble, mix through"
|
|
),
|
|
"prep_time_mins": 10,
|
|
"cook_time_mins": 15,
|
|
"default_servings": 2,
|
|
"gear_needed": "frying pan, saucepan",
|
|
"tags": ["quick", "one-pan"],
|
|
},
|
|
)
|
|
|
|
# Stir fry slots
|
|
sf_protein, _ = Slot.objects.get_or_create(
|
|
meta_recipe=stir_fry, name="protein", defaults={"required": True, "max_choices": 1}
|
|
)
|
|
sf_carb, _ = Slot.objects.get_or_create(
|
|
meta_recipe=stir_fry, name="carb", defaults={"required": True, "max_choices": 1}
|
|
)
|
|
sf_veg, _ = Slot.objects.get_or_create(
|
|
meta_recipe=stir_fry, name="veg", defaults={"required": True, "max_choices": 3}
|
|
)
|
|
sf_sauce, _ = Slot.objects.get_or_create(
|
|
meta_recipe=stir_fry, name="sauce", defaults={"required": False, "max_choices": 2}
|
|
)
|
|
|
|
# Stir fry slot options
|
|
SlotOption.objects.get_or_create(slot=sf_protein, ingredient=ingredients["chicken mini fillets"], defaults={"quantity_per_serving": Decimal("2"), "unit": "items", "notes": "defrost first"})
|
|
SlotOption.objects.get_or_create(slot=sf_protein, ingredient=ingredients["pork mince"], defaults={"quantity_per_serving": Decimal("250"), "unit": "g"})
|
|
SlotOption.objects.get_or_create(slot=sf_carb, ingredient=ingredients["egg noodles"], defaults={"quantity_per_serving": Decimal("2"), "unit": "nests"})
|
|
SlotOption.objects.get_or_create(slot=sf_carb, ingredient=ingredients["microwave rice"], defaults={"quantity_per_serving": Decimal("1"), "unit": "packets"})
|
|
SlotOption.objects.get_or_create(slot=sf_veg, ingredient=ingredients["frozen stir fry veg"], defaults={"quantity_per_serving": Decimal("0.5"), "unit": "bags"})
|
|
SlotOption.objects.get_or_create(slot=sf_veg, ingredient=ingredients["broccoli"], defaults={"quantity_per_serving": Decimal("0.5"), "unit": "heads"})
|
|
SlotOption.objects.get_or_create(slot=sf_veg, ingredient=ingredients["peppers"], defaults={"quantity_per_serving": Decimal("1"), "unit": "items"})
|
|
SlotOption.objects.get_or_create(slot=sf_sauce, ingredient=ingredients["soy sauce"], defaults={"quantity_per_serving": Decimal("1"), "unit": "splash"})
|
|
SlotOption.objects.get_or_create(slot=sf_sauce, ingredient=ingredients["Lao Gan Ma"], defaults={"quantity_per_serving": Decimal("1"), "unit": "spoonful"})
|
|
|
|
# Stir fry base ingredients
|
|
MetaRecipeBase.objects.get_or_create(meta_recipe=stir_fry, ingredient=ingredients["onions"], defaults={"quantity_per_serving": Decimal("1"), "unit": "items"})
|
|
MetaRecipeBase.objects.get_or_create(meta_recipe=stir_fry, ingredient=ingredients["garlic"], defaults={"quantity_per_serving": Decimal("2"), "unit": "cloves"})
|
|
MetaRecipeBase.objects.get_or_create(meta_recipe=stir_fry, ingredient=ingredients["olive oil"], defaults={"quantity_per_serving": Decimal("1"), "unit": "splash"})
|
|
|
|
# === TRAYBAKE ===
|
|
traybake, _ = MetaRecipe.objects.get_or_create(
|
|
name="Traybake",
|
|
defaults={
|
|
"method": (
|
|
"1. Preheat oven to 200°C\n"
|
|
"2. Dice onion, chop veg into chunks\n"
|
|
"3. Toss protein + veg in oil and seasoning on baking tray\n"
|
|
"4. Spread out in single layer\n"
|
|
"5. Oven for 30-40 mins, turning halfway\n"
|
|
"6. If using chicken thighs: remove skin before cooking"
|
|
),
|
|
"prep_time_mins": 10,
|
|
"cook_time_mins": 35,
|
|
"default_servings": 2,
|
|
"gear_needed": "baking tray",
|
|
"tags": ["easy", "one-tray", "batch-cook"],
|
|
},
|
|
)
|
|
|
|
# Traybake slots
|
|
tb_protein, _ = Slot.objects.get_or_create(
|
|
meta_recipe=traybake, name="protein", defaults={"required": True, "max_choices": 1}
|
|
)
|
|
tb_veg, _ = Slot.objects.get_or_create(
|
|
meta_recipe=traybake, name="veg", defaults={"required": True, "max_choices": 3}
|
|
)
|
|
tb_seasoning, _ = Slot.objects.get_or_create(
|
|
meta_recipe=traybake, name="seasoning", defaults={"required": True, "max_choices": 1}
|
|
)
|
|
|
|
# Traybake slot options
|
|
SlotOption.objects.get_or_create(slot=tb_protein, ingredient=ingredients["sausages"], defaults={"quantity_per_serving": Decimal("3"), "unit": "items"})
|
|
SlotOption.objects.get_or_create(slot=tb_protein, ingredient=ingredients["chicken thighs"], defaults={"quantity_per_serving": Decimal("2"), "unit": "items", "notes": "skin off"})
|
|
SlotOption.objects.get_or_create(slot=tb_veg, ingredient=ingredients["peppers"], defaults={"quantity_per_serving": Decimal("1"), "unit": "items"})
|
|
SlotOption.objects.get_or_create(slot=tb_veg, ingredient=ingredients["broccoli"], defaults={"quantity_per_serving": Decimal("0.5"), "unit": "heads"})
|
|
SlotOption.objects.get_or_create(slot=tb_veg, ingredient=ingredients["potatoes"], defaults={"quantity_per_serving": Decimal("2"), "unit": "items"})
|
|
SlotOption.objects.get_or_create(slot=tb_veg, ingredient=ingredients["frozen chips"], defaults={"quantity_per_serving": Decimal("1"), "unit": "handful"})
|
|
SlotOption.objects.get_or_create(slot=tb_seasoning, ingredient=ingredients["paprika"], defaults={"quantity_per_serving": Decimal("1"), "unit": "tsp"})
|
|
|
|
# Traybake base ingredients
|
|
MetaRecipeBase.objects.get_or_create(meta_recipe=traybake, ingredient=ingredients["onions"], defaults={"quantity_per_serving": Decimal("1"), "unit": "items"})
|
|
MetaRecipeBase.objects.get_or_create(meta_recipe=traybake, ingredient=ingredients["olive oil"], defaults={"quantity_per_serving": Decimal("1"), "unit": "drizzle"})
|
|
MetaRecipeBase.objects.get_or_create(meta_recipe=traybake, ingredient=ingredients["salt"], defaults={"quantity_per_serving": Decimal("1"), "unit": "pinch"})
|
|
MetaRecipeBase.objects.get_or_create(meta_recipe=traybake, ingredient=ingredients["black pepper"], defaults={"quantity_per_serving": Decimal("1"), "unit": "pinch"})
|
|
|
|
self.stdout.write(self.style.SUCCESS("✅ Seed complete!"))
|
|
self.stdout.write(f" Tags: {Tag.objects.count()}")
|
|
self.stdout.write(f" Ingredients: {Ingredient.objects.count()}")
|
|
self.stdout.write(f" Pantry items: {PantryItem.objects.count()}")
|
|
self.stdout.write(f" Meta-recipes: {MetaRecipe.objects.count()}")
|
|
self.stdout.write(f" Slots: {Slot.objects.count()}")
|
|
self.stdout.write(f" Slot options: {SlotOption.objects.count()}")
|