Models and Database

Django's ORM lets you work with your database using Python code instead of raw SQL. Models define your data structure, and Django handles queries, migrations, and relationships.

Field Types

Python
from django.db import models
from django.contrib.postgres.fields import ArrayField, JSONField

class Article(models.Model):
    # Text fields
    title = models.CharField(max_length=200)          # VARCHAR(200)
    slug = models.SlugField(unique=True)               # VARCHAR with URL-safe chars
    summary = models.TextField()                        # TEXT
    content = models.TextField(blank=True)              # TEXT, optional
    
    # Numbers
    views = models.PositiveIntegerField(default=0)      # INT >= 0
    rating = models.DecimalField(max_digits=3, decimal_places=1)  # DECIMAL(3,1)
    is_published = models.BooleanField(default=False)   # BOOLEAN
    
    # Dates
    created = models.DateTimeField(auto_now_add=True)   # Set on creation
    updated = models.DateTimeField(auto_now=True)       # Set on every save
    publish_date = models.DateField(null=True)          # DATE only
    
    # Files
    image = models.ImageField(upload_to='articles/', blank=True)
    attachment = models.FileField(upload_to='attachments/', blank=True)
    
    # Relationships (defined below)
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    category = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True)
    tags = models.ManyToManyField('Tag', blank=True)
    
    class Meta:
        ordering = ['-created']
        indexes = [
            models.Index(fields=['-created']),
            models.Index(fields=['slug']),
        ]
        verbose_name = 'article'
        verbose_name_plural = 'articles'
    
    def __str__(self):
        return self.title
    
    def get_absolute_url(self):
        return f'/articles/{self.slug}/'
    
    @property
    def reading_time(self):
        words = len(self.content.split())
        return max(1, words // 200)

Field Options Reference

Option Description Example
max_length Max string length CharField(max_length=200)
null Allow NULL in DB CharField(null=True)
blank Allow empty in forms CharField(blank=True)
default Default value IntegerField(default=0)
unique Enforce uniqueness SlugField(unique=True)
choices Limited choices CharField(choices=STATUS)
editable Show in admin/forms DateTimeField(editable=False)
help_text Help text in forms CharField(help_text='...')
verbose_name Human-readable name CharField(verbose_name='Title')

Relationships

Python
# One-to-Many (ForeignKey)
class Department(models.Model):
    name = models.CharField(max_length=100)

class Employee(models.Model):
    name = models.CharField(max_length=100)
    department = models.ForeignKey(
        Department,
        on_delete=models.CASCADE,       # Delete employees when dept deleted
        related_name='employees'         # dept.employees.all()
    )

# Many-to-Many
class Article(models.Model):
    tags = models.ManyToManyField('Tag', related_name='articles')

# One-to-One
class Profile(models.Model):
    user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
    bio = models.TextField(blank=True)

QuerySet API

Python
# Filtering
posts = Post.objects.filter(published=True, author__username='john')
posts = Post.objects.filter(created__date__year=2024)
posts = Post.objects.exclude(comments__isnull=True)

# Chaining (lazy evaluation — queries run when iterated)
recent_popular = (Post.objects
    .filter(published=True)
    .select_related('author')
    .prefetch_related('tags', 'comments')
    .annotate(num_comments=Count('comments'))
    .order_by('-created')[:10])

# Aggregation
from django.db.models import Count, Avg, Sum, Max
stats = Post.objects.aggregate(
    total_posts=Count('id'),
    avg_comments=Avg('comments__id'),
    max_views=Max('views'),
)

# F expressions (atomic database updates)
from django.db.models import F
Post.objects.filter(slug='my-post').update(views=F('views') + 1)

# Q objects (complex queries)
from django.db.models import Q
posts = Post.objects.filter(
    Q(title__icontains='python') | Q(body__icontains='python'),
    published=True
)

# Bulk operations
Post.objects.filter(published=False).update(published=True)
Post.objects.bulk_create([Post(title=f'Post {i}') for i in range(100)])

Custom Model Managers

Python
class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(published=True)


class Post(models.Model):
    objects = models.Manager()          # Default
    published = PublishedManager()      # Custom
    
    # Usage: Post.published.all() — only published posts

Signals

Python
# posts/signals.py
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.utils.text import slugify
from .models import Post


@receiver(pre_save, sender=Post)
def generate_slug(sender, instance, **kwargs):
    if not instance.slug:
        instance.slug = slugify(instance.title)
        # Ensure uniqueness
        original_slug = instance.slug
        counter = 1
        while Post.objects.filter(slug=instance.slug).exclude(pk=instance.pk).exists():
            instance.slug = f'{original_slug}-{counter}'
            counter += 1


@receiver(post_save, sender=Post)
def notify_followers(sender, instance, created, **kwargs):
    if created and instance.published:
        # Send notification to followers
        print(f'New post: {instance.title}')
Python
# posts/apps.py — register signals
class PostsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'posts'

    def ready(self):
        import posts.signals  # noqa

Migrations

Bash
# After changing models:
python manage.py makemigrations              # Create migration file
python manage.py makemigrations --name fix_slug  # Named migration
python manage.py migrate                     # Apply to database
python manage.py showmigrations              # Show migration status
python manage.py sqlmigrate posts 0003      # Show SQL for a migration
python manage.py migrate posts 0002         # Rollback to specific migration

💡 Tip: Use select_related() for ForeignKey and OneToOne, and prefetch_related() for ManyToMany — they eliminate N+1 queries which are the #1 cause of slow Django apps.