Project: Build a Blog Application with Django 6.0

Building complete blog application demonstrates Django fundamentals through practical project implementing posts, comments, categories, tags, user authentication, and CRUD operations maintaining production-ready architecture. Blog applications showcase core Django features including models for database design, views handling requests, templates rendering HTML, forms processing user input, and authentication controlling access. Traditional tutorial approaches explain isolated concepts without integration while complete project demonstrates how components work together solving real-world requirements. Without hands-on project experience, developers struggle applying theoretical knowledge to practical applications creating gaps between learning and implementation. This comprehensive tutorial builds fully-functional blog from scratch including project setup, database modeling with relationships, implementing CRUD operations for posts and comments, user authentication and authorization, creating categories and tags with many-to-many relationships, implementing rich text editor, adding pagination for post lists, creating RSS feeds, implementing search functionality, and deploying to production. Real-world features include draft/published status workflow, comment moderation, featured posts, author profiles, post slugs for SEO-friendly URLs, and social sharing integration. Project demonstrates Django 6.0 best practices including class-based views for reusability, model managers for custom queries, form validation maintaining data integrity, template inheritance reducing duplication, and security measures protecting against common vulnerabilities. This hands-on guide provides complete code building professional blog application from initial setup through production deployment teaching practical Django development maintaining code quality and maintainability throughout development lifecycle.
Project Setup and Configuration
Project setup creates Django environment installing dependencies, configuring database, and establishing project structure with separate apps for blog functionality and user management. Understanding project organization integrated with Django project structure enables maintaining clean architecture throughout development.
# Django 6.0 Blog Project Setup
# Create virtual environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install Django 6.0 and dependencies
pip install Django==6.0
pip install Pillow # For image handling
pip install django-taggit # For tagging functionality
pip install django-crispy-forms crispy-bootstrap5 # For form styling
# Create project
django-admin startproject blog_project
cd blog_project
# Create apps
python manage.py startapp blog
python manage.py startapp accounts
# Project structure:
# blog_project/
# βββ manage.py
# βββ blog_project/
# β βββ __init__.py
# β βββ settings.py
# β βββ urls.py
# β βββ asgi.py
# β βββ wsgi.py
# βββ blog/
# β βββ migrations/
# β βββ templates/
# β βββ static/
# β βββ models.py
# β βββ views.py
# β βββ urls.py
# β βββ forms.py
# βββ accounts/
# βββ templates/
# βββ models.py
# βββ views.py
# Configure settings.py
# blog_project/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third-party apps
'taggit',
'crispy_forms',
'crispy_bootstrap5',
# Local apps
'blog',
'accounts',
]
# Crispy forms settings
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
CRISPY_TEMPLATE_PACK = 'bootstrap5'
# Media files configuration
import os
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Static files
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# Database (SQLite for development)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# URL configuration
# blog_project/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
path('accounts/', include('accounts.urls')),
]
# Serve media files in development
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)Database Models and Relationships
Blog models define database schema including Post model with author relationship, Category and Tag models with many-to-many relationships, and Comment model linking to posts and users. Understanding model design integrated with Django relationships enables building normalized database maintaining data integrity.
# Blog models
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.urls import reverse
from django.utils.text import slugify
from taggit.managers import TaggableManager
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=100, unique=True)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = 'Categories'
ordering = ['name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('blog:category_posts', kwargs={'slug': self.slug})
class Post(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='posts')
content = models.TextField()
excerpt = models.TextField(max_length=300, blank=True)
featured_image = models.ImageField(upload_to='blog_images/', blank=True, null=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
featured = models.BooleanField(default=False)
views = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
tags = TaggableManager()
class Meta:
ordering = ['-published_at', '-created_at']
indexes = [
models.Index(fields=['-published_at']),
models.Index(fields=['status']),
]
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
# Set published_at when status changes to published
if self.status == 'published' and not self.published_at:
self.published_at = timezone.now()
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={'slug': self.slug})
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
approved = models.BooleanField(default=False)
class Meta:
ordering = ['created_at']
def __str__(self):
return f'Comment by {self.author.username} on {self.post.title}'
# User Profile model
# accounts/models.py
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True, null=True)
website = models.URLField(blank=True)
location = models.CharField(max_length=100, blank=True)
def __str__(self):
return f'{self.user.username} Profile'
# Create migrations
python manage.py makemigrations
python manage.py migrateViews and URL Routing
Blog views implement CRUD operations using class-based views with ListView displaying posts, DetailView showing individual posts, CreateView and UpdateView handling post creation and editing, and DeleteView removing posts. Understanding view implementation enables building complete application features maintaining code reusability.
# Blog views
# blog/views.py
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.shortcuts import render, get_object_or_404, redirect
from django.urls import reverse_lazy
from django.db.models import Q
from .models import Post, Category, Comment
from .forms import PostForm, CommentForm
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
return Post.objects.filter(status='published').select_related('author', 'category')
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_object(self):
post = super().get_object()
# Increment views
post.views += 1
post.save(update_fields=['views'])
return post
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = self.object.comments.filter(approved=True)
context['comment_form'] = CommentForm()
return context
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def test_func(self):
post = self.get_object()
return self.request.user == post.author
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url = reverse_lazy('blog:post_list')
template_name = 'blog/post_confirm_delete.html'
def test_func(self):
post = self.get_object()
return self.request.user == post.author
class CategoryPostsView(ListView):
model = Post
template_name = 'blog/category_posts.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
self.category = get_object_or_404(Category, slug=self.kwargs['slug'])
return Post.objects.filter(
category=self.category,
status='published'
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['category'] = self.category
return context
class SearchView(ListView):
model = Post
template_name = 'blog/search_results.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
query = self.request.GET.get('q')
if query:
return Post.objects.filter(
Q(title__icontains=query) |
Q(content__icontains=query) |
Q(excerpt__icontains=query),
status='published'
)
return Post.objects.none()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['query'] = self.request.GET.get('q', '')
return context
# Comment view
def add_comment(request, slug):
post = get_object_or_404(Post, slug=slug)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.author = request.user
comment.save()
return redirect('blog:post_detail', slug=post.slug)
return redirect('blog:post_detail', slug=post.slug)
# URL configuration
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.PostListView.as_view(), name='post_list'),
path('post/<slug:slug>/', views.PostDetailView.as_view(), name='post_detail'),
path('post/new/', views.PostCreateView.as_view(), name='post_create'),
path('post/<slug:slug>/edit/', views.PostUpdateView.as_view(), name='post_update'),
path('post/<slug:slug>/delete/', views.PostDeleteView.as_view(), name='post_delete'),
path('category/<slug:slug>/', views.CategoryPostsView.as_view(), name='category_posts'),
path('search/', views.SearchView.as_view(), name='search'),
path('post/<slug:slug>/comment/', views.add_comment, name='add_comment'),
]Forms and Templates
Forms handle user input with ModelForms automatically generating form fields from models while templates render HTML using template inheritance and Bootstrap styling. Understanding forms and templates integrated with validation enables building user-friendly interfaces maintaining data integrity.
# Forms
# blog/forms.py
from django import forms
from .models import Post, Comment
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'category', 'content', 'excerpt', 'featured_image', 'tags', 'status', 'featured']
widgets = {
'content': forms.Textarea(attrs={'rows': 15}),
'excerpt': forms.Textarea(attrs={'rows': 3}),
}
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['content']
widgets = {
'content': forms.Textarea(attrs={'rows': 3, 'placeholder': 'Write your comment...'}),
}
# Base template
# 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 %}My Blog{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'blog:post_list' %}">My Blog</a>
<div class="navbar-nav ms-auto">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'blog:post_create' %}">New Post</a>
<a class="nav-link" href="{% url 'accounts:profile' %}">Profile</a>
<a class="nav-link" href="{% url 'accounts:logout' %}">Logout</a>
{% else %}
<a class="nav-link" href="{% url 'accounts:login' %}">Login</a>
<a class="nav-link" href="{% url 'accounts:register' %}">Register</a>
{% endif %}
</div>
</div>
</nav>
<div class="container mt-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}" role="alert">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
# Post list template
# blog/templates/blog/post_list.html
{% extends 'base.html' %}
{% block title %}Blog Posts{% endblock %}
{% block content %}
<h1>Latest Blog Posts</h1>
<div class="row">
{% for post in posts %}
<div class="col-md-6 mb-4">
<div class="card">
{% if post.featured_image %}
<img src="{{ post.featured_image.url }}" class="card-img-top" alt="{{ post.title }}">
{% endif %}
<div class="card-body">
<h5 class="card-title">
<a href="{% url 'blog:post_detail' post.slug %}">{{ post.title }}</a>
</h5>
<p class="card-text">{{ post.excerpt|truncatewords:30 }}</p>
<p class="text-muted small">
By {{ post.author.username }} |
{{ post.published_at|date:"M d, Y" }} |
{{ post.views }} views
</p>
<a href="{% url 'blog:post_detail' post.slug %}" class="btn btn-primary btn-sm">Read More</a>
</div>
</div>
</div>
{% empty %}
<p>No posts available.</p>
{% endfor %}
</div>
{% if is_paginated %}
<nav>
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}Admin Interface Configuration
Admin configuration customizes Django admin interface adding list displays, filters, search capabilities, and inline editing enabling efficient content management through admin panel maintaining productivity for content creators and moderators.
# Admin configuration
# blog/admin.py
from django.contrib import admin
from .models import Post, Category, Comment
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'created_at']
prepopulated_fields = {'slug': ('name',)}
search_fields = ['name']
class CommentInline(admin.TabularInline):
model = Comment
extra = 0
readonly_fields = ['author', 'created_at']
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'status', 'featured', 'published_at', 'views']
list_filter = ['status', 'created_at', 'published_at', 'category', 'featured']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'published_at'
ordering = ['-published_at']
inlines = [CommentInline]
fieldsets = (
('Post Information', {
'fields': ('title', 'slug', 'author', 'category')
}),
('Content', {
'fields': ('content', 'excerpt', 'featured_image')
}),
('Settings', {
'fields': ('status', 'featured', 'tags')
}),
('Metadata', {
'fields': ('views', 'published_at'),
'classes': ('collapse',)
}),
)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ['author', 'post', 'created_at', 'approved']
list_filter = ['approved', 'created_at']
search_fields = ['author__username', 'content']
actions = ['approve_comments']
def approve_comments(self, request, queryset):
queryset.update(approved=True)
approve_comments.short_description = 'Approve selected comments'
# Create superuser
python manage.py createsuperuser| Feature | Implementation | Purpose | User Benefit |
|---|---|---|---|
| Post CRUD | Class-based views | Create, edit, delete posts | Full content control |
| Categories | ForeignKey relationship | Organize content | Easy navigation |
| Comments | Related model with approval | User engagement | Community interaction |
| Tagging | django-taggit | Flexible categorization | Better discovery |
| Search | Q objects filtering | Find content quickly | Improved usability |
Blog Development Best Practices
- Use slugs for URLs: Implement SEO-friendly URLs with unique slugs improving search engine visibility
- Implement draft workflow: Enable draft/published status allowing authors to preview before publishing
- Add comment moderation: Implement approval workflow preventing spam maintaining content quality
- Optimize queries: Use select_related and prefetch_related avoiding N+1 query problems maintaining performance
- Implement pagination: Add pagination for post lists improving page load times
- Create rich text editor: Integrate TinyMCE or CKEditor enabling formatted content creation
- Add social sharing: Implement sharing buttons increasing content reach through social networks
- Implement RSS feeds: Create syndication feeds enabling content distribution through feed readers
- Track analytics: Add view counters and analytics understanding content performance maintaining insights
- Test thoroughly: Write tests for models, views, and forms ensuring reliability
Conclusion
Building complete blog application demonstrates Django fundamentals through practical implementation combining models defining database schema, views handling business logic, templates rendering user interface, forms processing input, and admin interface managing content efficiently. Project setup establishes foundation installing Django 6.0 creating separate apps for blog and accounts functionality organizing code maintaining clean architecture throughout development. Database models define Post with author, category, tags, and status fields, Category organizing content, and Comment enabling user engagement with foreign key relationships maintaining referential integrity through CASCADE and SET_NULL delete behaviors. Views implement CRUD operations using class-based views with ListView displaying paginated posts, DetailView showing individual posts tracking views, CreateView and UpdateView handling post creation and editing with author validation, and DeleteView removing posts with permission checks ensuring only authors delete own content. Forms use ModelForms generating form fields automatically from models with custom widgets enhancing user experience while templates use inheritance reducing duplication rendering Bootstrap-styled interface maintaining responsive design across devices. Admin configuration customizes interface adding list displays showing key fields, filters enabling content sorting, search capabilities finding posts quickly, prepopulated fields generating slugs automatically, and inline editing managing comments directly within post edit page. Features include draft/published workflow enabling content preview before publishing, comment moderation preventing spam through approval system, category-based navigation organizing content logically, tag support enabling flexible categorization through django-taggit, search functionality finding content through title and body matching, and pagination handling large post collections maintaining page performance. Understanding complete blog development from project setup through deployment integrated with authentication, forms, templates, and admin customization provides practical foundation building production-ready Django applications serving real user needs throughout development lifecycle.
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


