The Django ORM: Defining Your Database with Models
1. What is an ORM?
An ORM (Object-Relational Mapper) is a programming layer that translates between two completely different worlds: the world of Python objects and the world of relational database tables.
Without an ORM, to store a blog post you would write raw SQL like this:
INSERT INTO blog_post (title, body, created_at)
VALUES ('My First Post', 'Hello world...', NOW());
With Django’s ORM, you write pure Python instead:
Post.objects.create(title='My First Post', body='Hello world...')
The ORM automatically generates and executes the SQL for you. This has three major advantages: you write less code, your code is database-agnostic (switch from SQLite to PostgreSQL by changing one line in settings.py), and you are protected from SQL injection attacks by default.
2. Defining Your First Model
Open blog/models.py and define a Post model for our blog:
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
# CharField stores short strings. max_length is required and sets
# the database column's character limit.
title = models.CharField(max_length=200)
# SlugField stores URL-friendly strings like 'my-first-post'.
# unique=True ensures no two posts share the same URL.
slug = models.SlugField(max_length=200, unique=True)
# TextField stores unlimited text. No max_length required.
body = models.TextField()
# ForeignKey creates a many-to-one relationship: many Posts can
# belong to one User (the author).
# on_delete=CASCADE means: if the User is deleted, delete their posts too.
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
# auto_now_add=True sets this field to the current timestamp when
# the record is first created, and never changes it afterwards.
created_at = models.DateTimeField(auto_now_add=True)
# auto_now=True updates this field to the current timestamp every
# time the record is saved.
updated_at = models.DateTimeField(auto_now=True)
# BooleanField stores True/False. We use this to draft posts
# without publishing them.
published = models.BooleanField(default=False)
def __str__(self):
# This controls how a Post object is displayed in the admin
# panel and in the Python shell.
return self.title
class Meta:
# Order posts by newest first in all queries by default.
ordering = ['-created_at']
3. Common Field Types Reference
| Field | Use Case | Key Options |
|---|---|---|
CharField | Short text (name, title, email) | max_length (required) |
TextField | Long text (article body, bio) | — |
IntegerField | Whole numbers | default |
DecimalField | Money, precise decimals | max_digits, decimal_places |
BooleanField | True/False flags | default |
DateTimeField | Date and time | auto_now_add, auto_now |
ImageField | Uploaded images (stores file path) | upload_to |
ForeignKey | Many-to-one relationship | on_delete, related_name |
ManyToManyField | Many-to-many relationship (tags) | related_name |
4. How Django Maps Models to Tables
When Django reads your Post model, it maps it to a database table with the following naming convention: {app_name}_{model_name_lowercase}. So our Post model in the blog app becomes the table blog_post.
Each field on the model becomes a column in the table. Django automatically adds an id column (a BigAutoField primary key) unless you explicitly define one. The author ForeignKey becomes an author_id integer column that stores the primary key of the related User record.