Introduction to Django Models
Django models are the heart of any Django application, serving as the definitive source for your data structure and business logic. A model represents a database table, and each attribute of the model represents a database field.
What makes Django's model system powerful is that it:
- Provides a high-level, Pythonic way to define your data structure
- Handles database creation and migrations automatically
- Creates a database-abstraction API for querying and manipulating data
- Integrates with Django's admin interface for immediate data management
- Encapsulates business logic with your data
The Blueprint Analogy
Think of Django models as blueprints for a building. Just as a blueprint defines the structure, dimensions, and characteristics of a building before construction begins, a Django model defines the structure, fields, and behaviors of your data before it's stored in the database. Once the blueprint (model) is finalized, construction (database creation) can proceed, resulting in a physical building (database table) that matches the specifications.
Basic Model Structure
Django models are Python classes that subclass django.db.models.Model. Each model class represents a database table, and each attribute represents a database field.
A Simple Model Example
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
description = models.TextField()
publication_date = models.DateField()
price = models.DecimalField(max_digits=6, decimal_places=2)
is_published = models.BooleanField(default=True)
cover_image = models.ImageField(upload_to='covers/', blank=True, null=True)
def __str__(self):
return self.title
When Django processes this model:
- It creates a database table named
appname_book(you can customize the table name) - It creates columns for each field in the model
- It adds an
idfield as a primary key automatically (unless you specify a different field) - It creates a database-abstraction API accessible through the model class and instances
Field Types
Django provides a rich set of field types to represent different types of data:
Common Field Types
| Field Type | Database Type | Description | Example |
|---|---|---|---|
CharField |
VARCHAR | String field with a maximum length | title = models.CharField(max_length=100) |
TextField |
TEXT | Unlimited text field | content = models.TextField() |
IntegerField |
INTEGER | Integer field | age = models.IntegerField() |
DecimalField |
DECIMAL | Fixed-precision decimal number | price = models.DecimalField(max_digits=6, decimal_places=2) |
BooleanField |
BOOLEAN | True/False field | is_active = models.BooleanField(default=True) |
DateField |
DATE | Date field (without time) | birth_date = models.DateField() |
DateTimeField |
DATETIME | Date and time field | created_at = models.DateTimeField(auto_now_add=True) |
EmailField |
VARCHAR | CharField that validates as an email address | email = models.EmailField() |
FileField |
VARCHAR | File upload field | document = models.FileField(upload_to='documents/') |
ImageField |
VARCHAR | Image upload field with validation | photo = models.ImageField(upload_to='photos/') |
URLField |
VARCHAR | CharField that validates as a URL | website = models.URLField() |
SlugField |
VARCHAR | CharField that only allows letters, numbers, underscores, and hyphens | slug = models.SlugField() |
JSONField |
JSON | Stores JSON-encoded data | metadata = models.JSONField() |
Real-World Example: User Profile Model
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(blank=True)
birth_date = models.DateField(null=True, blank=True)
profile_image = models.ImageField(upload_to='profile_pics/', default='profile_pics/default.jpg')
location = models.CharField(max_length=100, blank=True)
website = models.URLField(blank=True)
joined_at = models.DateTimeField(auto_now_add=True)
preferences = models.JSONField(default=dict)
def __str__(self):
return f"{self.user.username}'s profile"
Field Options
Each field type takes a set of field-specific arguments. There are also common arguments available to all field types:
Common Field Options
| Option | Description | Example |
|---|---|---|
null |
If True, Django will store empty values as NULL in the database |
age = models.IntegerField(null=True) |
blank |
If True, the field is allowed to be blank in forms |
bio = models.TextField(blank=True) |
default |
The default value for the field | is_active = models.BooleanField(default=True) |
help_text |
Additional help text to be displayed with the form widget | username = models.CharField(help_text="Enter your username") |
primary_key |
If True, this field is the primary key for the model |
id = models.UUIDField(primary_key=True, default=uuid.uuid4) |
unique |
If True, this field must be unique throughout the table |
email = models.EmailField(unique=True) |
verbose_name |
A human-readable name for the field | first_name = models.CharField(verbose_name="First Name") |
validators |
A list of validators to run for this field | code = models.CharField(validators=[validate_code]) |
choices |
A list of choices for this field | status = models.CharField(choices=STATUS_CHOICES) |
db_index |
If True, a database index will be created for this field |
last_name = models.CharField(db_index=True) |
Example: Using choices
class Book(models.Model):
GENRE_CHOICES = [
('SCI', 'Science Fiction'),
('DRA', 'Drama'),
('MYS', 'Mystery'),
('ROM', 'Romance'),
('HOR', 'Horror'),
('THR', 'Thriller'),
('NON', 'Non-Fiction'),
]
title = models.CharField(max_length=200)
genre = models.CharField(max_length=3, choices=GENRE_CHOICES, default='MYS')
def get_genre_display(self):
return dict(self.GENRE_CHOICES)[self.genre]
With choices, Django provides a get_<fieldname>_display() method that returns the "human-readable" value of the field:
book = Book.objects.get(id=1)
print(book.genre) # "MYS"
print(book.get_genre_display()) # "Mystery"
Model Relationships
Django provides three types of relationships between models:
1. One-to-Many (ForeignKey)
A many-to-one relationship. For example, many books can have the same author.
class Author(models.Model):
name = models.CharField(max_length=100)
bio = models.TextField()
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
# other fields...
def __str__(self):
return self.title
The on_delete parameter specifies what happens when the referenced object is deleted:
CASCADE: Delete the Book when the Author is deletedPROTECT: Prevent deletion of the Author if it has BooksSET_NULL: Set the author to NULL (requiresnull=True)SET_DEFAULT: Set the author to its default valueDO_NOTHING: Take no action
The related_name parameter allows you to access the relationship from the related object:
# Get all books by an author
author = Author.objects.get(name="J.K. Rowling")
books = author.books.all() # This works because of related_name='books'
2. Many-to-Many (ManyToManyField)
A many-to-many relationship. For example, a book can have multiple categories, and a category can contain multiple books.
class Category(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
# other fields...
categories = models.ManyToManyField(Category, related_name='books')
def __str__(self):
return self.title
You can add a custom "through" model to include additional fields in the relationship:
class BookCategory(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
added_on = models.DateTimeField(auto_now_add=True)
featured = models.BooleanField(default=False)
class Book(models.Model):
# other fields...
categories = models.ManyToManyField(Category, through='BookCategory', related_name='books')
3. One-to-One (OneToOneField)
A one-to-one relationship. For example, a user has one profile, and a profile belongs to one user.
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
birth_date = models.DateField(null=True, blank=True)
# other fields...
def __str__(self):
return f"{self.user.username}'s profile"
Creating and Modifying Models
After defining your models, you need to create the corresponding database tables. Django uses a migration system to track changes to your models and apply them to the database schema.
Migration Workflow
- Add or modify model classes in
models.py - Create migrations with
python manage.py makemigrations - Apply migrations with
python manage.py migrate
Understanding Migrations
Migrations are Django's way of propagating changes to your models into your database schema. They're like version control for your database schema, allowing you to:
- Track changes to your models over time
- Apply changes to your database schema
- Roll back changes if needed
- Share changes with team members
Migration Commands
# Create migrations for all apps with model changes
python manage.py makemigrations
# Create migrations for a specific app
python manage.py makemigrations myapp
# Apply all pending migrations
python manage.py migrate
# Apply migrations for a specific app
python manage.py migrate myapp
# Show migration status
python manage.py showmigrations
# Show the SQL that would be executed by a migration
python manage.py sqlmigrate myapp 0001
# Revert a specific migration
python manage.py migrate myapp 0001
Migration Best Practices
- Create migrations in small, incremental steps
- Include migrations in version control
- Test migrations before applying them to production
- Use meaningful names for migrations:
python manage.py makemigrations --name=create_book_model - Consider data migrations when changing field types:
python manage.py makemigrations --empty myapp
Model Inheritance
Django supports three types of model inheritance:
1. Abstract Base Classes
Create a parent class with common fields that won't be used directly.
class BaseItem(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True # This model won't be used to create a database table
class Book(BaseItem):
author = models.CharField(max_length=100)
isbn = models.CharField(max_length=13)
class DVD(BaseItem):
director = models.CharField(max_length=100)
duration = models.IntegerField() # In minutes
This creates two tables: app_book and app_dvd, each with its own fields plus the fields from BaseItem.
2. Multi-table Inheritance
Each model has its own database table, with a link to the parent table.
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
description = models.TextField()
class Book(Product): # Inherits from Product
author = models.CharField(max_length=100)
pages = models.IntegerField()
class Electronics(Product): # Also inherits from Product
brand = models.CharField(max_length=100)
warranty_period = models.IntegerField() # In months
This creates three tables: app_product, app_book, and app_electronics. The child tables have a one-to-one relationship with the parent table.
3. Proxy Models
Create a different interface for the same underlying database table.
class Person(models.Model):
name = models.CharField(max_length=100)
date_of_birth = models.DateField()
def get_age(self):
today = date.today()
return today.year - self.date_of_birth.year - (
(today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)
)
class Student(Person):
class Meta:
proxy = True # Uses the same database table as Person
def enroll(self, course):
# Student-specific behavior
pass
class Teacher(Person):
class Meta:
proxy = True # Also uses the same database table as Person
def assign_grade(self, student, grade):
# Teacher-specific behavior
pass
This creates only one table: app_person. The proxy models provide different behaviors but use the same underlying table.
Model Meta Options
The Meta class within a model provides metadata about the model, such as ordering, constraints, and table names.
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publication_date = models.DateField()
class Meta:
# Database table name (default: app_book)
db_table = 'books'
# Default ordering
ordering = ['-publication_date', 'title']
# Unique constraint
unique_together = ['title', 'author']
# Index definitions
indexes = [
models.Index(fields=['publication_date']),
models.Index(fields=['author', 'title']),
]
# Permissions
permissions = [
('can_publish', 'Can publish books'),
('can_unpublish', 'Can unpublish books'),
]
# Verbose names for the model
verbose_name = 'Book'
verbose_name_plural = 'Books'
Common Meta Options
| Option | Description | Example |
|---|---|---|
db_table |
The name of the database table | db_table = 'books' |
ordering |
Default ordering for queries | ordering = ['-created_at'] |
unique_together |
Sets of fields that must be unique together | unique_together = ['slug', 'author'] |
indexes |
Database indexes for the model | indexes = [models.Index(fields=['title'])] |
constraints |
Database constraints | constraints = [models.CheckConstraint(...)] |
abstract |
Whether the model is abstract | abstract = True |
proxy |
Whether the model is a proxy model | proxy = True |
verbose_name |
Human-readable singular name | verbose_name = 'Blog Post' |
verbose_name_plural |
Human-readable plural name | verbose_name_plural = 'Blog Posts' |
permissions |
Extra permissions for the model | permissions = [('publish_post', 'Can publish posts')] |
default_permissions |
Default permissions for the model | default_permissions = ('add', 'change', 'delete') |
Model Methods
Models can include methods to encapsulate business logic and common operations:
Common Model Methods
from django.utils import timezone
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
published_at = models.DateTimeField(null=True, blank=True)
is_published = models.BooleanField(default=False)
# String representation
def __str__(self):
return self.title
# Custom method for model logic
def publish(self):
self.published_at = timezone.now()
self.is_published = True
self.save()
# Method to check status
def is_recent(self):
if not self.published_at:
return False
return (timezone.now() - self.published_at).days < 7
# Method to get absolute URL
def get_absolute_url(self):
return reverse('post_detail', kwargs={'pk': self.pk})
# Method to calculate related data
def comment_count(self):
return self.comments.count() # Assuming a related_name='comments' on Comment model
# Method to filter related data
def approved_comments(self):
return self.comments.filter(approved=True)
Special Model Methods
| Method | Description |
|---|---|
__str__(self) |
Returns a string representation of the object. Used in the admin, shell, and templates. |
save(self, *args, **kwargs) |
Override to add custom saving behavior. |
delete(self, *args, **kwargs) |
Override to add custom deletion behavior. |
get_absolute_url(self) |
Returns the URL for displaying the object. |
clean(self) |
Used for model validation. Called by forms. |
full_clean(self) |
Calls clean on all fields and model. |
Example: Overriding save() for Auto-Slugify
from django.db import models
from django.utils.text import slugify
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True, max_length=200)
content = models.TextField()
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
# Check for duplicate slugs and make unique
original_slug = self.slug
counter = 1
while Article.objects.filter(slug=self.slug).exclude(pk=self.pk).exists():
self.slug = f"{original_slug}-{counter}"
counter += 1
super().save(*args, **kwargs)
Model Managers
Model managers control the database operations for models. Each model has at least one manager, and you can create custom managers for specialized queries.
The Default Manager
By default, Django adds a manager named objects to every model:
# Using the default objects manager
all_articles = Article.objects.all()
featured_articles = Article.objects.filter(featured=True)
recent_article = Article.objects.order_by('-created_at').first()
Custom Managers
You can create custom managers to encapsulate query logic:
class PublishedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='published')
def recent(self):
return self.get_queryset().order_by('-published_at')[:5]
class Article(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
]
title = models.CharField(max_length=200)
content = models.TextField()
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
published_at = models.DateTimeField(null=True, blank=True)
# Default manager
objects = models.Manager()
# Custom manager
published = PublishedManager()
def __str__(self):
return self.title
Now you can use the custom manager:
# Get all published articles
published_articles = Article.published.all()
# Get recent published articles
recent_articles = Article.published.recent()
Manager Methods
You can also add custom methods to managers:
class BookManager(models.Manager):
def get_queryset(self):
return super().get_queryset()
def fiction(self):
return self.filter(genre='fiction')
def non_fiction(self):
return self.filter(genre='non-fiction')
def by_author(self, author_name):
return self.filter(author__name__icontains=author_name)
def published_this_year(self):
from django.utils import timezone
year = timezone.now().year
return self.filter(publication_date__year=year)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey('Author', on_delete=models.CASCADE)
genre = models.CharField(max_length=50)
publication_date = models.DateField()
objects = BookManager()
def __str__(self):
return self.title
Using the custom manager methods:
# Get fiction books
fiction_books = Book.objects.fiction()
# Get books by a specific author
tolkien_books = Book.objects.by_author('Tolkien')
# Get books published this year
new_books = Book.objects.published_this_year()
Querying Models
Django provides a powerful API for querying models. Here are some common query operations:
Basic Queries
# Get all records
all_books = Book.objects.all()
# Get a single record by primary key
book = Book.objects.get(pk=1)
# Filter records
fiction_books = Book.objects.filter(genre='fiction')
# Exclude records
non_fiction_books = Book.objects.exclude(genre='fiction')
# Order records
sorted_books = Book.objects.order_by('title')
recent_first = Book.objects.order_by('-publication_date')
# Limit records
first_five = Book.objects.all()[:5]
pagination = Book.objects.all()[10:20] # Items 10-19
Field Lookups
Django's powerful field lookups allow for complex filtering:
# Exact match (case-sensitive)
Book.objects.filter(title__exact="The Hobbit")
# Case-insensitive match
Book.objects.filter(title__iexact="the hobbit")
# Contains (case-sensitive)
Book.objects.filter(title__contains="Hobbit")
# Case-insensitive contains
Book.objects.filter(title__icontains="hobbit")
# Greater than
Book.objects.filter(publication_date__gt='2020-01-01')
# Less than or equal to
Book.objects.filter(publication_date__lte='2020-12-31')
# In a list
Book.objects.filter(genre__in=['fiction', 'fantasy', 'adventure'])
# Range
Book.objects.filter(publication_date__range=('2020-01-01', '2020-12-31'))
# Startswith/endswith
Book.objects.filter(title__startswith='The')
Book.objects.filter(title__endswith='Rings')
# Null check
Book.objects.filter(summary__isnull=True)
# Regex matching
Book.objects.filter(title__regex=r'^The.*')
Complex Queries with Q Objects
For complex queries with logical operators, use Q objects:
from django.db.models import Q
# OR condition (fiction OR fantasy)
Book.objects.filter(Q(genre='fiction') | Q(genre='fantasy'))
# AND condition (fiction AND published after 2020)
Book.objects.filter(Q(genre='fiction') & Q(publication_date__gte='2020-01-01'))
# NOT condition (not fiction)
Book.objects.filter(~Q(genre='fiction'))
# Complex combinations
Book.objects.filter(
(Q(genre='fiction') | Q(genre='fantasy')) &
Q(publication_date__year=2020) &
~Q(title__contains='Harry Potter')
)
Aggregation and Annotation
Django provides methods for aggregating and annotating querysets:
from django.db.models import Count, Avg, Min, Max, Sum
# Count all books
book_count = Book.objects.count()
# Aggregations
stats = Book.objects.aggregate(
avg_price=Avg('price'),
max_price=Max('price'),
min_price=Min('price'),
total_value=Sum('price')
)
# Annotations (adds calculated fields to each object)
authors_with_book_count = Author.objects.annotate(
book_count=Count('books')
)
# Find authors with more than 5 books
prolific_authors = Author.objects.annotate(
book_count=Count('books')
).filter(book_count__gt=5)
Model Forms
Django's forms framework can automatically create forms from your models:
from django import forms
from .models import Book
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ['title', 'author', 'genre', 'publication_date', 'price']
# Or exclude some fields
# exclude = ['created_at', 'updated_at']
# Customize widgets
widgets = {
'publication_date': forms.DateInput(attrs={'type': 'date'}),
'genre': forms.Select(choices=Book.GENRE_CHOICES),
'price': forms.NumberInput(attrs={'step': '0.01', 'min': '0'}),
}
# Add help text
help_texts = {
'title': 'Enter the full title of the book',
'price': 'Price in USD',
}
# Custom error messages
error_messages = {
'title': {
'required': 'Please enter a title for the book',
'max_length': 'The title is too long',
},
}
# Custom labels
labels = {
'publication_date': 'Release Date',
}
Using the model form in a view:
from django.shortcuts import render, redirect, get_object_or_404
from .forms import BookForm
from .models import Book
def create_book(request):
if request.method == 'POST':
form = BookForm(request.POST)
if form.is_valid():
book = form.save() # Save the form data to the database
return redirect('book_detail', pk=book.pk)
else:
form = BookForm()
return render(request, 'books/create_book.html', {'form': form})
def edit_book(request, pk):
book = get_object_or_404(Book, pk=pk)
if request.method == 'POST':
form = BookForm(request.POST, instance=book)
if form.is_valid():
book = form.save()
return redirect('book_detail', pk=book.pk)
else:
form = BookForm(instance=book)
return render(request, 'books/edit_book.html', {'form': form, 'book': book})
Data Modeling Best Practices
Here are some best practices for designing Django models:
Schema Design
- Keep models focused: Each model should represent a specific entity or concept
- Normalize your data: Follow database normalization principles to avoid redundancy
- Use descriptive names: Choose clear, descriptive names for models and fields
- Consider performance: Add indexes to fields frequently used in filters and sorts
- Use appropriate field types: Choose the most appropriate field type for each attribute
- Define relationships carefully: Choose the right relationship type (ForeignKey, ManyToMany, OneToOne)
Model Organization
- Group related models: Keep related models in the same app
- Use abstract base classes: Extract common fields into abstract base classes
- Encapsulate business logic: Add model methods for operations on model instances
- Add custom managers: Create managers for common queries and operations
- Document your models: Add docstrings and comments to explain complex logic
Performance Considerations
- Avoid large text fields: Use
CharFieldinstead ofTextFieldwhen possible - Limit the use of
auto_nowandauto_now_add: These create database writes on every save - Be careful with related fields: Use
select_related()andprefetch_related()to optimize queries - Use bulk operations:
bulk_create(),bulk_update(), andupdate()are more efficient - Index wisely: Don't over-index, but add indexes to fields used in filtering and sorting
Real-World Example: Blog Application Models
Let's examine a comprehensive example of models for a blog application:
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=50, unique=True)
description = models.TextField(blank=True)
class Meta:
verbose_name_plural = 'Categories'
ordering = ['name']
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('blog:category_detail', kwargs={'slug': self.slug})
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=50, unique=True)
class Meta:
ordering = ['name']
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
class Post(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique_for_date='published_at')
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')
tags = models.ManyToManyField(Tag, blank=True, related_name='posts')
content = models.TextField()
excerpt = models.TextField(blank=True)
featured_image = models.ImageField(upload_to='blog/%Y/%m/', blank=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
featured = models.BooleanField(default=False)
views = models.PositiveIntegerField(default=0)
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)
if self.status == 'published' and not self.published_at:
self.published_at = timezone.now()
if not self.excerpt and self.content:
# Create excerpt from content (first 160 characters)
self.excerpt = self.content[:160] + '...' if len(self.content) > 160 else self.content
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={
'year': self.published_at.year,
'month': self.published_at.month,
'day': self.published_at.day,
'slug': self.slug
})
def increment_views(self):
self.views += 1
self.save(update_fields=['views'])
class PublishedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='published', published_at__lte=timezone.now())
def featured(self):
return self.get_queryset().filter(featured=True)
def popular(self, limit=5):
return self.get_queryset().order_by('-views')[:limit]
class Post(Post): # Extending the Post model with a custom manager
objects = models.Manager() # The default manager
published = PublishedManager() # Custom manager
class Meta:
proxy = True
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author_name = models.CharField(max_length=100)
author_email = models.EmailField()
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='blog_comments')
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
approved = models.BooleanField(default=False)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='replies')
class Meta:
ordering = ['created_at']
indexes = [
models.Index(fields=['post', 'approved']),
]
def __str__(self):
return f'Comment by {self.author_name} on {self.post}'
def approve(self):
self.approved = True
self.save(update_fields=['approved'])
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
profile_image = models.ImageField(upload_to='profiles/', blank=True)
website = models.URLField(blank=True)
social_twitter = models.CharField(max_length=255, blank=True)
social_facebook = models.CharField(max_length=255, blank=True)
social_instagram = models.CharField(max_length=255, blank=True)
social_linkedin = models.CharField(max_length=255, blank=True)
def __str__(self):
return f'Profile for {self.user.username}'
def get_absolute_url(self):
return reverse('blog:author_detail', kwargs={'username': self.user.username})
This example demonstrates a comprehensive blog application model structure with:
- Multiple related models (Post, Category, Tag, Comment, Profile)
- Various relationship types (ForeignKey, ManyToMany, OneToOne)
- Custom model methods for business logic
- Custom manager for specialized queries
- Meta options for ordering, indexes, and naming
- Proxy models for adding behavior without changing the database
Practice Activity
Design and implement a data model for a simple e-commerce application with the following requirements:
- The application should have products organized into categories
- Products can have multiple images and attributes (e.g., color, size)
- Customers can create accounts, place orders, and leave reviews
- Orders consist of multiple items and have different statuses
- The application should track inventory for each product
Create the following models:
Category- Product categories with hierarchy (parent-child relationship)Product- Product information, including name, description, price, etc.ProductImage- Images for productsProductAttribute- Attributes like color, size, etc.ProductVariant- Variants of products (e.g., specific color and size combinations)Customer- Customer profile informationOrder- Order details, including status, shipping address, etc.OrderItem- Items in an orderReview- Product reviews from customers
For each model:
- Define appropriate fields with types and options
- Set up relationships between models
- Add at least one custom method
- Configure Meta options
Further Topics to Explore
- Advanced query optimization techniques
- Database schema migration strategies
- Custom model validators and constraints
- Working with multiple databases
- Generic relations and content types
- Model signals and event handling
- Database transactions and atomicity
- Database aggregation and annotation techniques
- Custom field types
- Model serialization and deserialization