Open In App

Recipe Meal Planner using Django

Last Updated : 23 Jul, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

We will create the Recipe Meal Planner using Django step-by-step. Generally, we will implement the CRUD (Create, Read, Update, Delete) operations, allowing users to add the recipe name, day, and the recipe itself. Additionally, we will establish a login system, requiring users to register and log in before creating the recipe meal planner. Once the user has added all the daily recipe information, they simply need to click a single button. Subsequently, a PDF form will be generated, which the user can save for future reference.

Here, we will create the step-by-step Recipe Meal Planner using Django.

Create Project and App

To start the project and app use this command

django-admin startproject core
cd core
python manage.py startapp home

Register the App in core/settings.py

INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"home",
]

File Structure

file-structure

Building a recipe meal planner is a practical way to learn Django. If you want to enhance your skills and build more feature-rich applications, the Django Web Development-Basics to Advance Course is an ideal resource.

Define the Model

home/models.py: Here, the below code defines a Django model named Recipe with fields for user, day, name, and description. The user field is a foreign key to the built-in User model, allowing a null value on deletion. The default values for day, name, and description are set to 'something'.

Python
from django.db import models
from django.contrib.auth.models import User

class Recipe(models.Model):
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    day = models.CharField(max_length=100, default='something')
    name = models.CharField(max_length=100, default='something')
    description = models.CharField(max_length=100, default='something')

Create Views

home/views.py

This file handles all the logic for:

  • Showing and managing recipes (recipes)
  • Updating and deleting (update_recipe, delete_recipe)
  • Authentication (login_page, register_page, custom_logout)
  • PDF generation (pdf)
Python
from django.shortcuts import render, redirect
from .models import Recipe
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.contrib import messages
from django.contrib.auth import login, authenticate
from django.contrib.auth.decorators import login_required 
from django.contrib.auth.models import User
from django.contrib.auth import logout

#create recipes page
@login_required(login_url='/login/')
def recipes(request):
    if request.method == 'POST': 
        data = request.POST
        day = data.get('day')
        name = data.get('name')
        description = data.get('description')      
        Recipe.objects.create(
            day = day,
            name=name,
            description=description,        
        )
        return redirect('/')

    queryset = Recipe.objects.all()
    if request.GET.get('search'):
        queryset = queryset.filter(
            day__icontains=request.GET.get('search'))
         
    context = {'recipes': queryset}
    return render(request, 'recipe.html', context)

#Update the recipes data 
@login_required(login_url='/login/')
def update_recipe(request, id):
    queryset = Recipe.objects.get(id=id)

    if request.method == 'POST':
        data = request.POST   
        day = data.get('day')
        name = data.get('name')
        description = data.get('description')
        
        queryset.day = day
        queryset.name = name
        queryset.description = description
        queryset.save()
        return redirect('/')

    context = {'recipe': queryset}
    return render(request, 'update_recipe.html', context)

#delete the recipes data
@login_required(login_url='/login/')
def delete_recipe(request, id):
    queryset = Recipe.objects.get(id=id)
    queryset.delete()
    return redirect('/')

#login page for user
def login_page(request):
    if request.method == "POST":
        try:
            username = request.POST.get('username')
            password = request.POST.get('password')
            user_obj = User.objects.filter(username=username)
            if not user_obj.exists():
                messages.error(request, "Username not found")
                return redirect('/login/')
            user_obj = authenticate(username=username, password=password)
            if user_obj:
                login(request, user_obj)
                return redirect('recipes')
            messages.error(request, "Wrong Password")
            return redirect('/login/')
        except Exception as e:
            messages.error(request, "Something went wrong")
            return redirect('/register/')
    return render(request, "login.html")

#register page for user
def register_page(request):
    if request.method == "POST":
        try:
            username = request.POST.get('username')
            password = request.POST.get('password')
            user_obj = User.objects.filter(username=username)
            if user_obj.exists():
                messages.error(request, "Username is taken")
                return redirect('/register/')
            user_obj = User.objects.create(username=username)
            user_obj.set_password(password)
            user_obj.save()
            messages.success(request, "Account created")
            return redirect('/login')
        except Exception as e:
            messages.error(request, "Something went wrong")
            return redirect('/register')
    return render(request, "register.html")

#logout function
def custom_logout(request):
    logout(request)
    return redirect('login') 

#Generate the Bill
@login_required(login_url='/login/')
def pdf(request):
    if request.method == 'POST':
        data = request.POST    
        day = data.get('day')
        name = data.get('name')
        description = data.get('description')
        
        Recipe.objects.create(
            day = day,
            name=name,
            description=description,
          
        )
        return redirect('pdf')
    queryset = Recipe.objects.all()

    if request.GET.get('search'):
        queryset = queryset.filter(
            day__icontains=request.GET.get('search'))  

    context = {'recipes': queryset}
    return render(request, 'pdf.html', context)

