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.