認識RESTful
-
REST是設計風格而不是標准,簡單來講REST規定url是用來唯一定位資源,而http請求方式則用來區分用戶行為.
-
REST接口設計規范
-
HTTP常用動詞
-
GET /books:列出所有書籍 返回數據類型-->[{},{}]
-
GET /books/ID:獲取某個指定書籍的信息 -->{單本書籍}
-
POST /books:新建一本書籍 -->{新增的數據}
-
PUT /books/ID:更新某本指定書籍的信息(提供該書籍的全部信息) -->{修改后的數據}
-
PATCH /books/ID:更新某本指定書籍的信息(提供該書籍的部分信息) -->{修改后的數據}
-
DELETE /books/ID:刪除某本書籍 -->返回空
-
-
錯誤消息規范
-
{ error: "Invalid API key" }
-
-
APIView請求流程(源碼剖析)
-
from rest_framework.views import APIView(導入APIView)
-
啟動django后,加載settings及各個組件(視圖,路由等)
-
加載的時候urls中views.HomeView.as_view(),會執行as_view()類方法,這里APIView繼承了View的as_view方法,所以最后返回的還是view函數,實際上此時django內部就已經幫我們做了一層url和視圖函數的關系綁定
-
以訪問首頁為例,找到url對應的視圖函數,執行view(request),返回APIView中的dispatch()並執行該方法
-
找到對應的get或者post等方法並執行,最終將結果返回給瀏覽器
解析器組件
-
解析器組件的使用
-
request.data(通過該方法得到處理完的字典數據)
-
-
解析器請求流程(源碼剖析)
-
簡單的講解析器就是根據不同的content-type來解析數據,最終返回字典格式的數據
-
在執行view函數時,首次將request傳入,執行APIView中的dispatch()方法
-
在dispatch()中將原來的request對象傳遞給初始化函數: request = self.initialize_request(request, *args, **kwargs)
-
執行APIView中的initialize_request,返回Request(request,parsers=self.get_parsers(),..)
-
from rest_framework.request import Request,從這里可以看出,Request是一個類,所以Request()是一個實例化對象
-
而我們通過request.data觸發解析方法,此時調用Request實例的data方法(這里用了@property)
-
在data方法里面,執行self.load_data_and_files(),此時self指的是Request的實例化對象,所以執行Request中的load_data_and_files()方法
-
在load_data_and_files中,執行Request中的self.parse()方法
-
在self._parse()方法中,首先media_type = self.content_type,獲取了content_type,並執行了parser = self.negotiator.select_parser(self, self.parsers),這里傳入了self.parsers,而Request實例化的時候傳入了parsers=self.get_parsers(),所以這里要看下self.get_parsers()該方法
-
在APIView中執行get_parsers()方法,return [parser() for parser in self.parser_classes],返回了一個列表推導式,而parser_classes = api_settings.DEFAULT_PARSER_CLASSES,那api_settings是啥呢?
-
from rest_framework.settings import api_settings,從這里我們可以知道是從settings中導入的,在settings中api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS),APISettings是一個類,所以api_settings是APISettings的實例化對象
-
實例化對象后,要執行api_settings.DEFAULT_PARSER_CLASSES,但是APISettings類中並沒有該屬性或方法,則執行getattr方法, if attr in self.import_strings: val = perform_import(val, attr),這里動態導入,最后返回的是parser_classes 就等於[<class 'rest_framework.parsers.JSONParser'>,<class 'rest_framework.parsers.FormParser'>, <class 'rest_framework.parsers.MultiPartParser'>]
-
parser() for parser in self.parser_classes中parser()是實例化各個類,parsers=self.get_parsers(),parser = self.negotiator.select_parser(self, self.parsers),所以最后這個一執行就觸發前面的一步一步執行
-
最終self.data, self.files = self.parse(),將self.parse()的返回值賦給self.data,再將self.full_data = self.data,最終返回了self.full_data
-
序列化組件
-
django原生序列化步驟
-
導入模塊 from django.core.serializers import serialize
-
獲取queryset (data = Book.objects.all())
-
對queryset進行序列化 serialize_data = (serialize("json", data))
-
將序列化之后的數據,響應給客戶端,返回serialize_data
-
-
APIView說明
-
APIView繼承了View,並在內部將request重新賦值變成了Request()的實例化對象,因此繼承APIView后
-
獲取get請求數據要通過,request.query_params
-
獲取post請求要通過request.data
-
-
序列化組件使用
-
Get接口設計(序列化):
-
聲明序列化器
-
導入模塊:from rest_framework import serializers
-
建立一個序列化類 (寫在一個獨立的文件中): class BookSerializer(serializers.Serializer):字段自定義,最好跟model的一致
from rest_framework import serializers class PublishSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class BookSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) # 字段中的通用屬性source可以進行數據庫操作,get_xxx_display取choice字段的value.(source只能用於序列化) category = serializers.CharField(source="get_category_display") pub_time = serializers.DateField() # 外鍵,通過id找到publish對象並傳入PublishSerializer(publish對象),最終得到id和title,因為這里返回的是單個對象,所以不用寫many,而多對多返回的是一個queryset所以多個需要寫many=True,內部會循環遍歷 publish = PublishSerializer() # 多對多,記得有many=True authors = AuthorSerializer(many=True)
-
-
使用自定義的序列化器序列化queryset(將模型對象放入序列化器進行字段匹配,匹配上的就進行序列化,匹配不上的就丟棄)
-
序列化完成的數據在序列化返回值的data中(serializer_data.data)
from django.shortcuts import render,HttpResponse from django.views import View from django.http import JsonResponse import json from rest_framework.views import APIView from rest_framework.response import Response from .models import Book from .serializerses import BookSerializer class BookView(APIView): """書籍相關視圖""" def get(self, request): book_queryset = Book.objects.all() # 使用自定義的序列化器序列化queryset serializer_data = BookSerializer(book_queryset, many=True) # 注意這里是取序列化返回值.data return Response(serializer_data.data)
注意:外鍵關系的序列化是嵌套的序列化對象,多對多有many=True,還有如果需要在瀏覽器訪問請求的話需要在settings中INSTALLED_APPS下加入'rest_framework'.
-
上面是get所有數據,如果是請求單條數據,urls可以設計book/1,這樣的話需要重新定義View,序列化的時候因為是單個book_obj,所以也不用寫many=True了,默認是False
-
-
Post接口設計(反序列化):
-
步驟如下:
-
首先先確定新增的數據結構(跟前端溝通達成一致)
-
定義序列化器
-
正序和反序字段不統一,部分需要重新定義
-
required=False, 只序列化不走校驗
-
read_only=True, 只序列化用
-
write_only=True, 只反序列化用
-
重寫create方法,參數有驗證通過的數據validated_data
-
-
驗證通過返回ser_obj.validated_data,不通過則返回ser_obj.errors
-
還有些細節注意在代碼中,請看下面代碼.
serializerses.py文件內容
from rest_framework import serializers from app01.models import Book class PublishSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) title = serializers.CharField(max_length=32) # 字段中的通用屬性source可以進行數據庫操作,get_xxx_display取choice字段的value category = serializers.CharField(source="get_category_display", read_only=True) pub_time = serializers.DateField() # 外鍵 publish = PublishSerializer(read_only=True) # 多對多,記得有many=True authors = AuthorSerializer(many=True, read_only=True) category_id = serializers.IntegerField(write_only=True) publish_id = serializers.IntegerField(write_only=True) # 多對多傳入的是[],所以用ListField author_list = serializers.ListField(write_only=True) # 將通過驗證的validated_data數據插入數據庫 def create(self, validated_data): # 先將作者列表從字典中剔除 author_list = validated_data.pop('author_list') book_obj = Book.objects.create(title=validated_data['title'],category=validated_data['category_id'], pub_time = validated_data['pub_time'],publish_id=validated_data['publish_id']) book_obj.authors.add(*author_list) return book_obj
views.py內容
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView from rest_framework.response import Response from .models import Book from .serializerses import BookSerializer ''' 前端提交的格式如下: { "title": "西游1", "category_id": 3, "pub_time": "2019-04-22", "publish_id": 1, "author_list": [1,2] } ''' class BookView(APIView): """書籍相關視圖""" def get(self, request): book_queryset = Book.objects.all() serializer_data = BookSerializer(book_queryset, many=True) return Response(serializer_data.data) def post(self, request): # 獲取前端提交的請求數據 book_obj = request.data # 通過data來表示是反序列化 ser_obj = BookSerializer(data=book_obj) # 數據校驗 if ser_obj.is_valid(): # save()方法返回self.instance = self.create(validated_data) # 然后就會調用BookSerializer中create方法保存通過校驗的數據 # 最后 save()會返回self.instance,而self.instance就是create的返回數據book_obj(插入的書本對象) ser_obj.save() # 給前端返回新增成功的數據 return Response(ser_obj.validated_data) return Response(ser_obj.errors)
-
-
Put接口和單個get設計:
-
通過book/id去請求
在views中,記得put需要傳入要修改的book對象,還有修改的數據和設置部分字段校驗partial=True.並且定義update方法
class BookEditView(APIView): def get(self, request, book_id): book_obj = Book.objects.filter(id=book_id).first() ser_obj = BookSerializer(book_obj) return Response(ser_obj.data) def put(self, request, book_id): book_obj = Book.objects.filter(id=book_id).first() # 針對單本書籍對象進行修改,所以instance是單本書籍對象,data是傳入的書籍信息,partial為True表示只校驗提交的字段,也就是部分字段校驗 ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.validated_data) return ser_obj.errors
在序列化器中定義update方法
def update(self, instance, validated_data): # instance表示更新的book_obj對象,validated_data是校驗通過的數據 # 給每一個字段逐一賦值,如果沒有就用原來的值 instance.title = validated_data.get('title', instance.title) instance.category = validated_data.get('category_id', instance.category) instance.pub_time = validated_data.get('pub_time', instance.pub_time) instance.category = validated_data.get('category_id', instance.category) if validated_data.get('author_list'): instance.authors.set(validated_data.get('author_list')) # 基於對象操作,保存需要save一下 instance.save() return instance
-
-
-
序列化接口字段校驗
# 類似於form局部鈎子驗證,針對單個屬性 def validate_title(self, value): # 對書籍標題進行驗證,假設標題不能為純數字 if value.isdigit(): raise serializers.ValidationError("標題不能為純數字!") return value # 多個屬性校驗 def validate(self, attrs): # 對標題和出版社進行校驗 if attrs['title'].isdigit() or not attrs['publish_id'].isdigit(): raise serializers.ValidationError("標題不能為純數字,出版社必須是數字") return attrs
上面是單個屬性和多個屬性校驗,下面還有個自定義校驗,然后綁定到字段上的用法
def my_validata(value): # 自定義規則 if '敏感信息' in value: raise serializers.ValidationError("標題不能包含敏感信息") return value # 通過validators使用,可以有多個自定義校驗規則 title = serializers.CharField(max_length=32, validators=[my_validata,])
有沒有發現上面的序列化和反序列化都非常繁瑣,所以我們在真實開發中一般都用下面的這種
-
ModelSerializer序列化及反序列化
modelSerializers文件內容
from rest_framework import serializers from app01.models import ( Book, Publish, Author ) class PublishModelSerializer(serializers.ModelSerializer): class Meta: model = Publish fields = "__all__" class AuthorModelSerializer(serializers.ModelSerializer): class Meta: model = Author fields = "__all__" class BookModelSerializer(serializers.ModelSerializer): # 這里定義的都是序列化的情況,所以都是read_only=True,而且都是通過自定義方法返回需要展示的字段 category_info = serializers.SerializerMethodField(read_only=True) publish_info = serializers.SerializerMethodField(read_only=True) authors_info = serializers.SerializerMethodField(read_only=True) # get_字段名稱,這個是固定搭配,該方法會return值給category_info字段,而且傳入的是序列化的obj模型對象 def get_category_info(self, obj): # 自定義需要展示的數據,這里展示課程的中文, choices類型 return obj.get_category_display() def get_publish_info(self, obj): # 以字典形式展示的是出版社的id和name, 外鍵類型 publish_obj = obj.publish return {"id":publish_obj.id, "name":publish_obj.name} def get_authors_info(self, obj): # 由於作者可能有多個,所以以列表包含多個字典形式展示, 多對多類型 authors_querset = obj.authors.all() return [{"id":author.id, "name":author.name} for author in authors_querset] # 反序列化用原生的,部分序列化字段需要重新定義,記得不要和原生字段重名 class Meta: model = Book fields = "__all__" # exclude = ["id"] # 不包含id,但是fields和exclude只能有一個 # 字段是有序的,根據下面的字段進行排序展示 # fields = ["id", "pub_time","title", "category_info", "publish_info", "authors_info"] # depth=1是深度為1層,也就是把外鍵關系的第一層找出來,默認不寫是不顯示出版社名稱和作者信息的,寫了就找出了出版社的所有信息和作者所有信息 # 注意:depth會讓所有的外鍵關系字段都變成read_only=True,所以一般少用 # depth = 1 read_only_fields = ["id", ] # 指明只讀字段,可以寫多個 # 添加或修改原有參數,反序列化的個別字段只需要在反序列化看到 extra_kwargs = { 'category': {"write_only": True,}, # key為默認的字段名稱, value是自定義的參數配置信息 'publish': {"write_only": True}, 'authors': {"write_only": True}, }
-
-
from rest_framework.views import APIView from rest_framework.response import Response from .models import Book from .modelSerializers import BookModelSerializer class BookView(APIView): """書籍相關視圖""" def get(self, request): book_queryset = Book.objects.all() serializer_data = BookModelSerializer(book_queryset, many=True) return Response(serializer_data.data) def post(self, request): # 獲取前端提交的請求數據 book_obj = request.data # 通過data來表示是反序列化 ser_obj = BookModelSerializer(data=book_obj) # 數據校驗 if ser_obj.is_valid(): # save()方法返回self.instance = self.create(validated_data) # 然后就會調用BookSerializer中create方法保存通過校驗的數據 # 最后 save()會返回self.instance,而self.instance就是create的返回數據book_obj(插入的書本對象) ser_obj.save() # 給前端返回新增成功的數據 return Response(ser_obj.validated_data) return Response(ser_obj.errors) class BookEditView(APIView): def get(self, request, book_id): book_obj = Book.objects.filter(id=book_id).first() ser_obj = BookModelSerializer(book_obj) return Response(ser_obj.data) def put(self, request, book_id): book_obj = Book.objects.filter(id=book_id).first() # 針對單本書籍對象進行修改,所以instance是單本書籍對象,data是傳入的書籍信息,partial為True表示只校驗提交的字段,也就是部分字段校驗 ser_obj = BookModelSerializer(instance=book_obj, data=request.data, partial=True) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.validated_data) return Response(ser_obj.errors) def delete(self, request, book_id): book_obj = Book.objects.filter(id=book_id).first() if not book_obj: return Response("要刪除的對象不存在") book_obj.delete() return Response("")
-
-