Building an SEO-Optimised Blog in Django 5: The Complete Post Model
Most Django blog tutorials show a Post model with a title and content. For a real site where SEO matters, that is nowhere near enough. Here is the complete Post model — with every SEO field you need.
mubashar
Most Django blog tutorials show you a Post model with a title, content, and date. That is fine for a toy project. For a real site where SEO matters, it is nowhere near enough. Here is how to build a Post model that covers every SEO requirement — one that I have used across multiple production projects.
What a basic Post model gets wrong
A minimal Post model looks like this:
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
published_at = models.DateTimeField()
What is missing? No way to set a unique <title> tag separate from the post title. No meta description field. No Open Graph image. No canonical URL control. No robots directive for posts you want to index or exclude. In short: no SEO at all.
The SEO fields every post needs
class Post(models.Model):
# Core content
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=220, unique=True)
excerpt = models.TextField(max_length=300)
content = models.TextField()
featured_image = models.ImageField(upload_to='blog/images/%Y/%m/', blank=True)
# Primary SEO
meta_title = models.CharField(max_length=60, blank=True)
meta_description = models.CharField(max_length=160, blank=True)
canonical_url = models.URLField(blank=True)
robots = models.CharField(max_length=50, default='index, follow')
The key insight: meta_title and meta_description are separate from title and excerpt. Your post title might be "How I Built a SaaS in a Weekend" (great for readers) while your meta title is "Build a Django SaaS in 48 Hours: Step-by-Step Guide" (optimised for the keyword you are targeting).
Open Graph fields
# Open Graph / Social
og_title = models.CharField(max_length=95, blank=True)
og_description = models.CharField(max_length=200, blank=True)
og_image = models.ImageField(upload_to='blog/og/%Y/%m/', blank=True)
OG fields are separate from meta fields because social platforms and search engines have different constraints. Twitter cuts titles at around 70 characters; OG descriptions can be slightly longer than meta descriptions.
Computed properties — DRY fallbacks
Rather than requiring editors to fill every field, use properties that fall back gracefully:
@property
def effective_meta_title(self):
return self.meta_title or self.title
@property
def effective_meta_description(self):
return self.meta_description or self.excerpt
@property
def effective_og_title(self):
return self.og_title or self.effective_meta_title
@property
def effective_og_description(self):
return self.og_description or self.effective_meta_description
@property
def reading_time(self):
return max(1, round(len(self.content.split()) / 200))
Templates reference post.effective_meta_title — it returns whatever is most specific available.
Using SEO fields in templates
<title>{{ post.effective_meta_title }} | Site Name</title>
<meta name="description" content="{{ post.effective_meta_description }}">
<meta name="robots" content="{{ post.robots }}">
<link rel="canonical" href="{{ post.canonical_url|default:request.build_absolute_uri }}">
<meta property="og:title" content="{{ post.effective_og_title }}">
<meta property="og:description" content="{{ post.effective_og_description }}">
Admin with collapsible SEO sections
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
fieldsets = (
('Content', { 'fields': ('title', 'slug', 'excerpt', 'content') }),
('SEO', {
'fields': ('meta_title', 'meta_description', 'canonical_url', 'robots'),
'classes': ('collapse',),
}),
('Open Graph', {
'fields': ('og_title', 'og_description', 'og_image'),
'classes': ('collapse',),
}),
)
This is the exact Post model powering this blog. Every post gets proper meta tags, OG data, and schema.org markup without editors needing to think about it — the fallbacks handle the routine cases, and the explicit fields are there when precision matters.
Written by
Mubashar Iqbal
Web developer, SEO expert, and independent maker. I build products, write about what I've learned, and create free tools for developers and marketers.