本文首发于微信公众号:Hunter后端

原文链接:Django笔记十八之save函数的继承操作和指定字段更新等实例方法

这篇笔记主要介绍 Django 一些实例方法。

什么是 实例,我们知道通过filter() 的一些筛选方法,得到的是 QuerySet,而 QuerySet 取单条数据,通过索引,或者 first() 或者 last() 等方法,得到的单条数据,就是一个 model 的实例。

我们接下来要介绍的就是这种单条实例的一些方法。

  1. save() 的继承操作
  2. refresh from db, 从数据库中更新实例数据
  3. 自增的主键
  4. 指定字段更新 save()

1、save() 的继承操作

对于一个 model,我们可以通过 save() 的方式创建一条数据,比如:

from blog.models import Blog

blog = Blog(name="blog_1", tagline="tagline_1")
blog.save()

对于上面的 blog,我们就称其为 Blog 的一个实例。

我们可以通过继承覆盖原有的 save() 方法,然后新增一些我们需要的操作,比如打印日志,发送提醒等。

方法如下:

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()


    def save(self, *args, **kwargs):
        print("save")
        super(Blog, self).save(*args, **kwargs)

这样,我们在对 Blog 数据进行 save() 操作的时候,就可以看到控制台会输出 "save" 的记录。

除此之外,Django 的文档提出了一种方式,在 model 中定义一个类方法,可以方便我们对数据进行处理:

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()


    @classmethod
    def create(cls, name, tagline):
        blog = cls(name=name, tagline=tagline)
        print("get an unsaved Blog instance")
        return blog

然后通过调用该方法,传入参数,可以得到一个未保存的实例:

from blog.models import Blog
blog = Blog.create(name='test_create', tagline='test_tagline')
blog.save()

注意: 在我们执行 create() 方法的时候,程序还没有操作数据库,只是得到一个未保存的实例,我们仍然需要执行 save() 操作才能保存到数据库。

除了这种方法,还有一种官方文档更推荐的方法,就是使用 manager,这个的用法我们在后面几篇笔记中涉及,这里只做一个展示:

class BlogManager(models.Manager):
    def create_blog(self, name, tagline):
        blog = self.create(name=name, tagline=tagline)
        # do something with the blog
        print("get an unsaved Blog instance")
        return blog


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()


    objects = BlogManager()

需要注意的是,这里调用的是 create() 方法,所以直接保存到了数据库,不用再执行 save() 方法了。

2、refresh from db, 从数据库中更新实例数据

方法为 refresh_from_db()

作用为从数据库中获取实例数据的最新值。

blog = Blog.objects.first()

# 其他地方可能会对 blog 数据进行一些更改

# 然后从数据库中拉取 blog 的最新数据

blog.refresh_from_db()

这个操作我个人常常用在写单元测试,比如经过一系列操作之后,想要查看这个 obj 的数据有没有更改,这种情况下就可以使用这个函数。

说一下 refresh_from_db() 这个函数的性能问题,refresh_from_db() 的底层函数也是使用的 get() 方法

所以使用 refresh_from_db() 和 get(pk=xx) 这两者在性能上可能差别不会很大,但是 refresh_from_db() 则更为简洁。

3、自增的主键

如果我们没有为 model 设置 PrimaryKey,那么系统则会自动为 model 设置自增主键为 id 的字段,创建数据的时候,不指定该字段,系统会自动为其赋值。

而当我们想要复制一条数据记录的时候,我们可以将 id 字段置为 None,然后 save(),系统则会将其视为一条新数据,从而自动保存为新数据并为 id 字段赋值。

b = Blog.objects.first()
b.id = None
b.save()
b.id

4、指定字段更新 save()

假设有一个 TestModel,有一个 number 字段,我们想要对其执行 +1 的操作,大概行为可能如下:

obj = TestModel.objects.get(id=1)
obj.number += 1
obj.save()

我们也可以通过 F() 函数这种稍微快一点和避免竞争的方式(竞争的意思是,其他的进程可能也在使用这条数据):

from django.db.models import F
obj = TestModel.objects.get(id=1)
obj.number = F('number') + 1
obj.save()

指定字段保存
单纯的使用 save() 操作可能会造成一个问题,比如说,我们在某一个 get 了一条数据,对 name 字段进行了更改,但同时另一个进程对同一条数据也进行了更改,我们对这条数据进行 save() 操作,那么就可能造成数据不一致的情况。

blog = Blog.objects.get(id=1)
blog.name = "test_1"

# 在这个期间,另一个进程对 tagline 字段进行了更改
# 假设该操作为 Blog.objects.filter(id=1).update(tagline="new_tagline")

# 然后执行 save() 操作
blog.save()

那么这个时候,blog 的数据因为已经从数据库中获取了出来,再执行 save() 则会保存之前获取的数据,这样会导致在此期间对 tagline 字段进行的更新操作还原。

那么这个时候,为了避免这种情况发生,我们在 save() 的时候指定我们要更新的字段来保存数据:

blog.name = "test_1"
blog.save(update_fields=["name"])

以上就是本篇笔记全部内容,下一篇笔记将介绍 manager 的用法。

如果想获取更多相关文章,可扫码关注阅读:

image