Models and Databases

Defining Fields

Django model fields are the building blocks of your data structure. Each field type corresponds to a specific database column type and provides validation, conversion, and rendering capabilities. Understanding field options and customization is crucial for creating robust data models.

Defining Fields

Django model fields are the building blocks of your data structure. Each field type corresponds to a specific database column type and provides validation, conversion, and rendering capabilities. Understanding field options and customization is crucial for creating robust data models.

Core Field Types

Text Fields

# models.py
from django.db import models
from django.core.validators import RegexValidator, MinLengthValidator

class TextFieldExamples(models.Model):
    # CharField - for short text (required max_length)
    title = models.CharField(
        max_length=200,
        help_text='Maximum 200 characters'
    )
    
    # CharField with choices
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('published', 'Published'),
        ('archived', 'Archived'),
    ]
    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        default='draft'
    )
    
    # TextField - for long text (no max_length required)
    description = models.TextField(
        blank=True,
        help_text='Detailed description'
    )
    
    # TextField with max_length for validation
    summary = models.TextField(
        max_length=500,
        blank=True,
        help_text='Brief summary (max 500 characters)'
    )
    
    # SlugField - for URL-friendly strings
    slug = models.SlugField(
        max_length=200,
        unique=True,
        help_text='URL-friendly version of title'
    )
    
    # EmailField - validates email format
    contact_email = models.EmailField(
        blank=True,
        help_text='Valid email address'
    )
    
    # URLField - validates URL format
    website = models.URLField(
        blank=True,
        help_text='Full URL including http:// or https://'
    )
    
    # CharField with custom validation
    phone = models.CharField(
        max_length=20,
        blank=True,
        validators=[
            RegexValidator(
                regex=r'^\+?1?\d{9,15}$',
                message='Phone number must be entered in the format: "+999999999". Up to 15 digits allowed.'
            )
        ]
    )
    
    # CharField with minimum length
    username = models.CharField(
        max_length=30,
        unique=True,
        validators=[MinLengthValidator(3)]
    )

class AdvancedTextFields(models.Model):
    # Text field with custom widget attributes
    bio = models.TextField(
        max_length=1000,
        blank=True,
        help_text='Tell us about yourself'
    )
    
    # CharField with database index
    category = models.CharField(
        max_length=50,
        db_index=True,  # Creates database index for faster queries
        help_text='Product category'
    )
    
    # CharField that's not editable in forms
    internal_code = models.CharField(
        max_length=20,
        editable=False,  # Won't appear in ModelForms
        unique=True
    )
    
    # CharField with custom database column name
    external_id = models.CharField(
        max_length=50,
        db_column='ext_id',  # Database column will be named 'ext_id'
        null=True,
        blank=True
    )

Numeric Fields

# models.py
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

class NumericFieldExamples(models.Model):
    # IntegerField - standard integer
    quantity = models.IntegerField(
        default=0,
        validators=[MinValueValidator(0)]
    )
    
    # PositiveIntegerField - only positive integers
    stock_count = models.PositiveIntegerField(
        default=0,
        help_text='Number of items in stock'
    )
    
    # PositiveSmallIntegerField - positive integers (smaller range)
    rating = models.PositiveSmallIntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(5)],
        help_text='Rating from 1 to 5'
    )
    
    # BigIntegerField - for very large integers
    total_views = models.BigIntegerField(
        default=0,
        help_text='Total number of views'
    )
    
    # SmallIntegerField - for small integers (-32,768 to 32,767)
    priority = models.SmallIntegerField(
        default=0,
        validators=[MinValueValidator(-10), MaxValueValidator(10)],
        help_text='Priority from -10 to 10'
    )
    
    # DecimalField - for precise decimal numbers
    price = models.DecimalField(
        max_digits=10,  # Total number of digits
        decimal_places=2,  # Number of decimal places
        validators=[MinValueValidator(0)],
        help_text='Price in USD'
    )
    
    # DecimalField for percentages
    discount_percentage = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        null=True,
        blank=True,
        help_text='Discount percentage (0-100)'
    )
    
    # FloatField - for floating-point numbers
    weight = models.FloatField(
        validators=[MinValueValidator(0.0)],
        help_text='Weight in kilograms'
    )
    
    # FloatField with precision considerations
    latitude = models.FloatField(
        validators=[MinValueValidator(-90.0), MaxValueValidator(90.0)],
        null=True,
        blank=True,
        help_text='Latitude coordinate'
    )
    
    longitude = models.FloatField(
        validators=[MinValueValidator(-180.0), MaxValueValidator(180.0)],
        null=True,
        blank=True,
        help_text='Longitude coordinate'
    )

