django與django REST framework(DRF)開發筆記
前言
由於規畫上是做前後端分離的開發,django上採RESTful API,對於django本身著墨較少。以RESTful API為主,django本身只會用上admin、model。算是完全放棄templates了
不過django REST framework(後稱DRF)有些開發上仍會使用到部份django原生的功能
其實官方文件寫得相當清楚了!
這裡整理自己開發上遇到的一些坑及要注意的事項和一些使用筆記
可以使用左方的錨點快速定位
django
models.py
在model設定時間欄位,datetime.now不必加刮號。不然會變成model編譯時的時間
from django.db import models
from datetime import datetime
class myUserModel(models.model):
created_time = models.DateTimeFiled(default=datetime.now)
若忘記給default,執行migrate
時也會跳出提示問你要直接加還是回來程式改
settings.py
有自定義的user model,讓django以自定義的user model為主
建立好自己要的model樣式後,在settings.py裡全局指定到該model
AUTH_USER_MODEL = 'users.UserProfile'
# app名.model名
在settings.py
引入路徑,引用時就不必多打根目錄名字
如果有自己建立一個apps讓放所有app的話,可以在settings裡面引入路徑,這樣在使用時就可以少打一些字
import sys
sys.path.insert(0, BASE_DIR)
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
app
在app裡面,取得內建的user model
from django.contrib.auth import get_user_model
User=get_user_model()
若有自定義user model,django會優先去查找settings.py
的AUTH_USER_MODEL
,找不到再取得原生的
django REST framework
建議先照著DRF官方的Tutorial做一次,熟悉一下他的基本操作
會一路從最基本的功能帶到最終的功能:使用viewSet + 內建路由自動產生api路徑,只需使用少少的程式碼,就可以開發出一套RESTful API
而本身又可以自動產生說明文件,不必耗費心力維護文件
最好先熟悉django的CBV後在學習會比較容易上手
genericViewSet vs modelViewSet
modelViewSet預設就先做好所有crud,設好serializer和router後可直接使用
genericViewSet則沒有crud,要自行加入mixin才會有各crud功能
結論:crud全都要的話則使用modelViewSet;要自定義使用哪些crud的話,則使用genericViewSet
使用Router自動產生api路徑
django REST framework本身提供路由自動產生的功能
不必每新增一個功能就要自己寫路徑
在api的APP裡面新增一個urls.py
需配合viewset使用
from django.urls import include,path
from rest_framework.routers import DefaultRouter
from yourAPI.views import userViewSet,todoViewSet
router = DefaultRouter()
router.register('users',userViewSet)
router.register('todolist',todoViewSet)
urlpatterns = [
path('',include(router.urls)),
]
在到django的url.py
新增
path('api/',include('yourAPI.urls')),
Nested Router
預設DRF無此功能,官方推薦使用drf-nested-routers套件
套件官方文件寫得很清楚了!只是一開始沒理解花了不少時間測試QQ
假設以發票中獎期別分別再對應底下所擁有的發票號碼,而後方再輸入primary key時可以得到該發票的明細
可以產生/invoicesLottery/{sn}/myInvoicesList/{pk}/
這種Router
當網址輸入
/invoicesLottery/
→得到所有期別的中獎號碼/invoicesLottery/{sn}
→得到指定期別的中獎號碼明細(頭獎、二獎、三獎號碼…)/invoicesLottery/{sn}/myInvoicesList/
→得到自己當期已儲存的發票清單/invoicesLottery/{sn}/myInvoicesList/{id}/
→得到該發票的明細資料(比如pk、建立時間、消費記錄等)
而對應層級中若各別需指定權限,依各自指定的ViewSet而定
比如在取得第一層的/invoicesLottery/
時是不限定權限,而在取得第二層的/myInvoicesList/
時需驗證權限
那在invoicesLotteryViewSet
中就不必寫permission_classes = (IsAuthenticated,)
;在第二層的myInvoicesListViewSet
寫上permission_classes = (IsAuthenticated,)
作法如下
url.py
from rest_framework_nested import routers
from myAPI import myInvoicesListViewSet, # 取得user儲存自己發票的資料,即第二層路由
invoicesLotteryViewSet # 取得儲存在資料庫的中獎號碼的資料,即第一層路由
router = routers.SimpleRouter()
router.register('invoicesLottery',invoicesLotteryViewSet) # 註冊第一層路由 (跟drf原生用法一樣)
myInvoices_router = routers.NestedSimpleRouter(router,r'invoicesLottery',lookup='pk')
# 第二個參數是指定要掛在哪個路由之後。lookup指定要使用viewset中哪個欄位做查詢對象
myInvoices_router.register('myInvoicesList',myInvoicesListViewSet) # 註冊第二層路由
urlpatterns = [
path('',include(router.urls)),
path('',include(myInvoices_router.urls)), # 建立路徑時,仍需要將第二層的寫進去!
]
views.py
第一層invoicesLotteryViewSet
的寫法可以跟一般ViewSet一樣,不必特意改
而放在第二層的invoicesViewSet
,則需要修改get_queryset
,補上傳入的參數
class invoicesLotteryViewSet(ModelViewSet):
queryset = invoicesLottery.objects.all()
serializer_class = invoicesLotterySerializer
# 不指定權限,不必寫permission_classes
# 屬於第一層路由,也不必修改get_queryset
class myInvoicesListViewSet(ModelViewSet):
queryset = Invoices.objects.all()
serializer_class = InvoicesSerializer
permission_classes = (IsAuthenticated,) # 有權限要求的第二層路由
pagination_class = InvoicesPagination
def get_queryset(self):
return Invoices.objects.filter(Q(owner=self.request.user) & Q(id=self.kwargs['pk']))
# 後者的self.kwargs裡面的pk,是以url中傳入的lookup指定名稱為主。比如lookup="abc",這裡就要寫"abc"
關於serializer 基礎
修改DRF 頁面底下的顯示名稱
在資料欄上加入label="文字"
,可以修改drf的頁面底下的顯示名稱
等同於django model的verbose
serializer 的datetime可以自定義時間格式
# 若不指定,預設為2018-07-02T21:42:53
create_time = DateTimeField(read_only=True,format='%Y-%m-%d %H:%M:%S')
# 指定後,則會變成:2018-07-02 21:42:53
serializers.SerializerMethodField
可以自定義檢查條件
function名稱固定為get_欄位名稱
abc = serializers.SerializerMethodField()
def get_abc(self,obj):
# obj 是serializer對象(自己)
# 一般的charFiled是直接從db取值後轉型態後回傳。
# SerializerMethodField則是運算邏輯自己寫,再回傳計算後的結果
pass
搜尋及過濾 filter, search
需要配合django
原生的filter
功能
在viewset中配置
from django_filters.restframework import DjangoFilterBackend # 要記得import成.restframework的
from rest_framework import filters
class GoodsListViewSet():
queryset = Goods.objects.all()
serializer_class = GoodsSerializer
filter_backends = (DjangoFilterBackend,filters.searchFilter,filtersOrderingFielter)
search_fields= ('name','birth',help_text="搜尋")
ording_fields=('id','created_date')
分頁 Pagination
from rest_framework.pagination import PageNumberPagination
class productsPagination(PageNumberPagination):
page_size = 10 # 每頁最大筆數
page_size_query_param = 'page_size'
page_query_param="p" # 修改使用的參數名稱,預設是page
max_page_size = 100 # 最多回傳筆數
接著再要使用此calss做分頁條件的viewset中
新增如下
pagination_class = productsPagination
Pagination 增加總頁數(或自定義回傳的資料)
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
class InvoicesPagination(PageNumberPagination):
page_size = 10 # 每頁最大筆數
max_page_size = 100 # 最多回傳筆數
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'total_pages': self.page.paginator.num_pages,
'results': data
})
自動產生說明文件
在urls.py
裡
from rest_framework.documentation import include_docs_urls
#在url配置
path(r'docs/', include_docs_urls(title="只是個打字的")),
在model或serializer上,寫help_text
,文檔裡會自動新增至description裡
※文件的資訊是從serializer來,若跳過此步自定義api的話,無法自動產生
對於需要權限的api,左下方的Authentication可以測試登入
限制API訪問速率(避免爬蟲過量爬取)
在settings.py
裡新增
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day'
}
}
AnonRateThrottle:暱名用戶,用ip來判
UserRateThrottle:登入用戶,用session來判斷
超過限制後,會回傳http 429錯誤
修改API傳入參數
預設DRF是以Primary Key為主,若想要以其他欄位當參數的話
在viewset裡輸入
lookup_field='mobile'
傳入參數就會由/api/users/{id}
變成/api/users/{mobile}
嘍!
POST資料,獲得當下USER
寫在serializer裡
#user即model的user變數
user = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
serializer呼叫serializer時,將圖片網址補上域名
serializer預設是不會自動加上域名
平常使用時,都會自動傳入。所以不必另外寫
要在補上參數context={'request': self.context['request']}
原理:原始碼在context有看到request時,就會自動將域名補上
cache
使用套件drf-extensions
,有強化許多功能!具體細節參考官方文件
from rest_framework_extensions.cache.mixins import CacheResponseMixin
在viewset繼承CacheResponseMixin
繼承cache,盡量放在第一個
signals
apps.py
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
verbose_name = "用戶管理"
# 加ready後,import signals
def ready(self):
import users.signals
新建signals.py,裡面寫所有signals
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
User = get_user_model()
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
if created:
password = instance.password
instance.set_password(password) # password儲存加密後的密文至userModel
instance.save()
動態調整permission_classes、serializer_class
假設有一個使用情況:
UserViewSet在get與post時有權限的分別
create(post)建立帳號時權限是AllowAny
retrieve(get)取得個人資料時是IsAuthenticated
class UserViewset(CreateModelMixin, mixins.UpdateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
authentication_classes = (JSONWebTokenAuthentication, authentication.SessionAuthentication )
def get_permissions(self):
if self.action == "retrieve":
return [permissions.IsAuthenticated()]
elif self.action == "create":
return []
return []
def get_serializer_class(self):
if self.action == "retrieve":
return UserDetailSerializer
elif self.action == "create":
return UserRegSerializer
return UserDetailSerializer