Django RESTful API之序列化
前後端分離:就是前臺的開發和後臺的開發分離,這個技術方案的實現需要藉助API,簡單來說就是開發人員提供程式設計的介面被其他人呼叫,呼叫之後會返回資料供其使用
安裝:pip install djangorestframework
什麼是序列化?:把模型物件轉換為JSON格式然後響應出去,便於客戶端進行資料解析
建立序列化類
在應用目錄下建立名為serializers.py的檔案
from rest_framework import serializers
from myApp.models import Student, Grade
#給學生類建立序列化類
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ("id", "name", "sex", "age", "content", "isDelete", "grade")
#該班級建立序列化類
class GradeSerializer(serializers.ModelSerializer):
class Meta:
model = Grade
fields = ("id", "name", "boyNum", "girlNum", "isDelete")
使用系列化
- 進入shell環境:
python manage.py shell
- 引入序列化類,建立序列化物件檢視可序列化的欄位:
>>> from myApp.serializers import StudentSerializer
>>> serializer = StudentSerializer()
>>> print(serializer)
StudentSerializer():
id = IntegerField(label=`ID`, read_only=True)
name = CharField(max_length=20)
sex = BooleanField(required=False)
age = IntegerField(max_value=2147483647, min_value=-2147483648)
contend = CharField(max_length=40)
isDelete = BooleanField(label=`IsDelete`, required=False)
grade = PrimaryKeyRelatedField(queryset=Grade.objects.all())
- 找到一個學生:
>>> from myApp.models import Student
>>> stu = Student.objects.get(pk=1)
>>> print(stu)
薛延美
- 依據學生建立序列化物件,再對物件進行序列化操作:
>>> serializer = StudentSerializer(stu)
>>> print(serializer.data)
{`id`: 1, `name`: `薛延美`, `sex`: False, `age`: 20, `contend`: `我叫薛延美`, `isDelete`: False, `grade`: 4}
>>> print(type(serializer.data))
<class `rest_framework.utils.serializer_helpers.ReturnDict`>
- 將資料渲染成JSON格式
>>> from rest_framework.renderers import JSONRenderer
>>> content = JSONRenderer().render(serializer.data)
>>> print(content)
b`{"id":1,"name":"xe8x96x9bxe5xbbxb6xe7xbex8e","sex":false,"age":20,"contend":"xe6x88
x91xe5x8fxabxe8x96x9bxe5xbbxb6xe7xbex8e","isDelete":false,"grade":4}`
- 反序列化:當客戶需要修改、增加、刪除資料時,就要這個過程反過來,就叫反序列化
>>> from rest_framework.parsers import JSONParser
>>> from django.utils.six import BytesIO
>>> stream = BytesIO(content)
>>> print(stream)
<_io.BytesIO object at 0x000001EECF597E08>
>>> stu2 = JSONParser().parse(stream)
>>> print(stu2)
{`id`: 1, `name`: `薛延美`, `sex`: False, `age`: 20, `contend`: `我叫薛延美`, `isDelete`: False, `grade`: 4}
>>> print(type(stu2))
<class `dict`>
- 檢測資料並儲存
>>> stu2.save()
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: `dict` object has no attribute `save`
>>> serializer = StudentSerializer(data=stu2)
>>> print(serializer.is_valid())
True
>>> print(serializer.validated_data)
OrderedDict([(`name`, `薛延美`), (`sex`, False), (`age`, 20), (`contend`, `我叫薛延美`), (`isDel
ete`, False), (`grade`, <Grade: python04>)])
>>> print(type(serializer.validated_data))
<class `collections.OrderedDict`>
>>> print(serializer.validated_data["name"])
薛延美
>>> serializer.save()
<Student: 薛延美>
檢視實現使用序列化
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from myApp.models import Student, Grade
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from django.utils.six import BytesIO
from myApp.serializers import StudentSerializer, GradeSerializer
def studentsList(request):
if request.method == "GET":
stus = Student.objects.all()
#序列化
serializer = StudentSerializer(stus, many=True)
return JsonResponse(serializer.data, safe=False)
elif request.method == "POST":
# content = JSONRenderer().render(request.POST)
# stream = BytesIO(content)
# stuDict = JSONParser().parse(stream)
# serializer = StudentSerializer(data=stuDict)
serializer = StudentSerializer(data=request.POST)
if serializer.is_valid():
#存資料
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse({"error":serializer.errors}, status=400)
def studentDetail(request, pk):
try:
stu = Student.objects.get(pk=pk)
except Student.DoesNotExist as e:
return JsonResponse({"error":str(e)}, status=404)
if request.method == "GET":
serializer = StudentSerializer(stu)
return JsonResponse(serializer.data)
elif request.method == "PUT":
#content = JSONRenderer().render(request.data)
#stream = BytesIO(content)
#stuDict = JSONParser().parse(stream)
# print(stuDict)
#修改
serializer = StudentSerializer(stu, data=request.data)
if serializer.is_valid():
#存資料
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse({"error":serializer.errors}, status=400)
elif request.method == "DELETE":
stu.delete()
return HttpResponse(status=204,content_type="application/json")
Django RESTful API 之請求與響應
啟用應用
INSTALLED_APPS = [
`django.contrib.admin`,
`django.contrib.auth`,
`django.contrib.contenttypes`,
`django.contrib.sessions`,
`django.contrib.messages`,
`django.contrib.staticfiles`,
`myApp`,
`rest_framework`,
]
Request物件
request.POST: 只能處理表單資料,並且只能處理POST請求
擴充套件: request.data 能處理各種請求的資料,可以處理PUT和PATCH請求的資料
Response物件
HttpResponse、JsonResponse類: 用於返回json資料,在return的時候需要指明json格式
擴充套件: Reponse類 會根據客戶端的請求頭資訊返回正確的內容型別
狀態碼
傳送http請求會返回各種各樣的狀態碼,但是狀態碼都是數字,不能夠明確的讓程式設計師瞭解是什麼問題
擴充套件 HTTP_400_BAD_REQUEST 極大提高了可讀性
檢視
- @api_view: 是裝飾器,用在基於函式的檢視上
- APIView: 是類,用在基於類的檢視上
- 作用: 提供一些功能,讓程式設計師省去了很多工作,確保在檢視中收到request物件或在物件中新增上下文 裝飾器可以在接收到輸入錯誤的request.data時丟擲ParseError異常,在適當的時候返回405狀態碼
程式碼
# from django.shortcuts import render
# from django.http import HttpResponse, JsonResponse
from myApp.models import Student, Grade
# from rest_framework.renderers import JSONRenderer
# from rest_framework.parsers import JSONParser
# from django.utils.six import BytesIO
from myApp.serializers import StudentSerializer
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
@api_view(["GET", "POST"])
def studentsList(request):
if request.method == "GET":
stus = Student.objects.all()
#序列化
serializer = StudentSerializer(stus, many=True)
# 不需要指定json格式,返回客戶端可以返回json或者HTML,返回HTML內容的話,會在瀏覽器中經過渲染成頁面
return Response(serializer.data, status=status.HTTP_200_OK)
elif request.method == "POST":
# content = JSONRenderer().render(request.POST)
# stream = BytesIO(content)
# stuDict = JSONParser().parse(stream)
serializer = StudentSerializer(data=request.data)
if serializer.is_valid():
#存資料
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response({"error":serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET", "PUT", "DELETE"])
def studentDetail(request, pk):
try:
stu = Student.objects.get(pk=pk)
except Student.DoesNotExist as e:
return Response({"error":str(e)}, status=status.HTTP_404_NOT_FOUND)
if request.method == "GET":
serializer = StudentSerializer(stu)
return Response(serializer.data)
elif request.method == "PUT":
# content = JSONRenderer().render(request.POST)
# stream = BytesIO(content)
# stuDict = JSONParser().parse(stream)
# print(stuDict)
#修改
serializer = StudentSerializer(stu, data=request.data)
if serializer.is_valid():
#存資料
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response({"error":serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
elif request.method == "DELETE":
stu.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
向URL新增可選的字尾
檢視
def studentsList(request, format=None):
def studentDetail(request, pk, format=None):
路由
from django.conf.urls import url
from myApp import views
#格式字尾
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
# GET /students/
# POST /students/
url(r`^students/$`, views.studentsList),
# GET /students/id
# PUT /students/id
# PATCH /students/id
# DELETE /students/id
url(r`^students/(?P<pk>d+)/$`, views.studentDetail),
]
urlpatterns = format_suffix_patterns(urlpatterns)
測試
http://127.0.0.1:8000/students.api
http://127.0.0.1:8000/students.json
Django RESTful API 之基於類的檢視
把檢視變成類
from myApp.models import Student
from myApp.serializers import StudentSerializer
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from django.http import Http404
class StudentsList(APIView):
def get(self, request, format=None):
stus = Student.objects.all()
serializer = StudentSerializer(stus, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, format=None):
serializer = StudentSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
class StudentDetail(APIView):
def getObject(self, pk):
try:
return Student.objects.get(pk=pk)
except Student.DoesNotExist as e:
raise Http404
def get(self, request, pk, format=None):
stu = self.getObject(pk)
serializer = StudentSerializer(stu)
return Response(serializer.data)
def put(self, request, pk, format=None):
stu = self.getObject(pk)
serializer = StudentSerializer(stu, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
stu = self.getObject(pk)
stu.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
修改路由匹配類檢視
from django.conf.urls import url
from myApp import views
#格式字尾
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
# GET /students/
# POST /students/
url(r`^students/$`, views.StudentsList.as_view()),
# GET /students/id
# PUT /students/id
# PATCH /students/id
# DELETE /students/id
url(r`^students/(?P<pk>d+)/$`, views.StudentDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
優點
- 把各種HTTP請求分離開
- 可以輕鬆構成可重複使用的行為
- 可以大大簡化程式碼
- 增加了可讀性
使用Mixins類
基本使用
from myApp.models import Student
from myApp.serializers import StudentSerializer
from rest_framework import mixins, generics
#父類中有且只有一個能繼承自APIView類
class StudentsList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class StudentDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
通用檢視使用
from myApp.models import Student
from myApp.serializers import StudentSerializer
from rest_framework import generics
class StudentsList(generics.ListCreateAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
class StudentDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
Django RESTful API 之認證和許可權
如果沒有許可權認證功能,任何資源都會被任何使用者隨意修改,所以實現如下功能
- Student與其建立者相互關聯
- 只有經過身份驗證(登陸)的使用者才可以建立Student物件
- 只有建立該Student物件的使用者才可以對齊進行更新或者刪除
- 未經驗證的使用者只有訪問(只讀)的功能
給學生新增所屬使用者欄位:owner = models.ForeignKey("auth.User", related_name="students")
重新生成表
建立幾個使用者 python manage.py createsuperuser
在serializers.py檔案中給User新增序列化類
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("id", "username", "students")
增加使用者的介面
路由
from django.conf.urls import url
from myApp import views
#格式字尾
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
url(r`^students/$`, views.StudentsList.as_view()),
url(r`^students/(?P<pk>d+)/$`, views.StudentDetail.as_view()),
url(r`^users/$`, views.UsersList.as_view()),
url(r`^users/(?P<pk>d+)/$`, views.UserDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
檢視
from django.contrib.auth.models import User
class UsersList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
把Student和User關聯
概述: 還不能把Student和User關聯,因為在使用的時候User的資料時通過Request傳入的,而不是以序列化資料傳遞的,此時剛才新增了一個owner作為外來鍵,此時使用外來鍵
class StudentsList(generics.ListCreateAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
#讓使用者在通過post請求建立一個新的student時,在保證建立學生時會把request中的user賦值給該學生的owner欄位
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
在顯示學生時還需要顯示學生屬於哪個使用者
class StudentSerializer(serializers.ModelSerializer):
class Meta:
owner = serializers.ReadOnlyField(source="owner.username")
model = Student
fields = ("id", "name", "sex", "age", "contend", "isDelete", "grade", "owner")
新增許可權
from rest_framework import permissions
class StudentsList(generics.ListCreateAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
#讓使用者在通過post請求建立一個新的student時,在保證建立學生時會把request中的user賦值給該學生的owner欄位
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class StudentDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
# 只有所有者使用者才能刪除、修改,其他使用者只能訪問
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
為可瀏覽的API新增登陸功能
工程目錄下與工程目同名目錄下的urls.py檔案
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r`^admin/`, admin.site.urls),
url(r`^api-auth/`, include("rest_framework.urls", namespace="rest_framework")),
url(r`^`, include("myApp.urls")),
]
新增物件許可權
要實現讓所有的Students可以被所有人訪問,但是每個學生只能被其建立者所操作。
需要自定義許可權,讓每個學生只能被其建立者所操作,在應用目錄下建立permissions.py的檔案
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
# 使用者請求為GET 可以只讀
return True
return obj.owner == request.user
新增自定義許可權
from myApp.permissions import IsOwnerOrReadOnly
class StudentDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
# 只有所有者使用者才能刪除、修改,其他使用者只能訪問
permission_classes = (permissions.IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly)
API授權
- 由於現在我們還沒有使用Authentication類,所以專案目前還是使用預設的SessionAuthentication和BaseAuthentication
- 在使用瀏覽器訪問API的時候,瀏覽器會幫助我們儲存會話資訊,所以當許可權滿足是就可以對一個學生物件進行刪除或者更新,還可以建立學生
- 當如果通過命令來操作API,我們就必須在每次傳送請求是附帶驗證資訊 : http://user1:sunck1999@127.0.0.1:8000/students/1/
- 程式中使用
from django.contrib.auth import login
Django RESTful API 之ViewSet和Routers
目的: 介紹另一種基於類的檢視的寫法,它的抽象程度更高,程式碼更少
使用ViewSets重構檢視
from myApp.models import Student
from myApp.serializers import StudentSerializer, UserSerializer
from rest_framework import permissions
from myApp.permissions import IsOwnerOrReadOnly
from django.contrib.auth.models import User
from rest_framework import viewsets
class StudentViewSet(viewsets.ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
重構路由
from django.conf.urls import url, include
from myApp.views import StudentViewSet, UserViewSet
from rest_framework.urlpatterns import format_suffix_patterns
students_list = StudentViewSet.as_view({
"get":"list",
"post":"create"
})
student_detail = StudentViewSet.as_view({
"get":"retrieve",
"put":"update",
"patch":"partial_update",
"delete":"destroy"
})
users_list = UserViewSet.as_view({
"get":"list"
})
user_detail = UserViewSet.as_view({
"get":"retrieve"
})
urlpatterns = format_suffix_patterns([
url(r`^students/$`, students_list, name="students_list"),
url(r`^students/(?P<pk>d+)/$`, student_detail, name="student_detail"),
url(r`^users/$`, users_list, name="users_list"),
url(r`^users/(?P<pk>d+)/$`, user_detail, name="user_detail"),
])
使用Routers
from django.conf.urls import url, include
from myApp import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r`students`, views.StudentViewSet)
router.register(r`users`, views.UserViewSet)
urlpatterns = [
url(r`^`, include(router.urls))
]