Skip to content
This repository was archived by the owner on Mar 11, 2026. It is now read-only.

Commit 4fcda26

Browse files
authored
Populate database with recipes for the demo (#874)
* added recipe population script * added the recipes json file * migrations * solve build issues * fixed custom unit issues * fixed the recipe custom size and missing ingredients
1 parent c09e9a5 commit 4fcda26

3 files changed

Lines changed: 921 additions & 0 deletions

File tree

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
"""
2+
Recipe loader script for populating the database with realistic recipes.
3+
4+
Usage:
5+
python backend/api/db_initialization/load_recipes.py
6+
7+
This script loads recipes from recipes_data.json and creates:
8+
- Post entries for each recipe
9+
- Recipe entries linked to posts
10+
- RecipeIngredient entries linking recipes to food items
11+
- Tags associated with posts
12+
"""
13+
14+
import json
15+
import os
16+
import sys
17+
import django
18+
from pathlib import Path
19+
20+
# Django setup
21+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
22+
sys.path.append(BASE_DIR)
23+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
24+
django.setup()
25+
26+
from django.contrib.auth import get_user_model
27+
from foods.models import FoodEntry
28+
from forum.models import Post, Recipe, RecipeIngredient, Tag
29+
30+
31+
class RecipeLoader:
32+
"""Loads recipes from JSON file into the database."""
33+
34+
def __init__(self):
35+
self.count = 0
36+
self.failed = 0
37+
self.User = get_user_model()
38+
39+
def load_recipes(self, json_file, limit=None):
40+
"""Main entry point: load recipes from JSON file."""
41+
json_file = Path(json_file)
42+
43+
if not json_file.exists():
44+
raise FileNotFoundError(f"JSON file not found: {json_file}")
45+
46+
print(f"Loading recipes from {json_file}...")
47+
48+
with open(json_file, "r", encoding="utf-8") as f:
49+
recipes = json.load(f)
50+
51+
print(f"Found {len(recipes)} recipes in JSON")
52+
53+
iterable = recipes[:limit] if limit else recipes
54+
for idx, recipe_data in enumerate(iterable, start=1):
55+
try:
56+
self.create_recipe(recipe_data)
57+
self.count += 1
58+
print(f" Created: {recipe_data.get('title', 'Unknown')[:40]:<40}", end="\r")
59+
except Exception as e:
60+
self.failed += 1
61+
print(f"⚠️ Failed to load recipe '{recipe_data.get('title', 'Unknown')}': {str(e)}")
62+
63+
print(f"\n✓ Successfully loaded {self.count} recipes.")
64+
print(f"❌ Failed: {self.failed}")
65+
66+
def get_or_create_user(self, username):
67+
"""Get or create a user for recipe authorship."""
68+
user, created = self.User.objects.get_or_create(
69+
username=username,
70+
defaults={"email": f"{username}@example.com"}
71+
)
72+
return user
73+
74+
def create_recipe(self, recipe_data):
75+
"""Create a recipe with its post and ingredients."""
76+
title = recipe_data.get("title", "").strip()
77+
body = recipe_data.get("body", "").strip()
78+
author_username = recipe_data.get("author_username", "demo")
79+
tag_names = recipe_data.get("tags", [])
80+
instructions = recipe_data.get("instructions", "")
81+
ingredients_data = recipe_data.get("ingredients", [])
82+
83+
if not title:
84+
raise ValueError("Recipe title is missing")
85+
86+
# Check if recipe already exists
87+
if Post.objects.filter(title=title).exists():
88+
print(f" Skipping (exists): {title[:40]:<40}")
89+
return
90+
91+
# Get or create author
92+
author = self.get_or_create_user(author_username)
93+
94+
# Create post
95+
post = Post.objects.create(
96+
title=title,
97+
body=body,
98+
author=author
99+
)
100+
101+
# Add tags
102+
for tag_name in tag_names:
103+
tag, _ = Tag.objects.get_or_create(name=tag_name)
104+
post.tags.add(tag)
105+
106+
# Create recipe
107+
recipe = Recipe.objects.create(
108+
post=post,
109+
instructions=instructions
110+
)
111+
112+
# Create ingredients
113+
for ing_data in ingredients_data:
114+
food_name = ing_data.get("foodName", "")
115+
amount = ing_data.get("amount", 0)
116+
custom_unit = ing_data.get("customUnit", "grams")
117+
custom_amount = ing_data.get("customAmount", 0)
118+
119+
# Find food entry (try exact match first, then partial)
120+
food = FoodEntry.objects.filter(name=food_name).first()
121+
if not food:
122+
# Try partial match
123+
food = FoodEntry.objects.filter(name__icontains=food_name).first()
124+
125+
if not food:
126+
print(f" ⚠️ Food not found: {food_name}")
127+
continue
128+
129+
RecipeIngredient.objects.create(
130+
recipe=recipe,
131+
food=food,
132+
amount=amount,
133+
customUnit=custom_unit,
134+
customAmount=custom_amount
135+
)
136+
137+
138+
def load_recipes_for_migration(apps, schema_editor):
139+
"""
140+
Function to be called from Django migration.
141+
Uses historical models provided by the migration framework.
142+
"""
143+
Post = apps.get_model("forum", "Post")
144+
Recipe = apps.get_model("forum", "Recipe")
145+
RecipeIngredient = apps.get_model("forum", "RecipeIngredient")
146+
Tag = apps.get_model("forum", "Tag")
147+
FoodEntry = apps.get_model("foods", "FoodEntry")
148+
User = apps.get_model("accounts", "User")
149+
150+
# Load JSON file
151+
json_file = Path(__file__).parent / "recipes_data.json"
152+
if not json_file.exists():
153+
print(f"⚠️ Recipe data file not found: {json_file}")
154+
return
155+
156+
with open(json_file, "r", encoding="utf-8") as f:
157+
recipes = json.load(f)
158+
159+
count = 0
160+
for recipe_data in recipes:
161+
title = recipe_data.get("title", "").strip()
162+
body = recipe_data.get("body", "").strip()
163+
author_username = recipe_data.get("author_username", "demo")
164+
tag_names = recipe_data.get("tags", [])
165+
instructions = recipe_data.get("instructions", "")
166+
ingredients_data = recipe_data.get("ingredients", [])
167+
168+
if not title:
169+
continue
170+
171+
# Skip if exists
172+
if Post.objects.filter(title=title).exists():
173+
continue
174+
175+
# Get or create author
176+
user, _ = User.objects.get_or_create(
177+
username=author_username,
178+
defaults={"email": f"{author_username}@example.com"}
179+
)
180+
181+
# Create post
182+
post = Post.objects.create(
183+
title=title,
184+
body=body,
185+
author=user
186+
)
187+
188+
# Add tags
189+
for tag_name in tag_names:
190+
tag, _ = Tag.objects.get_or_create(name=tag_name)
191+
post.tags.add(tag)
192+
193+
# Create recipe
194+
recipe = Recipe.objects.create(
195+
post=post,
196+
instructions=instructions
197+
)
198+
199+
# Create ingredients
200+
for ing_data in ingredients_data:
201+
food_name = ing_data.get("foodName", "")
202+
amount = ing_data.get("amount", 0)
203+
custom_unit = ing_data.get("customUnit", "grams")
204+
custom_amount = ing_data.get("customAmount", 0)
205+
206+
food = FoodEntry.objects.filter(name=food_name).first()
207+
if not food:
208+
food = FoodEntry.objects.filter(name__icontains=food_name).first()
209+
210+
if not food:
211+
continue
212+
213+
RecipeIngredient.objects.create(
214+
recipe=recipe,
215+
food=food,
216+
amount=amount,
217+
customUnit=custom_unit,
218+
customAmount=custom_amount
219+
)
220+
221+
count += 1
222+
223+
print(f"✓ Loaded {count} recipes via migration")
224+
225+
226+
if __name__ == "__main__":
227+
import argparse
228+
229+
parser = argparse.ArgumentParser(description="Load recipes from JSON file")
230+
parser.add_argument(
231+
"--json-file",
232+
type=str,
233+
default=str(Path(__file__).parent / "recipes_data.json"),
234+
help="Path to JSON file containing recipes",
235+
)
236+
parser.add_argument(
237+
"--limit",
238+
type=int,
239+
default=None,
240+
help="Limit number of recipes to load",
241+
)
242+
243+
args = parser.parse_args()
244+
245+
loader = RecipeLoader()
246+
try:
247+
loader.load_recipes(args.json_file, limit=args.limit)
248+
except Exception as e:
249+
print(f"❌ Error: {e}")
250+
sys.exit(1)

0 commit comments

Comments
 (0)