Mini-Build: Blog Data Models
Let's design a complete blog data model with posts, categories, tags, comments, and user profiles. This mini-build applies all model concepts — fields, relationships, methods, and custom managers.
25 min•By Priygop Team•Updated 2026
Blog Schema Design
- User (built-in) — One-to-One -> Profile
- Category — One-to-Many -> Posts
- Post — ForeignKey to User and Category
- Tag — ManyToMany with Posts
- Comment — ForeignKey to Post and User
- Custom manager for published posts
- Model methods for computed properties
Blog Models
Blog Models
# blog/models.py
# from django.db import models
# from django.contrib.auth.models import User
# from django.urls import reverse
# from django.utils.text import slugify
# class Profile(models.Model):
# user = models.OneToOneField(User, on_delete=models.CASCADE)
# bio = models.TextField(blank=True)
# avatar = models.ImageField(upload_to='avatars/', blank=True)
# class Category(models.Model):
# name = models.CharField(max_length=100)
# slug = models.SlugField(unique=True)
# class Meta:
# verbose_name_plural = 'Categories'
# def __str__(self):
# return self.name
# class Tag(models.Model):
# name = models.CharField(max_length=50, unique=True)
# 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, blank=True)
# content = models.TextField()
# author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
# category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='posts')
# tags = models.ManyToManyField(Tag, related_name='posts', blank=True)
# published = models.BooleanField(default=False)
# created_at = models.DateTimeField(auto_now_add=True)
# updated_at = models.DateTimeField(auto_now=True)
#
# class Meta:
# ordering = ['-created_at']
#
# def __str__(self):
# return self.title
#
# def save(self, *args, **kwargs):
# if not self.slug:
# self.slug = slugify(self.title)
# super().save(*args, **kwargs)
#
# def get_absolute_url(self):
# return reverse('blog:detail', kwargs={'slug': self.slug})
#
# @property
# def comment_count(self):
# return self.comments.count()
# 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)Tip
Tip
Add db_index=True to fields you filter/order by frequently. Missing indexes cause slow queries on large tables.
Diagram
Loading diagram…
QuerySets are LAZY — no DB hit until evaluated.
Common Mistake
Warning
Not running makemigrations after model changes. The database won't reflect your new fields until you migrate.
Practice Task
Note
(1) Create a complete model with all field types. (2) Add custom model methods. (3) Write model unit tests.
Quick Quiz
Key Takeaways
- Let's design a complete blog data model with posts, categories, tags, comments, and user profiles.
- User (built-in) — One-to-One -> Profile
- Category — One-to-Many -> Posts
- Post — ForeignKey to User and Category