下载工作台
Python 数据库实战

Django ORM 对照速通

试读上半部分 · 解锁后可读全文

第 12 章 · Django ORM 对照速通

本章目标:在不强制安装 Django 的前提下,通过 独立可读示例 理解 Django ORM 的 ModelQuerySetmigrateF()/Q();与 SQLAlchemy 2.0(ch09~ch10)逐项对照;明确 shop_db 字段 slug + is_published + price(分) 在 Django 中的等价写法;衔接 django-web ch05/ch07db-demo

学时建议:4~5 小时(含 2 小时对照表与练习题)

前置:完成 python-database ch07~ch11;可选阅读 django-web ch05/ch07(非必须)。


12.1 场景说明:同一 shop_db,三种访问方式

                    shop_db (SQLite / MySQL)
                           │
         ┌─────────────────┼─────────────────┐
         ▼                 ▼                 ▼
   sqlite3 ch08      SQLAlchemy ch10    Django ORM
   (DB-API)          (Session/ORM)      (QuerySet)
路线项目典型用途
数据库专精db-demo本章对照
Web 全栈shop-demo(django-web)后台 + DRF
API 服务svc-demo(fastapi-web)SQLAlchemy async
独立示例:本章代码块标注 # django-style 处,可复制到 shop-demo 验证;未装 Django 的学员以对照表 + 伪代码理解即可,不影响 db-demo 进度。

12.2 Django Model 基础(独立示例)

catalog/models.py(django-style,与 shop_db 对齐):

# django-style — 需 Django 环境;只读学员可跳过运行
from django.db import models


class Category(models.Model):
    name = models.CharField("分类名", max_length=64)
    slug = models.SlugField(max_length=64, unique=True)
    sort_order = models.IntegerField(default=0)

    class Meta:
        db_table = "categories"          # 与 db-demo DDL 表名一致
        ordering = ["sort_order", "id"]

    def __str__(self):
        return self.name


class Product(models.Model):
    category = models.ForeignKey(
        Category,
        on_delete=models.PROTECT,
        related_name="products",
    )
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=64, unique=True)
    description = models.TextField(blank=True)
    # Django 常用 Decimal 存「元」;与 db-demo「分」二选一,见 §12.3
    price_cents = models.PositiveIntegerField("单价(分)", default=0)
    stock = models.PositiveIntegerField(default=0)
    is_published = models.BooleanField("是否上架", default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "products"
        indexes = [
            models.Index(fields=["category", "is_published", "-created_at"]),
        ]

    @property
    def price_yuan(self) -> str:
        return f"¥{self.price_cents / 100:.2f}"
Django 字段db-demo / SQLAlchemy说明
SlugFieldslug: Mapped[str]URL 标识
BooleanFieldis_published: Mapped[bool]上架开关
PositiveIntegerFieldprice: Mapped[int]
ForeignKeyrelationship + FK多对一
Meta.db_table__tablename__显式表名

12.3 价格:分 vs 元(全栈约定)

方案DjangoSQLAlchemy优劣
整数分(db-demo 标准)PositiveIntegerFieldMapped[int]无浮点误差,支付友好
小数元(django-web 早期)DecimalField(10,2)Numeric(10,2)直观,需 Decimal 运算

django-web ch07 提示:Web 模块 Product 常用 DecimalField 存元;python-databaseshop-db 毕业项目 统一 。对照换算:

# django-style
from decimal import Decimal
yuan = Decimal("59.90")
cents = int(yuan * 100)   # 5990

# SQLAlchemy / db-demo
product.price = 5990

12.4 migrate vs Alembic

步骤DjangoAlembic(ch11)
检测模型变更makemigrationsrevision --autogenerate
应用migrateupgrade head
回滚migrate app zerodowngrade -1
版本记录django_migrationsalembic_version
脚本位置app/migrations/alembic/versions/
# django-style
python manage.py makemigrations catalog --name evolve_product_fields
python manage.py migrate

django-web ch07 字段演进(与 ch07 对照表呼应):

旧字段新字段迁移要点
skuslugRenameField
is_activeis_publishedRenameField
descriptionAddField
updated_atAddField

Django 迁移片段(独立阅读):

# django-style migrations/0002_evolve_product_fields.py
from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [("catalog", "0001_initial")]

    operations = [
        migrations.RenameField(model_name="product", old_name="sku", new_name="slug"),
        migrations.RenameField(model_name="product", old_name="is_active", new_name="is_published"),
        migrations.AddField(model_name="product", name="description", field=models.TextField(blank=True)),
        migrations.AddField(model_name="product", name="updated_at", field=models.DateTimeField(auto_now=True)),
    ]

Alembic 等价见 ch11 §11.7


12.5 QuerySet 基础

Django ORM 查询入口:Model.objects 返回 QuerySet(惰性,链式)。

# django-style
# 全表(慎用)
Product.objects.all()

# 过滤 — 等价 SQL WHERE
Product.objects.filter(is_published=True)
Product.objects.filter(category__slug="digital", is_published=True)

# 排除
Product.objects.exclude(stock=0)

# 排序
Product.objects.filter(is_published=True).order_by("-created_at")
Product.objects.filter(is_published=True).order_by("price_cents")

# 切片分页 — LIMIT/OFFSET
Product.objects.filter(is_published=True)[0:20]

# 单条
Product.objects.get(slug="wireless-mouse")      # 不存在 → DoesNotExist
Product.objects.filter(slug="x").first()          # 不存在 → None

# 计数
Product.objects.filter(is_published=True).count()

# 值列表 / 字典
Product.objects.filter(is_published=True).values("slug", "price_cents")
Product.objects.filter(is_published=True).values_list("slug", flat=True)

SQLAlchemy 对照

# db-demo / SQLAlchemy 2.0
from sqlalchemy import select

stmt = (
    select(Product)
    .where(Product.is_published.is_(True))
    .where(Product.category.has(Category.slug == "digital"))
    .order_by(Product.price.asc())
    .limit(20)
)
products = session.scalars(stmt).all()

12.6 F() 与 Q() 表达式

12.6.1 F() — 引用字段值

# django-style
from django.db.models import F

# 原子库存递减(django-web ch05)
Product.objects.filter(pk=1).update(stock=F("stock") - 1)

# 同表字段比较
Product.objects.filter(stock__lte=F("price_cents"))  # 教学举例

# Order 总额 annotate
from django.db.models import Sum
Order.objects.annotate(
    computed_total=Sum(F("lines__quantity") * F("lines__unit_price"))
)
F() 用途SQL 层效果
update(stock=F("stock")-1)SET stock = stock - 1
避免读-改-写竞态单条 UPDATE 原子

SQLAlchemy 对照

from sqlalchemy import update

stmt = (
    update(Product)
    .where(Product.id == 1, Product.stock >= 1)
    .values(stock=Product.stock - 1)
)
session.execute(stmt)

12.6.2 Q() — 复杂逻辑组合

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。