Building a Complete Blog
Let's tie everything together by building a practical blog application from scratch. This example covers models, views, templates, forms, and admin — the core of any Django project. By the end, you'll have a working blog you can extend.
We'll keep it simple but realistic. A blog needs posts, categories, and comments. We'll use class-based views because they're efficient and follow Django conventions.
The Models
Every blog starts with its data. Our Post model stores the title, content, author, and publication status. Category organizes posts, and Comment lets readers interact.
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
content = models.TextField()
published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
name = models.CharField(max_length=100)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.name} on {self.post.title}'
Notice the related_name='comments' on Comment. This lets you access comments from a post instance with post.comments.all(), which is much cleaner than filtering manually.
The Views
We'll use Django's generic class-based views for the standard operations. ListView shows published posts, DetailView shows a single post with its comments, and CreateView handles new comments.
from django.views.generic import ListView, DetailView, CreateView
from django.urls import reverse_lazy
from .models import Post, Comment
from .forms import CommentForm
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
def get_queryset(self):
return Post.objects.filter(published=True).order_by('-created_at')
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = CommentForm()
return context
class CommentCreateView(CreateView):
model = Comment
form_class = CommentForm
template_name = 'blog/post_detail.html'
def form_valid(self, form):
form.instance.post = Post.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('post_detail', kwargs={'pk': self.kwargs['pk']})
Try it Yourself →
Templates and Admin
Create templates that extend your base layout. The post list displays cards with titles and excerpts. The detail view shows full content, the comment form, and existing comments.
from django.contrib import admin
from .models import Post, Category, Comment
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'category', 'published', 'created_at')
list_filter = ('published', 'category')
prepopulated_fields = {'slug': ('title',)}
admin.site.register(Category)
admin.site.register(Comment)
With list_display and list_filter, your admin becomes a powerful management interface. The prepopulated_fields feature auto-fills the slug from the title, saving you manual work.
Putting It All Together
Wire up your URLs, register the models in admin, run migrations, and you have a complete blog. This pattern — models for data, views for logic, templates for presentation, admin for management — is the foundation of every Django project.
From here, you could add user authentication for authors, pagination on the post list, search functionality, or an RSS feed. The blog is a perfect starting point because it touches every part of Django.