$ cat /posts/django-models-introduction-to-orm-and-database-design.md

Django Models: Introduction to ORM and Database Design

drwxr-xr-x2026-01-225 min0 views
Django Models: Introduction to ORM and Database Design

Django models define the structure and behavior of data stored in databases using Python classes that map to database tables through Django's Object-Relational Mapping (ORM) system. Models eliminate the need to write SQL queries manually by providing a high-level Pythonic interface for database operations enabling developers to create, read, update, and delete records using intuitive object-oriented syntax. Each model class represents a database table with class attributes defining table columns, data types, constraints, and relationships between different models creating a complete database schema entirely in Python code. Django's ORM abstracts database-specific SQL dialects allowing the same model code to work across PostgreSQL, MySQL, SQLite, Oracle, and other supported databases without modifications ensuring database portability. Understanding Django models is fundamental to web application development as they form the data layer containing business logic, validation rules, and data structures that views and templates interact with to display and manipulate information. Mastering model design patterns, field types, and ORM capabilities enables building robust data-driven applications with clean separation between data structures and application logic following Django's philosophy of convention over configuration.

Model Basics

Django models are Python classes that inherit from django.db.models.Model providing built-in functionality for database operations. Each model class represents a database table with class attributes defined as model fields representing table columns. Field types like CharField, IntegerField, DateTimeField, and BooleanField specify data types, validation rules, and database column types. Django automatically creates a primary key field named 'id' unless explicitly defined providing unique identification for each record. The __str__ method defines string representation of model instances appearing in admin interface and debugging output. Model methods can implement business logic, calculated properties, or custom behaviors keeping data-related functionality within model classes. Understanding model basics enables defining database schemas declaratively, leveraging Django's field types and validation, and organizing data structures following object-oriented principles maintaining clean separation between data models and application logic.

pythonmodel_basics.py
# blog/models.py - Basic Django model
from django.db import models
from django.utils import timezone

class Post(models.Model):
    """Blog post model"""
    # Field types define column types and validation
    title = models.CharField(max_length=200)  # VARCHAR(200)
    content = models.TextField()  # TEXT
    created_at = models.DateTimeField(default=timezone.now)  # TIMESTAMP
    updated_at = models.DateTimeField(auto_now=True)  # Auto-update on save
    published = models.BooleanField(default=False)  # BOOLEAN
    views = models.IntegerField(default=0)  # INTEGER
    
    # String representation
    def __str__(self):
        return self.title
    
    # Custom method
    def was_published_recently(self):
        return self.created_at >= timezone.now() - timezone.timedelta(days=7)
    
    # Meta class for model options
    class Meta:
        ordering = ['-created_at']  # Default ordering
        verbose_name = 'Blog Post'
        verbose_name_plural = 'Blog Posts'

# Creating model instances
# In Django shell or views:
post = Post(title='My First Post', content='Hello World')
post.save()  # INSERT INTO blog_post...

# Or use create() method
post = Post.objects.create(
    title='Another Post',
    content='More content',
    published=True
)

# Accessing fields
print(post.title)  # 'Another Post'
print(post.created_at)  # datetime object

# Updating
post.title = 'Updated Title'
post.save()  # UPDATE blog_post SET...

# Deleting
post.delete()  # DELETE FROM blog_post WHERE id=...

Common Model Field Types

Django provides extensive field types covering common data storage requirements from simple strings to complex file uploads and JSON data. CharField stores short text with required max_length parameter, TextField for longer text without length limits, IntegerField for integers, FloatField and DecimalField for numbers with decimals. DateField stores dates, TimeField for times, DateTimeField for timestamps. BooleanField stores True/False values, EmailField validates email addresses, URLField validates URLs. FileField and ImageField handle file uploads with automatic storage management. JSONField stores JSON data natively in PostgreSQL and other supporting databases. Field options include null allowing NULL database values, blank permitting empty form values, default setting default values, unique enforcing uniqueness, choices limiting valid values, and validators for custom validation. Understanding field types and options enables modeling diverse data structures, implementing data integrity constraints, and leveraging Django's built-in validation reducing custom validation code while maintaining data quality.

