Adding Manual Recipes

Introduction: The Need for Manual Recipe Entry

Welcome back! So far, you have set up your project, designed your database models, and learned how to safely reset your database. Now, let’s take the next step: adding recipes to your app.

While it’s possible to add recipes automatically or through an API, sometimes you need a simple way to enter recipes by hand. This is especially useful for testing, quick data entry, or when you want to add a recipe that you found or created yourself. In this lesson, you’ll learn how to build a script that lets you add recipes directly from the command line. This script will prompt you for the recipe name, ingredients, and steps, then save everything to your database.

By the end of this lesson, you’ll be able to run a script, enter a new recipe, and see it appear in your app’s database.

Quick Recall: Models and Database Access

Before we dive in, let’s quickly remind ourselves of the key models and how we interact with the database. You’ve already created Recipe and Ingredient models. These models are connected by a many-to-many relationship, which means a recipe can have many ingredients, and an ingredient can belong to many recipes.

Here’s a reminder of what these models look like:

from django.db import models

class Ingredient(models.Model):
    name = models.CharField(max_length=100, unique=True)

class Recipe(models.Model):
    name = models.CharField(max_length=255, unique=True)
    steps = models.TextField()
    ingredients = models.ManyToManyField(Ingredient, related_name="recipes")

In this lesson, we’ll use these models and the built-in Object-Relational Mapper (ORM) to save new recipes and ingredients without writing raw database queries.

Preparing the Script Environment

To create a script that can add recipes, you need to make sure it can access your project settings and its database models. This requires a specific setup to initialize the environment before you can import your models.

Let’s look at how to set up the script:

import os
import sys

# 1. Identify paths and add the project root to the system path
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.abspath(os.path.join(CURRENT_DIR, ".."))
if PROJECT_ROOT not in sys.path:
    sys.path.insert(0, PROJECT_ROOT)

# 2. Tell the framework where the settings are located
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cooking_helper.settings")

# 3. Initialize the framework
import django
django.setup()

# Now we can safely import models and tools
from django.db import transaction
from recipes.models import Ingredient, Recipe
  • os and sys are used to adjust the import path so the script can find your project modules.
  • os.environ.setdefault points to your project's settings file.
  • django.setup() initializes the application environment, allowing you to use models outside of a standard web request.

Collecting Recipe Information from the User

Now, let’s build the part of the script that asks the user for recipe details. We want to collect three things: the recipe name, a list of ingredients, and the steps.

Prompting for the Recipe Name

We start by asking for the recipe name:

name = input("Recipe name: ").strip()
if not name:
    print("Recipe name is required.")
    return

Prompting for Ingredients

Next, we ask for ingredients, one per line. The user can press Enter on an empty line to finish:

def prompt_for_ingredients():
    print("Enter ingredients (one per line). Leave empty and press Enter to finish.")
    ingredients = []
    while True:
        try:
            item = input("> ")
        except EOFError:
            break
        item = item.strip()
        if not item:
            break
        ingredients.append(item)
    return ingredients

Prompting for Steps

Finally, we ask for the steps. The user can type as many lines as they want and type END on a line by itself to finish:

def prompt_for_steps():
    print("Enter steps (multiple lines). Type END on a line by itself to finish.")
    lines = []
    while True:
        try:
            line = input()
        except EOFError:
            break
        if line.strip().upper() == "END":
            break
        lines.append(line)
    return "\n".join(lines)

Saving the Recipe and Ingredients

Now that we have the recipe details, let’s save them to the database. We need to handle this carefully: if something goes wrong halfway through, we don't want a partial recipe saved. We use an atomic transaction to ensure everything is saved together or not at all.

Here’s the logic for saving the data:

def _normalize(value: str) -> str:
    return (value or "").strip().lower()

# Check for duplicates
existing = Recipe.objects.filter(name__iexact=name).first()
if existing:
    print(f"Recipe '{name}' already exists (id={existing.id}).")
    return

# Save using an atomic transaction
with transaction.atomic():
    recipe = Recipe.objects.create(name=name, steps=steps)

    for ing_name in ingredients_input:
        norm = _normalize(ing_name)
        if not norm:
            continue
        # Get the ingredient if it exists, otherwise create it
        ingredient, _created = Ingredient.objects.get_or_create(name=norm)
        # Link the ingredient to the recipe
        recipe.ingredients.add(ingredient)

print(f"Recipe '{name}' added successfully with {recipe.ingredients.count()} ingredients (id={recipe.id}).")

Let’s break down the database operations:

  • Recipe.objects.filter(name__iexact=name): Checks if a recipe with that name already exists in a case-insensitive way.
  • with transaction.atomic(): This block ensures that the recipe and all its ingredients are saved as a single unit of work. If an error occurs, the database rolls back to its previous state.
  • Recipe.objects.create(...): Creates and saves a new Recipe instance in one step.
  • Ingredient.objects.get_or_create(name=norm): A very helpful method that tries to find an ingredient by its name. If it doesn't find one, it creates it. This prevents duplicate ingredients in your database.
  • recipe.ingredients.add(ingredient): This manages the many-to-many relationship by creating a link between the recipe and the ingredient in the background.

Summary and What’s Next

In this lesson, you learned how to build a script that lets you add recipes manually to your cooking app’s database. You saw how to:

  • Set up your script environment and initialize the framework to access models.
  • Prompt the user for recipe details and validate their input using loops and custom functions.
  • Use get_or_create to efficiently manage related data.
  • Use atomic transactions to ensure data integrity when saving complex relationships.

This script is a practical tool for quickly adding recipes and testing your app. Now that you understand how it works, you’re ready to try it yourself in the practice exercises. Focus on running the script, entering different recipes, and checking your database to see the results.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal