Skip to main content

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

# 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
# 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()