Configure URLs

core/urls.py: Here, the Django URL patterns include routes for user authentication (login, logout, register), recipe handling (view, update, delete), and a PDF generation endpoint. These paths are associated with corresponding views from the 'home' app.

Python
from django.contrib import admin
from django.urls import path
from home import views

urlpatterns = [
    path('logout/', views.custom_logout, name="logout"),
    path('pdf/', views.pdf , name='pdf'),
    path('admin/', admin.site.urls),
    path('login/' , views.login_page, name='login'),
    path('register/', views.register_page, name='register'),
    
    path('', views.recipes, name='recipes'),
    path('update_recipe/<id>', views.update_recipe, name='update_recipe'),
    path('delete_recipe/<id>', views.delete_recipe, name='delete_recipe'),
]

Build Templates

Create HTML templates in home/templates/:

login.html: Below, HTML code is a concise Bootstrap-based login form for a job portal, featuring input fields for username and password. Success messages are displayed using Bootstrap's alert, and a link is included for users to create a new account.

HTML
<!doctype html>
<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <title>Job Portal</title>
</head>
<body><br><br><br><br>

   
   <br><br>
    
    <div class="container bg-white col-md-2 card shadow p-3 " id="log">
        <div class="login-form">
            {% if messages %}
            {% for message in messages %}
            <div class="alert alert-success {{ message.tags }} mt-4" 
                 role="alert">
                {{ message }}
            </div>
            {% endfor %}
            {% endif %}
            <form action="" method="post">
                {% csrf_token %}
                <h4  >  Login </h4>
                <div class="">
                    <input type="text"  name="username" 
                           placeholder="Username" required
                        >
                </div>
                <div class="mt-2">
                    <input type="password"  name="password" 
                           placeholder="Password" required>
                </div>
                <div class="mt-2">
                    <button   >Login</button>
                </div>
                <br>
            </form>
            <p  ><a href="{% url 'register' %}" >Create an
                    Account.</a></p>
        </div>
    </div>

</body>

</html>

register.html: The provided HTML code creates a registration form for a job portal using Bootstrap. It includes input fields for username and password, a registration button, and a link to the login page. Bootstrap's alert component is utilized for displaying success messages.

HTML
<!doctype html>
<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <title>Job Portal</title>
</head>

<body>
    <body>
        <br> <br><br><br><br><br>

        <div class="container bg-white mx-auto col-md-2 card shadow p-3">
            <div class="login-form">
                {% if messages %}
                {% for message in messages %}
                <div class="alert alert-success {{ message.tags }}" role="alert">
                    {{ message }}
                </div>
                {% endfor %}
                {% endif %}
                <form action="" method="post">
                    {% csrf_token %}
                    <h4 > Register </h4>
                    <div >
                        <input type="text"  name="username" placeholder="Username" required>
                    </div>
                
                    <div class="mt-2">
                        <input type="password" name="password" placeholder="Password" required>
                    </div>
                    <div class="mt-2">
                        <button >Register</button>
                    </div>
                </form>
                <p ><a href="{% url 'login' %}">Log In </a></p>
            </div>
        </div>

    </body>

</html>

recipe.html:  The Django template extends a base HTML file and presents a form for adding recipe data. It includes a button to generate a PDF recipe plan and displays a table of existing recipes with options to delete or update. The styling includes a hover effect for link color change.

HTML
{% extends "base.html" %}
{% block start %}

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<style>
.ok{
    color: white;
    text-decoration: none;
  }
  .ok:hover{
    color: white;
    text-decoration: none;
  }
 
</style>

<div class="container mt-3 col-6">
  <br><br>
    <form class="col-6 mx-auto card p-3 shadow-lg" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <h4> Recipe </h4>
        <hr>
        <div class="form-group">
          <label for="exampleInputEmail1">Day-Time </label>
          <input type="text" name="day" required>
        </div>
        <div class="form-group">
          <label for="exampleInputEmail1">Recipe </label>
          <input name="name" type="text" required>
         
          
         </div>
        <div class="form-group">
          <label for="exampleInputPassword1">Description </label>
          <!-- <input name="description" type="text" rows="10" cols="50" required> -->

          <textarea  name="description"  type="text" rows="5" cols="30"></textarea>
        </div>
        <button type="submit" class="">Add Data</button>
    </form>
    <hr>
    <div class="class mt-5">
        <form action="">
          <button > <a  href="{% url 'pdf' %}">Generate Plan </a></button>
        </form>

        <table class="table mt-6">
            <thead>
                <tr>
                    <th scope="col">S.No. </th>
                    <th scope="col">Day-Time </th>
                    <th scope="col">Recipe Name </th>
                    <th scope="col">Description </th>
                    <th scope="col">Actions</th>
                </tr>
            </thead>
            <tbody>
                {% for recipe in recipes %}
                <tr>
                    <th scope="row">{{forloop.counter}}</th>
                    <td>{{recipe.day}}</td>
                    <td> {{recipe.name}}</td>
                    <td>{{recipe.description}}</td>
                    <td>
                        <a href="/delete_recipe/{{recipe.id }}" >Delete </a>
                        <a href="/update_recipe/{{recipe.id }}">Update </a>
                    </td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>


    {% endblock %}

