# Django Rest FrameWork基礎筆記
[](https://hackmd.io/Gx1LYIs3Tk2VFwyoizxYfQ)
 
## 認證(EX:用戶登入):【先做認證,在做權限】
### 一、使用:
- 創建類:
- 繼承BaseAuthentication,並實現authenticate方法
- 返回值(三種):
- None:
- 自己不處理,交給下認證來處理
- 拋出異常:
```python
from rest_framework import exceptions
raise exceptions.AuthenticationFailed('用戶驗證失敗')
```
- (元素1, 元素2):
- 元素1賦值給request.user;元素2賦值給request.auth
- 局部使用(在view中加入靜態字段authentication_classes):
```python
class OrderView(APIView):
authentication_classes = [MyAuthentication, ]
```
- 全局使用(加到settings.py):
```python
REST_FRAMEWORK = {
# 全局使用認證類(若想讓view不使用全局認證類,則在其中加入authentication_classes=[])
# 通常不會寫到views中,而是會再另外創一個py檔(EX:auth.py)
"DEFAULT_AUTHENTICATION_CLASSES": ('api.utils.auth.MyAuthentication', )
}
```
### 二、源碼流程:
- 從view中的dispatch()進入
- 封裝到request
- 獲取所有定義的認證類(全局/局部定義的),在通過列表生成式(列表推導)創建了各個認證類之對象
- initial
- perform_authentication
- request.user (在內部循環了認證類的所有對象)
### 三、內置類:
```python
class BaseAuthentication(object):
# 具體做認證操作的方法
def authenticate(self, request):
raise NotImplementedError(".authenticate() must be overridden.")
# 當認證失敗時,給瀏覽器返回的響應頭
def authenticate_header(self, request):
pass
```
### 四、用戶類別:
- 遊客(Anonymous user):代表校驗通過,直接進入下一步校驗(權限校驗)
- 合法用戶:代表校驗通過,將用戶存儲在request.user中,再進入下一步校驗(權限校驗)
- 非法用戶:代表校驗失敗,拋出異常,返回403權限異常結果
 
## 權限(EX:用戶等級權限):【先做認證,在做權限】
### 一、使用:不同視圖不同權限可以訪問
- 創建類:
- 繼承BasePermission,並實現has_permission方法
- 返回值(兩種):
- True:有權限
- False:無權限
- 局部使用(在view中加入靜態字段permission_classes):
```python
class OrderView(APIView):
permission_classes = [OtherPermission, ]
```
- 全局使用(加到settings.py):
```python
REST_FRAMEWORK = {
# 全局使用認證類(若想讓view不使用全局認證類,則在其中加入authentication_classes=[])
"DEFAULT_PERMISSION_CLASSES": ('api.utils.permission.SVIPPermission',)
}
```
### 二、源碼流程:
- 從view中的dispatch()進入
- initial
- check_permissions
- for permission in self.get_permissions(): (循環了權限類的所有對象)
### 三、內置類:
```python
class BasePermission(object):
# 具體做權限操作的方法
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
return True
```
 
## 節流(EX:訪問頻率限制):
### 一、使用:
- 創建類:
- 繼承BaseThrottle,並實現allow_request方法
- (優)繼承SimpleRateThrottle,並實現scope屬性(setting.py中的key)和get_cache_key方法
- 返回值(兩種):
- True:可繼續訪問
- False:訪問頻率太高,不可繼續訪問
- 局部使用(在view中加入靜態字段throttle_classes):
```python
class OrderView(APIView):
throttle_classes = [VisitThrottle, ]
```
- 全局使用(加到settings.py):
```python
REST_FRAMEWORK = {
# 通常不會寫到views中,而是會再另外創一個py檔(EX:throttle.py)
'DEFAULT_THROTTLE_CLASSES': ('api.utils.throttle.VisitThrottle',),
# 使用內置的訪問節流器(SimpleRateThrottle),需要設置訪問的頻率
'DEFAULT_THROTTLE_RATES': {
'ip': '3/m', # ip為自定義字段,3/m表示一分鐘三次
'username': '10/m',
}, # 若繼承SimpleRateThrottle的話就必須設置
}
```
### 二、源碼流程:
- 從view中的dispatch()進入
- initial
- check_throttles
- for throttle in self.get_throttles() (循環了節流器類的所有對象)
### 三、內置類:
```python
# SimpleRateThrottle的歷史紀錄是存在Django中的內置緩存裡
class VisitThrottle(SimpleRateThrottle): # 只要實現這樣就能得到一個訪問節流器了
# 作為key,到時要到settings.py裡取我們設定的頻率
scope = 'ip'
# get_cache_key表示訪問紀錄的key,這要我們自己覆寫
def get_cache_key(self, request, view):
# 使用用戶IP當作key
return self.get_ident(request) # 父類的get_ident(request)方法可以直接取IP
```
 
## 版本:
### 一、方式:
- 在URL中透過get參數傳參。
- URL:
- http://127.0.0.1:8000/api/users/?version=v1
- 類:
- 使用DRF內置提供的QueryParameterVersioning
- 配置文件(settings.py):
```python
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默認版本號
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本號
'VERSION_PARAM': 'version', # 在get請求中版本號的key(EX: ?version)
}
```
- 路由設置(urls.py):
```python
urlpatterns = [
path('users/', views.UsersView.as_view()),
]
```
- (推薦)在URL的路徑中傳參。
- URL:
- http://127.0.0.1:8000/api/v1/users/
- 類:
- 使用DRF內置提供的URLPathVersioning
- 配置文件(settings.py):
```python
REST_FRAMEWORK = {
'DEFAULT_VERSION': 'v1', # 默認版本號
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本號
'VERSION_PARAM': 'version', # 在get請求中版本號的key(EX: ?version)
}
```
- 路由設置(urls.py):
```python
urlpatterns = [
# re_path:要使用正則表達式時使用
re_path(r'^(?P<version>[v1|v2]+)/users/$', views.UsersView.as_view()),
]
```
### 二、使用:
- 透過版本類中的reverse方法反向生成出當前url
- url = request.versioning_scheme.reverse(viewname='uuu', request=request)
- 創建類:
- 不需要自定義,內建的很夠用了。
- 局部使用(在view中加入靜態字段versioning_class):
```python
class UsersView(APIView):
versioning_class = URLPathVersioning
```
- 全局使用(加到settings.py):
```python
REST_FRAMEWORK = {
# 全局使用版本號
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
'DEFAULT_VERSION': 'v1', # 默認版本號
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本號
'VERSION_PARAM': 'version', # 在get請求中版本號的key
}
```
### 三、源碼流程:
- 從view中的dispatch()進入
- initial
- determine_version(返回版本號和所使用的版本類)
 
## 解析器:【對請求體數據進行解析】
### 一、前戲(Django的request.POST和request.BODY):
- request.POST中會有值的條件(值為去request.body中解析出來的數據):
1. 請求頭中的Content-Type須為 application/x-www-form-urlencoded。
2. 請求體中的數據格式須為:
- 範例:name=jamie&age=18&height=179
- 什麼場合會有值:
- form表單提交(默認情況下)
- ajax提交(默認情況下)
- 什麼場合不會有值:
- 解決方法
- 使用解析器
- 範例:
```javascript
$.ajax({
url: ... ,
type: 'POST',
headers: {'Content-Type': 'application/json'}, # 但請求頭不符合條件
data: {'name': 'jamie', 'age': 18}, # 內部會自動轉換為name=jamie&age=18(符合條件)
})
```
- 範例二:
```javascript
$.ajax({
url: ... ,
type: 'POST',
data: JSON.stringify({'name': 'jamie', 'age': 18}), # 數據格式不符合條件
})
```
### 二、流程:
1. 獲取用戶請求
2. 獲取用戶請求體
3. 獲取用戶請求頭並和parser_classes進行比對,選擇要用的解析器
4. 解析器對請求體進行解析
5. 數據封裝到request.data
### 三、使用:
- 創建類:
- 不需要自定義,內建的很夠用了
- 局部使用(在view中加入靜態字段parser_classes):
```python
class ParserView(APIView):
parser_classes = [JSONParser, FormParser, MultiPartParser]
```
- 全局使用(加到settings.py):
```python
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
),
}
```
### 四、源碼流程:
- 從view中的dispatch()進入
- 封裝到request
- 獲取所有定義的解析器類(全局/局部定義的),在通過列表生成式(列表推導)創建了各個解析器類之對象
 
## 序列化:
### 一、序列化類:
1. 繼承內置類:
- 方法一(繼承serializers.Serializer):
```python
class UsersSerializer(serializers.Serializer):
# 字段名必須跟數據庫一致,除非有加source參數
# 一般字段
id = serializers.IntegerField()
username = serializers.CharField()
password = serializers.CharField()
# choices字段
user_type_num = serializers.CharField(source='user_type') # source表示數據庫的某個字段
# get_字段名_display: 他會先取出字段名對應的數據,但如果是可被執行的話,就會繼續找下去
user_type = serializers.CharField(source='get_user_type_display') # 取出來的是VIP、SIP等
# 一對多字段
groups = serializers.CharField(source='group.title') # 取出外鍵對象.title字段
# 多對多字段
rls = serializers.SerializerMethodField() # 自定義顯示
def get_rls(self, obj): # 自定義顯示的方法,名稱為get_字段名
roles_obj_list = obj.roles.all()
ret = []
for item in roles_obj_list:
ret.append({'id': item.id, 'title': item.title})
return ret # 要顯示的數據
```
- (推薦)方法二(繼承serializers.ModelSerializer):
```python
class UsersSerializer(serializers.ModelSerializer):
# choices字段
# get_字段名_display: 他會先取出字段名對應的數據,但如果是可被執行的話,就會繼續找下去
type = serializers.CharField(source='get_user_type_display') # 取出來的是VIP、SIP等
# 一對多字段
groups = serializers.CharField(source='group.title')
# 多對多字段
rls = serializers.SerializerMethodField() # 自定義顯示
def get_rls(self, obj): # 自定義顯示的方法,名稱為get_字段名
roles_obj_list = obj.roles.all()
ret = []
for item in roles_obj_list:
ret.append({'id': item.id, 'title': item.title})
return ret # 要顯示的數據
class Meta:
model = models.UserInfo # 關聯的表
# fields = '__all__'
# 只能用於簡單的字段
fields = ['id', 'username', 'password', 'type', 'groups', 'rls'] # 要顯示的字段
```
- (推薦)方法三(繼承serializers.ModelSerializer):
```python
class UsersSerializer(serializers.ModelSerializer):
type = serializers.CharField(source='get_user_type_display')
class Meta:
model = models.UserInfo # 關聯的表
fields = ['id', 'username', 'password', 'type', 'group', 'roles']
# 透過設置深度可以很輕鬆的獲取關聯表的所有數據。(建議範圍1~10)
# 但無法取到choices字段
depth = 1 # 深度,若不想要關聯表詳細數據可以設為0
```
- 方法四(透過生成鏈結進行深度查詢):
```python
class UsersSerializer(serializers.ModelSerializer):
# lookup_field為要去深度查的外鍵字段
# lookup_url_kwarg為url中我們設置的變量名
# view_name為url的別名
gp_url = serializers.HyperlinkedIdentityField(lookup_field='group_id', lookup_url_kwarg='fk', view_name='gp')
class Meta:
model = models.UserInfo # 關聯的表
fields = ['id', 'username', 'password', 'gp_url'] ```
2. 字段:
- get_字段名_display:
```python
user_type = serializers.CharField(source='get_user_type_display') # 他會先取出字段名對應的數據,但如果是可被執行的話,就會繼續找下去
```
- 要取外鍵屬性之字段:
```python
groups = serializers.CharField(source='group.title') # 可透過外鍵字段.字段名
```
- 自定義顯示字段:
```python
rls = serializers.SerializerMethodField() # 自定義顯示
def get_rls(self, obj): # 自定義顯示的方法,名稱為get_字段名
roles_obj_list = obj.roles.all()
ret = []
for item in roles_obj_list:
ret.append({'id': item.id, 'title': item.title})
return ret # 回傳要顯示的數據
```
- 生成深度查找鏈結:
```python
# lookup_field為要去深度查的外鍵字段id
# lookup_url_kwarg為url中我們設置的變量名
# view_name為url的別名
gp_url = serializers.HyperlinkedIdentityField(lookup_field='group_id', lookup_url_kwarg='fk', view_name='gp')
```
### 二、源碼流程:
- 實例化:
- 對象給Serializer類處理;Queryset給ListSerializer類處理。
- to_representation
- 封裝到ser.data中
### 三、作驗證規則使用:
```python
# 方法一:validators設置自定義驗證規則
class Myvalidator(object):
def __init__(self, x):
self.x = x
# 驗證規則
def __call__(self, value):
if not value.startswith(self.x):
message = 'title開頭必須為 %s' % self.x
raise serializers.ValidationError(message)
class VerifySerializer(serializers.Serializer):
# error_messages為錯誤信息
# 方法一:validators設置自定義驗證規則
title = serializers.CharField(
error_messages={'required': 'title不能為空'}, validators=[Myvalidator('jamie')])
# (推薦)方法二:鉤子函數設置自定義驗證規則
# def validate_字段名(self, data),data為request.data.get(字段名)
def validate_title(self, data):
from rest_framework import exceptions
if not data.endswith('老男人'):
raise exceptions.ValidationError('title結尾必須為老男人')
return data # 驗證成功回傳data
class VerifyView(APIView):
def post(self, request, *args, **kwargs):
# request.data 為獲取經過解析器解析的數據
ser = VerifySerializer(data=request.data)
# 驗證
if ser.is_valid():
print(ser.validated_data)
else:
print(ser.errors)
return HttpResponse('提交數據')
```
 
## 分頁:
### 一、分頁內置類:
1. 繼承或直接使用PageNumberPagination:
- 概念:
- 看到第n頁,每頁顯示m條數據。
- 代碼:
```python
class MyPageNumberPagination(PageNumberPagination):
# 每頁數據量
page_size = 3
# 使用get傳參(key=page)來獲取分頁
page_query_param = 'page'
# 用戶自定義page_size,可以透過get傳參(key=size),來獲取更多的數據量
page_size_query_param = 'size'
# 每頁數據量上限
max_page_size = 10
class RolesView(APIView):
def get(self, request, *args, **kwargs):
# 獲取所有數據
roles = models.Role.objects.all()
# 創建分頁對象
pg = MyPageNumberPagination()
# 獲取分頁大小的數據
page_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
# 序列化
ser = RolesSerializer(instance=page_roles, many=True) # many=True表取出多條數據
# 使用DRF分頁提供的渲染器,功能更強(幫我們生成了上下一頁)
return pg.get_paginated_response(data=ser.data)
```
2. 繼承或直接使用LimitOffsetPagination:
- 概念:
- 在第n個位置,向後顯示m條數據。
- 代碼:
```python
class MyLimitOffsetPagination(LimitOffsetPagination):
# 每次取幾條數據
default_limit = 3
# 用戶自定義limit,可以透過get傳參(key=limit),來獲取更多的數據量
limit_query_param = 'limit' # 默認也叫limit
# 用戶自定義offset,可以透過get傳參(key=offset),來設定初始位置
offset_query_param = 'offset' # 默認也叫offset,offset:從第幾個位置後開始取
# 最大獲取量
max_limit = 10
```
3. 繼承或直接使用CursorPagination:
- 概念:
- 加密頁碼分頁,只能透過上下一頁進行切換,但當頁數多的話,此方法不會變慢,效率較好。
- 代碼:
```python
class MyCursorPagination(CursorPagination):
# 加密頁碼參數(不需要改,用默認的就好
# cursor_query_param = 'cursor'
# 每頁大小
page_size = 3
# 排序規則(-id表示照倒敘的id排)
ordering = '-id'
# 用戶自定義page_size,可以透過get傳參(key=size),來獲取更多的數據量
page_size_query_param = 'size'
# 每頁數據量上限
max_page_size = 10
```
 
## 視圖:
1. 演變:
- 過去:
- (1) class MyView(View)
- (2) class MyView(APIView)
- 繼承:APIView繼承View
- 代碼:
```python
class RolesView(APIView):
def get(self, request, *args, **kwargs):
# 獲取所有數據
roles = models.Role.objects.all()
# 創建分頁對象
pg = MyCursorPagination()
# 獲取分頁大小的數據
page_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
# 序列化
ser = RolesSerializer(instance=page_roles, many=True) # many=True表取出多條數據
# 使用DRF分頁提供的渲染器,功能更強(幫我們生成了上下一頁)
return pg.get_paginated_response(data=ser.data)
```
- 無用(因為跟過去的沒差多少):
- (3) class MyView(GenericAPIView)
- 繼承:GenericAPIView繼承APIView
- 代碼:
```python
from rest_framework.generics import GenericAPIView
class RolesView(GenericAPIView):
queryset = models.Role.objects.all()
serializer_class = RolesSerializer
pagination_class = MyPageNumberPagination
def get(self, request, *args, **kwargs):
# 獲取數據
roles = self.get_queryset()
# 分頁
page_roles = self.paginate_queryset(roles)
# 序列化
ser = self.get_serializer(instance=page_roles, many=True)
return Response(ser.data)
```
- 詳解:
- GenericAPIView是繼承於APIView,而我們可以根據需求多繼承mixins的ListModelMixin, CreateModelMixin等類
- URL傳遞過程中,只需調用APIView的as_view()方法即可,然後URL就會根據請求調用對應HTTP方法(EX: get, post, put, delete,...)
- 基本代碼:
```python
class SnippetList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
# 不過須定義初始queryset和serializer_class
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer # 指定視圖使用的序列化器
# 不需在get, post方法中去ORM調用以及序列化調用(因為都封裝到了list, create等方法,這些方法在mixins中)
def get(self, request, *args, **kwargs):
return self.list(*args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(*args, **kwargs)
```
- ORM調用是透過GenericAPIView下的get_queryset()方法使用
- 序列化對象則透過GenericAPIView下的方法:
- get_serializer_class():
- 返回序列化器類,默認返回serializer_class
- 也可通過覆寫get_serializer_class()方法來應對不同情況下,使用不同序列器類
- 覆寫範例:
```python
def get_serializer_class(self):
if self.request.user.is_staff:
return FullAccountSerializer
return BasicAccountSerializer
```
- get_serializer_context():
- 為Serializer對象封裝一個key為context的鍵值對
- 該方法在提供序列化器對象的時,會向序列化器對象的context屬性補充三個數據:request、format、view,這三個數據對象可以在定義序列化器時使用
- 源碼:
```python
def get_serializer_context(self):
return {
'request': self.request,
'format': self.format_kwarg,
'view': self }
```
- get_serializer():
- 裡面調用了get_serializer_context()和get_serializer_class()方法
- 返回序列化器對象,主要用來提供給Mixin擴展類使用,如果我們在視圖中想要獲取序列化器對象,也可以直接調用此方法。
特點整理:
- 定義類視圖使用的序列化器和查詢集(屬性);
- queryset: 列表視圖的查詢集(queryset = xxx)
- serializer_class: 視圖使用的序列化器(serializer_class = sss)
- 分頁和過濾的控制(屬性);
- pagination_class: 分頁控制類
- filter_backends: 過濾控制後端
- 配合get_obiect()使用的(屬性);
- lookup_field: 查詢單一數據庫對象時使用的條件字段,默認為'pk'
- lookup_url_kwarg: 查詢單一數據時URL中的參數關鍵字名稱,默認與look_field相同
- 查詢集的獲取/更改:
- get_queryset(self): 默認返回queryset屬性,可以重寫成自己需要的查詢集;
- 獲取序列化器對象:
- get_serializer(): 默認返回的是系列化器對象;
注意,在提供序列化器對象的時候,REST framework會向對象的context屬性補充三個數據:request、format、view,這三個數據對象可以在定義序列化器時使用,也就是說在序列化器類中,可以通過self.context['request'],獲取對應的請求對象
- context屬性:
- get_serializer_context(self):
這個是給序列化器返回的一個context屬性,context屬性裡面有'request','format','view'值可以在序列化器類中使用。與5中的描述相對應
- 獲取序列化器;
- get_serializer_class(self):獲取需要使用的序列化器,根據需求可以重寫獲取不同的序列化器;
- 獲取對應的模型類對象:
- get_object(self): 默認使用lookup_field參數來過濾queryset。
若詳情訪問的模型類對像不存在,會返回404。
該方法會默認使用APIView提供的check_object_permissions方法檢查當前對像是否有權限被訪問。
- 有用(當想大量自訂製查詢時能使用):
- (4) class MyView(GenericViewSet)
- 繼承: GenericViewSet(ViewSetMixin, generics.GenericAPIView)
- 路由:
```python
# GenericViewSet要額外設置路由映射
re_path(r'^(?P<version>[v1|v2]+)/roles/$', views.RolesView.as_view({'get': 'list', 'post': 'create'}))
```
- 代碼:
```python
from rest_framework.viewsets import GenericViewSet
class RolesView(GenericViewSet):
queryset = models.Role.objects.all()
serializer_class = RolesSerializer
pagination_class = MyPageNumberPagination
# 自定義名稱的get方法
def list(self, request, *args, **kwargs):
# 獲取數據
roles = self.get_queryset()
# 分頁
page_roles = self.paginate_queryset(roles)
# 序列化
ser = self.get_serializer(instance=page_roles, many=True)
return Response(ser.data)
# 自定義名稱的post方法
def create(self, request, *args, **kwargs):
pass
```
- 現在(若不需大量自定義可以使用、開發快速):
- (5) class MyView(ModelViewSet)
- 繼承:
- ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet)
- 路由:
※ 要設置兩種網址,需要ID的和不需要的
※ 對應名稱只能這麼寫(因為繼承裡面的方法名是這樣寫)
```python
re_path(r'^(?P<version>[v1|v2]+)/roles/$', views.RolesView.as_view({
'get': 'list', # 數據列表
'post': 'create', # 創建
})),
re_path(r'^(?P<version>[v1|v2]+)/roles/(?P<pk>\d+)/$', views.RolesView.as_view({
'get': 'retrieve', # 獲取單條數據
'delete': 'destroy', # 刪除
'put': 'update', # 更新
'patch': 'partial_update', # 局部更新
})),
```
- 代碼:
```python
from rest_framework.viewsets import ModelViewSet
class RolesView(ModelViewSet):
"""
mixins.CreateModelMixin, => 添加
mixins.RetrieveModelMixin, => 查詢單條數據(要id)
mixins.UpdateModelMixin, => 更新(要id)
mixins.DestroyModelMixin, => 刪除(要id)
mixins.ListModelMixin, => 數據列表
"""
queryset = models.Role.objects.all()
serializer_class = RolesSerializer
pagination_class = MyPageNumberPagination
```
 
