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 already have Python3 along with git installed in your system and you are using Linux-based system, macOS or WSL. 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.