CRUD
Django CRUD Roadmap
This page shows a complete roadmap for building a simple Django CRUD app (Create, Read, Update, Delete)
using a Person model.
CRUD stands for Create, Read, Update, Delete. In this mini project, we will:
- Create people (name, surname, email) using a form on the homepage.
- Read all people in a Bootstrap table on the
/data/page. - Update existing records using an edit form.
- Delete records from the database with a delete button.
Goal: Simple and clean CRUD example with Django models, views, templates and Bootstrap.
Terminal commands to set up the project and app:
Terminal
0) Create empty file
1) PS C:\Users\Alvin\Desktop\CRUD-roadmap> mkdir core
2) PS C:\Users\Alvin\Desktop\CRUD-roadmap> cd .\core\
3) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> python -m venv .venv
4) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> .venv\Scripts\activate
5) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> pip install Django
6) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> django-admin startproject core .
7) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> python manage.py startapp home
8) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> python manage.py runserver
Structure is ready ✅ – You now have a Django project
core and app home.
Open core/core/settings.py and add home into INSTALLED_APPS:
core/core/settings.py
INSTALLED_APPS = [
...
'home',
]
1) First migration
1) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> python manage.py migrate
2) Add Person model
Open core/home/models.py and add:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
surname = models.CharField(max_length=50)
email = models.EmailField()
def __str__(self):
return f"Name: {self.name}"
3) Make migrations & migrate
2) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> python manage.py makemigrations
3) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> python manage.py migrate
Register the Person model in core/home/admin.py (not shown here), then create a superuser:
1) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> python manage.py createsuperuser
Username (leave blank to use 'alvin'): alvin
Email address: elvinbabanli0@gmail.com
Password: 12345678 (Password going to be invisible)
Password (again): 12345678 (Password going to be invisible)
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
1) core/core/urls.py
Add imports and include home.urls:
from django.contrib import admin
from django.urls import path, include
from home.views import *
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('home.urls')),
]
2) core/home/urls.py
Create home/urls.py and add the CRUD routes:
from django.urls import path
from .views import index, get_data, delete_data, update_data
urlpatterns = [
path('', index, name='main'),
path('data/', get_data, name='get_data'),
path('delete/int:person_id/', delete_data, name='delete_data'),
path('update/int:person_id', update_data, name='update'),
]
Note: These routes map directly to the view functions that implement Create, Read, Update and Delete.
Create these HTML templates:
core/home/templates/base.htmlcore/home/templates/index.htmlcore/home/templates/data.htmlcore/home/templates/update.html
Also create a static folder:
core/home/static/styles.css(optional extra styling)
You can use Bootstrap examples for modern tables and forms.
Add the views to core/home/views.py:
from django.shortcuts import render, redirect, get_object_or_404
from .models import Person
def index(request):
edit_id = request.GET.get('edit')
if request.method == 'GET' and edit_id:
obj = get_object_or_404(Person, id=edit_id)
return render(request, 'index.html', {'person': obj, 'is_edit': True})
if request.method == 'POST':
person_id = request.POST.get('person_id')
name = request.POST.get('name')
surname = request.POST.get('surname')
email = request.POST.get('email')
if person_id:
obj = get_object_or_404(Person, id=person_id)
obj.name = name
obj.surname = surname
obj.email = email
obj.save()
return redirect('/data/')
else:
Person.objects.create(name=name, surname=surname, email=email)
return redirect('/')
return render(request, 'index.html')
def get_data(request):
people = Person.objects.all()
return render(request, 'data.html', {'data': people})
def update_data(request, person_id):
obj = get_object_or_404(Person, id=person_id)
if request.method == 'POST':
obj.name = request.POST.get('name')
obj.surname = request.POST.get('surname')
obj.email = request.POST.get('email')
obj.save()
return redirect('/data/')
return render(request, 'update.html', {'data': obj})
def delete_data(request, person_id):
obj = get_object_or_404(Person, id=person_id)
obj.delete()
return redirect('/data/')
- Create – POST without
person_idinindex. - Read –
get_datashows all people in a table. - Update – either via
editquery parameter inindexorupdate_dataview. - Delete –
delete_dataremoves a record and redirects back to the table.
1) base.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<title>{% block title %}{% endblock %}</title>
</head>
<body class="p-3">
<nav class="mb-3 d-flex gap-2">
<a class="btn btn-primary" href="/">Home</a>
<a class="btn btn-secondary" href="/data/">Data</a>
</nav>
{% block content %}{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
2) index.html – Create & inline edit form
{% extends 'base.html' %}
{% block title %}Home{% endblock %}
{% block content %}
<form method="post" class="card p-3 shadow-sm">
{% csrf_token %}
{% if is_edit %}
<input type="hidden" name="person_id" value="{{ person.id }}">
{% endif %}
<div class="mb-3">
<input class="form-control" name="name" placeholder="Name"
value="{{ person.name|default:'' }}" required>
</div>
<div class="mb-3">
<input class="form-control" name="surname" placeholder="Surname"
value="{{ person.surname|default:'' }}" required>
</div>
<div class="mb-3">
<input class="form-control" name="email" type="email" placeholder="Email"
value="{{ person.email|default:'' }}" required>
</div>
<div class="d-flex gap-2 mt-2">
<button class="btn btn-primary" type="submit">
{% if is_edit %}Save Changes{% else %}Add User{% endif %}
</button>
{% if is_edit %}
<a class="btn btn-secondary" href="/">Cancel</a>
{% endif %}
</div>
</form>
{% endblock %}
3) data.html – Read & action buttons
{% extends 'base.html' %}
{% block title %}Data{% endblock %}
{% block content %}
<table class="table table-striped align-middle">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Surname</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for p in data %}
<tr>
<th scope="row">{{ forloop.counter }}</th>
<td>{{ p.name }}</td>
<td>{{ p.surname }}</td>
<td>{{ p.email }}</td>
<td class="d-flex gap-2">
<a href="/?edit={{ p.id }}" class="btn btn-sm btn-warning">Edit</a>
<a href="/delete/{{ p.id }}/" class="btn btn-sm btn-danger">Delete</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
4) update.html – Separate update form
{% extends 'base.html' %}
{% block title %}Update{% endblock %}
{% block content %}
<form method="post" class="card p-3 shadow-sm">
{% csrf_token %}
<div class="mb-3">
<input class="form-control" name="name" value="{{ data.name }}" required>
</div>
<div class="mb-3">
<input class="form-control" name="surname" value="{{ data.surname }}" required>
</div>
<div class="mb-3">
<input class="form-control" type="email" name="email" value="{{ data.email }}" required>
</div>
<button class="btn btn-primary" type="submit">Save</button>
</form>
{% endblock %}
Finally, run the development server:
1) PS C:\Users\Alvin\Desktop\CRUD-roadmap\core> python manage.py runserver
- Open the link shown in the terminal (e.g.
http://127.0.0.1:8000/). - Test:
/– create or edit a person./data/– list, edit, delete records.
Your Django CRUD app is ready – you can now extend it with search, pagination, filters and more.