## 路由:
1. 演變:
- 過去:
- 最開始
```python
re_path(r'^(?P<version>[v1|v2]+)/roles/$', views.RolesView.as_view()),
```
- 細化函數分配
```python
re_path(r'^(?P<version>[v1|v2]+)/roles/$', views.RolesView.as_view({
# 也可以用這樣呈現(http://127.0.0.1:8000/api/v1/roles/?format=json)
'get': 'list', # 數據列表
'post': 'create', # 創建
})),
```
- 可以選定什麼格式呈現
```python
re_path(r'^(?P<version>[v1|v2]+)/roles\.(?P<format>\w+)/$', views.RolesView.as_view({
# 可以選定什麼格式呈現(EX: http://127.0.0.1:8000/api/v1/roles.json/)
'get': 'list', # 數據列表
'post': 'create', # 創建
})),
```
- 現在:
- 等同於幫我們生成了以上(過去)的所有URL,但如果只想設置增刪等局部的話,還是建議用過去的方式生成。
```python
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'roles', viewset=views.RolesView)
urlpatterns = [
# 使用DRF路由器幫我們生成URL
# 使用這個就等於幫我們生成了以上全部URL
re_path(r'(?P<version>[v1|v2]+)/', include(router.urls))
]
```
 
## 過濾器:pip install django-filter
1. 演變:
- 使用DRF自定義過濾器類(低階):
```python
class RolesView(ModelViewSet):
...
filter_backends = (LimitFilter,)
class LimitFilter(BaseFilterBackend):
# 必須實現filter_queryset()方法,並return一個queryset
def filter_queryset(self, request, queryset, view):
lim = request.query_params.get('lim', None)
if lim:
return queryset[:int(lim)]
return queryset
```
- 使用django-filter自定義高級過濾器:
```python
class RolesView(ModelViewSet):
...
filter_backends = (DjangoFilterBackend,)
filter_class = RoleFilter
class RoleFilter(filters.FilterSet):
# 自定義查詢字段
# field_name為要查的字段名,lookup_expr為查找時使用的表達式,label為前端顯示的名字,required=True為必填欄位
# 透過 d_g=&title_c=
id_g = filters.NumberFilter(field_name="id", lookup_expr='gte', label="大於id")
title_c = filters.CharFilter(field_name="title", lookup_expr='icontains')
class Meta:
model = models.Role # 模型名
# 透過 title__icontains=&id__gte=&id__lte=
fields = {
'title': ['icontains'], # title字段可使用icontains包含查找
'id': ['gte', 'lte'], # id字段可使用gte大於等於和lte小於等於查找
}
```
- 環境(若要使用高級的過濾器必須這樣):
```python
INSTALLED_APPS = [
...,
'django_filters', # pip install django-filter
]
```
 
