Django 5 CRUD tutorial

In this tutorial, we’ll create a typical CRUD (Create, Read, Update and Delete) app using Django 5. This tutorial assumes that you are using Linux-based system, macOS or WSL and you already have Python3 installed in your system. So, let’s open the terminal and execute following command to create a new folder:

mkdir myapp

Go inside this newly created folder:

cd myapp

Then create virtual environment by executing following command:

python3 -m venv .env

Mount virtual environment:

source .env/bin/activate

(Once virtual environment is mounted, you’ll use command python instead of python3)

Now, install Django 5 by executing following command:

pip install django==5

Then create the project:

django-admin startproject project .

Next, create an app named posts:

python manage.py startapp posts

In order to define routes for our posts app, create urls.py inside posts folder by executing:

touch posts/urls.py

Then, open posts/urls.py and put following code inside it:

from django.urls import path
from . import views

app_name = "posts"
urlpatterns = [
    path('', views.posts, name='posts'),
    path('post_add',views.post_add,name='post_add'),
    path('post_post', views.post_post, name='post_post'),
    path('post_edit/<int:id>', views.post_edit, name='post_edit'),
    path('post_edit_post', views.post_edit_post, name='post_edit_post'),
    path('post_delete/<int:id>', views.post_delete, name='post_delete'),
]

Now, let’s propagate our app’s routes by amending project/urls.py as follows:

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("posts/", include("posts.urls")),
    path("admin/", admin.site.urls),
]

In order to define our app’s models, let’s change posts/models.py as follows:

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField(blank=False, default=None)
    def __str__(self):
        return self.title

Next, amend project/settings.py as follows, in order to add posts app into project:

INSTALLED_APPS = [
    "posts.apps.PostsConfig",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

Now, let’s have our views’ logic by amending posts/views.py as follows:

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.contrib import messages
from .models import Post

def posts(request):
	posts = Post.objects.all()
	return render(request, 'posts/index.html', context={'posts':posts})

def post_add(request):
	return render(request, 'posts/post_add.html', context={})

def post_post(request):
	title = request.POST['title']
	body = request.POST['body']
	post = Post(title=title, body=body)
	post.save()
	messages.success(request, 'The Post has been added successfully.')
	return HttpResponseRedirect('/posts/')

def post_edit(request,id):
	post = get_object_or_404(Post, pk=id)
	return render(request, 'posts/post_edit.html', context={"post":post})

def post_edit_post(request):
	post = get_object_or_404(Post, pk=request.POST['id'])
	post.title = request.POST['title']
	post.body = request.POST['body']
	post.save()
	messages.success(request, 'The post has been updated successfully.')
	return HttpResponseRedirect('/posts/')

def post_delete(request,id):
	post = get_object_or_404(Post, pk=id)
	post.delete()
	messages.success(request, 'The post has been deleted successfully.')
	return HttpResponseRedirect('/posts/')

Execute following command to create migration:

python manage.py makemigrations

Run the migrations:

python manage.py migrate

Now let’s move on to client-side. So, create template folder posts/templates/posts by executing following command:

mkdir -p posts/templates/posts

Create base.html, index.html, post_add.html and post_edit.html inside above folder. You may execute following command to create these files in one go:

touch posts/templates/posts/{base,index,post_add,post_edit}.html

Put following code inside posts/templates/posts/base.html:

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <title>{% block title %}My App{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
  <body>

    <div class="container-fluid">
      <div class="row">

        <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">

          {% block content %}
          {% endblock %}

        </main>
      </div>
    </div>
  
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
  </body>
</html>

Then put following code inside posts/templates/posts/index.html:

{% extends "posts/base.html" %}
{% block content %}

{% if messages %}
    {% for message in messages %}
        <div class="alert alert-info">
            <strong>{{ message|escape }}</strong>
        </div>
    {% endfor %}
{% endif %}

<div>
  <a href="{% url 'posts:post_add' %}" class="btn btn-outline-primary btn-sm">Add Post</a>
</div>
<div class="">
  <table class="table table-striped table-sm">
    <thead>
      <tr>
        <th>#</th>
        <th>Title</th>
        <th>Body</th>
        <th>Action</th>
      </tr>
    </thead>
    <tbody>
      {% for post in posts %}
      <tr>
        <td>{{ post.id }}</td>
        <td>{{ post.title }}</td>
        <td>{{ post.body }}</td>
        <td>
          <div class="row row-cols-sm-auto">
            <a href="{% url 'posts:post_edit' post.id %}" class="btn btn-outline-secondary btn-sm">Update</a>
            <form action="{% url 'posts:post_delete' post.id %}" method="post" class="">
              {% csrf_token %}
              <input type="submit" value="Delete" class="btn btn-outline-danger btn-sm"/>
            </form>
          </div>
        </td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
</div>

{% endblock %}

Next, put following code inside posts/templates/posts/post_add.html:

{% extends "posts/base.html" %}
{% block content %}

<form action={% url 'posts:post_post' %} method="post" class="row gx-3 gy-2 align-items-end" >
{% csrf_token %}

{% if errors %}
    {% for error in errors %}
        <div class="alert alert-danger">
            <strong>{{ error|escape }}</strong>
        </div>
    {% endfor %}
{% endif %}

    <div class="col-md-4">
        <label for="title" class="form-label">Title</label>
        <input type="text" class="form-control form-control-sm" id="title" name="title">
    </div>

    <div class="col-sm-4">
        <label for="body" class="form-label">Body</label>
        <input type="text" class="form-control form-control-sm" id="body" name="body">
    </div>

    <div class="col-md-2">
        <input type="submit" class="btn btn-sm btn-primary" value="Add">
    </div>
</form>

{% endblock %}

And then put following code inside posts/templates/posts/post_edit.html:

{% extends "posts/base.html" %}
{% block content %}

<form action="{% url 'posts:post_edit_post' %}" method="post" class="row gx-3 gy-2 align-items-end" >
{% csrf_token %}

{% if errors %}
    {% for error in errors %}
        <div class="alert alert-danger">
            <strong>{{ error|escape }}</strong>
        </div>
    {% endfor %}
{% endif %}

    <input name="id" value="{{post.id}}" hidden>
    <div class="col-sm-4">
        <label for="title" class="col-form-label">Title</label>
        <input type="text" class="form-control form-control-sm" id="title" value="{{post.title}}" name="title">
    </div>
    <div class="col-sm-4">
        <label for="body" class="form-label">Body</label>
        <input type="text" class="form-control form-control-sm" id="body" value="{{post.body}}" name="body">
    </div>
    <div class="col-sm-12">
        <button type="submit" class="btn btn-sm btn-primary">Save</button>
    </div>
</form>

{% endblock %}

To have super powers, let’s create superuser by entering following command:

python manage.py createsuperuser

When asked, enter username, email and password…and remember them.

You can also add post model to admin panel by amending posts/admin.py:

from django.contrib import admin
from .models import Post

admin.site.register(Post)

Finally, run dev server:

python manage.py runserver

Access admin panel by visiting:

localhost:8000/admin

You may use admin panel to add some posts. Or you may go to localhost:8000/posts to see what you’ve just accomplished through templates.