Restful 2 --DRF解析器,序列化組件使用(GET/POST接口設計)

DRF - 解析器

一、解析器的引出

  咱們知道,瀏覽器能夠向django服務器發送json格式的數據,此時,django不會幫咱們進行解析,只是將發送的原數據保存在request.body中,只有post請求發送urlencoded格式的數據時,django會幫咱們將數據解析成字典放到reques.POST中,咱們可直接獲取並使用,下面是django對數據解析的相關源碼:html

def _load_post_and_files(self):
    if self.method != 'POST':
        self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
        return
    if self._read_started and not hasattr(self, '_body'):
        self._mark_post_parse_error()
        return
    if self.content_type == 'multipart/form-data':
        if hasattr(self, '_body'):
            data = BytesIO(self._body)
        else:
            data = self
        try:
            self._post, self._files = self.parse_file_upload(self.META, data)
        except MultiPartParserError:
            self._mark_post_parse_error()
            raise
    elif self.content_type == 'application/x-www-form-urlencoded':
        self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
    else:
        self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()

  分析:有源碼可見,django並無解析json數據的操做,那麼咱們本身是否能夠解析,固然能夠,以下代碼:前端

class LoginView(View):
    def get(self, request):
        return render(request, 'login.html')

    def post(self, request):
        print(request.body)  # b'{"name":"alex","password":123}'
        origin_data = request.body.decode('utf-8')
        parsed_data = json.loads(origin_data)
        print(parsed_data)  # {'name': 'alex', 'password': 123}
        print(type(parsed_data))  # <class 'dict'>
        return HttpResponse("Ok")

  分析:上面代碼能夠看出,咱們徹底能夠拿到用戶發送的數據,而後進行解碼和反序列化,那麼問題來了,咱們的項目中可能不止一次須要發送json格式數據,這是面臨的問題就是拿到數據都要本身進行解析,有沒有這樣的一個工具能夠爲咱們解析用戶發送的json格式數據,答案固然有,DRF的APIView就爲咱們提供了這樣的功能,看以下代碼:python

from rest_framework.views import APIView
class LoginView(APIView):
    def get(self, request):
        return render(request, 'login.html')

    def post(self, request):
        # request是被drf封裝的新對象,基於django的request
        # request.data是一個被property裝飾的屬性方法
        # request.data最後會找到self.parser_classes中的解析器 
        # 來實現對數據進行解析
        print(request.data)   # {'name': 'alex', 'password': 123}
        print(type(request.data))  # <class 'dict'>
        return HttpResponse("Ok")

  分析:上面代碼能夠看出,咱們經過使用APIView代替CBV中的View後,就能夠經過request。data獲取到通過解析後的用戶發送的json格式數據。由此,咱們能夠猜想,DRF中的APIView繼承了View而且對它進行了功能的豐富。接下來咱們經過源碼尋找答案。git

二、解析器源碼解讀

  APIView類中的dispatch方法實現View類中dispath的反射以外,還對request進行了封裝,APIView類部分源碼以下:web

class APIView(View):
   ...
   # api_settings是APISettings類的實例化對象,
   parser_classes = api_settings.DEFAULT_PARSER_CLASSES
   # APIView類加載時parser_classes已經有值,就是解析器,print(parser_classes)
   # 程序啓動就能看見打印結果,結果以下
   # [<class 'rest_framework.parsers.JSONParser'>, 
   # <class 'rest_framework.parsers.FormParser'>, 
   # <class 'rest_framework.parsers.MultiPartParser'>]
   ...
    settings = api_settings
    schema = DefaultSchema()

    @classmethod
    def as_view(cls, **initkwargs):   # cls指LoginView
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            ...
     # 下面一句表示去執行APIView父類(即View類)中的as_view方法
        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs
        return csrf_exempt(view)

  def dispatch(self, request, *args, **kwargs):
        ...
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        ...

        try:
            self.initial(request, *args, **kwargs)
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                              self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
            response = handler(request, *args, **kwargs)
        except Exception as exc:
            response = self.handle_exception(exc)
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

  使用initialize_request方法,對request進行加工,添加功能,APIView中initalize_request函數代碼以下:算法