class FinancialModel(models.Model):
    """Example of financial data with proper decimal handling"""
    
    # Use DecimalField for money to avoid floating-point precision issues
    amount = models.DecimalField(
        max_digits=12,  # Allows up to 999,999,999.99
        decimal_places=2,
        validators=[MinValueValidator(0)],
        help_text='Amount in USD'
    )
    
    # Currency exchange rate
    exchange_rate = models.DecimalField(
        max_digits=10,
        decimal_places=6,  # More precision for exchange rates
        validators=[MinValueValidator(0)],
        help_text='Exchange rate to USD'
    )
    
    # Interest rate as percentage
    interest_rate = models.DecimalField(
        max_digits=5,
        decimal_places=3,  # Allows rates like 12.375%
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        help_text='Annual interest rate percentage'
    )

Date and Time Fields

# models.py
from django.db import models
from django.utils import timezone
from datetime import date, time

class DateTimeFieldExamples(models.Model):
    # DateTimeField - date and time
    created_at = models.DateTimeField(
        auto_now_add=True,  # Set only when object is created
        help_text='When the record was created'
    )
    
    updated_at = models.DateTimeField(
        auto_now=True,  # Updated every time object is saved
        help_text='When the record was last updated'
    )
    
    # DateTimeField with manual setting
    published_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text='When the content was published'
    )
    
    # DateTimeField with default value
    scheduled_at = models.DateTimeField(
        default=timezone.now,  # Use function reference, not function call
        help_text='When the task is scheduled to run'
    )
    
    # DateField - date only
    birth_date = models.DateField(
        null=True,
        blank=True,
        help_text='Date of birth'
    )
    
    # DateField with validation
    event_date = models.DateField(
        help_text='Date of the event'
    )
    
    # TimeField - time only
    start_time = models.TimeField(
        help_text='Start time (HH:MM:SS)'
    )
    
    end_time = models.TimeField(
        help_text='End time (HH:MM:SS)'
    )
    
    # TimeField with default
    default_meeting_time = models.TimeField(
        default=time(9, 0),  # 9:00 AM
        help_text='Default meeting time'
    )
    
    def clean(self):
        """Custom validation for date/time fields"""
        from django.core.exceptions import ValidationError
        
        errors = {}
        
        # Validate birth date is not in the future
        if self.birth_date and self.birth_date > date.today():
            errors['birth_date'] = 'Birth date cannot be in the future.'
        
        # Validate event date is not in the past
        if self.event_date and self.event_date < date.today():
            errors['event_date'] = 'Event date cannot be in the past.'
        
        # Validate time range
        if self.start_time and self.end_time and self.start_time >= self.end_time:
            errors['end_time'] = 'End time must be after start time.'
        
        if errors:
            raise ValidationError(errors)

class TimezoneAwareModel(models.Model):
    """Example of timezone-aware datetime handling"""
    
    # Always use timezone-aware datetimes
    created_at = models.DateTimeField(auto_now_add=True)
    
    # For user-input datetimes, consider timezone
    appointment_datetime = models.DateTimeField(
        help_text='Appointment date and time (in your local timezone)'
    )
    
    # Store timezone separately if needed
    timezone_name = models.CharField(
        max_length=50,
        default='UTC',
        help_text='Timezone for the appointment'
    )
    
    def save(self, *args, **kwargs):
        # Ensure datetime is timezone-aware
        if self.appointment_datetime and timezone.is_naive(self.appointment_datetime):
            self.appointment_datetime = timezone.make_aware(self.appointment_datetime)
        super().save(*args, **kwargs)
    
    @property
    def appointment_local_time(self):
        """Get appointment time in specified timezone"""
        if self.appointment_datetime and self.timezone_name:
            import pytz
            tz = pytz.timezone(self.timezone_name)
            return self.appointment_datetime.astimezone(tz)
        return self.appointment_datetime

