Create a Recipe App in Django - Tutorial
Tags: django, python, webdev, tutorial
Published: 2022-06-16 23:48:27

In this tutorial we'll build a recipes app in Django where users can register and create, read, update, and delete recipes. I will also link videos 📽️ associated with each step below.


### Final Product: 
![Recipes Django App Final Product](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zcqfjgt70g00yhis29kn.png)

---

## Step 1: Project Setup

{% embed https://www.youtube.com/watch?v=w7EJu9Gd5Ns %}

By the end of this step we'll have a Django project running. We'll be using python 3.8. Anything above 3.3 should work.

1. Create a virtual environment for the project so it's packages are separated from others on your computer. In your terminal type:
Windows:
`>> python -m venv env`
`>> .\env\scripts\activate`
Mac:
`>> python3 -m venv env`
`>> source env/scripts/activate`

2. Next we need to install Django,  in your terminal type:
`>> pip install django==3.2.*`

3. Create a Django project. Each Django project may consist of 1 or more apps, in your terminal type (the '.' creates in current dir):
`>> django-admin startproject config .`

4. Run the server and view localhost:8000. In your terminal type (type ctr+c to stop the server):
`>> python manage.py runserver`

### 🍾🎉 Woohoo our Django project is up and running!

---

## Step 2: Django Apps, URLs, and Views
{% embed https://www.youtube.com/watch?v=FInbHmxAL6c %}

By the end of this step we'll have a web page with text!

1) Create your first Django app, in the terminal type:
`>> python manage.py startapp recipes`

2) Open config/settings.py and add 'recipes' under installed apps
```
# config/settings.py
INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles',

    #local apps
    'recipes',
]
```

3) Let's create a view to return something to the user. Open the recipes/views.py file, import HttpResponse and create a function home(request) that returns the HttpResponse with some text:

```
# recipes/views.py
from django.http import HttpResponse

def home(request):
  return HttpResponse('<h1>Welcome to the Recipes home page</h1>')
```

4) Create a new file in the recipes folder 'urls.py' for the routing. Import views.py from the same dir and add a new path to the urlpatterns for the home route:

```
# recipes/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.home, name="recipes-home"),
]
```

5) Update the urls.py file in the config folder to include the urls from the recipes app. in config/urls.py add:

```
# config/urls.py
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('recipes.urls')),
]
```

### Go ahead and run the server again, go to localhost:8000 and you should see 'Welcome to the Recipes home page' 🚀

---

## Step 3: HTML Templates + UI
{% embed https://www.youtube.com/watch?v=BFqvDCIKFNs %}

In this step we'll return an actual html page!

1) Create a templates dir to hold the files with this path: recipes/templates/recipes

2) In that templates/recipes folder create a file 'base.html' and let's add some html that all our pages will use:

```
# /templates/recipes/base.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    {% if title %}
    <title>Django Recipes - {{title}}</title>
    {% else %}
    <title>Django Recipes</title>
    {% endif %}
  </head>
  <body>
    {% block content %} {% endblock %}
  </body>
</html>
```

3) Create another html template 'home.html' and add some html for the homepage, let's pass in some dummy data:

```
# templates/recipes/home.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipes:</h1>
{% for recipe in recipes %}
<h1>{{recipe.title}}</h1>
<p>{{recipe.author}} | {{recipe.date_posted}}</p>
<h5>{{recipe.content}}</h5>
<hr />
{% endfor %}
<!-- -->
{% endblock content %}
```

4) Now let's update the home view and pass the dummy data. In the recipes/views.py file add:

```
# recipes/views.py
recipes = [
  {
    'author': 'Dom V.',
    'title': 'Meatballs',
    'content': 'Combine ingredients, form into balls, brown, then place in oven.',
    'date_posted': 'May 18th, 2022'
  },
  {
    'author': 'Gina R.',
    'title': 'Chicken Cutlets',
    'content': 'Bread chicken, cook on each side for 8 min',
    'date_posted': 'May 18th, 2022'
  },
  {
    'author': 'Bella O.',
    'title': 'Sub',
    'content': 'Combine ingredients.',
    'date_posted': 'May 18th, 2022'
  }
]

# Create your views here.
def home(request):
  context = {
    'recipes': recipes
  }
  return render(request, 'recipes/home.html', context)
```

### Now go back to the home page (run the server!) and you should see recipe data displaying 🍕🍟🍔