## 實用技巧:
- 動態選擇使用序列器字段:
- 透過get_serializer傳參數時,添加額外參數,並在序列器的init方法中處理
```python
class RankMNView(generics.ListAPIView):
def list(self, request, *args, **kwargs):
...
# 此fields參數是自定義的,為了讓在序列化時能自定義字段
serializer = self.get_serializer(data,
many=True,
fields=('Stno', 'ObsTime', field, 'Year', 'Month', 'Day', 'TenDays'))
class RankMNSerializer(serializers.ModelSerializer):
...
def __init__(self, *args, **kwargs):
# 「fields」中在「self.fields」中不存在的字段將無法被序列化,也就是說「fields」中的字段須定義為「self.fields」的子集
# 在super(父類init)執行之前,需先將「fields」參數從kwargs取出並剔除,不然傳給父類init會報錯(因為接收到了drf本身未定義的參數)
fields = kwargs.pop('fields', None)
super(RankMNSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# 從「self.fields」中剔除非「fields」中指定的字段
# 原始self.fields中會包括的字段有資料表的欄位和在此RankMNSerializer中定義的字段(EX: Year)
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed: # 先取差集,在剔除差集內的字段
self.fields.pop(field_name)
```
- 自定義字段:
- 可自定義資料表中沒有的字段欄位,這裡產生的字段也可以與(一)技巧共用
```python
class RankMNSerializer(serializers.ModelSerializer):
# 自定義額外字段
Year = serializers.SerializerMethodField()
Month = serializers.SerializerMethodField()
def get_Year(self, obj): # DRF會自動調用get_<字段名>之方法去獲取字段值
# 此obj是我們view下的list方法傳給Serializer的值,所以是{紀錄字典}形式
return obj.get('Year', None)
def get_Month(self, obj):
return obj.get('Month', None)
class Meta:
model = models.Mn
fields = '__all__'
```
```python
class ProcessRuleSerializer(serializers.ModelSerializer):
archive_path = serializers.SerializerMethodField()
class Meta:
model = models.ProcessRule # 關聯的表
fields = [
'process_rule_sn',
'process_rule_name',
'archive_path',
]
# 此obj是使用Django ORM產生的
def get_archive_path(self, obj):
return '{}/{}'.format(obj.volume_path, obj.archive_path_template)
```
- 自定義返回的資料集:
- 通過覆寫list()方法
```python
class RankMNSerializer(serializers.ModelSerializer):
...
def list(self, request, *args, **kwargs):
# 篩選出想要的資料集
queryset = ...
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
```
- 動態選擇不同序列器:
- 覆寫get_serializer_class()方法
```python
class RankMNSerializer(serializers.ModelSerializer):
...
def get_serializer_class(self):
if self.request.user.is_staff:
return FullAccountSerializer
return BasicAccountSerializer
```
- 序列器之間可互相嵌套:
- 當然這個字段(shop)的格式也必須符合嵌套之序列器(BookshopsSerializer)
- 此例的shop是一個外鍵,對應到Bookshops表
```python
class BookshopsSerializer(serializers.ModelSerializer):
# 更改顯示之字段名,因為前端工程師可能會要求字段規格
shop_id = serializers.IntegerField(source='id')
shop_name = serializers.CharField(source='title')
class Meta:
model = models.Bookshops # 關聯的表
fields = ['shop_id', 'shop_name']
class BooksSerializer(serializers.ModelSerializer):
...
shop = BookshopsSerializer(many=False) # 嵌套另一個Serializer
class Meta:
model = models.Books
fields = ['book_id', 'book_name', 'shop']
```
- 序列器顯示指定關聯欄位字段
```python
class ReceiveRuleSerializer(serializers.ModelSerializer):
upstream_user = serializers.SlugRelatedField(
many=False,
read_only=True,
slug_field='user_name' # 選定關聯對象所要顯示的字段
)
class Meta:
model = models.ReceiveRule # 關聯的表
fields = ['receive_rule_sn', 'receive_rule_name', 'upstream_user', 'name_regex', 'description_text', 'create_at', 'modify_at']
```
- Views類屬性使用:
```python
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.pagination import LimitOffsetPagination
class ProductListView(generics.ListAPIView):
# 必填屬性
queryset = Product.objects.all()
serializer_class = ProductListSerializer
# 可選屬性
permissin_classes = (permissions.AllowAny,) # AllowAny誰都可以訪問,DRF默認也是這個權限
ordering_fields = ('category', 'manufacturer', 'created', 'sold',) # 提供給用戶可以排序的字段
search_fields = ('description', 'model') # 提供給用戶可以查詢的字段
filter_backends = (OrderingFilter, SearchFilter,) # 須加上這行,才能讓用戶進行排序和搜索
ordering = ('-id',) # 照id倒敘排序(默認)
pagination_class = LimitOffsetPagination
```
- 使用路由器最好要設basename:
```python
# 要設basename,因為如果沒設basename,路由器會默認使用裡面定義的queryset當作名稱,
# 所以當沒設basename且有多個ViewSet的queryset相同時,路由給的url可能就會錯誤(會重複)
router.register(r'Rank/yr', Rank.views.RankYrViewSet, basename='r_yr')
router.register(r'Rank/mn', Rank.views.RankMnViewSet, basename='r_mn')
router.register(r'Rank/dy', Rank.views.RankDyViewSet, basename='r_dy')
router.register(r'Rank/tenday', Rank.views.RankTenDayViewSet, basename='r_tenday')
```
###### tags: `網頁` `API` `Django`