從今天開始的幾天會著重在Django REST framework(DRF)的介紹,關於DRF的簡介可以看
Django REST framework: 讓Django great again
介紹一下我們在DRF的篇章會做哪些介紹:
今天的程式碼:https://github.com/class83108/drf_demo/tree/serializer
而今天的重點如下:
流程基本上跟之前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
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(工作區)的欄位解釋:
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吧
在Django REST framework: 讓Django great again中我們有稍微提到序列化器就是將特定語言內的資料類型轉換成可以傳輸的通用格式,在我們開發API的過程中第一步就是定義序列化器,在DRF中序列化器定義了Model實例對象中哪些欄位需要做序列化或是反序列化
跟Django Form類似,DRF的serializer下具備Serializer
和ModelSerializer
,前者可以自定義需要序列化的欄位,後者則是根據你的model所產生,那就開始進行操作吧
# 需要先在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的區別,如果要看完整資訊可以參考底下參考資料的官方文檔
models.fields.CharField
與models.fields.TextField
,可以設置allow_blank
讓空字符串是可行的,官方有提到不建議使用allow_null
,因為兩者代表不同的空值,前者是空字串而後者是Noneread_only=True
,表示只讀屬性。如果是多對多時,可以加入many=True,並且可以透過設置queryset
來過濾只想返回的資料。有read_only=True
就不能設置queryset
同時我們也定義了POST(建立對象)與PUT(更新對象)時的序列器應該如何操作
validated_data
instance
,則是進行PUT或是PATCH請求更新時,因為已經先取得要更新的對象,這些對象就是instance
,傳遞給序列化器進行更新validated_data
取出,然後用剩餘資料先建立Workspace對象,最後再透過set方法
設置member欄位我們用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。Representational State Transfer (REST)
是一種架構,而依循REST架構風格的API就可以被稱為RESTful API
從上圖可以看到,非常簡略的來說,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
有幾個功能:
serializer.data
是python的字典或是包含字典的列表,而使用Response
則可以將其序列化,轉成JSON,並附上狀態碼status
來讓HTTP響應更加的直覺,有多種表示方法,詳情可以看官方文檔
workspace_list
而GET、PUT與DELETE是workspace_detail
,可以簡單來說:
/workspaces/
)/workspaces/
),我們是對一個工作區的集合進行操作,而不是針對單一工作去。可以想像成我們要把一本書放到書架裡/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"),
]
最後我們來使用內建的介面來進行API測試
首先直接在瀏覽器輸入對應的路由,就可以看到相對應的資料,不論是請求方法,響應狀態碼與對應的資料,甚至包含部分Header的資料都可以看到
我們也能透過輸入底下的表單來完成POST請求,當然內建的這個很不好輸入這也是真的,可以在其他地方打好再貼上,或是乾脆直接使用postman
[
{
"name": "demo post 1",
"owner": 2,
"members": [2]
},
{
"name": "demo post 2",
"owner": 2,
"members": [2]
}
]
我們今天從Django來到了Django Rest framework的世界,並且建立了我們第一支Restful API,並且成功的完成了GET與POST請求
雖然今天使用了DRF成功建立出API了,但是會不會覺得整體的程式碼雖然簡潔,並且在資料形式轉換很方便,但是整體的程式碼量好像沒有少太多,而且還需要不斷的判斷請求的方法
沒關係,DRF提供了(Class Based View, CBV)來幫助我們用更少的程式碼來達成目標!