Django基础教程(二十一)初识 Django QuerySet之修改记录:Django QuerySet整容术:别再用土办法改数据了,老司机带你飞!

嘿,各位Django航海家们!今天咱们不聊虚的,来点实实在在的硬货——如何优雅地(或者说,如何用最Django的方式)修改数据库里的记录。

想象一下这个场景:你老板突然说,“把所有‘待处理’的订单状态都改成‘已发货’,并且给VIP用户的订单额外加10个积分。”你脑子里是不是立马闪现出一个for循环,然后order.status = '已发货',再order.save(),看着屏幕上的进度条慢慢爬?

停!快停下你危险的想法!

这么做,就像是用勺子给游泳池换水,不是不行,是实在太慢了,而且容易把自己累趴下。今天,我就是来给你送“抽水机”的——它就是Django QuerySet的记录修改魔法

第一章:初识我们的武器库——QuerySet

在开始“整容”之前,得先搞清楚我们的手术刀是什么。QuerySet,你可以把它理解为一条懒洋洋的、但威力无穷的数据库查询命令

它有多懒?你光是定义它,比如books = Book.objects.all(),它压根不会去动数据库。直到你真正需要数据的时候(比如打印它、遍历它),它才会不情不愿地去执行查询。

这种“懒惰”是天大的优点,意味着我们可以不断地在它上面叠加各种筛选条件,而不会产生额外的数据库开销。今天,我们聚焦在它的“破坏力”——修改数据。

第二章:闪电战——update()方法,批量修改之王

update()是QuerySet给我们的一把“尚方宝剑”,专为批量修改而生。它的特点就一个字:

怎么用?
简单到令人发指:模型类.objects.filter(筛选条件).update(字段=新值)

完整示例剧场①:老板的紧急任务

假设我们有一个订单模型(Order):

# models.py
class Order(models.Model):
    STATUS_CHOICES = (
        ('pending', '待处理'),
        ('shipped', '已发货'),
        ('cancelled', '已取消'),
    )
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    is_vip = models.BooleanField(default=False)
    points = models.IntegerField(default=0)

# 假设数据库里有一堆状态为‘pending’的订单

任务1:把所有“待处理”的订单状态都改成“已发货”。

新手村的办法(不推荐!):

orders = Order.objects.filter(status='pending')
for order in orders:
    order.status = 'shipped'
    order.save()  # 每循环一次,就执行一条UPDATE SQL语句

如果有一万条订单,就会向数据库发送一万次UPDATE请求。数据库内心是崩溃的。

老司机的做法(推荐!):

# 一行代码,搞定所有!
Order.objects.filter(status='pending').update(status='shipped')

发生了什么?Django只会生成一条SQL语句,类似于:
UPDATE app_order SET status = 'shipped' WHERE status = 'pending';
数据库瞬间完成所有更新,效率提升了N个数量级!

任务2:给所有VIP用户的订单额外增加10个积分。

这也很简单,Django支持在update里对字段进行原地操作

from django.db.models import F

Order.objects.filter(is_vip=True).update(points=F('points') + 10)

这里引入了F对象这个神器。F('points')代表的是数据库中points字段本身的值。F('points') + 10就是在告诉数据库:“嘿,把每个记录的points字段值拿出来,自己加10,再存回去。”
这同样是在一条SQL中完成的,避免了先查询到Python内存,计算后再保存的繁琐过程。