def initialize_request(self, request, *args, **kwargs):
    parser_context = self.get_parser_context(request)
    # 返回Request的實例化對象
    return Request(
        request,
        parsers=self.get_parsers(),  # 這裏的self指LoginView實例對象
        authenticators=self.get_authenticators(),
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context
    )

  APIView類所在文件views.py中導入了Request和api_settings,以下:數據庫

  from rest_framework.request import Request
  from rest_framework.settings import api_settings

  Request類的部分代碼以下:django

class Request(object):
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )

        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)

  @property
  def data(self):
      if not _hasattr(self, '_full_data'):
          self._load_data_and_files()
      return self._full_data

  def _load_data_and_files(self):
      if not _hasattr(self, '_data'):
          # _parse()的執行結果是返回(parsed.data, parsed.files)
          self._data, self._files = self._parse()
          if self._files:
              self._full_data = self._data.copy()
              self._full_data.update(self._files)
          else:
              self._full_data = self._data 
           # 此時self._full_data就是parsed.data,即解析後的數據

          if is_form_media_type(self.content_type):
              self._request._post = self.POST
              self._request._files = self.FILES

  def _parse(self):
      media_type = self.content_type
      try:
          stream = self.stream
      except RawPostDataException:
          if not hasattr(self._request, '_post'):
              raise
          if self._supports_form_parsing():
              return (self._request.POST, self._request.FILES)
          stream = None

      if stream is None or media_type is None:
          if media_type and is_form_media_type(media_type):
              empty_data = QueryDict('', encoding=self._request._encoding)
          else:
              empty_data = {}
          empty_files = MultiValueDict()
          return (empty_data, empty_files)

      parser = self.negotiator.select_parser(self, self.parsers)    # 這裏的self.parsers就是解析類

      if not parser:
          raise exceptions.UnsupportedMediaType(media_type)

      try:
          parsed = parser.parse(stream, media_type, self.parser_context)
      except Exception:
          self._data = QueryDict('', encoding=self._request._encoding)
          self._files = MultiValueDict()
          self._full_data = self._data
          raise

      try:
          return (parsed.data, parsed.files)
      except AttributeError:
          empty_files = MultiValueDict()
          return (parsed, empty_files)

  api_settings所在的settings.py中部分相關代碼以下:json

DEFAULTS = {
    ...,
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ),
    ...
}

class APISettings(object):
    def __init__(self, user_settings=None, defaults=None, import_strings=None):
        if user_settings:
            self._user_settings = self.__check_user_settings(user_settings)
        self.defaults = defaults or DEFAULTS
        self.import_strings = import_strings or IMPORT_STRINGS
        self._cached_attrs = set()

        @property
  def user_settings(self):
      if not hasattr(self, '_user_settings'):
          self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
      return self._user_settings

  def __getattr__(self, attr):   # 形參attr對應實參是DEFAULT_PARSER_CLASSES
      if attr not in self.defaults:
          raise AttributeError("Invalid API setting: '%s'" % attr)

      try:
          val = self.user_settings[attr]
      except KeyError:
          val = self.defaults[attr]

      if attr in self.import_strings:
          val = perform_import(val, attr)  # 參考動態import理解

      self._cached_attrs.add(attr)
      setattr(self, attr, val)
      return val

      api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
     # 注意:api_settings是APISettings類的實例化對象,由於對象api_settings沒有DEFAULT_PARSER_CLASSES屬性,因此api_settings.DEFAULT_PARSER_CLASSES時,會執行APISettings類的__getattr__方法,而且將DEFAULT_PARSER_CLASSES做爲參數傳入。

三、本身指定解析數據類型

  知道了DRF的APIView封裝了哪幾個解析器類(JSONParser, FormParser,MultiPartParser)以後,咱們能夠根據須要本身定義解析器,以下:api