---

## Step 4: Bootstrap styling
{% embed https://www.youtube.com/watch?v=cTf-Hsqq3GA %}

In this step we'll style the site so it looks a little better and has some navigation 🧭

1) Update the templates/recipes/base.html file so it loads the bootstrap css and js from a cdn, and has a basic navbar:

```
# templates/recipes/base.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    {% if title %}
    <title>Django Recipes - {{title}}</title>
    {% else %}
    <title>Django Recipes</title>
    {% endif %}
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
      <div class="container">
        <a class="navbar-brand" href="{% url 'recipes-home' %}">Recipes App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarNav"
          aria-controls="navbarNav"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
          <ul class="navbar-nav">
            <li class="nav-item">
              <a
                class="nav-link"
                aria-current="page"
                href="{% url 'recipes-home' %}"
                >Recipes</a
              >
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <div class="container mt-4 col-8">{% block content %} {% endblock %}</div>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.5/dist/umd/popper.min.js"
      integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.min.js"
      integrity="sha384-kjU+l4N0Yf4ZOJErLsIcvOU2qSb74wXpOhqTvwVx3OElZRweTnQ6d31fXEoRD1Jy"
      crossorigin="anonymous"
    ></script>
  </body>
</html>
```

2) Give the home.html some bootstrap styling so it looks nicer!

```
# templates/recipes/home.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipes:</h1>
{% for recipe in recipes %}
<div class="card my-4">
  <div class="card-body">
    <h5 class="card-title">{{ recipe.title }}</h5>
    <h6 class="card-subtitle mb-2 text-muted">{{ recipe.author }}</h6>
    <p class="card-text">{{ recipe.content }}</p>
    <h6 class="card-subtitle mb-2 text-muted">{{ recipe.date_posted }}</h6>
    <a href="#" class="card-link">View Recipe</a>
  </div>
</div>
{% endfor %}
<!-- -->
{% endblock content %}
```

### Now we have some navigation and it looks a bit better 🦋

---

## Step 5: Django Admin Setup
{% embed https://www.youtube.com/watch?v=zGCI948FoRU %}

Django comes out-of-the-box with an admin interface to manipulate data, it's very convenient. By the end of this we'll setup a superuser that can edit data.

1) Stop the server and in the terminal let's create a superuser, but first we need to apply some pre-built migrations:
`>> python manage.py migrate`

2) Now we can create a superuser in the terminal (create your credentials when prompted):
`>> python manage.py createsuperuser`

3) Run the server and go to localhost:8000/admin and login with credentials from step 2

4) You should see a 'users' section where you'll find the superuser you created. You can click 'add' to create another user.

### The django admin is a handy tool when developing and even when live to add/edit/update/delete data.

---

## Step 6: Creating the database model
{% embed https://www.youtube.com/watch?v=8v1xUhwqkAc %}

In this section we'll setup the database so data is stored and create some data via the admin ⚙️

1) Create a recipes model in the recipes/models.py file:

```
# recipes/models.py
from django.db import models
from django.contrib.auth.models import User

# Create your models here.
class Recipe(models.Model):
  title = models.CharField(max_length=100)
  description = models.TextField()

  author = models.ForeignKey(User, on_delete=models.CASCADE)

  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)

  def __str__(self):
    return self.title
```

2) Make the migrations to update the database, in the terminal run:
`>> python manage.py makemigrations`
`>> python manage.py migrate`

3) In order to see the model in the admin we need to register it, so in recipes/admin.py add:

```
# recipes/admin.py
from django.contrib import admin
from . import models

# Register your models here.
admin.site.register(models.Recipe)
```

4) Now run the server, head back to localhost:8000/admin, login and add some recipes

5) Let's use this real data in our template, so first update the recipes/views.py file home view to query the db:

```
# recipes/views.py
from . import models

def home(request):
  recipes = models.Recipe.objects.all()
  context = {
    'recipes': recipes
  }
  return render(request, 'recipes/home.html', context)

def about(request):
  return render(request, 'recipes/about.html', {'title': 'about page'})
```

6) And let's update the home.html template to use the attributes stored on the model:

