iT邦幫忙

2024 iThome 鐵人賽

DAY 13
0
Software Development

Django 2024: 從入門到SaaS實戰系列 第 15

Django REST framework: 序列化器與視圖函式 開啟API之旅

  • 分享至 

  • xImage
  •  

從今天開始的幾天會著重在Django Rest framework(DRF)的介紹,關於DRF的簡介可以看

Django REST framework: 讓Django great again

介紹一下我們在DRF的篇章會做哪些介紹:

  • DRF的安裝與配置
  • 序列化器與視圖函式的介紹
  • DRF的視圖類
  • DRF序列化器的進階技巧
  • DRF的認證與權限設計
  • DRF的流量限制與分類(filter)

今天的程式碼:https://github.com/class83108/drf_demo/tree/serializer

而今天的重點如下:

  • 前置作業
  • 編寫我們的第一支API
    • 序列化器(serializers)
    • Restful API是什麼
    • 建立視圖
    • 建立路由
    • 使用DRF內建介面進行API測試

前置作業

  • 安裝與配置settings.py

流程基本上跟之前Django in 2024: 總是得從這裡開始,Hello world!第一個Django專案 沒什麼區別

只是需要額外安裝DRF的套件,這邊我就用指令帶過

# 用poetry建立新專案
poetry new django-rest-framework

# 安裝相關套件
poetry add django==4.2 load-dotenv psycopg2-binary djangorestframework

# 進入poetry shell
poetry shell

# 啟動Django專案
django-admin startproject drf_demo

# 設定settings.py 參考
https://github.com/class83108/drf_demo/blob/serializer/drf_demo/drf_demo/settings.py
  • 建立app、model與匯入資料

建立app

python3 manage.py startapp note

接著設定model

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Workspace(models.Model):
    name = models.CharField(max_length=100)
    owner = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="owned_workspaces"
    )
    members = models.ManyToManyField(User, related_name="workspaces")
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

class Document(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    workspace = models.ForeignKey(
        Workspace, on_delete=models.CASCADE, related_name="documents"
    )
    created_by = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="created_documents"
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

Workspace(工作區)的欄位解釋:

  • owner:代表這個工作區的擁有者,用戶資料是連接到User,而User的取得方式就透過get_user_model拿到系統默認的User model,詳情可以參考Django in 2024: 沒有第三方登入怎麼行!django-allauth登場
  • member:表示有哪些人是隸屬於這個工作區的,owner就一定會是其中一員,至於每個member對於整個工作區有哪些權限這裡先不設置

Document(文檔)就先對單純,每個文檔都會隸屬於一個工作區

可以把這兩個表格想像成你今天有一個雲端空間(工作區),然後裡面有許多不同的檔案(文檔)

然後你可以開放不同人來編輯或是只讀權限你內部的文檔,但是工作區的隸屬者還是你

最後進行遷移

python3 manage.py makemigrations
python3 manage.py migrate

我們一樣拿寫好的模型請AI幫我們生成資料,腳本放在跟manage.py同級,腳本的程式碼在下方連結:https://github.com/class83108/drf_demo/blob/serializer/drf_demo/init_data.py

唯一要注意的就是我們希望工作區與文檔目前都是屬於同一個User,方便我們之後Demo權限設置

python3 manage.py shell < init_data.py

# 輸出
Sample data created successfully!
Users: <QuerySet [<User: alice>, <User: bob>]>
Workspace: <QuerySet [<Workspace: DRF Project>]>
Documents: <QuerySet [<Document: DRF Serializers>, <Document: DRF Views>, <Document: DRF Permissions>]>

既然都有資料了,就來準備開始撰寫第一支API吧

編寫我們的第一支API

序列化器(serializers)

Django REST framework: 讓Django great again中我們有稍微提到序列化器就是將特定語言內的資料類型轉換成可以傳輸的通用格式,在我們開發API的過程中第一步就是定義序列化器,在DRF中序列化器定義了Model實例對象中哪些欄位需要做序列化或是反序列化

跟Django Form類似,DRF的serializer下具備SerializerModelSerializer ,前者可以自定義需要序列化的欄位,後者則是根據你的model所產生,那就開始進行操作吧

  • Serializer
# 需要先在app下建立serializers.py

class WorkspaceSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(max_length=100)
    owner = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
    members = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all())
    created_at = serializers.DateTimeField(read_only=True)

    def create(self, validated_data):
        members = validated_data.pop('members', [])
        workspace = Workspace.objects.create(**validated_data)
        workspace.members.set(members)
        return workspace

    def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        instance.owner = validated_data.get('owner', instance.owner)
        
        members = validated_data.get('members')
        if members is not None:
            instance.members.set(members)
        
        instance.save()
        return instance