pythonmodel_fields.py
# Common Django model fields
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

class Product(models.Model):
    # Text fields
    name = models.CharField(max_length=100)  # Required max_length
    description = models.TextField(blank=True)  # Optional, can be empty
    sku = models.CharField(max_length=50, unique=True)  # Must be unique
    
    # Numeric fields
    price = models.DecimalField(max_digits=10, decimal_places=2)  # 99999999.99
    quantity = models.IntegerField(default=0)
    weight = models.FloatField(null=True, blank=True)  # Optional
    rating = models.IntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(5)]
    )
    
    # Boolean
    active = models.BooleanField(default=True)
    featured = models.BooleanField(default=False)
    
    # Date and time
    created_at = models.DateTimeField(auto_now_add=True)  # Set on creation
    updated_at = models.DateTimeField(auto_now=True)  # Update on every save
    published_date = models.DateField(null=True, blank=True)
    
    # Email and URL
    contact_email = models.EmailField(blank=True)
    website = models.URLField(blank=True)
    
    # File uploads
    image = models.ImageField(upload_to='products/', blank=True)
    manual = models.FileField(upload_to='manuals/', blank=True)
    
    # Choices
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('published', 'Published'),
        ('archived', 'Archived'),
    ]
    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        default='draft'
    )
    
    # JSON field (PostgreSQL, MySQL 5.7+, SQLite 3.9+)
    metadata = models.JSONField(default=dict, blank=True)
    
    # Slug field (URL-friendly)
    slug = models.SlugField(max_length=100, unique=True)
    
    def __str__(self):
        return self.name

# Field options:
# null=True - Allows NULL in database
# blank=True - Allows empty in forms
# default=value - Default value
# unique=True - Must be unique
# db_index=True - Creates database index
# editable=False - Excludes from forms
# help_text='Help text' - Form help text
# verbose_name='Name' - Human-readable name

Model Meta Options

The Meta class within Django models defines metadata and behavior options controlling how Django handles model instances beyond field definitions. Common Meta options include ordering specifying default query result ordering, verbose_name and verbose_name_plural setting human-readable names for admin interface, db_table customizing database table name, unique_together enforcing uniqueness across multiple fields, indexes defining database indexes for query performance, and permissions adding custom permissions beyond default add, change, delete. The abstract option creates abstract base classes for model inheritance without database tables, proxy enables creating different Python interfaces to the same database table, and managed controls whether Django creates database tables during migrations. Understanding Meta options enables fine-tuning model behavior, optimizing database performance through indexes, enforcing complex constraints, and implementing model inheritance patterns for code reuse while maintaining clear database schema organization supporting scalable application architecture.

pythonmodel_meta.py
# Model Meta options
from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    content = models.TextField()
    category = models.CharField(max_length=50)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    published = models.BooleanField(default=False)
    
    class Meta:
        # Default ordering (- for descending)
        ordering = ['-created_at', 'title']
        
        # Human-readable names
        verbose_name = 'Article'
        verbose_name_plural = 'Articles'
        
        # Custom database table name
        db_table = 'blog_articles'
        
        # Unique together constraint
        unique_together = [['slug', 'author']]
        
        # Database indexes for performance
        indexes = [
            models.Index(fields=['created_at']),
            models.Index(fields=['slug', 'published']),
            models.Index(fields=['-created_at', 'category']),
        ]
        
        # Custom permissions
        permissions = [
            ('can_publish', 'Can publish articles'),
            ('can_feature', 'Can feature articles'),
        ]
        
        # Get latest by field
        get_latest_by = 'created_at'
        
        # Default manager ordering
        default_manager_name = 'objects'

# Abstract base model
class TimeStampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        abstract = True  # No database table created

# Inheriting abstract model
class BlogPost(TimeStampedModel):
    title = models.CharField(max_length=200)
    content = models.TextField()
    # Inherits created_at and updated_at fields

# Proxy model (same table, different behavior)
class PublishedArticle(Article):
    class Meta:
        proxy = True
        ordering = ['-published_date']
    
    def get_recent_comments(self):
        return self.comments.filter(created_at__gte=timezone.now() - timedelta(days=7))

