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.
# 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
)
# 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'
)
# 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
# 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'
)
# 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)
# 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)'
})
# 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.
Understanding Django Models
Django models are the single, definitive source of information about your data. They contain the essential fields and behaviors of the data you're storing. Each model maps to a single database table, and each attribute of the model represents a database field.
Relationships and Foreign Keys
Django's ORM provides powerful tools for defining and working with relationships between models. Understanding how to properly design and implement relationships is crucial for creating efficient, maintainable database schemas.