```
# templates/recipes/home.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipes:</h1>
{% for recipe in recipes %}
<div class="card my-4">
  <div class="card-body">
    <h5 class="card-title">{{ recipe.title }}</h5>
    <h6 class="card-subtitle mb-2 text-muted">{{ recipe.author }}</h6>
    <p class="card-text">{{ recipe.description }}</p>
    <h6 class="card-subtitle mb-2 text-muted">
      {{ recipe.updated_at|date:"F d, Y" }}
    </h6>
    <a href="#" class="card-link">View Recipe</a>
  </div>
</div>
{% endfor %}
<!-- -->
{% endblock content %}
```

### Amazing - now we have real data being stored and displayed on our site!

---

## Step 7: User Registration 📝
{% embed https://www.youtube.com/watch?v=2WDgX6cxub4 %}

In this step we'll make it so users can register on our site and create accounts!

1) Create a new Django app 'users' that will handle this functionality, in the terminal type:
`>> python manage.py startapp users`

2) Then add the app to our config/settings.py file:
```
# config/settings.py
INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles',

    #local apps
    'recipes',
    'users',
]
```
3) Create a register view in the users/views.py file to handle new users registering (we'll create the form next):
```
# users/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages
from . import forms

# Create your views here.
def register(request):
  if request.method == "POST":
    form = forms.UserRegisterForm(request.POST)
    if form.is_valid():
      form.save()
      # cleaned data is a dictionary
      username = form.cleaned_data.get('username')
      messages.success(request, f"{username}, you're account is created!")
      return redirect('recipes-home')
  else:
    form = forms.UserRegisterForm()
  return render(request, 'users/register.html', {'form': form})
```

4) Now let's create the form that the view uses. in users/forms.py add:
```
#users/forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm

class UserRegisterForm(UserCreationForm):
  email = forms.EmailField()

  class Meta:
    model = User
    fields = ['username', 'email', 'password1', 'password2']
```

5) Setup a url pattern for registration that makes the view from step 1. We'll add this to the project urls, so in config/urls.py add:
```
# users/urls.py
...
from users import views as user_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('recipes.urls')),
    path('register/', user_views.register, name="user-register"),
]
```

6) Create the template for the registration form, so under the users app create another directory at path 'users/templates/users' and add the file 'register.html' with the following markup:

```
# users/templates/users/register.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
  <form method="POST">
    {% csrf_token %}
    <fieldset class="form-group">
      <legend class="border-bottom mb-4">Sign Up!</legend>
      {{ form|crispy }}
    </fieldset>
    <div class="form-group py-3">
      <input class="btn btn-outline-primary" type="submit" value="Sign Up" />
    </div>
  </form>
  <div class="border-top pt-3">
    <a class="text-muted" href="#">Already have an account? Log in.</a>
  </div>
</div>

<!-- -->
{% endblock content %}
```

7) We used crispy forms (a 3rd party package) to make the forms look better so we need to install that, in the terminal type:
`>> pip install django-crispy-forms`

8) Add crispy forms to installed apps in the config/settings.py file and add another variable CRISPY_TEMPLATE_PACK.

```
# configs/settings.py file
INSTALLED_APPS = [
...
    #local apps
    'recipes',
    'users',

    # 3rd party
    'crispy_forms',
]
...
CRISPY_TEMPLATE_PACK = 'bootstrap4'
```

### Now new users can signup!

---

## Step 8: Login and Logout
{% embed https://www.youtube.com/watch?v=qZDtAtHWwLk %}

We'll finish up user auth in this step and users will be able to register, sign in, and sign out.

1) In the project urls add the built-in views for log in and log out. in 'config/urls.py' add (we'll also add a profile url that we'll add later on in this step:

```
# config/urls.py
from django.contrib import admin
from django.urls import path, include
from users import views as user_views
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('recipes.urls')),
    path('register/', user_views.register, name="user-register"),
    # new urls
    path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name="user-login"),
    path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name="user-logout"),
    path('profile/', user_views.profile, name="user-profile"),
]
```

2) Let's create the templates we passed in the urls file above. So in users/templates/users create a login.html and logout.html file, in login.html add:

```
# users/templates/users/login.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
  <form method="POST">
    {% csrf_token %}
    <fieldset class="form-group">
      <legend class="border-bottom mb-4">Log In!</legend>
      {{ form|crispy }}
    </fieldset>
    <div class="form-group py-3">
      <input class="btn btn-outline-primary" type="submit" value="Login" />
    </div>
  </form>
  <div class="border-top pt-3">
    <a class="text-muted" href="{% url 'user-register' %}"
      >Don't have an account? Sign up.</a
    >
  </div>
</div>

<!-- -->
{% endblock content %}
```

