drf的request請求
這裡的request請求是基於APIView的,也就是新的request
正常情況下,request請求分為:urlcoded、json、form-data,可以控制只接受哪一個請求
匯入模組
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
模組 | 描述 | 請求 |
---|---|---|
JSONParser | 用於解析 JSON 請求內容。request.data 將被填充為一個資料字典。 |
application/json |
FormParser | 用於解析 HTML 表單內容。request.data 將被填充為一個資料 QueryDict。通常與 MultiPartParser 一起使用以完全支援 HTML 表單資料。 |
application/x-www-form-urlencoded |
MultiPartParser | 用於解析多部分 HTML 表單內容,支援檔案上傳。request.data 和 request.FILES 將分別被填充為一個 QueryDict 和 MultiValueDict。通常與 FormParser 一起使用以完全支援 HTML 表單資料。 |
multipart/form-data |
1)區域性使用 檢視層中 放在檢視類屬性中,不要放在方法中
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
class HeroView(APIView):
# 放在檢視類中 列表裡面就是允許的請求
parser_classes = [JSONParser]
def get(self, request):
pass
def post(self, request):
pass
2) 全域性使用 settings裡面設定
這個檔案在哪裡,在rest_framework.settings.py裡面,自己扣出來該就行了。
後續drf要配置的東西,全部都放到這裡即可
- 如果三個全部都使用(預設就是)
- 如果要限制某個使用,區域性匯入就行
- 區域性優先順序高於全域性,也就是說,如果全域性註釋了 JSONParser, 區域性允許了 JSONParser 那麼還是會允許JSONParser的,其他一樣的道理。
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
],
}
關於request.data檔案物件
- 能正常接受檔案物件
- 不建議,透過request.data取檔案物件,正常建議從request.FILES中去取檔案資料。
- 為什麼?
- 規範
def post(self, request):
serializer = HeroSerializer(data=request.data)
from_data = request.data
from_files = request.FILES
print("從requet.data中", from_data)
print("從request.FILES中", from_files)
"""
從requet.data中 <QueryDict: {'age': ['4'], 'name': ['大喬'], 'addr': ['召喚師峽谷'], 'myfile': [<InMemoryUploadedFile: BB外來鍵關係.png (image/png)>]}>
從request.FILES中 <MultiValueDict: {'myfile': [<InMemoryUploadedFile: BB外來鍵關係.png (image/png)>]}>
"""
drf的response響應
# Response的原始碼
class Response(SimpleTemplateResponse):
def __init__(self, data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
data 返回到響應體中
data的型別可以是:
- Python字典(
dict
)- 列表(
list
)- 序列化後的資料物件
- 查詢集(
QuerySet
)的序列化結果- 自定義的序列化結果
status 返回HTTP狀態碼
匯入模組
from rest_framework import status
使用
# ...
from rest_framework import status
class HeroView(APIView):
def post(self, request):
# ...
return Response(data={"code": 201, "msg": "建立成功!"}, status=status.HTTP_201_CREATED)
template_name (瞭解)
在DRF中,template_name
引數是用於指定檢視返回的HTML模板的名稱。它通常用於基於類的檢視中的APIView
或ViewSet
,用於指定渲染HTML響應時要使用的模板檔案。
例如,如果你有一個基於類的檢視,你可以像這樣設定template_name
引數:
from rest_framework.views import APIView
from rest_framework.response import Response
class MyAPIView(APIView):
template_name = 'my_template.html'
def get(self, request):
# 一些邏輯處理
return Response({'key': 'value'})
在這個例子中,當MyAPIView
處理GET請求時,它會渲染名為my_template.html
的模板,並將資料作為上下文傳遞給模板。
headers 響應頭 可以自己定製
def get(self, request):
# ...
headers = {"name": "Ava", "age": 3}
# 這是一個沒有意義的響應頭,只是做演示而已
return Response({"code": 100, "msg": "成功", "results": serializer.data}, headers=headers)
exception 異常 需要設定為布林值 (瞭解)
exception
引數是用於指示響應是否代表一個異常情況的布林值。預設情況下,它是False
,表示響應不是一個異常。當設定為True
時,它表示響應代表一個異常情況。
在實際使用中,如果你的檢視處理了一個異常,並且你想要返回一個異常響應,你可以建立一個Response
物件,並將exception
引數設定為True
。這樣,客戶端在接收到這個響應時就會知道這是一個異常情況。
例如,在檢視中處理一個異常並返回一個異常響應的示例:
from rest_framework.response import Response
def my_view(request):
try:
# 這裡是你的檢視邏輯
result = do_something()
return Response(result)
except Exception as e:
# 如果發生異常,返回一個異常響應
return Response(status=500, exception=True)
在這個示例中,如果do_something()
函式丟擲了一個異常,檢視將返回一個帶有500狀態碼的異常響應。透過將exception
引數設定為True
,客戶端就會知道這是一個異常情況,而不是正常的成功響應。
舉例說明
# ...
class HeroView(APIView):
# ...
def post(self, request):
try:
int("a")
if serializer.is_valid():
serializer.save()
return Response(data={"code": 201, "msg": "建立成功!"}, status=status.HTTP_201_CREATED)
except Exception as e:
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True, headers={"Error": e})
-----------
響應格式
drf介面會根據客戶端型別返回對應的格式,瀏覽器返回HTML格式,其他客戶端返回json格式
注意:如果是返回的json格式,一般是建議指定 JSONOpenAPIRenderer 這樣體驗會好一些。
匯入模組
from rest_framework.renderers import JSONOpenAPIRenderer, BrowsableAPIRenderer
# JSONOpenAPIRenderer 用於按照 OpenAPI 規範將響應內容呈現為 JSON 格式。
# BrowsableAPIRenderer 用於將響應內容呈現為 HTML,用於可瀏覽的 API,提供了一個使用者友好的介面,方便與 API 進行互動。
區域性使用 檢視層的類屬性裡面 不要放在方法裡面
from rest_framework.renderers import JSONOpenAPIRenderer, BrowsableAPIRenderer
class HeroView(APIView):
# 放在類屬性中,透過 render_classes = [型別指定即可]
renderer_classes = [JSONOpenAPIRenderer]
def get(self, request):
..
# 其他請求...
全域性使用 settings裡面設定
- 如果不做任何設定,預設是根據客戶端關係返回
- 如果指定,那麼瀏覽器和非瀏覽器都會按照指定格式(比如讓瀏覽器返回json格式,而不是自帶的drf api)
- 區域性權重高於全域性,也就是全域性設定了返回JSONRenderer,區域性設定了TemplateHTMLRenderer,一樣按TemplateHTMLRenderer去返回
- 一般區域性設定即可
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
]
}
2個檢視基類 APIView和GenericAPIView
匯入模組
from rest_framework.generics import GenericAPIView
from rest_framework.views import APIView
基於APIView + Response + 序列化類 寫介面
class HeroDetailView(APIView):
def get(self, request, pk):
hero_instance = Hero.objects.get(pk=pk)
serializer = HeroSerializer(instance=hero_instance)
return Response({"code": 200, "msg": "ok", "result": serializer.data})
def put(self, request, pk):
hero_instance = Hero.objects.get(pk=pk)
serializer = HeroSerializer(instance=hero_instance, data=request.data)
if serializer.is_valid():
serializer.save()
return Response({"code": 200, "msg": "ok", "result": serializer.data})
else:
return Response({"code": 100, "msg": serializer.errors})
def delete(self, request, pk):
instance = get_object_or_404(Hero, pk=pk)
instance.delete()
return Response({"code": 100, "msg": "刪除成功!"}, status=status.HTTP_204_NO_CONTENT)
基於GenericAPIView + Response + 序列化類 寫介面
- 拿類屬性透過方法去獲取,而不要透過之前的方法。
- 如果要寫其他模型的5個介面(有關聯關係),所有程式碼不變,只需要改兩個類屬性即可
屬性
# 查詢所有物件
queryset = Hero.objects.all()
# 獲取序列化類
serializer_class = HeroSerializer
方法
# 獲取全部資料
hero_instance = self.get_queryset()
# 獲取單個物件
hero_instance = self.queryset.filter(pk=pk).first() # 很low 不建議
hero_instance = self.get_object() # 推薦的寫法
# 使用序列化類
serializer = self.get_serializer(instance=hero_instance, many=True)
GenericAPIView 的前端介面長這樣
# 基於GenericAPIView去寫
class HeroDetailView(GenericAPIView):
# 兩個類屬性
queryset = Hero.objects.all() # 全部
serializer_class = HeroSerializer # 序列化類
def get(self, request, pk):
# hero_instance = self.queryset.filter(pk=pk).first() 不要這樣寫
hero_instance = self.get_object()
serializer = self.get_serializer(instance=hero_instance)
return Response({"code": 200, "msg": "ok", "result": serializer.data})
def put(self, request, pk):
hero_instance = Hero.objects.get(pk=pk)
serializer = HeroSerializer(instance=hero_instance, data=request.data)
if serializer.is_valid():
serializer.save()
return Response({"code": 200, "msg": "ok", "result": serializer.data})
else:
return Response({"code": 100, "msg": serializer.errors})
def delete(self, request, pk):
self.get_object().delete()
有什麼問題?程式碼重複
# 有部分重複的程式碼,應該可以嘗試繼續最佳化掉
hero_instance = self.get_object()
serializer = self.get_serializer(instance=hero_instance)
5個檢視擴充套件類
匯入模組
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin
擴充套件類 | 對應方法 | 說明 |
---|---|---|
CreateModelMixin | post | 建立單個例項,返回建立後的例項。 |
UpdateModelMixin | put/patch | 更新單個例項,返回修改後的例項。 |
DestroyModelMixin | delete | 刪除單個例項,返回的結果為空。 |
ListModelMixin | get | 獲取全部,返回一個queryset列表。 |
RetrieveModelMixin | get/<int:pk>/ |
獲取單個,返回一個具體的例項。 |
透過5個檢視擴充套件類 + GenericAPIView + 序列化類寫介面
# 原始碼
class CreateModelMixin:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) # 校驗資料
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save() # 和我們之前使用APIView的save() 一樣 所以不需要我們寫儲存了,它內部呼叫了 perform_create 完成建立。
# 這裡是校驗欄位的方法
# 如果包含了資料庫中不存在的欄位,DRF 預設會忽略這些欄位,不會引發錯誤。
# 這是因為 DRF 預設情況下會忽略未知的欄位,只處理已定義的欄位。
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
程式碼
# ================ 五個檢視擴充套件類
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin
class HeroView(GenericAPIView, CreateModelMixin, ListModelMixin):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
""" 註釋的是之前寫的程式碼 現在可以不用寫了
hero_instance = self.get_queryset()
serializer = self.get_serializer(instance=hero_instance, many=True)
"""
def get(self, request):
return super().list(request)
def post(self, request):
return super().create(request)
# 基於五個檢視擴充套件類去寫
class HeroDetailView(GenericAPIView, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin):
# 兩個類屬性
queryset = Hero.objects.all() # 全部
serializer_class = HeroSerializer # 序列化類
def get(self, request, pk):
return super().retrieve(request)
def put(self, request, pk):
return super().update(request)
def delete(self, request, pk):
return super().destroy(request)
關於return時候的super
方法 | return的時候,需要返回父類的哪一個方法 |
---|---|
get | list |
post | create |
get/int:pk/ | retrieve |
put | update |
delete | destroy |
有什麼問題?程式碼重複
return super().list(request)
return super().create(request)
return super().retrieve(request)
return super().update(request)
return super().destroy(request)
9個檢視子類
思考?為什麼要有9個檢視子類
- GenericAPIView + 對應的5個檢視擴充套件類
- 5個
- GenericAPIView + CreateModelMixin + ListModelMixin
- 即建立一個和查詢全部
- ListCreateAPIView
- 1個
- GenericAPIView + RetrieveModelMixin+ DestroyModelMixin
- 查詢單條,刪除單條
- RetrieveDestroyAPIView
- 1個
- GenericAPIView + RetrieveModelMixin + UpdateModelMixin
- 查詢單條,修改單條
- RetrieveUpdateAPIView
- 1個
- GenericAPIView + RetrieveModelMixin + DestroyModelMixin
- 查詢單條,既修改又刪除
- GenericAPIView + RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin
- RetrieveUpdateDestroyAPIView
- 1個
思考:為什麼沒有更新和刪除的組合?
因為更新和刪除通常不會結合在一起使用,它們通常被認為是不同的操作,分別用於修改和刪除資源。因此,DRF沒有提供一個預設的檢視類來結合這兩個操作。
自己寫一個Mixin
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin
class CreatListAPIView(GenericAPIView, CreateModelMixin, ListModelMixin):
def get(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
class HeroView(CreatListAPIView):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
用模板寫好的,我們直接使用
匯入模組
from rest_framework.generics import CreateAPIView, ListAPIView, UpdateAPIView, DestroyAPIView, RetrieveAPIView
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView
五個方法
from rest_framework.generics import CreateAPIView, ListAPIView, UpdateAPIView, DestroyAPIView, RetrieveAPIView
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView
# 查詢所有 新增一條
class HeroView(ListCreateAPIView):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
# 查詢 修改 刪除 1條
class HeroDetailView(RetrieveUpdateDestroyAPIView):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
檢視集
- 如果要用檢視集,一定要去修改路由,因為需要有對映關係。
- action是一個字典,字典的key是請求方式,字典的value是執行的方法。
- 我們可以自己定製對映關係,比如{"post": "login"},這樣當傳送post請求的時候,就執行login方法,不過需要繼承 ViewSetMixin
透過檢視集繼續最佳化程式碼
# 檢視層
from .serialinzer import HeroSerializer
from rest_framework.response import Response
from .models import Hero
from rest_framework.viewsets import ModelViewSet
class HeroView(ModelViewSet):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
class HeroDetailView(ModelViewSet):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
# 路由層
from django.urls import path
from .views import HeroView, HeroDetailView
urlpatterns = [
path("heros/", HeroView.as_view({"get": "list", "post": "create"})),
path("heros/<int:pk>/", HeroDetailView.as_view({"put":"retrieve", "get": "update", "delete": "destroy"}))
]
檢視集原始碼分析
# 1 檢視類:繼承了APIView----》GenericAPIView
# 2 有5個方法---》繼承了5個檢視擴充套件類:
CreateModelMixin
RetrieveModelMixin
DestroyModelMixin
ListModelMixin
UpdateModelMixin
# 3 寫沒寫 get put post delete--》使用對映
get---》list
get---》retrieve
put---》update
delete--》destroy
post-->create
# 4 繼承了5個檢視擴充套件類和 GenericViewSet 【不是GenericAPIView】
# 5 GenericViewSet:ViewSetMixin+GenericAPIView
# 6 ViewSetMixin 核心---》只要繼承它--》路由寫法就變了--》必須加action--》
-action是請求方式和檢視類中方法的對映關係
# 7 以後只要繼承ViewSetMixin的檢視類
1 as_view 必須加action做對映
2 檢視類中,可以寫任意名的方法,只要做好對映,就能執行
# 8 ViewSetMixin原始碼分析--》透過重寫as_view使得路由寫法變了
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
# 0 跟APIView的as_view差不多
# 1 actions must not be empty,如果為空拋異常
# 2 透過反射把請求方式同名的方法放到了檢視類中--》對應咱們的對映
def view(request, *args, **kwargs):
self = cls(**initkwargs)
# actions 咱們傳入的字典--》對映關係
# {'get': 'list', 'post': 'create'}
for method, action in actions.items():
# method=get action=list
# method=post action=create
# 檢視類物件中反射:list 字串--》返回了 list方法
# handler就是list方法
handler = getattr(self, action)
# 把handler:list方法 透過反射--》放到了檢視類的物件中、
# method:get
# 檢視類的物件中有個get--》本質是list
setattr(self, method, handler)
return self.dispatch(request, *args, **kwargs) # APIView的dispatch
return csrf_exempt(view)
繼承ViewSetMixin,定製對映關係
注意,ViewSetMixin需要是直接父類,也就是要寫在括號的左邊,因為它重寫了as_view,如果放在右邊,能找到as_view,就無法執行action而觸發報錯。
# 常規寫法
from rest_framework.viewsets import ViewSetMixin
from rest_framework.generics import GenericAPIView
class Game(ViewSetMixin, GenericAPIView):
def login(self, request):
return Response({"code": 100, "msg": "登入成功!"})
"""
(<class 'app01.views.Game'>,
<class 'rest_framework.viewsets.ViewSetMixin'>,
<class 'rest_framework.generics.GenericAPIView'>,
<class 'rest_framework.views.APIView'>,
<class 'django.views.generic.base.View'>,
<class 'object'>)
"""
# 檢視原始碼
class ViewSet(ViewSetMixin, views.APIView):
"""
The base ViewSet class does not provide any actions by default.
"""
pass
ViewSet = ViewSetMixin, GenericAPIView
# 更新寫法
from rest_framework.viewsets import ViewSet
class Game(ViewSet):
def login(self, request):
return Response({"code": 100, "msg": "登入成功!"})
檢視集下常用的類
# 五個介面
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
# 查詢1條,查詢全部 get/<int:pk>/ get
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
# 定製對映關係,路由寫法要修改 --> action= {"post": "login"}
class ViewSet(ViewSetMixin, views.APIView):
"""
The base ViewSet class does not provide any actions by default.
"""
pass
# 路由寫法要修改
# 繼承自 GenericAPIView 與 ViewSetMixin,在實現了呼叫 as_view() 時傳入字典(如 {'get':'list'})的對映處理工作的同時
# 還提供了 GenericAPIView 提供的基礎方法,可以直接搭配 Mixin 擴充套件類使用。
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass
重寫
查詢部分資料,重寫GenericAPIView.get_queryset
class HeroView(ListCreateAPIView):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
def get_queryset(self):
# 對get_queryset進行重寫,查詢id大於20小於40的結果
queryset = self.queryset.filter(id__gt="20", id__lt="40")
return queryset
根據請求執行不同序列化類,重寫GenericAPIView.get_serializer_class
def get_serializer_class(self):
if self.request.method == "GET":
return ASerializer # 替換 "a序列化類" 為實際的序列化類
elif self.request.method == "POST":
return BSerializer # 替換 "b序列化類" 為實際的序列化類
新增資料後,返回指定訊息給客戶端 重寫 CreateModelMixin.create
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"code": 201, "msg": "新增成功!", "result": serializer.data})
控制只能傳送某一個請求
class HeroDetailView(RetrieveUpdateDestroyAPIView):
# 透過這個指定,裡面填寫允許的請求方式, 在views裡面
http_method_names = [
"get",
]
queryset = Hero.objects.all()
serializer_class = HeroSerializer
# HeroDetailView.__mro__
(<class 'app01.views.HeroDetailView'>,
<class 'rest_framework.generics.RetrieveUpdateDestroyAPIView'>,
<class 'rest_framework.mixins.RetrieveModelMixin'>,
<class 'rest_framework.mixins.UpdateModelMixin'>,
<class 'rest_framework.mixins.DestroyModelMixin'>,
<class 'rest_framework.generics.GenericAPIView'>,
<class 'rest_framework.views.APIView'>,
<class 'django.views.generic.base.View'>,
<class 'object'>)
路由
自動生成路由SimpleRouter
from django.urls import path
from .views import HeroView
# 1 匯入模組
from rest_framework.routers import SimpleRouter
# 例項化得到物件
router = SimpleRouter()
# 呼叫物件的方法
router.register("heros", HeroView, basename="HeroView")
urlpatterns = [
]
# 把生成的路由放進去路徑裡面
urlpatterns += router.urls
自動生成路由DefaultRouter
和SimpleRouter用法一樣,只是會多一個api-root,後面路徑多了看到的效果會更加明顯。
admin/
app01/ ^heros/$ [name='HeroView-list']
app01/ ^heros\.(?P<format>[a-z0-9]+)/?$ [name='HeroView-list']
app01/ ^heros/(?P<pk>[^/.]+)/$ [name='HeroView-detail']
app01/ ^heros/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='HeroView-detail']
app01/ [name='api-root']
app01/ <drf_format_suffix:format> [name='api-root']
# 訪問app01路徑,之前會報錯,現在會返回一個json
{
"heros": "http://127.0.0.1:8000/app01/heros/"
}
使用action定製詳細路由
匯入模組
from rest_framework.decorators import action
程式碼
# 檢視層
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
class Game(ViewSetMixin, GenericAPIView):
@action(methods=['POST'], detail=False)
def login(self, request):
return Response({"code": 100, "msg": "登入成功!"})
# 路由層 有兩個自動生成才能看到效果
from django.urls import path
from .views import HeroView, HeroDetailView, Game
from rest_framework.routers import SimpleRouter, DefaultRouter
router = DefaultRouter()
router.register("heros", HeroView, basename="HeroView")
reouter1 = SimpleRouter()
reouter1.register("game", Game, basename="Game")
urlpatterns = [
]
urlpatterns += router.urls
urlpatterns += reouter1.urls