# Unmanaged model (existing database table)
class LegacyData(models.Model):
    data = models.TextField()
    
    class Meta:
        managed = False  # Django won't create/modify table
        db_table = 'legacy_table'  # Existing table name

Model Methods and Properties

Custom model methods encapsulate business logic, calculations, and behaviors specific to model instances keeping data-related functionality within model classes following object-oriented design principles. Instance methods access fields using self providing object-specific operations like calculating totals, formatting display strings, or implementing workflows. Properties using @property decorator create computed attributes appearing as fields while executing code dynamically like combining first and last names into full_name. Class methods using @classmethod access the model class rather than instances useful for alternative constructors or class-level operations. The save() method can be overridden for custom behavior before or after database writes implementing validation, automatic field population, or related object updates. The get_absolute_url() method returns canonical URLs for model instances enabling generic view references and template URL generation. Understanding model methods enables implementing clean business logic, reducing code duplication, and maintaining data-related operations close to data definitions promoting maintainable codebases following Django best practices.

pythonmodel_methods.py
# Model methods and properties
from django.db import models
from django.urls import reverse
from django.utils import timezone
import datetime

class Order(models.Model):
    customer = models.ForeignKey('Customer', on_delete=models.CASCADE)
    order_number = models.CharField(max_length=20, unique=True)
    total = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)
    status = models.CharField(max_length=20, default='pending')
    
    def __str__(self):
        return f"Order {self.order_number}"
    
    # Instance method
    def calculate_total(self):
        """Calculate order total from items"""
        return sum(item.get_subtotal() for item in self.items.all())
    
    def can_cancel(self):
        """Check if order can be cancelled"""
        return self.status in ['pending', 'processing']
    
    # Property - computed attribute
    @property
    def is_recent(self):
        """Check if order is less than 7 days old"""
        return self.created_at >= timezone.now() - datetime.timedelta(days=7)
    
    @property
    def days_since_order(self):
        """Days since order was placed"""
        return (timezone.now() - self.created_at).days
    
    # Override save method
    def save(self, *args, **kwargs):
        # Generate order number if not set
        if not self.order_number:
            self.order_number = self.generate_order_number()
        
        # Recalculate total before saving
        if self.pk:  # If updating existing order
            self.total = self.calculate_total()
        
        super().save(*args, **kwargs)
    
    @classmethod
    def generate_order_number(cls):
        """Generate unique order number"""
        import random
        return f"ORD-{random.randint(10000, 99999)}"
    
    # Canonical URL
    def get_absolute_url(self):
        return reverse('order_detail', kwargs={'pk': self.pk})
    
    # Custom manager method (define in manager class)
    @classmethod
    def get_pending_orders(cls):
        return cls.objects.filter(status='pending')