3) And in logout.html add:

```
# users/templates/users/logout.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<div class="container">
  <h2>You have been logged out!</h2>
  <div class="border-top pt-3">
    <a class="text-muted" href="{% url 'user-login' %}">Log back in here.</a>
  </div>
</div>

<!-- -->
{% endblock content %}
```

4) Update the project settings to redirect users before/after the login. So in config/settings.py add:

```
# config/settings.py
...
LOGIN_REDIRECT_URL = 'recipes-home'
LOGIN_URL = 'user-login'
```

5) Update the register view form the last step to redirect a user to login after registering. So in users/views.py update the register function (also create the profile view):

```
# users/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages
from django.contrib.auth.decorators import login_required

from . import forms

# Create your views here.
def register(request):
  if request.method == "POST":
    form = forms.UserRegisterForm(request.POST)
    if form.is_valid():
      form.save()
      # cleaned data is a dictionary
      username = form.cleaned_data.get('username')
      messages.success(request, f"{username}, you're account is created, please login.")
      return redirect('user-login')
  else:
    form = forms.UserRegisterForm()
  return render(request, 'users/register.html', {'form': form})

@login_required()
def profile(request):
  return render(request, 'users/profile.html')
```

6) Let's update the base.html navbar to link to these different actions based on if a user is logged in or now:

```
# recipes/templates/recipes/base.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    {% if title %}
    <title>Django Recipes - {{title}}</title>
    {% else %}
    <title>Django Recipes</title>
    {% endif %}
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
      <div class="container">
        <a class="navbar-brand" href="{% url 'recipes-home' %}">Recipes App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarNav"
          aria-controls="navbarNav"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
          <ul class="navbar-nav">
            <li class="nav-item">
              <a
                class="nav-link"
                aria-current="page"
                href="{% url 'recipes-home' %}"
                >Recipes</a
              >
            </li>
          </ul>
        </div>
        <div class="navbar-nav">
          {% if user.is_authenticated %}
          <a class="nav-item nav-link" href="{% url 'user-profile' %}"
            >My Profile</a
          >
          <a class="nav-item nav-link" href="{% url 'user-logout' %}">Logout</a>
          {% else %}
          <a class="nav-item nav-link" href="{% url 'user-login' %}">Login</a>
          <a class="nav-item nav-link" href="{% url 'user-register' %}"
            >Register</a
          >
          {% endif %}
        </div>
      </div>
    </nav>
    <div class="container mt-4 col-8">
      {% if messages %} {% for message in messages %}
      <div class="alert alert-{{ message.tags }}">{{ message }}</div>
      {% endfor %} {% endif %} {% block content %} {% endblock %}
    </div>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.5/dist/umd/popper.min.js"
      integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.min.js"
      integrity="sha384-kjU+l4N0Yf4ZOJErLsIcvOU2qSb74wXpOhqTvwVx3OElZRweTnQ6d31fXEoRD1Jy"
      crossorigin="anonymous"
    ></script>
  </body>
</html>
```

7) Finally let's create the profile template. so add a file users/templates/users/profile.html and add:

```
# users/templates/users/profile.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
  <h1>{{ user.username}}</h1>
</div>

<!-- -->
{% endblock content %}
```

### Amazing! Now our app has full user auth, users can register, sign in, and sign out!

---

## Step 9: Recipes CRUD:
{% embed https://www.youtube.com/watch?v=sUW-X1pvCz8&t=533s %}
{% embed https://www.youtube.com/watch?v=kLrSoPQq4Bk&t=2s %}

Now we'll add the final functionality - letting users create, update, and delete recipes. This will make this a fully functioning web app! We'll also use class based views that speed up dev for common use cases.

1) Let's update the home page to use a class based view instead of the function. So updated recipes/views.py and add this class:

```
# recipes/views.py
from django.shortcuts import render
from django.views.generic import ListView

from . import models

class RecipeListView(ListView):
  model = models.Recipe
  template_name = 'recipes/home.html'
  context_object_name = 'recipes'
```

2) Update the recipes url config to point to this class. so in recipes/urls.py update:

```
# recipes/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.RecipeListView.as_view(), name="recipes-home"),
]
```

