10-django——RESTful API 之序列化

rottengeek發表於2019-02-16

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))
]

相關文章