Subject
Here is a complete Django project from scratch with solution that uses the concepts from Tutorials 13 to 20.
It includes:
- a simple blog
- adding posts
- editing posts
- deleting posts
- Django forms
ModelForm- CRUD operations
- template inheritance
- Django messages
- custom 404 page
Project Title
Mini Blog Manager with Django
This project is a small blog application where a user can:
- view all blog posts
- view one post in detail
- add a new post
- edit a post
- delete a post
- see success messages after actions
- use a shared base layout
- see a custom 404 page when a page does not exist
1) Concepts Covered
This project covers the content of:
Tutorial 1 — Building a Simple Blog with Django
Create and display blog posts.
Tutorial 2 — Django Forms Introduction
Handle form submission with GET/POST.
Tutorial 3 — Django Forms with forms.py
Use Django forms properly.
Tutorial 4 — ModelForms in Django
Create forms directly from a model.
Tutorial 5 — CRUD Application in Django
Create, Read, Update, Delete blog posts.
Tutorial 6 — Template Inheritance and Reusable Layouts
Use base.html and shared layout blocks.
Tutorial 7 — Django Messages Framework
Show success messages after add/edit/delete.
Tutorial 8 — Handling 404 and Custom Error Pages
Create a friendly 404.html.
2) Final Project Structure
mini_blog/
│
├── manage.py
├── mini_blog/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py
│ └── wsgi.py
│
├── blog/
│ ├── migrations/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── models.py
│ ├── urls.py
│ └── views.py
│
└── templates/
├── base.html
├── 404.html
└── blog/
├── post_list.html
├── post_detail.html
├── post_form.html
├── post_confirm_delete.html3) Step 1 — Create the Project
Open terminal and run:
django-admin startproject mini_blog
cd mini_blog
python manage.py startapp blog4) Step 2 — Register the App
Open mini_blog/settings.py and add blog:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
]Also make sure Django knows your templates folder:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]5) Step 3 — Create the Model
Open blog/models.py:
from django.db import models
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post_detail', args=[self.id])This model stores:
- title of the post
- content of the post
- creation date
- update date
6) Step 4 — Create and Apply Migrations
Run:
python manage.py makemigrations
python manage.py migrate7) Step 5 — Register the Model in Admin
Open blog/admin.py:
from django.contrib import admin
from .models import Post
admin.site.register(Post)Create admin user:
python manage.py createsuperuser8) Step 6 — Create the Form
Open blog/forms.py:
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content']
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter post title'
}),
'content': forms.Textarea(attrs={
'class': 'form-control',
'placeholder': 'Write your content here',
'rows': 6
}),
}This is a ModelForm, so Django automatically creates the form from the model.
9) Step 7 — Create the Views
Open blog/views.py:
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from .models import Post
from .forms import PostForm
def post_list(request):
posts = Post.objects.order_by('-created_at')
return render(request, 'blog/post_list.html', {'posts': posts})
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id)
return render(request, 'blog/post_detail.html', {'post': post})
def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save()
messages.success(request, 'Post created successfully.')
return redirect('post_detail', post_id=post.id)
else:
form = PostForm()
return render(request, 'blog/post_form.html', {
'form': form,
'page_title': 'Add New Post'
})
def post_update(request, post_id):
post = get_object_or_404(Post, id=post_id)
if request.method == 'POST':
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
messages.success(request, 'Post updated successfully.')
return redirect('post_detail', post_id=post.id)
else:
form = PostForm(instance=post)
return render(request, 'blog/post_form.html', {
'form': form,
'page_title': 'Edit Post'
})
def post_delete(request, post_id):
post = get_object_or_404(Post, id=post_id)
if request.method == 'POST':
post.delete()
messages.success(request, 'Post deleted successfully.')
return redirect('post_list')
return render(request, 'blog/post_confirm_delete.html', {'post': post})10) Step 8 — Create Blog URLs
Open blog/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<int:post_id>/', views.post_detail, name='post_detail'),
path('create/', views.post_create, name='post_create'),
path('edit/<int:post_id>/', views.post_update, name='post_update'),
path('delete/<int:post_id>/', views.post_delete, name='post_delete'),
]
11) Step 9 — Connect App URLs to Project URLs
Open mini_blog/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
]
12) Step 10 — Create the Base Template
Create templates/base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Mini Blog{% endblock %}</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f4f6f9;
margin: 0;
padding: 0;
}
nav {
background: #0d6efd;
padding: 15px 30px;
}
nav a {
color: white;
text-decoration: none;
margin-right: 20px;
font-weight: bold;
}
.container {
width: 85%;
max-width: 900px;
margin: 30px auto;
background: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
}
.btn {
display: inline-block;
padding: 10px 16px;
text-decoration: none;
border-radius: 6px;
margin-top: 10px;
border: none;
cursor: pointer;
}
.btn-primary {
background: #0d6efd;
color: white;
}
.btn-warning {
background: orange;
color: white;
}
.btn-danger {
background: crimson;
color: white;
}
.btn-secondary {
background: gray;
color: white;
}
.post-card {
border-bottom: 1px solid #ddd;
padding: 15px 0;
}
.message {
padding: 12px;
margin-bottom: 20px;
border-radius: 6px;
background: #d1e7dd;
color: #0f5132;
}
input, textarea {
width: 100%;
padding: 10px;
margin-top: 6px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 6px;
}
</style>
</head>
<body>
<nav>
<a href="{% url 'post_list' %}">Home</a>
<a href="{% url 'post_create' %}">Add Post</a>
</nav>
<div class="container">
{% if messages %}
{% for message in messages %}
<div class="message">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
</div>
</body>
</html>This is the shared layout used by all pages.
13) Step 11 — Create Post List Template
Create templates/blog/post_list.html:
{% extends 'base.html' %}
{% block title %}All Posts{% endblock %}
{% block content %}
<h1>All Blog Posts</h1>
<a href="{% url 'post_create' %}" class="btn btn-primary">Create New Post</a>
{% for post in posts %}
<div class="post-card">
<h2>{{ post.title }}</h2>
<p>{{ post.content|truncatewords:20 }}</p>
<small>Created: {{ post.created_at }}</small><br>
<a href="{% url 'post_detail' post.id %}" class="btn btn-primary">Read More</a>
</div>
{% empty %}
<p>No posts available yet.</p>
{% endfor %}
{% endblock %}14) Step 12 — Create Post Detail Template
Create templates/blog/post_detail.html:
{% extends 'base.html' %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<p><strong>Created:</strong> {{ post.created_at }}</p>
<p><strong>Last updated:</strong> {{ post.updated_at }}</p>
<a href="{% url 'post_update' post.id %}" class="btn btn-warning">Edit</a>
<a href="{% url 'post_delete' post.id %}" class="btn btn-danger">Delete</a>
<a href="{% url 'post_list' %}" class="btn btn-secondary">Back</a>
{% endblock %}15) Step 13 — Create Form Template
Create templates/blog/post_form.html:
{% extends 'base.html' %}
{% block title %}{{ page_title }}{% endblock %}
{% block content %}
<h1>{{ page_title }}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Save</button>
<a href="{% url 'post_list' %}" class="btn btn-secondary">Cancel</a>
</form>
{% endblock %}16) Step 14 — Create Delete Confirmation Template
Create templates/blog/post_confirm_delete.html:
{% extends 'base.html' %}
{% block title %}Delete Post{% endblock %}
{% block content %}
<h1>Delete Post</h1>
<p>Are you sure you want to delete this post?</p>
<h3>{{ post.title }}</h3>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Yes, Delete</button>
<a href="{% url 'post_detail' post.id %}" class="btn btn-secondary">Cancel</a>
</form>
{% endblock %}
17) Step 15 — Create Custom 404 Page
Create templates/404.html:
{% extends 'base.html' %}
{% block title %}Page Not Found{% endblock %}
{% block content %}
<div style="text-align:center; padding:40px;">
<h1 style="font-size:70px; color:crimson;">404</h1>
<h2>Page Not Found</h2>
<p>Sorry, the page you are looking for does not exist.</p>
<a href="{% url 'post_list' %}" class="btn btn-primary">Go Back Home</a>
</div>
{% endblock %}
18) Step 16 — Test the Project
Run the server:
python manage.py runserverNow test these pages:
/→ list of posts/create/→ add new post/post/1/→ detail page/edit/1/→ edit post/delete/1/→ delete post
For 404 test, visit:
/non-existing-page/To see custom 404.html, set in settings.py:
DEBUG = False
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
19) How This Project Uses Tutorials 13 to 20
Tutorial 13 — Simple Blog
The Post model and blog list/detail pages create the basic blog.
Tutorial 14 — Forms Introduction
The create and update pages handle form submission with GET and POST.
Tutorial 15 — Django Forms with forms.py
The file forms.py is used to define the form properly.
Tutorial 16 — ModelForms
PostForm is a ModelForm, so the form comes from the model.
Tutorial 17 — CRUD
The project has:
- Create →
post_create - Read →
post_list,post_detail - Update →
post_update - Delete →
post_delete
Tutorial 18 — Template Inheritance
All templates extend base.html.
Tutorial 19 — Messages Framework
After create, update, and delete, success messages are shown.
Tutorial 20 — Custom 404 Page
A custom 404.html is included.
20) Full Solution Summary
This project gives the student a complete real mini-application using the lessons from 13 to 20. It is simple enough for beginners but strong enough to teach real Django workflow.
The student learns how to:
- create a Django app
- define models
- create forms
- use
ModelForm - implement CRUD
- organize templates
- show success messages
- handle missing pages professionally
21) Optional Improvements
After finishing this project, you can improve it by adding:
- slug field for cleaner URLs
- categories
- search form
- image upload
- user authentication
- pagination
- Bootstrap styling
22) Mini Exercise
Try extending the project by adding:
- a
categoryfield to each post - a search bar on the homepage
- a confirmation message before deleting
- a custom
500.htmlpage
💬 Comments
No comments yet. Be the first to comment!
Login to comment.