3) Let's setup the detail view for each recipe, let's add this class to the recipes/views.py file:

```
# recipes/views.py
...
from django.views.generic import ListView, DetailView,
...
class RecipeDetailView(DetailView):
  model = models.Recipe
```

4) And setup the url config to include the detail view (let's just add the create and delete as well):

```
# recipes/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.RecipeListView.as_view(), name="recipes-home"),
    path('recipe/<int:pk>', views.RecipeDetailView.as_view(), name="recipes-detail"),
    path('recipe/create', views.RecipeCreateView.as_view(), name="recipes-create"),
    path('recipe/<int:pk>/update', views.RecipeUpdateView.as_view(), name="recipes-update"),
    path('recipe/<int:pk>/delete', views.RecipeDeleteView.as_view(), name="recipes-delete"),
]
```

5) Let's create the expected template for this recipe detail page. create an html file 'recipes/templates/recipes/recipe_detail.html', and add the following markup:

```
# recipes/templates/recipes/reipe_detail.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipe # {{object.id}}</h1>
<div class="card my-4">
  <div class="card-body">
    <h5 class="card-title">{{ object.title }}</h5>
    <h6 class="card-subtitle mb-2 text-muted">{{ object.author }}</h6>
    <p class="card-text">{{ object.description }}</p>
    <h6 class="card-subtitle mb-2 text-muted">
      {{ object.updated_at|date:"F d, Y" }}
    </h6>
  </div>
</div>
{% if object.author == user or user.is_staff %}
<div class="col-4">
    <a class="btn btn-outline-info" href="{% url 'recipes-update' object.id %}">Update</a>
    <a class="btn btn-outline-danger" href="{% url 'recipes-delete' object.id %}">Delete</a>
</div>
{% endif %}
<!-- -->
{% endblock content %}
```

6) Update the home page html template to link to the detail page for each recipe so in the recipes/templates/recipes/home.html page:

```
# recipes/templates/recipes/home.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipes:</h1>
{% for recipe in recipes %}
<div class="card my-4">
  <div class="card-body">
    <h5 class="card-title">{{ recipe.title }}</h5>
    <h6 class="card-subtitle mb-2 text-muted">{{ recipe.author }}</h6>
    <p class="card-text">{{ recipe.description }}</p>
    <h6 class="card-subtitle mb-2 text-muted">
      {{ recipe.updated_at|date:"F d, Y" }}
    </h6>
    <a href="{% url 'recipes-detail' recipe.pk %}" class="card-link">View Recipe</a>
  </div>
</div>
{% endfor %}
<!-- -->
{% endblock content %}
```

7) Let's create the views now for the create, update, and delete views in the recipes/views.py, so that file looks like:

```
# recipes/views.py
from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin

from . import models

class RecipeListView(ListView):
  model = models.Recipe
  template_name = 'recipes/home.html'
  context_object_name = 'recipes'

class RecipeDetailView(DetailView):
  model = models.Recipe

class RecipeDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
  model = models.Recipe
  success_url = reverse_lazy('recipes-home')

  def test_func(self):
    recipe = self.get_object()
    return self.request.user == recipe.author

class RecipeCreateView(LoginRequiredMixin, CreateView):
  model = models.Recipe
  fields = ['title', 'description']

  def form_valid(self, form):
    form.instance.author = self.request.user
    return super().form_valid(form)

class RecipeUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
  model = models.Recipe
  fields = ['title', 'description']

  def test_func(self):
    recipe = self.get_object()
    return self.request.user == recipe.author

  def form_valid(self, form):
    form.instance.author = self.request.user
    return super().form_valid(form)
```

8) Create the template for the create and update view, so create a file 'recipes/templates/recipes/recipe_form.html' with the following:

```
# recipes/templates/recipes/recipe_form.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
  <form method="POST">
    {% csrf_token %}
    <fieldset class="form-group">
      <legend class="border-bottom mb-4">Add Recipe</legend>
      {{ form|crispy }}
    </fieldset>
    <div class="form-group py-3">
      <input class="btn btn-outline-primary" type="submit" value="Save" />
    </div>
  </form>
  <div class="border-top pt-3">
    <a class="text-muted" href="{% url 'recipes-home' %}"
      >Cancel</a
    >
  </div>
</div>

<!-- -->
{% endblock content %}
```