這邊來比對一下serializers.field跟django model.field的區別,如果要看完整資訊可以參考底下參考資料的官方文檔

  • CharField:相當於models.fields.CharFieldmodels.fields.TextField ,可以設置allow_blank讓空字符串是可行的,官方有提到不建議使用allow_null,因為兩者代表不同的空值,前者是空字串而後者是None
  • PrimaryKeyRelatedField:使用在外鍵,通常搭配read_only=True,表示只讀屬性。如果是多對多時,可以加入many=True,並且可以透過設置queryset來過濾只想返回的資料。有read_only=True 就不能設置queryset

同時我們也定義了POST(建立對象)與PUT(更新對象)時的序列器應該如何操作

  • 當客戶端使用POST或是PUT請求時內部的數據經過序列化器進行驗證後並且通過後,將數據儲存在validated_data
  • 而update方法中的instance,則是進行PUT或是PATCH請求更新時,因為已經先取得要更新的對象,這些對象就是instance,傳遞給序列化器進行更新
  • create時,因為我們的member是多對多關係,所以將他從validated_data 取出,然後用剩餘資料先建立Workspace對象,最後再透過set方法設置member欄位
  • set的原理是會將現有關係先刪除,然後再添加新的指定關係,不用額外調用save方法,讓程式碼非常簡潔
  • upade時則是根據有什麼資料就進行更新,最後再save。而多對多也是使用set方法進行更新

我們用Document當作範例,展示一下ModelSerializer

根據我們的模型來產生要序列化的欄位,也不用我們再一一設定

class DocumentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Document
        fields = ['id', 'title', 'content', 'workspace', 'created_by', 'created_at', 'updated_at']
        read_only_fields = ['id', 'created_at', 'updated_at']

    def create(self, validated_data):
        return Document.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.title = validated_data.get('title', instance.title)
        instance.content = validated_data.get('content', instance.content)
        instance.workspace = validated_data.get('workspace', instance.workspace)
        instance.save()
        return instance

Restful API是什麼

在建立視圖之前,我們必須要先說明什麼是Restful APIRepresentational State Transfer (REST) 是一種架構,而依循REST架構風格的API就可以被稱為RESTful API

https://ithelp.ithome.com.tw/upload/images/20240925/201618661ZWjyd5TeT.png

從上圖可以看到,非常簡略的來說,Restful API中請求需要包含幾樣元素,首先是請求端點(統一資源識別符URI)讓伺服器知道想要指定的哪個位置的資源,以及想要操作的資源目標,並且附上想要操作的方法,而返回值則需要付上常見的狀態碼,來讓客戶端知道請求的結果如何,並且根據請求方法決定是否需要附上相對應的資料

建立視圖

接著我們來寫視圖,今天會先展示基於函式的視圖(Functional Based View, FBV)

# note.views.py

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Workspace
from .serializers import WorkspaceSerializer