Boolean and Choice Fields

# models.py
from django.db import models

class BooleanFieldExamples(models.Model):
    # BooleanField - True/False
    is_active = models.BooleanField(
        default=True,
        help_text='Whether the record is active'
    )
    
    # BooleanField with custom verbose names
    email_notifications = models.BooleanField(
        default=True,
        verbose_name='Email Notifications',
        help_text='Receive email notifications'
    )
    
    # NullBooleanField is deprecated, use BooleanField with null=True
    is_verified = models.BooleanField(
        null=True,
        blank=True,
        help_text='Verification status (True/False/Unknown)'
    )

class ChoiceFieldExamples(models.Model):
    # Simple choices
    SIZE_CHOICES = [
        ('XS', 'Extra Small'),
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
        ('XL', 'Extra Large'),
    ]
    
    size = models.CharField(
        max_length=2,
        choices=SIZE_CHOICES,
        default='M'
    )
    
    # Grouped choices
    MEDIA_CHOICES = [
        ('Audio', (
            ('vinyl', 'Vinyl'),
            ('cd', 'CD'),
        )),
        ('Video', (
            ('vhs', 'VHS Tape'),
            ('dvd', 'DVD'),
            ('bluray', 'Blu-ray'),
        )),
    ]
    
    media_type = models.CharField(
        max_length=10,
        choices=MEDIA_CHOICES,
        blank=True
    )
    
    # Integer choices
    PRIORITY_CHOICES = [
        (1, 'Low'),
        (2, 'Medium'),
        (3, 'High'),
        (4, 'Critical'),
    ]
    
    priority = models.IntegerField(
        choices=PRIORITY_CHOICES,
        default=2
    )
    
    # Using TextChoices (Django 3.0+)
    class Status(models.TextChoices):
        DRAFT = 'draft', 'Draft'
        PUBLISHED = 'published', 'Published'
        ARCHIVED = 'archived', 'Archived'
    
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.DRAFT
    )
    
    # Using IntegerChoices (Django 3.0+)
    class Rating(models.IntegerChoices):
        POOR = 1, 'Poor'
        FAIR = 2, 'Fair'
        GOOD = 3, 'Good'
        VERY_GOOD = 4, 'Very Good'
        EXCELLENT = 5, 'Excellent'
    
    rating = models.IntegerField(
        choices=Rating.choices,
        null=True,
        blank=True
    )
    
    def get_status_color(self):
        """Get CSS color class for status"""
        colors = {
            self.Status.DRAFT: 'warning',
            self.Status.PUBLISHED: 'success',
            self.Status.ARCHIVED: 'secondary',
        }
        return colors.get(self.status, 'primary')

# Dynamic choices example
class Country(models.Model):
    name = models.CharField(max_length=100)
    code = models.CharField(max_length=2, unique=True)
    
    def __str__(self):
        return self.name

class Address(models.Model):
    street = models.CharField(max_length=200)
    city = models.CharField(max_length=100)
    
    # Dynamic choices from database
    country = models.ForeignKey(
        Country,
        on_delete=models.CASCADE,
        help_text='Select country'
    )
    
    # State/Province with choices that depend on country
    state_province = models.CharField(
        max_length=100,
        blank=True,
        help_text='State or Province'
    )

File and Image Fields

# models.py
from django.db import models
from django.core.validators import FileExtensionValidator
import os

def user_directory_path(instance, filename):
    """Generate upload path based on user"""
    return f'user_{instance.user.id}/{filename}'

def validate_file_size(value):
    """Validate file size (max 5MB)"""
    filesize = value.size
    if filesize > 5 * 1024 * 1024:  # 5MB
        from django.core.exceptions import ValidationError
        raise ValidationError('File size cannot exceed 5MB.')

