diff options
Diffstat (limited to 'kitchen/management/commands/seed.py')
| -rw-r--r-- | kitchen/management/commands/seed.py | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/kitchen/management/commands/seed.py b/kitchen/management/commands/seed.py new file mode 100644 index 0000000..4aa74bf --- /dev/null +++ b/kitchen/management/commands/seed.py @@ -0,0 +1,245 @@ +""" +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()}") |
