Models and databases
In this chapter, you will learn to use Django ORM to define data models, create database tables, and insert test data.
Why are models needed?
In the previous chapter, we hardcoded page text. A real blog needs to store multiple articles, and the content can change at any time.
ModelIt is Django's database layer: use Python classes to define data structures, and Django automatically generates the corresponding database tables.
You don't need to write a single line of SQL—this isORM (Object-Relational Mapping)core value.
Define the Post model
Openblog/models.py, define the article model.
Example
from django.db import models
from django.utils import timezone
class Category(models.Model):
"""Article category"""
name = models.CharField('category name', max_length=50, unique=True)
# slug is used for English identifiers in URLs (e.g., /category/django/)
slug = models.SlugField('URL identifier', max_length=50, unique=True)
class Meta:
verbose_name = 'Categories'
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
class Post(models.Model):
"""Blog Post"""
# Title: CharField is used for short text; max_length is required.
title = models.CharField('Title', max_length=200)
# slug: Article's English identifier in the URL
slug = models.SlugField('URL identifier', max_length=200, unique=True)
# Summary: TextField is used for long text, blank=True means optional
summary = models.TextField('Summary', blank=True)
# Body: TextField with no length limit
content = models.TextField('Body')
# Category: ForeignKey one-to-many relationship, an article belongs to one category
# on_delete=models.CASCADE means that when deleting a category, all articles under it are also deleted
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
verbose_name='Categories',
related_name='posts' # Reverse query: category.posts.all()
)
# auto_now_add: automatically fill in the current time at creation
created_at = models.DateTimeField('Creation Time', auto_now_add=True)
# auto_now: automatically updated to the current time on every save
updated_at = models.DateTimeField('Update Time', auto_now=True)
class Meta:
verbose_name = 'Articles'
verbose_name_plural = 'Articles'
# Sorted by creation time in descending order by default
ordering = ['-created_at']
def __str__(self):
# Display the title when showing objects in the Admin backend and Shell
return self.title
Quick reference for common field types
| Field Type | Corresponding database types | Use Case |
|---|---|---|
| CharField(max_length=N) | VARCHAR(N) | Short text for titles, tags, names, etc. |
| TextField() | TEXT | Article body, introduction, comments, and other content |
| IntegerField() | INTEGER | Integers for counts, ages, etc. |
| DateTimeField() | DATETIME | Publish time, update time |
| ForeignKey() | Foreign Key Constraint | One-to-many relationship (article → category) |
| ManyToManyField() | junction table | Many-to-many relationship (article ←→ tag) |
| SlugField() | VARCHAR | URL-friendly identifier |
| ImageField() | VARCHAR(path) | Cover image, avatar (requires Pillow library) |
__str__The method determines how the object is displayed as a string. It is called in the Admin backend's dropdown selection boxes and Shell output to display the object name. If you don't write this method, you will seePost object (1)such meaningless text.
Migration: Sync models to the database
After writing the model classes, you need to useMigrationCreate database tables.
(venv) $ python manage.py makemigrations # 生成迁移文件(检测模型变化)
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Category
- Create model Post
(venv) $ python manage.py migrate # 执行迁移(创建数据库表)
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying blog.0001_initial... OK
Division of labor between the two commands:
- makemigrationsScan models.py and generate migration scripts (Python files).
- migrate: Run the migration scripts to create actual tables in the database.
After each modification to models.py, you need to run
makemigrations+migrateThese two commands are both indispensable: running only makemigrations without migrate means the tables won't be created; running migrate directly without makemigrations means Django won't know you've changed the models.
Use the Django Shell to insert test data.
Before starting to write pages, first use Django Shell to enter a few pieces of data into the database to verify that the model works properly.
(venv) $ python manage.py shell
This opens an interactive Python Shell where you can operate on the database.
Example
from blog.models import Category, Post
# 1. Create category
cat_django = Category.objects.create(name='Django', slug='django')
cat_python = Category.objects.create(name='Python', slug='python')
cat_css = Category.objects.create(name='CSS', slug='css')
# 2. Create article
Post.objects.create(
title='The Complete Django Beginner's Guide',
slug='django-beginner-guide',
summary='Learn Django from scratch, covering core concepts such as models, views, templates, etc.',
content='<h2>is什么学 Django?</h2><p>Django Yes Python 最flowOf the line Web FrameworkOne of...</p>',
category=cat_django
)
Post.objects.create(
title='Detailed Explanation of Python Asynchronous Programming',
slug='python-async',
summary=Understand asyncio, coroutines, and event loops in one article.,
content=<h2>What Is a Coroutine?</h2><p>A coroutine is a lighter-weight concurrency solution than threads...</p>,
category=cat_python
)
Post.objects.create(
title=Practical CSS Grid Layout,
slug='css-grid-layout',
summary=Easily implement complex responsive layouts with CSS Grid.,
content=<h2>Getting Started with Grid</h2><p>Grid is a two-dimensional layout system...</p>,
category=cat_css
)
# 3. Verify Queries
print(Post.objects.all()) # Output all articles
print(Post.objects.count()) # Total Articles
print(Post.objects.filter(category=cat_django)) # Articles under the Django category
Django ORM basic queries
ORM lets you operate on the database using Python method chains, without writing SQL.
| Methods | Equivalent SQL | Return Value |
|---|---|---|
Post.objects.all() | SELECT * FROM blog_post | QuerySet (all articles) |
Post.objects.get(id=1) | SELECT ... WHERE id=1 | Single object (throws an exception if not found) |
Post.objects.filter(category__slug='django') | WHERE category.slug='django' | QuerySet |
Post.objects.order_by('-created_at') | ORDER BY created_at DESC | QuerySet |
Post.objects.exclude(status='draft') | WHERE status != 'draft' | QuerySet |
QuerySet isLazy evaluationThis is: the query is not executed immediately; SQL is only sent when data is actually needed (such as when iterating, printing, or converting to a list). So you can chain multiple filter calls without generating multiple queries.
Hands-on: Prepare data for subsequent chapters
In the Shell, enter several more articles and categories, covering the three categories Django, Python, and CSS, with at least 2 articles per category.
You could write a management command or script for batch insertion, but at this stage it is sufficient to insert them one by one using the Shell.
Chapter summary
In this chapter, you've mastered the core workflow of the Django ORM: defining Python classes (models) in models.py, generating migration files with makemigrations, syncing to the database with migrate, and using the Shell and ORM to query and insert data.
At this point, the structure of articles and categories has been established in the database.
other extensions