咱們知道,瀏覽器能夠向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")
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)
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
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
# 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
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文件,經過在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
可見,這種方式更加靈活高效,建議使用。
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')