update_recipe.html:  The Django template, extending a base HTML file, displays a form for updating recipe data. It pre-fills fields with existing data and allows users to modify day, recipe name, and description. The styling uses Bootstrap, creating a centered card with a shadow effect.

HTML
{% extends "base.html" %}
{% block start %}

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<style>

</style>


<div class="container mt-5 col-5">

  <form class="col-6 mx-auto card p-3 shadow-lg" method="post" enctype="multipart/form-data">
    {% csrf_token %}

   
    <div class="form-group">
      <label for="exampleInputEmail1">Day-Time </label>
      <input type="text" name="day" value="{{recipe.day}}"  required>
    </div>
    <div class="form-group">
      <label for="exampleInputEmail1">Recipe </label>
      <input name="name" type="text" value="{{recipe.description}}" 
        required>

    </div>
    <div class="form-group">
      <label for="exampleInputPassword1">Description </label>
        <textarea  name="description"  type="text" rows="5" cols="30" value="{{recipe.description}}"></textarea>
    <br> 
    <br>

    <button type="submit" >Update Data</button>
  </form>


</div>

{% endblock %}

pdf.html:  Below, HTML document defines a Recipe Meal Planner webpage with Bootstrap styling. It includes a table displaying recipe details and a button to generate a PDF using the html2pdf library. The styling features a clean layout with a card container and a green-themed table. JavaScript functionality is added to trigger PDF generation on button click.

HTML
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Recipe Meal Planner</title>

    <!-- Add Bootstrap CSS Link -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Add html2pdf library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.js"></script>
    <style>
        body {
            background-color: #f8f9fa;
        }

        .recipe-container {
            padding: 20px;
            margin-top: 30px;
            background-color: #ffffff;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        .recipe-header {
            color: black;
        }

        .recipe-table th,
        .recipe-table td {
            text-align: center;
            border: 1px solid #dee2e6;
            padding: 8px;
        }

        .recipe-table th {
            background-color: #70e78c; 
            color: #fff; 
        }

        .generate-pdf-btn {
            margin-top: 20px;
        }
    </style>
</head>

<body>

    <div class="container recipe-container col-md-8">
        <div class="card">
            <div class="card-body">
                <h2 class="recipe-header">Recipe Meal Planner</h2>
                <br><br>
                <table class="table recipe-table">
                    <thead class="recipe-table-head">
                        <tr>
                            <th>Day-Time</th>
                            <th>Recipe Name</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for recipe in recipes %}
                        <tr>
                            <td>{{recipe.day}}</td>
                            <td>{{recipe.name}}</td>
                            <td>{{recipe.description}}</td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>

                <button class="btn btn-danger  generate-pdf-btn" onclick="generatePDF()">Generate PDF</button>
            </div>
        </div>
    </div>

    <!-- Add Bootstrap JS and Popper.js Scripts -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>

    <script>
        function generatePDF() {
            var element = document.querySelector('.recipe-container');
            html2pdf(element);
        }
    </script>
</body>

</html>

base.html: The HTML template serves as a base for Django views, with a dynamic title based on the variable page. It includes a block for content rendering, allowing customization in extending templates.

HTML
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{page}}</title>
</head>
<body>

    {% block start %}
    {% endblock %}

    <script>
        console.log('Hey Django')
    </script>
</body>

</html>

Admin Setup (Optional)

To manage recipes via the Django admin:

home/admin.py

Python
from django.contrib import admin
from .models import *
from django.db.models import Sum

admin.site.register(Recipe) 

Run:

python manage.py createsuperuser

Then log in at http://127.0.0.1:8000/admin/.

Deployment and Running the Project

Run Migrations:

python manage.py makemigrations
python manage.py migrate

Run Server:

python manage.py runserver

Output

register-

login-

sheet

update

final-sheetii


Similar Reads