9) Weneed to add a absolute url to the recipe model so django knows where to redirect to after creating or updating an object, so in recipes/model.py:

```
# recipes/models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse

# Create your models here.
class Recipe(models.Model):
  title = models.CharField(max_length=100)
  description = models.TextField()

  author = models.ForeignKey(User, on_delete=models.CASCADE)

  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)

  def get_absolute_url(self):
      return reverse("recipes-detail", kwargs={"pk": self.pk})

  def __str__(self):
    return self.title
```

10) Now last thing is the delete template, so create a file 'recipes/templates/recipes/recipe_confirm_delete.html' and add this markup:

```
# recipes/templates/recipes/recipe_confirm_delete.html
{% extends "recipes/base.html" %}
<!-- -->
{% load crispy_forms_tags %}
<!-- -->
{% block content %}
<div class="container">
  <form method="POST">
    {% csrf_token %}
    <fieldset class="form-group">
      <legend class="mb-4">Are you sure you want to delete?</legend>
      <h2>{{object}}</h2>
      {{ form|crispy }}
    </fieldset>
    <div class="form-group py-3">
      <input class="btn btn-outline-danger" type="submit" value="Delete" />
    </div>
  </form>
  <div class="border-top pt-3">
    <a class="text-muted" href="{% url 'recipes-home' %}"
      >Cancel</a
    >
  </div>
</div>

<!-- -->
{% endblock content %}
```

11) Update the UI to link to these pages, we need to update the detail template and the base.html:

```
# template/recipes/recipe_detail.html
{% extends "recipes/base.html" %}
<!-- -->
{% block content %}
<h1>Recipe # {{object.id}}</h1>
<div class="card my-4">
  <div class="card-body">
    <h5 class="card-title">{{ object.title }}</h5>
    <h6 class="card-subtitle mb-2 text-muted">{{ object.author }}</h6>
    <p class="card-text">{{ object.description }}</p>
    <h6 class="card-subtitle mb-2 text-muted">
      {{ object.updated_at|date:"F d, Y" }}
    </h6>
  </div>
</div>
{% if object.author == user or user.is_staff %}
<div class="col-4">
    <a class="btn btn-outline-info" href="{% url 'recipes-update' object.id %}">Update</a>
    <a class="btn btn-outline-danger" href="{% url 'recipes-delete' object.id %}">Delete</a>
</div>
{% endif %}
<!-- -->
{% endblock content %}
```
And base.html
```
# templates/recipes/base.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    {% if title %}
    <title>Django Recipes - {{title}}</title>
    {% else %}
    <title>Django Recipes</title>
    {% endif %}
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
      <div class="container">
        <a class="navbar-brand" href="{% url 'recipes-home' %}">Recipes App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarNav"
          aria-controls="navbarNav"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
          <ul class="navbar-nav">
            <li class="nav-item">
              <a
                class="nav-link"
                aria-current="page"
                href="{% url 'recipes-home' %}"
                >Recipes</a
              >
            </li>
            <li class="nav-item">
              <a class="nav-link" href="{% url 'recipes-about' %}">About</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="{% url 'recipes-create' %}">Add Recipe</a>
            </li>
          </ul>
        </div>
        <div class="navbar-nav">
          {% if user.is_authenticated %}
          <a class="nav-item nav-link" href="{% url 'user-profile' %}"
            >My Profile</a
          >
          <a class="nav-item nav-link" href="{% url 'user-logout' %}">Logout</a>
          {% else %}
          <a class="nav-item nav-link" href="{% url 'user-login' %}">Login</a>
          <a class="nav-item nav-link" href="{% url 'user-register' %}"
            >Register</a
          >
          {% endif %}
        </div>
      </div>
    </nav>
    <div class="container mt-4 col-8">
      {% if messages %} {% for message in messages %}
      <div class="alert alert-{{ message.tags }}">{{ message }}</div>
      {% endfor %} {% endif %} {% block content %} {% endblock %}
    </div>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.5/dist/umd/popper.min.js"
      integrity="sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.min.js"
      integrity="sha384-kjU+l4N0Yf4ZOJErLsIcvOU2qSb74wXpOhqTvwVx3OElZRweTnQ6d31fXEoRD1Jy"
      crossorigin="anonymous"
    ></script>
  </body>
</html>
```

### You made it! Now you have a sweet recipes app that you can customize to your liking or use to create something new!