class FileFieldExamples(models.Model):
    user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    
    # Basic FileField
    document = models.FileField(
        upload_to='documents/',
        blank=True,
        null=True,
        help_text='Upload a document'
    )
    
    # FileField with custom upload path
    resume = models.FileField(
        upload_to=user_directory_path,
        blank=True,
        null=True,
        help_text='Upload your resume'
    )
    
    # FileField with extension validation
    pdf_file = models.FileField(
        upload_to='pdfs/',
        validators=[
            FileExtensionValidator(allowed_extensions=['pdf']),
            validate_file_size
        ],
        blank=True,
        null=True,
        help_text='PDF files only, max 5MB'
    )
    
    # ImageField - extends FileField for images
    profile_picture = models.ImageField(
        upload_to='profiles/',
        blank=True,
        null=True,
        help_text='Profile picture (JPG, PNG, GIF)'
    )
    
    # ImageField with size validation
    banner_image = models.ImageField(
        upload_to='banners/',
        blank=True,
        null=True,
        help_text='Banner image (recommended: 1200x400px)'
    )
    
    def clean(self):
        """Custom validation for image dimensions"""
        from django.core.exceptions import ValidationError
        from PIL import Image
        
        if self.banner_image:
            try:
                img = Image.open(self.banner_image)
                width, height = img.size
                
                # Validate dimensions
                if width < 800 or height < 200:
                    raise ValidationError({
                        'banner_image': 'Image must be at least 800x200 pixels.'
                    })
                
                if width > 2000 or height > 600:
                    raise ValidationError({
                        'banner_image': 'Image must not exceed 2000x600 pixels.'
                    })
                
            except Exception as e:
                raise ValidationError({
                    'banner_image': 'Invalid image file.'
                })

class ProductImage(models.Model):
    """Example of multiple image handling"""
    product = models.ForeignKey('Product', on_delete=models.CASCADE, related_name='images')
    
    image = models.ImageField(
        upload_to='products/',
        help_text='Product image'
    )
    
    alt_text = models.CharField(
        max_length=200,
        help_text='Alternative text for accessibility'
    )
    
    is_primary = models.BooleanField(
        default=False,
        help_text='Primary product image'
    )
    
    order = models.PositiveSmallIntegerField(
        default=0,
        help_text='Display order'
    )
    
    class Meta:
        ordering = ['order', 'id']
    
    def save(self, *args, **kwargs):
        # Ensure only one primary image per product
        if self.is_primary:
            ProductImage.objects.filter(
                product=self.product,
                is_primary=True
            ).exclude(pk=self.pk).update(is_primary=False)
        
        super().save(*args, **kwargs)
    
    def delete(self, *args, **kwargs):
        # Delete the file when the model instance is deleted
        if self.image:
            if os.path.isfile(self.image.path):
                os.remove(self.image.path)
        super().delete(*args, **kwargs)

JSON and Advanced Fields

# models.py
from django.db import models
from django.contrib.postgres.fields import ArrayField
import uuid

class JSONFieldExamples(models.Model):
    """Examples using JSONField (available in Django 3.1+)"""
    
    # Basic JSON field
    metadata = models.JSONField(
        default=dict,
        blank=True,
        help_text='Additional metadata as JSON'
    )
    
    # JSON field with default structure
    settings = models.JSONField(
        default=lambda: {
            'notifications': True,
            'theme': 'light',
            'language': 'en'
        },
        help_text='User settings'
    )
    
    # JSON field for flexible data
    custom_fields = models.JSONField(
        default=dict,
        blank=True,
        help_text='Custom form fields and values'
    )
    
    def get_setting(self, key, default=None):
        """Get a specific setting value"""
        return self.settings.get(key, default)
    
    def set_setting(self, key, value):
        """Set a specific setting value"""
        self.settings[key] = value
        self.save(update_fields=['settings'])

class UUIDFieldExample(models.Model):
    """Example using UUID as primary key"""
    
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        help_text='Unique identifier'
    )
    
    name = models.CharField(max_length=100)
    
    # UUID for external references
    external_id = models.UUIDField(
        default=uuid.uuid4,
        unique=True,
        editable=False,
        help_text='External system identifier'
    )