class Customer(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    email = models.EmailField(unique=True)
    
    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"
    
    def get_orders(self):
        """Get all customer orders"""
        return self.order_set.all().order_by('-created_at')
    
    def get_total_spent(self):
        """Calculate total amount spent"""
        return sum(order.total for order in self.get_orders())

# Using methods in views/templates
# order = Order.objects.get(pk=1)
# print(order.is_recent)  # Property
# print(order.calculate_total())  # Method
# url = order.get_absolute_url()
# 
# In template:
# {{ order.is_recent }}
# {{ order.days_since_order }}
# <a href="{{ order.get_absolute_url }}">View Order</a>

Model Validation

Django model validation ensures data integrity by checking field values before saving to the database preventing invalid data from corrupting database records. Field-level validation occurs through field type constraints, max_length limits, choices restrictions, and validators parameter accepting callable validators. The clean() method provides model-level validation enabling complex validation across multiple fields, business rule enforcement, and custom error messages. The full_clean() method runs all validators and clean() method typically called by forms before saving but not automatically in queryset operations requiring explicit calls. ValidationError exceptions raised during validation contain error messages displayed to users through forms or API responses. Custom validators defined as functions accepting values and raising ValidationError when validation fails enable reusable validation logic across multiple models and fields. Understanding validation enables maintaining data quality, implementing business rules, providing user-friendly error messages, and preventing invalid data states ensuring database consistency and application reliability through defense-in-depth validation strategies.

pythonmodel_validation.py
# Model validation
from django.db import models
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator, RegexValidator
import re

# Custom validator function
def validate_even(value):
    if value % 2 != 0:
        raise ValidationError(f'{value} is not an even number')

def validate_file_size(value):
    filesize = value.size
    if filesize > 5 * 1024 * 1024:  # 5MB
        raise ValidationError('File size cannot exceed 5MB')

class Product(models.Model):
    name = models.CharField(max_length=100)
    
    # Built-in validators
    price = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        validators=[MinValueValidator(0), MaxValueValidator(10000)]
    )
    
    # Custom validator
    quantity = models.IntegerField(validators=[validate_even])
    
    # Regex validator
    sku = models.CharField(
        max_length=20,
        validators=[
            RegexValidator(
                regex=r'^[A-Z]{3}-\d{4}$',
                message='SKU must be in format ABC-1234'
            )
        ]
    )
    
    # File validator
    image = models.ImageField(
        upload_to='products/',
        validators=[validate_file_size]
    )
    
    stock = models.IntegerField(default=0)
    discount = models.DecimalField(max_digits=5, decimal_places=2, default=0)
    
    def clean(self):
        """Model-level validation"""
        # Call parent clean
        super().clean()
        
        # Custom validation logic
        if self.discount > 50:
            raise ValidationError('Discount cannot exceed 50%')
        
        if self.stock < 0:
            raise ValidationError('Stock cannot be negative')
        
        # Validate across multiple fields
        if self.price < 10 and self.discount > 20:
            raise ValidationError(
                'Discount over 20% not allowed for items under $10'
            )
    
    def save(self, *args, **kwargs):
        # Run validation before saving
        self.full_clean()
        super().save(*args, **kwargs)

class User(models.Model):
    username = models.CharField(max_length=50, unique=True)
    email = models.EmailField(unique=True)
    age = models.IntegerField()
    
    def clean(self):
        super().clean()
        
        # Username validation
        if not re.match(r'^[a-zA-Z0-9_]+$', self.username):
            raise ValidationError({
                'username': 'Username can only contain letters, numbers and underscores'
            })
        
        # Age validation
        if self.age < 18:
            raise ValidationError({
                'age': 'User must be at least 18 years old'
            })
        
        # Email domain validation
        if not self.email.endswith(('@example.com', '@company.com')):
            raise ValidationError({
                'email': 'Email must be from example.com or company.com domain'
            })

# Using validation in views
from django.core.exceptions import ValidationError

def create_product(request):
    product = Product(
        name='Test Product',
        price=100,
        quantity=5,
        sku='ABC-1234'
    )
    
    try:
        product.full_clean()  # Run all validators
        product.save()
    except ValidationError as e:
        print(e.message_dict)  # {'field': ['error message']}
        # Handle validation errors

Model Design Best Practices

Effective model design follows established patterns ensuring maintainable, scalable database schemas supporting application growth. Keep models focused representing single entities avoiding god models with excessive unrelated fields promoting clear data organization. Use descriptive field and model names clearly indicating purpose without abbreviations improving code readability. Set appropriate null and blank options distinguishing between NULL database values and empty form values maintaining data integrity. Define string representation through __str__ method improving debugging and admin interface display. Implement custom methods and properties for business logic keeping data operations within model classes. Use Meta class for ordering, indexes, and constraints optimizing query performance. Avoid duplicate data preferring relationships over denormalization unless performance requires it. Document complex models with docstrings explaining purpose and important fields. Use abstract base models for common fields reducing duplication across models. Validate data thoroughly through validators and clean methods preventing invalid states. These practices result in clean, maintainable models supporting long-term application development and evolution.

$ cat /comments/ (0)

new_comment.sh

// Email hidden from public

>_

$ cat /comments/

// No comments found. Be the first!

[session] guest@{codershandbook}[timestamp] 2026

Navigation

Connect

Subscribe

// 2026 {Coders Handbook}. EOF.