咱们知道,浏览器能够向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')