# PostgreSQL-specific fields (if using PostgreSQL)
class PostgreSQLFieldExamples(models.Model):
    """Examples of PostgreSQL-specific fields"""
    
    # ArrayField - store arrays of values
    tags = ArrayField(
        models.CharField(max_length=50),
        size=10,  # Maximum 10 items
        default=list,
        blank=True,
        help_text='List of tags'
    )
    
    # ArrayField with integers
    scores = ArrayField(
        models.IntegerField(),
        size=5,
        default=list,
        blank=True,
        help_text='List of scores'
    )
    
    def add_tag(self, tag):
        """Add a tag to the array"""
        if tag not in self.tags:
            self.tags.append(tag)
            self.save(update_fields=['tags'])
    
    def remove_tag(self, tag):
        """Remove a tag from the array"""
        if tag in self.tags:
            self.tags.remove(tag)
            self.save(update_fields=['tags'])

class CustomFieldExample(models.Model):
    """Example of custom field usage"""
    
    # Custom field for storing color values
    primary_color = models.CharField(
        max_length=7,
        default='#000000',
        help_text='Hex color code (e.g., #FF0000)'
    )
    
    def clean(self):
        """Validate hex color format"""
        import re
        from django.core.exceptions import ValidationError
        
        if self.primary_color:
            if not re.match(r'^#[0-9A-Fa-f]{6}$', self.primary_color):
                raise ValidationError({
                    'primary_color': 'Enter a valid hex color code (e.g., #FF0000)'
                })

Field Options and Attributes

Common Field Options

# models.py
from django.db import models

class FieldOptionsExample(models.Model):
    # null - database level, allows NULL values
    optional_field = models.CharField(
        max_length=100,
        null=True,  # Database allows NULL
        help_text='This field can be NULL in database'
    )
    
    # blank - form validation level, allows empty values
    form_optional = models.CharField(
        max_length=100,
        blank=True,  # Forms allow empty values
        help_text='This field can be empty in forms'
    )
    
    # Both null and blank for truly optional fields
    truly_optional = models.CharField(
        max_length=100,
        null=True,
        blank=True,
        help_text='Optional in both database and forms'
    )
    
    # default - default value
    status = models.CharField(
        max_length=20,
        default='active',
        help_text='Default status is active'
    )
    
    # unique - enforce uniqueness
    email = models.EmailField(
        unique=True,
        help_text='Must be unique across all records'
    )
    
    # db_index - create database index
    category = models.CharField(
        max_length=50,
        db_index=True,  # Creates index for faster queries
        help_text='Indexed for performance'
    )
    
    # editable - whether field appears in forms
    internal_id = models.CharField(
        max_length=20,
        editable=False,  # Won't appear in ModelForms
        help_text='Internal use only'
    )
    
    # verbose_name - human-readable name
    full_name = models.CharField(
        max_length=200,
        verbose_name='Full Name',
        help_text='Person\'s complete name'
    )
    
    # help_text - help text for forms
    password = models.CharField(
        max_length=128,
        help_text='Use a strong password with at least 8 characters'
    )
    
    # choices - predefined options
    PRIORITY_CHOICES = [
        ('low', 'Low Priority'),
        ('medium', 'Medium Priority'),
        ('high', 'High Priority'),
    ]
    priority = models.CharField(
        max_length=10,
        choices=PRIORITY_CHOICES,
        default='medium'
    )
    
    # db_column - custom database column name
    external_ref = models.CharField(
        max_length=50,
        db_column='ext_ref',  # Column name in database
        null=True,
        blank=True
    )
    
    # db_tablespace - specify tablespace (advanced)
    large_text = models.TextField(
        db_tablespace='large_data',  # Custom tablespace
        blank=True
    )

class ValidationExample(models.Model):
    """Example of field validation options"""
    
    # validators - list of validator functions
    from django.core.validators import MinLengthValidator, MaxLengthValidator
    
    username = models.CharField(
        max_length=30,
        validators=[
            MinLengthValidator(3),
            MaxLengthValidator(30)
        ]
    )
    
    # Custom error messages
    age = models.PositiveIntegerField(
        error_messages={
            'invalid': 'Please enter a valid age.',
            'required': 'Age is required.',
        }
    )

Understanding Django field types and options enables you to create precise, well-validated data models that accurately represent your business requirements while providing optimal database performance and user experience.