Skip to content

Django - 模型

模型 (model) 在 Django 中是关于你数据的权威信息源。它是一个 Python 类,代表数据库中的一张表(在关系型数据库中)或一个集合(在 NoSQL 数据库如 MongoDB 中)。模型类的每个属性都映射到数据库字段(列)。

模型通常在应用的 models.py 文件中定义(例如,myapp/models.py)。Django 使用这些模型定义来:

  • 通过其迁移系统 (migration system) 自动生成数据库模式(例如,CREATE TABLE 语句)。
  • 提供一个高级 Python API(ORM - 对象关系映射器 (Object-Relational Mapper)),用于创建、检索、更新和删除 (CRUD) 数据库记录。

让我们定义一个简单的模型,代表虚构目录中的公司。

myapp/models.py
from django.db import models
class Company(models.Model):
# 字段定义数据库表中的列
name = models.CharField(max_length=100, unique=True, help_text="Name of the company")
website = models.URLField(max_length=200, blank=True, help_text="Company website URL (optional)")
email = models.EmailField(max_length=100, help_text="Contact email address")
phone_number = models.CharField(max_length=20, blank=True, help_text="Contact phone number (optional)")
founded_date = models.DateField(null=True, blank=True, help_text="Date the company was founded")
is_active = models.BooleanField(default=True, help_text="Is the company currently active?")
# Django 自动添加的字段:
# id = models.AutoField(primary_key=True) # 默认主键
class Meta:
db_table = "companies" # 可选:明确指定数据库表名
verbose_name = "Company" # 可选:人类可读的单数名称
verbose_name_plural = "Companies" # 可选:人类可读的复数名称
ordering = ['name'] # 可选:查询时的默认排序
def __str__(self):
# 用于 admin 和 shell 中的字符串表示
return self.name

要点:

  • 每个模型都是一个继承自 django.db.models.Model 的 Python 类。
  • 每个属性代表一个数据库字段,使用 models.Field 类型定义(例如,CharField, URLField, IntegerField, DateField, BooleanField, ForeignKey 等)。
  • 常见的字段选项包括 max_length, unique, blank(与验证相关,允许表单中的空值),null(与数据库相关,允许数据库中的 NULL),default, help_text。
  • 除非你在另一个字段上指定 primary_key=True,否则 Django 会自动添加一个名为 id 的自增主键字段。
  • 内部的 Meta 类是可选的,允许你配置模型元数据,如数据库表名 (db_table)、默认排序 (ordering) 以及人类可读的名称 (verbose_name, verbose_name_plural)。
  • __str__() 方法定义了对象的人类可读表示,这在 Django admin 和 shell 中非常有用。

你可以在 Django 文档中找到模型字段和选项的完整列表:https://docs.djangoproject.com/en/stable/ref/models/fields/

定义模型类并不会自动创建相应的数据库表。你需要使用 Django 的迁移系统 (migration system):

  1. 确保你的应用在 INSTALLED_APPS 中: 在项目 settings.py 的 INSTALLED_APPS 中添加 'myapp' 或 'myapp.apps.MyappConfig'。

  2. 创建迁移: 这个命令会分析 models.py 中的更改(与之前的迁移进行比较),并在 myapp/migrations/ 目录中创建迁移文件。

python manage.py makemigrations myapp
  1. 应用迁移: 这个命令会读取迁移文件,并将更改应用到数据库模式(例如,创建 companies 表)。
python manage.py migrate

每当你对模型进行更改时(添加/删除字段、更改字段类型、修改选项等),都需要运行 makemigrations 和 migrate。

Django 提供了一个强大的数据库抽象 API (the ORM) 来与你的模型进行交互。你通过附加到模型类上的管理器 (Manager)(通常称为 objects)来访问这个 API。

让我们看看使用 Django shell (python manage.py shell) 或在视图 (view) 中进行 CRUD 操作的示例:

# 假设你运行了 'python manage.py shell'
from myapp.models import Company
import datetime
# === 创建 ===
# 方法 1:分两步创建并保存
c1 = Company(name="TechCorp", email="contact@techcorp.com", website="https://techcorp.com")
c1.save() # 执行 INSERT SQL 查询
# 方法 2:使用管理器一步创建并保存
c2 = Company.objects.create(
name="Innovate Ltd",
email="info@innovate.com",
phone_number="123-456-7890",
founded_date=datetime.date(2020, 1, 1)
)
print(f"Created company: {c1}")
print(f"Created company: {c2}")
# === 检索 / 读取 ===
# 获取所有公司
all_companies = Company.objects.all() # 返回一个 QuerySet (惰性查询)
print(f"\nAll Companies ({all_companies.count()}):")
for company in all_companies:
print(f"- {company.name} (ID: {company.id})")
# 通过主键 (id) 获取特定公司
try:
techcorp = Company.objects.get(pk=c1.id) # 或者 get(id=c1.id)
print(f"\nFound by PK: {techcorp.name}")
except Company.DoesNotExist:
print(f"\nCompany with pk={c1.id} not found.")
# 使用其他字段查找(返回一个对象,或抛出 DoesNotExist / MultipleObjectsReturned 异常)
try:
innovate = Company.objects.get(name="Innovate Ltd")
print(f"Found by name: {innovate.name}")
except Company.DoesNotExist:
print("\nInnovate Ltd not found.")
# 过滤以获取多个公司(返回一个 QuerySet)
active_companies = Company.objects.filter(is_active=True)
print(f"\nActive companies ({active_companies.count()}):")
for company in active_companies:
print(f"- {company.name}")
# 更复杂的查找 (field__lookuptype)
recent_companies = Company.objects.filter(founded_date__year__gte=2020) # 成立于 2020 年或之后
print(f"\nRecent companies ({recent_companies.count()}): {list(recent_companies)}")
# 排除公司
non_tech_companies = Company.objects.exclude(name__icontains="tech") # 不区分大小写的包含
print(f"\nNon-tech companies: {list(non_tech_companies)}")
# 排序结果
companies_by_name_desc = Company.objects.order_by('-name') # 降序
print(f"\nOrdered by name DESC: {list(companies_by_name_desc)}")
# === 更新 ===
# 获取对象,修改属性,然后保存
try:
company_to_update = Company.objects.get(name="TechCorp")
company_to_update.website = "https://new-techcorp.com"
company_to_update.is_active = False
company_to_update.save() # 执行 UPDATE SQL 查询
print(f"\nUpdated TechCorp: Website='{company_to_update.website}', Active={company_to_update.is_active}")
except Company.DoesNotExist:
print("\nTechCorp not found for update.")
# 一次更新多个对象(更高效,绕过 save() 方法)
num_updated = Company.objects.filter(founded_date__isnull=True).update(is_active=False)
print(f"\nSet {num_updated} companies with no founding date to inactive.")
# === 删除 ===
# 获取对象并删除
try:
company_to_delete = Company.objects.get(name="Innovate Ltd")
delete_result = company_to_delete.delete() # 执行 DELETE SQL 查询
print(f"\nDeleted Innovate Ltd. Result: {delete_result}")
except Company.DoesNotExist:
print("\nInnovate Ltd not found for deletion.")
# 一次删除多个对象(更高效)
delete_result_multi = Company.objects.filter(is_active=False).delete()
print(f"\nDeleted inactive companies. Result: {delete_result_multi}")

模型可以相互关联,以表示数据之间的关系:

  1. ForeignKey (多对一): 最常见的类型。将一个模型实例链接到另一个。示例:一个 Employee(员工)模型可以有一个指向 Company(公司)模型的 ForeignKey(多个员工属于一个公司)。

  2. OneToOneField (一对一): 类似于 ForeignKey,但确保关系一方的每个实例最多链接到另一方的一个实例。示例:一个 UserProfile(用户个人资料)模型可以有一个指向 Django 内置 User(用户)模型的 OneToOneField。

  3. ManyToManyField (多对多): 将一个模型的实例链接到另一个模型的多个实例(反之亦然)。示例:一个 Post(帖子)模型和一个 Tag(标签)模型可以有一个 ManyToManyField(一篇帖子可以有多个标签,一个标签可以应用于多篇帖子)。Django 会为此自动创建一个中间数据库表。

使用 ForeignKey 的示例:

myapp/models.py
from django.db import models
# 假设上面定义的 Company 模型已存在
class Employee(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
# 链接到 Company (多个员工 -> 一个公司)
company = models.ForeignKey(
Company,
on_delete=models.CASCADE, # 当引用的 Company 被删除时会发生什么?
related_name='employees' # 如何从 Company 回溯引用到 Employee
)
start_date = models.DateField()
class Meta:
ordering = ['company', 'name']
def __str__(self):
return f"{self.name} ({self.company.name})"
# --- 定义/修改模型后,运行:---
# python manage.py makemigrations myapp
# python manage.py migrate
# --- 使用示例(在 shell 或视图中)---
# from myapp.models import Company, Employee
# # 假设公司 'c1' 已存在 (TechCorp)
# c1 = Company.objects.get(name='TechCorp')
# # 为 TechCorp 创建员工
# emp1 = Employee.objects.create(name='Alice', email='alice@techcorp.com', company=c1, start_date='2022-08-15')
# emp2 = Employee.objects.create(name='Bob', email='bob@techcorp.com', company=c1, start_date='2023-01-10')
# # 从员工访问关联的公司
# print(f"{emp1.name}'s company: {emp1.company.name}")
# # 访问公司关联的所有员工(使用 related_name)
# print(f"Employees at {c1.name}:")
# for emp in c1.employees.all(): # 使用 related_name 'employees'
# print(f"- {emp.name}")
# # 根据公司属性过滤员工(双下划线查找)
# techcorp_employees = Employee.objects.filter(company__name='TechCorp')
# print(f"Query for TechCorp employees: {list(techcorp_employees)}")

重要的 ForeignKey 参数:

  • on_delete: 指定当引用的对象(即 Company)被删除时会发生什么。常见选择:models.CASCADE(也删除员工),models.PROTECT(如果存在员工则阻止删除),models.SET_NULL(将员工的公司字段设为 NULL,需要字段设置 null=True),models.SET_DEFAULT(设置为默认值,需要字段设置 default)。
  • related_name: 从目标模型访问关联对象集合时使用的名称(例如,company_instance.employees.all())。如果省略,Django 将使用 modelname_set(例如,employee_set)。

模型是 Django 开发的基石,为你的数据提供了结构,并提供了强大的交互 API。

延伸阅读: