Django筆記十八之save函式的繼承操作和指定欄位更新等例項方法

XHunter發表於2023-04-06

本文首發於微信公眾號: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

相關文章