from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
class LoginView(APIView):
   parser_classes = [JSONParser]   # 只須要解析JSON數據
   # parser_classes = [] 則不能解析任何數據類型
    def get(self, request):
        return render(request, 'login.html')

    def post(self, request):
        request.data    # 解析後的數據
        return HttpResponse("Ok")

2、序列化組件的使用及接口設計

一、django原生serializer(序列化)的使用

from django.core.serializers import serialize    # 1.導入模塊
class CourseView(APIView):
    def get(self, request):
        course_list = Course.objects.all()    # 2.獲取queryset
         # 3.對queryset進行序列化
        serialized_data = serialize('json', course_list) 
         # 4.返回序列化後的數據
        return HttpResponse(serialized_data)

二、經過DRF的序列化組件進行接口設計

  1)參考圖書管理系統的表結構,models.py以下:

from django.db import models
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to='Publish', related_name='book', on_delete=models.CASCADE)
    authors = models.ManyToManyField(to='Author')


class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

    def __str__(self):
        return self.name


class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    def __str__(self):
        return self.name

  2)有以下幾個接口

GET       127.0.0.1:8000/books/        # 獲取全部數據,返回值: [{}, {}]
GET       127.0.0.1:8000/books/{id}    # 獲取一條數據,返回值:{}
POST      127.0.0.1:8000/books/        # 新增一條數據,返回值:{}
PUT       127.0.0.1:8000/books/{id}    # 修改數據,返回值:{}
DELETE    127.0.0.1:8000/books/{id}    # 刪除數據,返回空

  3)經過序列化組件進行get接口(獲取全部數據)設計,序列化組建使用步驟以下:

    - 導入序列化組件 from rest_feanmework import serializers

    - 定義序列化類,繼承serializers.Serializer(建議單首創建一個模塊存放全部序列化類);

    - 定義須要返回的字(字段類型能夠與model中類型不一致,參數也可調整),字段名稱要與model中一致,若不一直則經過source參數指定原始的字段名;

    - 在GET接口邏輯中,獲取queryset;

    -  開始序列化: 獎queryset做爲第一個參數傳給序列化類,many默認爲false,若是返回的數據是一個含多個對象的queryset,須要改many=True;

    - 返回:將序列化對象的data屬性返回便可;

  4)爲了解耦,咱們新建一個名爲app_serializers.py的模塊,將全部的序列化的使用集中在這個模塊中:

from rest_framework import serializers  #  導入序列化模塊

from .models import Book

# 建立序列化類
class BookSerializer(serializers.Serializer):
    nid = serializers.CharField(max_length=32)
    title = serializers.CharField(max_length=128)
    price  = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField(max_length=32)
    authors = serializers.CharField(max_length=32)

  5)視圖代碼以下:

from rest_framework.views import APIView
from rest_framework.response import Response
from .app_serializers import BookSerializer
from .models import Book, Publish, Author

class BookView(APIView):
    def get(self, request):
        origin_data = Book.objects.all()  # 獲取queryset
        # 開始序列化(參數many=True表示有多條數據,默認爲False)
        serialized_data = BookSerializer(origin_data, many=True)
        # 將序列化對象的data屬性返回
        return Response(serialized_data.data)

  上面的接口邏輯中,咱們使用了Response對象,它是drf從新封裝的響應對象,該對象在返回響應數據時會判斷客戶端類型(瀏覽器或者postman),若是是瀏覽器,它會以web頁面的形式返回,若是時postman這類工具,就直接返回json類型的數據。

  下面是經過postman請求該接口後的返回數據,能夠看到,除了ManyToManyField字段不是咱們想要的的外,其餘都沒有問題:

[
    {
        "nid": "1",
        "title": "python初級",
        "price": "188.00",
        "publish": "清華大學出版社",
        "authors": "serializer.Author.None"
    },
    {
        "nid": "2", 
        "title": "python中級",
        "price": "78.00",
        "publish": "清華大學出版社",
        "authors": "serializer.Author.None"
    },
]

  那麼,多對多來講怎麼處理呢?若是將source參數定義爲「authors.all」,那麼取出來的結果將是要給QuerySet,對於前端來講,這樣的數據並非特別友好,咱們可使用以下方式:

from rest_framework import serializers  # 導入序列化模塊
# 建立序列化類
class BookSerializer(serializers.Serializer):
    nid = serializers.CharField(max_length=32)
    title = serializers.CharField(max_length=128)
    price  = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField(max_length=32)
    authors = serializers.SerializerMethodField()

    def get_authors(self, author_object):
        authors = list()

        for author in author_object.authors.all():
            authors.append(author.name)

        return authors

  注意:get_必須與字段字段名稱一致,不然報錯。

  6)經過序列化組件進行post接口(提交一條數據)設計,步驟以下:

    - 定義post方法:在視圖類中定義post方法;

    - 開始序列化:經過上面定義的序列化類,建立一個序列化對象,傳入參數data=request.data(application/json)數據;

    - 校驗數據:經過實例對象的is_valid()方法,對請求數據的合法性進行校驗;

    - 保存數據:調用save()方法,將數據插入數據庫;

    - 插入數據到多對多關係表:若是有多對多字段,手動插入數據到多對多關係表;

    - 返回:將插入的對象返回;

  注意:由於多對多關係字段是咱們自定義的,並且必須這樣定義,返回的數據纔有意義,而用戶插入數據的時候,沒法找到這個字段類型SerializerMethodField,因此,序列化類不能幫咱們插入數據到多對多表,咱們必須手動插入數據,所以序列化類要作以下修改:

from rest_framework import serializers  # 1.導入序列化模塊

from .models import Book

# 2.建立序列化類
class BookSerializer(serializers.Serializer):
    # nid字段只須要傳給客戶端,用戶提交不須要id,因此read_only=True
    nid = serializers.CharField(read_only=True, max_length=32)
    title = serializers.CharField(max_length=128)
    price  = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField(max_length=32)
    # SerializerMethodField默認read_only=True
    authors = serializers.SerializerMethodField()

    def get_authors(self, author_object):
        authors = list()
        for author in author_object.authors.all():
            authors.append(author.name)
        print(authors)

        return authors

    # 必須手動插入數據,所以post方法提交數據必須有create方法
    def create(self, validated_data):
        print(validated_data) # validated_data爲過濾以後的數據
            # {'title': '手冊', 'price': Decimal('123.00'), 'publish': '3'}
            validated_data['publish_id'] = validated_data.pop('publish')
            book = Book.objects.create(**validated_data)

            return book

  根據接口規範,咱們不須要新增url,只須要在上面視圖類中定義一個post方法便可,代碼以下:

from rest_framework.views import APIView
from rest_framework.response import Response
from .app_serializers import BookSerializer
from .models import Book, Publish, Author

class BookView(APIView):
    def get(self, request):
        origin_data = Book.objects.all()
        serialized_data = BookSerializer(origin_data, many=True)
        return Response(serialized_data.data)

    def post(self, request):
        verfied_data = BookSerializer(data=request.data)

        if verfied_data.is_valid():
            book = verfied_data.save()
         # 手動綁定多對多關係,也能夠放到create方法中去
            authors = Author.objects.filter(nid__in=request.data['authors'])
            book.authors.add(*authors)
            return Response(verfied_data.data)
        else:
            return Response(verfied_data.errors)

 分析:上面這種方法有兩個問題:一個是須要手動插入數據(寫序列化類中寫create方法),另外一個是若是字段不少,寫序列化類的字段也會變成一種負擔,那麼有沒有更簡單的方式呢?固然,那就是用ModelSerializer。

  7)使用ModelSerializer序列化組件寫上面的get和post接口,修改app_serializers.py代碼以下:

from rest_framework import serializers  
        from .models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = (
            'title',
            'price',
            'publish',
            'authors',
            'author_list',
            'pubName',
            'pubCity'
        )
        extra_kwargs = {
            'publish':{'write_only':True},
            'authors':{'write_only':True}
        }

    pubName = serializers.CharField(max_length=32, read_only=True, source='publish.name')
    pubCity = serializers.CharField(max_length=32, read_only=True, source='publish.city')

    # 多對多字段
    author_list = serializers.SerializerMethodField()

    def get_author_list(self, book_obj):
        authors = list()

        for author in book_obj.authors.all():
            authors.append(author.name)

        return authors

3、補充知識點

一、訪問對象一個不存在的屬性會執行類的__getattr__方法,以下:

class Person(object):
    def __init__(self, name, age): 
        self.name = name
        self.age = age

    def __getattr__(self, item):
        print(item)

jihong = Person("jihong", 20)
print(jihong.name)  # jihong
jihong.hobby   # hobby

二、動態import

# foo.py文件
def foo():
    print('this is foo')

# test.py文件
import importlib
module_path = input('請輸入要導入的模塊')  # 輸入 foo
module = importlib.import_module(module_path)
print(module)
# <module 'foo' from 'D:\\@Lily\\drfserver\\classbasedview\\foo.py'>
module.foo()   # 執行foo.py模塊中的foo函數
# this is foo

三、多繼承(參考面向對象的對繼承C3算法)

class A(object):
    def foo(self):
        print('A.foo')

class B(A):
    def foo(self):
        print('B.foo')
        super().foo()

class C(A):
    def foo(self):
        print('C.foo')
        super().foo()

class D(B, C):
    def foo(self):
        print('D.foo')
        super().foo()

d = D()
d.foo()

 執行結果以下:

  D.foo

  B.foo

  C.foo

  A.foo

四、Django settings文件查找順序

  咱們在使用django的時候,常常會使用到它的settings文件,經過在settings文件中定義變量,

  咱們能夠在程序的任何地方使用這個變量,好比,假設在settings裏邊定義了一個變量NAME='Lily',雖然能夠在項目的任何地方使用:

1
2
>>>  from  drf_server  import  settings
>>>  print (settings.NAME)    # Lily

  可是,這種方式並非被推薦和建議的,由於除了項目自己的settings文件以外,django程序自己也有許多配置信息,都存在django/conf/global_settings.py模塊裏面,包括緩存、數據庫、密鑰等,若是咱們寫from drf_server import settings,只是導入了項目自己的配置信息,當須要用到django默認的配置信息的時候,還須要再次導入,即from django.conf import settings,因此建議的導入方式是:

1
2
>>>  from  django.conf  import  settings
>>>  print (setting.NAME)

  使用上面的方式,咱們除了可使用自定義的配置信息(NAME)外,還可使用global_settings中的配置信息,不須要重複導入,django查找變量的順序是先從用戶的settings中查找,而後在global_settings中查找,若是用戶的settings中找到了,則不會繼續查找global_settings中的配置信息,假設我在用戶的settings裏面定義了NAME='Lily',在global_settings中定義了NAME='Alex',則請看下面的打印結果:

>>> from django.conf import settings
>>> print(settings.NAME)   # Lily

  可見,這種方式更加靈活高效,建議使用。

五、序列化類中的字段名能夠與model中的不一致,可是須要使用source參數來告訴組件原始的字段名,以下:

from rest_framework import serializers  # 導入序列化模塊

# 建立序列化類
class BookSerializer(serializers.Serializer):
    nid = serializers.CharField(max_length=32)
    bookTitle = serializers.CharField(max_length=128, source='title')
    price  = serializers.DecimalField(max_digits=5, decimal_places=2)
    # source也能夠用於ForeignKey字段
    pubName = serializers.CharField(max_length=32, source='publish.name')
    pubCity = serializers.CharField(max_length=32, source='publish.city')
    # 多對多字段source參數爲「authors.all」,則取出來的結果是QuerySet,不推薦
    authors = serializers.CharField(source='authors.all')
相關文章
相關標籤/搜索