Django - 模型序列化返回天然主鍵值

場景

在設計表結構時,不免須要創建一些外鍵關聯。例如這樣兩個模型:前端

from django.db import models

class Person(models.Model):
    username = models.CharField(max_length=100)
    birthdate = models.DateField()

class Book(models.Model):
    name = models.CharField(max_length=100)
    author = models.ForeignKey(Person, on_delete=models.CASCADE)
複製代碼

Book 的字段 author 是表 Person 的外鍵,咱們試用 Django 原生的 Serializer 模塊來對 Book 實例序列化:python

from django.core import serializers
book_json = serializers.serialize("json", Book.objects.get(pk=1))
複製代碼

JSON 序列化結果以下:django

{
    "pk": 1,
    "model": "store.book",
    "fields": {
        "name": "Mostly Harmless",
        "author": 42
    }
}
複製代碼

這個 "author": 42 對用戶來講至關於未知,咱們須要的是 Person 表中主鍵爲 42 的用戶姓名,即 username 的值。json

解決方案

在 Django 官方文檔的「序列化」一節中提到了用 models.Manager 處理的方案;在搜索解決方案過程當中,也接觸到 Django-REST-Framework(DRF) ,瞭解到 DRF 中的 Serializer 模塊也能解決這類問題。那咱們不妨對比一下兩種解決方案。後端

方案一:models.Manager

根據文檔,要返回天然主鍵,咱們須要定義一個模型管理器,建立一個 get_by_natural_key 方法,以下:api

from django.db import models

class PersonManager(models.Manager):
    def get_by_natural_key(self, username):
        return self.get(username=username)

class Person(models.Model):
    username = models.CharField(max_length=100)
    birthdate = models.DateField()
    objects = PersonManager()
複製代碼

而後再次序列化 Book 實例:less

from django.core import serializers
book_json = serializers.serialize("json", Book.objects.get(pk=1), use_natural_foreign_keys=True)
複製代碼

獲得新的結果以下:ide

{
    "pk": 1,
    "model": "store.book",
    "fields": {
        "name": "Mostly Harmless",
        "author": ["DouglasAdams"]
    }
}
複製代碼

若是須要對其餘應用的數據模型作修改,例如使用了 django.auth.User(默認認證後端)做爲 Book 的外鍵,要想不修改 User 模型又使用新的模型管理器,可使用代理模式完成:學習

from django.db import models

class NewManager(models.Manager):
    # ...
    pass

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True
複製代碼

總的來講,這個方案能夠完美解決我所遇到的問題,代碼量稍微大一些,可是也更靈活。ui

方案二:DRF 的 Serializer

下面咱們試試用 Django-REST-Framework 的序列化模塊:

from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    author_name = serializers.CharField(source='author.username')

    class Meta:
        model = Book
        fields = '__all__'
複製代碼

這段代碼表示,在序列化 Book 實例時,添加一個新的屬性 author_name,該值的來源爲 source 參數定義的外鍵 author 實例的天然主鍵 username

而後是執行序列化的過程:

queryset = Book.objects.get(pk=1)
BookSerializer(instance=queryset)
複製代碼

序列化結果:

{
    "id": 1,
    "name": "Mostly Harmless",
    "author": 42,
    "author_name": "DouglasAdams"
}
複製代碼

固然,序列化一批 Book 實例也是能夠的:

queryset = Book.objects.all()
BookSerializer(instance=queryset, many=True)
複製代碼

序列化結果:

[
    {
        "id": 1,
        "name": "Mostly Harmless",
        "author": 42,
        "author_name": "DouglasAdams"
    },
    {
        "id": 2,
        "name": "Harry Potter",
        "author": 2,
        "author_name": "JKRowling"
    }
]
複製代碼

能夠看到,使用 DRF 的序列化模塊返回天然主鍵,不只代碼清晰改動少,並且效果也很不錯,序列化數據少了一個層級,對前端也是十分友好的。

方案三:手動修改序列化後的外鍵

固然,還有一種最傻也是最容易想到的辦法,就是在序列化後,手動修改 JSON 串中對應的外鍵值爲天然主鍵值。

這種作法能夠獲得和方案一同樣的效果,可是遇到查詢結果爲列表時咱們須要遍歷替換。同時試想一下,若是咱們在每一個視圖中都這麼處理,那代碼會變得十分糟糕。不建議使用該方案。

總結

對比兩種序列化方案,我我的更偏向於 DRF 優雅的處理方式。固然,除了序列化,DRF 還有不少功能,例如分頁等,強烈建議學習學習。

固然,可能不存在最好的最好的技術方案,遇到這類問題選擇最合適本身的就好。也可能還有更多的方法能夠解決標題的問題,也歡迎留言探討!

參考

相關文章
相關標籤/搜索