update()的优缺点总结:

  • 优点:极致性能,原子操作(一条SQL,成功就全成功,失败就全失败)。
  • 缺点
    1. 不会调用模型的save()方法。
    2. 因此,save()方法里可能存在的业务逻辑(比如自动更新修改时间auto_now不会被执行
    3. 它也不会触发Django的信号(Signals),比如post_save

所以,当你确定你的修改是纯粹的、直接的字段更新,且不需要触发额外逻辑时,请毫不犹豫地使用update()

第三章:精准手术——save()方法,精细化操作

那如果我们的修改很复杂,需要用到模型的方法,或者必须触发信号呢?这时,save()方法就派上用场了。

怎么用?
就是我们最熟悉的“先获取,再修改,后保存”三部曲。

完整示例剧场②:复杂的用户升级逻辑

假设有个用户模型(UserProfile):

class UserProfile(models.Model):
    username = models.CharField(max_length=100)
    level = models.CharField(max_length=20, default='bronze')
    total_spent = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    def upgrade_level(self):
        """一个复杂的升级逻辑"""
        if self.total_spent > 1000:
            self.level = 'platinum'
        elif self.total_spent > 500:
            self.level = 'gold'
        elif self.total_spent > 100:
            self.level = 'silver'
        # 可能还会发送邮件、通知等...
        self.save()

现在,我们要给某个用户充值,并触发他的升级逻辑。

# 1. 精准获取一个对象
user = UserProfile.objects.get(username='大壮')

# 2. 修改属性
user.total_spent += 888

# 3. 调用save(),这会自动执行升级逻辑,并触发所有信号!
user.save()

# 或者,更清晰地调用自定义方法
user.upgrade_level()

在这个过程中,save()方法被完整执行,模型的auto_now字段会更新,任何监听post_save信号的代码也会被触发。

save()的优缺点总结:

  • 优点:逻辑完整,会触发save()方法和所有信号。
  • 缺点:批量操作时性能极差(N+1查询问题)。
第四章:军团作战——bulk_update(),性能与功能的平衡术

Django 3.2 带来了一个强大的折中方案:bulk_update()。它允许你批量更新多个模型实例,但仍然不触发信号和save()方法

适用场景:你需要更新大量对象,这些对象的新值已经在Python内存中计算好了,但你依然想通过一次或少数几次SQL操作来完成。

完整示例剧场③:批量计算后更新

# 假设我们有一批书,需要根据一个复杂的算法重新计算价格
books = Book.objects.filter(category='programming')

# 在Python中循环计算新价格(这里只是简单示例)
for book in books:
    book.price = calculate_new_price(book)  # 假设的复杂计算函数

# 使用bulk_update,指定要更新的字段
Book.objects.bulk_update(books, ['price'])

这会把所有booksprice更新,通过一条(或少量,取决于数据库限制)SQL语句发送到数据库。它比循环save()快得多,但又比update()更灵活,因为允许你先进行复杂的Python逻辑计算。

bulk_update的优缺点:

  • 优点:在需要复杂预处理的大批量更新中,性能远超循环save()
  • 缺点:和update()一样,不触发save()和信号。
第五章:总结与抉择——你的改数据决策树

好了,手术刀都介绍完了,到底该用哪把?记住下面这个决策树,你就是团队最亮的崽:

  1. 问:要改很多条记录,且修改逻辑能用简单的数据库表达式(比如F对象)描述吗?
    • -> 恭喜!直接用 update(),性能爆炸!
    • -> 进入第2步。
  1. 问:需要触发模型的save()方法、自定义逻辑或Django信号吗?并且只改少数几条记录?
    • -> 老老实实用 save(),逻辑完整最重要。
    • -> 进入第3步。
  1. 问:需要更新大量记录,但这些记录的新值必须先通过复杂的Python逻辑计算出来?
    • -> 这就是 bulk_update() 的完美舞台!
    • -> 回去重新想清楚你的需求!

最后的友情提醒(老司机翻车经历):

  • get()之后不能用update()obj = MyModel.objects.get(id=1) 之后,obj.update(...)是会报错的!update()是QuerySet的方法,不是单个对象的方法。单个对象请用save()
  • 事务是保障:在进行任何重要的批量更新前,考虑用django.db.transaction.atomic()把它包起来,确保要么全部成功,要么全部回滚,避免出现“改了一半”的尴尬局面。

希望这篇充满“网感”的教程,能让你对Django QuerySet的修改操作有全新的认识。别再让数据库在你的循环里哭泣了,拿起这些高效的工具,去享受代码带来的速度和激情吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值