Subject

Here is a complete Django project from scratch with solution that uses the concepts from Tutorials 13 to 20.

It includes:

Project Title

Mini Blog Manager with Django

This project is a small blog application where a user can:

1) Concepts Covered

This project covers the content of:

Tutorial 13 — Building a Simple Blog with Django

Create and display blog posts.

Tutorial 14 — Django Forms Introduction

Handle form submission with GET/POST.

Tutorial 15 — Django Forms with forms.py

Use Django forms properly.

Tutorial 16 — ModelForms in Django

Create forms directly from a model.

Tutorial 17 — CRUD Application in Django

Create, Read, Update, Delete blog posts.

Tutorial 18 — Template Inheritance and Reusable Layouts

Use base.html and shared layout blocks.

Tutorial 19 — Django Messages Framework

Show success messages after add/edit/delete.

Tutorial 20 — 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.html

3) Step 1 — Create the Project

Open terminal and run:

django-admin startproject mini_blog
cd mini_blog
python manage.py startapp blog

4) 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:

6) Step 4 — Create and Apply Migrations

Run:

python manage.py makemigrations
python manage.py migrate

7) 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 createsuperuser

8) 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 runserver

Now test these pages:

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:

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:

21) Optional Improvements

After finishing this project, you can improve it by adding:

22) Mini Exercise

Try extending the project by adding:

  1. a category field to each post
  2. a search bar on the homepage
  3. a confirmation message before deleting
  4. a custom 500.html page