Models & Database
Django's Object-Relational Mapping (ORM) provides a powerful way to interact with databases using Python code.
Model Basics
Defining Models
Models are Python classes that define the structure of your database tables:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField(unique=True)
birth_date = models.DateField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
Common Field Types
# Text fields
CharField(max_length=100) # Short text
TextField() # Long text
EmailField() # Email validation
URLField() # URL validation
SlugField() # URL-friendly text
# Numeric fields
IntegerField() # Integer
DecimalField(max_digits=10, decimal_places=2) # Decimal
FloatField() # Float
# Date/Time fields
DateField() # Date only
TimeField() # Time only
DateTimeField() # Date and time
# Boolean and choice fields
BooleanField() # True/False
choices = [('M', 'Male'), ('F', 'Female')]
CharField(max_length=1, choices=choices)
# File fields
FileField(upload_to='uploads/') # File upload
ImageField(upload_to='images/') # Image upload
Relationships
Foreign Key (One-to-Many)
class Category(models.Model):
name = models.CharField(max_length=100)
class Product(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
Many-to-Many
class Tag(models.Model):
name = models.CharField(max_length=50)
class Article(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag, blank=True)
One-to-One
class User(models.Model):
username = models.CharField(max_length=100)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField()
Migrations
Creating Migrations
After modifying models, create migrations:
python manage.py makemigrations
python manage.py makemigrations app_name # For specific app
Applying Migrations
python manage.py migrate
python manage.py migrate app_name # For specific app
Migration Commands
# View migration status
python manage.py showmigrations
# View SQL for migration
python manage.py sqlmigrate app_name 0001
# Create empty migration
python manage.py makemigrations --empty app_name
Database Operations
Creating Objects
# Method 1: Create and save
person = Person(first_name='John', last_name='Doe')
person.save()
# Method 2: Create directly
person = Person.objects.create(
first_name='Jane',
last_name='Smith'
)
Querying Objects
# Get all objects
all_people = Person.objects.all()
# Filter objects
adults = Person.objects.filter(age__gte=18)
johns = Person.objects.filter(first_name='John')
# Get single object
person = Person.objects.get(id=1)
try:
person = Person.objects.get(email='john@example.com')
except Person.DoesNotExist:
person = None
# Exclude objects
non_admins = User.objects.exclude(is_staff=True)
Query Lookups
# Exact match
Person.objects.filter(age=25)
# Case-insensitive
Person.objects.filter(first_name__iexact='john')
# Contains
Person.objects.filter(first_name__contains='Jo')
# Starts/ends with
Person.objects.filter(last_name__startswith='Sm')
Person.objects.filter(email__endswith='@gmail.com')
# Comparisons
Person.objects.filter(age__gt=18) # Greater than
Person.objects.filter(age__gte=18) # Greater than or equal
Person.objects.filter(age__lt=65) # Less than
Person.objects.filter(age__lte=65) # Less than or equal
# Date lookups
Person.objects.filter(birth_date__year=1990)
Person.objects.filter(created_at__date=timezone.now().date())
Updating Objects
# Update single object
person = Person.objects.get(id=1)
person.first_name = 'Johnny'
person.save()
# Bulk update
Person.objects.filter(age__lt=18).update(is_minor=True)
Deleting Objects
# Delete single object
person = Person.objects.get(id=1)
person.delete()
# Bulk delete
Person.objects.filter(age__lt=0).delete()
Query Optimization
Select Related (Forward ForeignKey)
# Bad: N+1 queries
products = Product.objects.all()
for product in products:
print(product.category.name) # Database hit for each product
# Good: Single query with JOIN
products = Product.objects.select_related('category')
for product in products:
print(product.category.name) # No additional database hits
Prefetch Related (Reverse ForeignKey, ManyToMany)
# Bad: N+1 queries
categories = Category.objects.all()
for category in categories:
print(category.product_set.count()) # Database hit for each category
# Good: Two queries total
categories = Category.objects.prefetch_related('product_set')
for category in categories:
print(category.product_set.count()) # No additional database hits
Only and Defer
# Only fetch specific fields
Person.objects.only('first_name', 'last_name')
# Defer (exclude) specific fields
Person.objects.defer('bio', 'created_at')
Advanced Features
Custom Managers
class PersonManager(models.Manager):
def adults(self):
return self.filter(age__gte=18)
def by_last_name(self, last_name):
return self.filter(last_name=last_name)
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
age = models.IntegerField()
objects = PersonManager()
# Usage
adults = Person.objects.adults()
smiths = Person.objects.by_last_name('Smith')
Model Methods
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
birth_date = models.DateField()
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
@property
def age(self):
from datetime import date
return (date.today() - self.birth_date).days // 365
def save(self, *args, **kwargs):
# Custom save logic
self.first_name = self.first_name.title()
super().save(*args, **kwargs)
Database Transactions
from django.db import transaction
# Decorator
@transaction.atomic
def create_person_and_profile(data):
person = Person.objects.create(**data['person'])
Profile.objects.create(person=person, **data['profile'])
# Context manager
def transfer_money(from_account, to_account, amount):
with transaction.atomic():
from_account.balance -= amount
from_account.save()
to_account.balance += amount
to_account.save()