@api_view(["GET", "POST"])
def workspace_list(request):
    if request.method == "GET":
        workspaces = Workspace.objects.all()
        serializer = WorkspaceSerializer(workspaces, many=True)
        return Response(serializer.data)

    elif request.method == "POST":
        serializer = WorkspaceSerializer(data=request.data, many=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        
@api_view(["GET", "PUT", "DELETE"])
def workspace_detail(request, pk):
    try:
        workspace = Workspace.objects.get(pk=pk)
    except Workspace.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        serializer = WorkspaceSerializer(workspace)
        return Response(serializer.data)

    elif request.method == "PUT":
        serializer = WorkspaceSerializer(workspace, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    elif request.method == "DELETE":
        workspace.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
  • @api_view 有幾個功能:
    • 將普通的視圖轉換為 Django REST framework 的 API 視圖
    • 指定視圖可以響應的 HTTP 方法
  • serializer.data是python的字典或是包含字典的列表,而使用Response則可以將其序列化,轉成JSON,並附上狀態碼
  • status來讓HTTP響應更加的直覺,有多種表示方法,詳情可以看官方文檔
  • 可以注意到,我們在使用GET跟POST是使用workspace_listGET、PUT與DELETEworkspace_detail ,可以簡單來說:
    • 當想查看所有工作區(GET /workspaces/
    • 當想要添加工作區到工作區的集合(POST /workspaces/),我們是對一個工作區的集合進行操作,而不是針對單一工作去。可以想像成我們要把一本書放到書架裡
    • 當想要看到或是更新特定的工作區(GET 或 PUT /workspaces/<id>/

這樣的設計才更符合Restful API,而不是

https://api.example.com/workspaces/{id}/edit/ # 編輯工作區
https://api.example.com/workspaces/{id}/delete/ # 刪除工作區

建立路由

# 根目錄urls.py

from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("note/", include(("note.urls", "note"), namespace="note")),
]

# 建立note.urls.py

from django.urls import path
from .views import workspace_list, workspace_detail

urlpatterns = [
    path("workspaces/", workspace_list, name="workspace-list"),
    path("workspaces/<int:pk>/", workspace_detail, name="workspace-detail"),
]

使用DRF內建介面進行API測試

最後我們來使用內建的介面來進行API測試

首先直接在瀏覽器輸入對應的路由,就可以看到相對應的資料,不論是請求方法,響應狀態碼與對應的資料,甚至包含部分Header的資料都可以看到

https://ithelp.ithome.com.tw/upload/images/20240925/201618666S4AXxDBtW.png

我們也能透過輸入底下的表單來完成POST請求,當然內建的這個很不好輸入這也是真的,可以在其他地方打好再貼上,或是乾脆直接使用postman

[
  {
    "name": "demo post 1",
    "owner": 2,
    "members": [2]
  },
  {
    "name": "demo post 2",
    "owner": 2,
    "members": [2]
  }
]

https://ithelp.ithome.com.tw/upload/images/20240925/20161866HCecur0q8Y.png

今日總結

我們今天從Django來到了Django Rest framework的世界,並且建立了我們第一支Restful API,並且成功的完成了GET與POST請求

  • 了解序列化器在DRF中的作用,除了快速建立我們想要響應的資料欄位,也幫助我們更快的進行資料形式的轉換
  • 認識到什麼叫做Restful API
  • 透過DRF的裝飾器來將普通視圖函式轉換成Django REST framework的API 視圖,並且依據傳過來的資料來決定返回的狀態碼
  • 透過官方內建的UI介面來進行我們首支API的操作

雖然今天使用了DRF成功建立出API了,但是會不會覺得整體的程式碼雖然簡潔,並且在資料形式轉換很方便,但是整體的程式碼量好像沒有少太多,而且還需要不斷的判斷請求的方法

沒關係,DRF提供了(Class Based View, CBV)來幫助我們用更少的程式碼來達成目標!

參考資料

  • drf field:https://www.django-rest-framework.org/api-guide/fields/
  • status code:https://www.django-rest-framework.org/api-guide/status-codes/#status-codes

上一篇
Django REST framework: 讓Django great again
下一篇
Django REST framework: 深入探討視圖類之前,不可不知道的序列化器原理
系列文
Django 2024: 從入門到SaaS實戰16
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言