django-rest-framework-源码解析002-序列化/请求模块/响应模块/异常处理模块/渲染模块/十大接口
简介
当我们使用django-rest-framework框架时, 项目必定是前后端分离的, 那么前后端进行数据交互时, 常见的数据类型就是xml和json(现在主流的是json), 这里就需要我们django后台对json和python字典(dict)进行频繁的转化, 当然我们可以使用json模块的loads和dumps方法去手动转换, 但是这样的操作步骤固定且频繁, 于是可以将这个转化=换步骤进行封装, 让我们实际开发时无需在数据转换上花太多的时间.
rest_framework模块就提供了序列化器这个功能, 专门用来处理数据转换, 将python格式的数据转化为json被称为序列化, 一般是用在返回给前端时使用. 将json数据转化为python格式的数据被称为反序列化, 一般是用在接收前端提交的数据时使用, 且我们一般都要对前台提供过来的数据进行校验, 序列化器中也提供了校验相关的hook, 可以理解为提供了让我们编写自定义的校验函数的位置
rest_framework提供了多个序列化器类供我们使用, 他们都在rest_framework.serializers中:
Serializer: 是DRF提供的序列化基本类, 需要自己编写所有的字段以及create和update方法,比较底层,抽象度较低,接近Django 的form表单类的层次。
ModelSerializer: 更常用的序列化类, 无需自己编写字段以及create和update方法, 它会根据指向的model,自动生成默认的 字段和简单的create及update方法。
ListSerializer: 一般直接使用的比较少, 需要使用到的多数情况是要定制ListSerializer行为, 如当需要批量更新时, 就需要额外定义一个ListSerializer类来重写update方法
HyperlinkedModelSerializer: 类似于ModelSerializer
类,不同之处在于它使用超链接来表示关联关系而不是主键。默认情况下序列化器将包含一个url
字段而不是主键字段。
Serializer
定义Serializer类, 需要定义待序列化或反序列化的字段, 重写create和update方法
from rest_framework import serializer
from app.models import Comment class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField() def create(self, validated_data):
return Comment(**validated_data) def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
instance.content = validated_data.get('content', instance.content)
instance.created = validated_data.get('created', instance.created)
return instance
ModelSerializer和ListSerializer
定义ModelSerializer类
from rest_framework.serializers import ModelSerializer, ListSerializer
from rest_framework.exceptions import ValidationError
from books.models import Book, Author, AuthorDetail, Publish # 群改调用update时, 需要通过 ListSerializer 重写update方法才能做更新操作
# instance为需要修改的对象列表, validated_data为对应的更新后的数据
class V2BookListSerializer(ListSerializer):
def update(self, instance, validated_data):
for index, obj in enumerate(instance):
self.child.update(instance=obj, validated_data=validated_data[index])
return instance class V2BookSerializer(ModelSerializer):
class Meta:
model = Book
# 序列化和反序列化的字段都合并到了fields中
fields = ['name', 'price', 'img', 'author_list', 'publish_name', 'publish', 'authors']
# 所有字段
# fields = '__all__'
# 除去这些字段不展示
# exclude = ['id', 'is_delete', 'create_time']
# 自动展示深度
# depth = 1
# 通过write_only设置只参与反序列化, read_only只参与序列化
extra_kwargs = {
# 校验规则
'name': {
# 哪些校验规则
'min_length': 1,
'required': True,
# 这些校验规则对应的错误消息
'error_messages': {
'required': '为必填项'
}
},
# 只参与反序列化
'publish': {
'write_only': True
},
'authors': {
'write_only': True
},
# 只参与序列化
'img': {
'read_only': True
},
# 以下自定义字段可以省略, 默认只参与序列化
# 'author_list': {
# 'read_only': True
# },
# 'publish_name': {
# 'read_only': True
# }
}
# 群改时需要使用ListSerializer并重写update()方法
list_serializer_class = V2BookListSerializer # 反序列化校验规则
def validate_name(self, value):
"""校验书名"""
# 长度
if len(value) > 15:
raise ValidationError('不能超过10位')
return value # 全局校验
def validate(self, attrs):
"""联合校验"""
# context是视图类传给序列化类的参数
print(self.context.get('request').method)
# 校验:同一出版社不能的书名不能重复
book = Book.objects.filter(name=attrs.get('name'), publish=attrs.get('publish'), is_delete=False)
if book:
# 已存在同名书籍
raise ValidationError({'status': 1, 'msg': '该出版社已存在同名书籍'})
return attrs
定义一个Meta类, 在Meta下面可以定义如下属性:
1. 设置model = Book, 将序列化类与模型类进行关联
2. 将需要序列化和反序列化的字段都放在fields列表中
2.1 一些自定的序列化字段如 'author_list' 和 'publish_name', 这些字段不是数据库字段, 只是用于序列化给前台更好的展示, 需要在Book的model类中额外添加:
@property
def publish_name(self):
return self.publish.name @property
def author_list(self):
# mobile=models.F('detail__mobile')用来将查询的detail__mobile重命名为mobile
authors = self.authors.values('name', 'age', mobile=models.F('detail__mobile'))
return authors
3. 设置额外关键字参数extra_kwargs,每个字段都有很多属性可以设置, 在extra_kwargs设置时格式为:
extra_kwargs = {
'字段名1': {
'属性名1': 属性值1,
'属性名2': 属性值2,
...
},
'字段名2': {
'属性名1': 属性值1,
'属性名2': 属性值2,
...
},
....
}
3.1 read_only(默认为False
)只读字段, 只能在序列化时转换为json数据给前台读取, 而在反序列化时不能使用该字段, 因为反序列化需要将前台传入的数据写入数据库中, 如果想设置只能写入, 不能读取, 那么就要设置write_only(默认为False
)为True. 这样就能将只参与序列化或反序列化的字段拆开. 同一个字段不能同时设置read_only为True和write_only为True
3.2 可以设置一些字段的简单验证规则, 如最大长度(max_length), 最小长度(min_length), 是否必输(requierd(默认为False
))等
3.3 error_messages用于将前面定义的简单验证规与自定义的错误消息进行映射
4. 还有一些其他属性如
# fields列表为model中定义的所有字段
# fields = '__all__'
# 除去下面这些字段, 其他model字段都需要
# exclude = ['id', 'is_delete', 'create_time']
# 自动展示深度, 当不展示深度时, 若图书中有出版社信息, 则出版社字段暂时的是其对应的出版商ID, 如果设置了深度为1, 则出版社字段默认展示为所有出版社序列化类中展示的字段, 深度为2以此类推
# depth = 1
5. 在进行批量更新操作时, 需要使用ListSerializer并重写update()方法, 重写update方法的逻辑其实就是遍历model类对象, 再单独调用ModelSerializer的update方法
定义反序列化的验证
反序列化时需要校验request中的data是否有效, 因此在序列化类中可以重写 ''validate_字段名(self, value)'' 和 ''validate(self, attrs)'' 方法, 分别用来做字段的单独校验和字段的联合校验, 如果校验失败, 则抛出ValidationError('错误信息xxx') 异常
在视图中使用序列化类进行序列化和反序列化
在view中的method中使用序列化类, 这里主要实现10种接口: 单查, 群查, 单增, 群增, 单删, 群删, 单改(整体字段改), 单改(局部字段改), 群改(整体字段改), 群改(局部字段改)
序列化(以get查询为例)
from rest_framework.views import APIView
from rest_framework.response import Response
from books.serializers import BookSerializer
from books import models class V2BookView(APIView):
def get(self, request, *args, **kwargs):
# 获取pk
pk = kwargs.get('pk')
try:
book = models.Book.objects.get(pk=pk, is_delete=False)
except Exception:
return MyResponse(status=1, msg='该书不存在')
# 创建序列化对象, instance参数为获取到的model对象book
serializer = V2BookSerializer(instance=book)
# serializer.data返回的是序列化后的json格式数据
data = {
'status': 0,
'msg': 'post OK',
'result': serializer.data
}
return Response(data=data)
主要步骤为:
1. 查询model对象
2. 使用序列化类通过model对象创建序列化对象
3. 调用序列化对象的data属性获取到序列化后的结果
4. 将结果创建Response对象并返回
反序列化(以post为例)
def post(self, request, *args, **kwargs):
# 创建反序列化对象, data参数为request.data, request.data能获取到前台提交的常见格式的数据
serializer = V2BookSerializer(data=request.data)
# 调用is_valid进行数据校验
serializer.is_valid(raise_exception=True)
# 调用save创建model对象并保存
serializer.save()
data = {
'status': 0,
'msg': 'post OK',
'result': serializer.data
}
# 返回结果
return Response(result=serializer.data)
主要步骤为:
1. 获取前台传来的数据, 直接丢给序列化类创建序列化对象
2. 调用序列化对象的is_valid方法进行序列化类中的校验, 若校验失败则会直接抛出异常
3. 若校验成功则调用save()将数据保存至数据库
4. 最后将结果创建Response对象并返回
分析序列化的源码
序列化的继承关系MRO
查看序列化的继承关系MRO可以看到继承顺序, 也是我们看源码的优先顺序
print(BookSerializer.__mro__)
# 打印结果
(<class 'books.serializers.BookSerializer'>,
<class 'rest_framework.serializers.ModelSerializer'>,
<class 'rest_framework.serializers.Serializer'>,
<class 'rest_framework.serializers.BaseSerializer'>,
<class 'rest_framework.fields.Field'>,
<class 'object'>)
创建序列化类的对象
序列化与反序列化都创建了序列化类的对象, 不同的是序列化传入的参数对为 instance=book , 反序列化传入的参数对为 data=request.data, 源码中根据MRO顺序可以找到序列化基类BaseSerializer的__init__方法中将instance参数和data参数分别存到了不同的属性中
def __init__(self, instance=None, data=empty, **kwargs):
self.instance = instance
if data is not empty:
self.initial_data = data
self.partial = kwargs.pop('partial', False)
self._context = kwargs.pop('context', {})
kwargs.pop('many', None)
super().__init__(**kwargs)
除了instance和data参数外, 还能接受
1. many=True, (默认False)用来序列化或反序列化多个对象, 在群增, 群删, 群查, 群改时使用
2. partial=True, (默认False)用来序列化或反序列化类中fields中定义的部分字段, 本质上是失效掉了其他字段的required=True的属性, 在单改(局部字段), 群改(局部字段)时使用
3. context={}, (默认None)用来给序列化类传递额外所需的信息, 如request对象等, 实现view和serializer的信息传递
反序列化的验证
序列化类的is_valid用来校验数据是否正确, 根据MRO顺序可以找到源码在BaseSerializer.is_valid中
它接收一个参数raise_exception(默认False), 若设置为True, 则在调用run_validation()校验时如果出错了, 那么就直接继续把异常抛出, 如果设置为False, 则返回bool类型是否校验通过(是否合法)
可以看到具体的校验逻辑还在self.run_validation(中)
def is_valid(self, raise_exception=False):
代码省略......
if not hasattr(self, '_validated_data'):
try:
# 运行验证逻辑
self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
else:
self._errors = {}
# 判断是否直接抛出异常
if self._errors and raise_exception:
raise ValidationError(self.errors)
# 不抛出异常的话返回Bool类型
return not bool(self._errors)
继续根据MRO顺序可以找到Serializer.run_validation
def run_validation(self, data=empty):
代码省略......
# 校验自定义校验中的单个字段
value = self.to_internal_value(data)
try:
# 运行验证器中的验证
self.run_validators(value)
# 调用验证方法
value = self.validate(value)
assert value is not None, '.validate() should return the validated data'
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc)) return value
1. 首先运行的是 self.to_internal_value(data) , 根据MRO找到serializers.to_internal_value, 可以看到通过反射获取到序列化类中自定的单个字段的验证'validate_字段名'的校验方法validate_method, 然后运行该方法
def to_internal_value(self, data):
代码省略......
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
代码省略......
else:
set_value(ret, field.source_attrs, validated_value) if errors:
raise ValidationError(errors) return ret
2. 获取验证器 self.run_validators(value) , 点入代码可以看到默认的验证器 default_validators = [] 是一个空列表, 如果需要自定义验证器的话需要在序列化类中定义验证器
3. 调用验证方法 self.validate(value) , 这里根据MRO优先找到的就是我们序列化类中自定义的 validate 方法
save()保存操作
根据MRO找到BaseSerializer.save()方法, 若实例存在, 则调用self.update()更新, 不存在则调用self.create()创建, 继续在MRO中从左往右找update和create方法
def save(self, **kwargs):
代码省略.....
# 存在则update
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
# 不存在则create
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
return self.instance
发现ModelSerializer中就重写了create()和update()方法,create()中调用了 instance = ModelClass._default_manager.create(**validated_data) 创建model实例对象, update()中遍历验证后的字段, 调用setattr方法设置model对象的属性 setattr(instance, attr, value) , 最后调用 instance.save() 保存修改
请求模块
上一章讲过在RDF真正入口为rest_framework.view的dispatch(), dispatch的第一步就是 request = self.initialize_request(request, *args, **kwargs) 封装请求, 具体代码为
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
# 获取需要解析的数据
parser_context = self.get_parser_context(request) return Request(
request,
parsers=self.get_parsers(), # 获取解析器, 在调用request.data时会使用到
authenticators=self.get_authenticators(), # 获取权限器, 在权限认证时会使用到
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
创建了一个DRF的Request对象, 查看rest_framework.request.Request类的__init__方法, 可以看到将django原生的request对象封装至了DRF的Request的_request属性中, 在具有了原生request所有的功能的同时, 又添加了一些其他属性, 如解析器, 认证器等等
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__)
)
# 将原生request封装至_request属性中
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,)
之前原生request获取url参数和form表单提交的参数是分别使用request.GET和request.POST, 现在我们就可分别使用DRF的request.query_params和request.data获取, 并且request.data能获取除form表单格式外的其他常见格式的数据, 如json等等
响应模块
DRF提供了一个Response类来支持HTTP内容响应, 该类是Django中 SimpleTemplateResponse 类的一个子类, 我们并不一定非要使用DRF的 Response 类进行响应,也可以返回常规的 HttpResponse 或 者 StreamingHttpResponse 对象,但是使用 Response 类可以提供一个多种格式的更漂亮 的界面。响应模块的源码在rest_framework.response.py中
class Response(SimpleTemplateResponse):
"""
An HttpResponse that allows its data to be rendered into
arbitrary media types.
"""
def __init__(self, data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None): super().__init__(None, status=status) if isinstance(data, Serializer):
msg = (
'You passed a Serializer instance as data, but '
'probably meant to pass serialized `.data` or '
'`.error`. representation.'
)
raise AssertionError(msg) self.data = data
self.template_name = template_name
self.exception = exception
self.content_type = content_type if headers:
for name, value in headers.items():
self[name] = value
查看__init__()方法可以看到其接受的参数如下:
data : 一个字典, 包含想要响应的数据
status : 响应的状态码。默认是200。
template_name : 当选择 HTMLRenderer 渲染器时,指定要使用的模板的名称。
headers : 一个字典,包含响应的HTTP头部信息。
content_type : 响应的内容类型。通常由渲染器自行设置,由协商内容确定,但是在某 些情况下,你需要明确指定内容类型。
自定义响应类
直接调用Response时 return Response(data=serializer.data), 返回的结果只有查询出来的数据, 而一般我们都会加上一些额外的与前台约定的字段, 如status或者error_msg等, 所以我们可以自定义响应类
from rest_framework.response import Response class MyResponse(Response):
"""继承Response, 封装自己的response"""
def __init__(self, status=0, msg='ok', http_status=None, headers=None, exception=False, **kwargs):
# 设置data
data = {
'status': status,
'msg': msg,
**kwargs
}
# 继承父类
super().__init__(data=data, status=http_status, template_name=None, headers=headers, exception=exception,
content_type=None)
1. 继承rest_framework的Response类
2. 在init方法中添加自定义的参数, 如status, msg等, 其他键值对参数通过**kwargs获取
3. 将自定义的参数和**kwargs(拆包)参数封装到data字典中
4. 调用父类的__init__方法, 将data字典传入
渲染模块(DRF常用模块的配置方法)
在rest_framework的dispatch方法中, 最后调用了 self.response = self.finalize_response(request, response, *args, **kwargs) 生成最终的response, 在 finalize_response 中设置了response的渲染器 response.accepted_renderer = request.accepted_renderer , 渲染器最终是通过 get_renderers 返回的, 其实返回的就是一个个渲染类 renderer_classes 所实例化的对象组成的列表
def get_renderers(self):
"""
Instantiates and returns the list of renderers that this view can use.
"""
return [renderer() for renderer in self.renderer_classes]
DRF的rest_framework.views.py中提供了很多类似 get_renderers 的方法, 如获取解释器 get_parsers , 获取认证器 get_authenticators , 获取权限器 get_permissions 等等
且这些get_xxx方法都是返回一个对应类的列表, 且这些类基本都可以手动全局或局部配置, 若没有手动配置则默认取APIView默认的设置
class APIView(View):
# The following policies may be set at either globally, or per-view.
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
这些默认配置都在rest_framework.settings.py中, 默认的配置如下
DEFAULTS = {
# Base API policies
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
'DEFAULT_THROTTLE_CLASSES': [],
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
'DEFAULT_VERSIONING_CLASS': None, # Generic view behavior
'DEFAULT_PAGINATION_CLASS': None,
'DEFAULT_FILTER_BACKENDS': [], # Schema
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema', # Throttling
'DEFAULT_THROTTLE_RATES': {
'user': None,
'anon': None,
},
'NUM_PROXIES': None, # Pagination
'PAGE_SIZE': None, # Filtering
'SEARCH_PARAM': 'search',
'ORDERING_PARAM': 'ordering', # Versioning
'DEFAULT_VERSION': None,
'ALLOWED_VERSIONS': None,
'VERSION_PARAM': 'version', # Authentication
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_TOKEN': None, # View configuration
'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description', # Exception handling
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
'NON_FIELD_ERRORS_KEY': 'non_field_errors', # Testing
'TEST_REQUEST_RENDERER_CLASSES': [
'rest_framework.renderers.MultiPartRenderer',
'rest_framework.renderers.JSONRenderer'
],
'TEST_REQUEST_DEFAULT_FORMAT': 'multipart', # Hyperlink settings
'URL_FORMAT_OVERRIDE': 'format',
'FORMAT_SUFFIX_KWARG': 'format',
'URL_FIELD_NAME': 'url', # Input and output formats
'DATE_FORMAT': ISO_8601,
'DATE_INPUT_FORMATS': [ISO_8601], 'DATETIME_FORMAT': ISO_8601,
'DATETIME_INPUT_FORMATS': [ISO_8601], 'TIME_FORMAT': ISO_8601,
'TIME_INPUT_FORMATS': [ISO_8601], # Encoding
'UNICODE_JSON': True,
'COMPACT_JSON': True,
'STRICT_JSON': True,
'COERCE_DECIMAL_TO_STRING': True,
'UPLOADED_FILES_USE_URL': True, # Browseable API
'HTML_SELECT_CUTOFF': 1000,
'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...", # Schemas
'SCHEMA_COERCE_PATH_PK': True,
'SCHEMA_COERCE_METHOD_NAMES': {
'retrieve': 'read',
'destroy': 'delete'
},
}
我们可以在django项目的settings.py中全局配置这些属性类, 如配置渲染器
REST_FRAMEWORK = {
# 渲染器类
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer', # 渲染json
'rest_framework.renderers.BrowsableAPIRenderer', # 渲染带有API返回结果的浏览器界面
],
# 异常处理类
'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler'
}
当然我们也可以进行局部配置, 即在具体的视图类中配置, 如
class V2BookView(APIView):
# 渲染器类
renderer_classes = [
'rest_framework.renderers.JSONRenderer', # 渲染json
'rest_framework.renderers.BrowsableAPIRenderer', # 渲染带有API返回结果的浏览器界面
] def get(self, request, *args, **kwargs):
.........
异常处理模块
在rest_framework的dispatch方法中, 把三大校验模块和反射处理定义的method方法都包在一个try...except中
try:
# 三大校验: 认证模块/权限模块/频率模块
self.initial(request, *args, **kwargs) # Get the appropriate handler method
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)
其中抛出的异常都被 response = self.handle_exception(exc) 处理了, 进入 self.handle_exception(exc) 可以看到 exception_handler = self.get_exception_handler() 获取了异常处理句柄, 而句柄返回的就是 return self.settings.EXCEPTION_HANDLER , 其和上面的渲染模块类似, 都是默认从rest_framework.settings中获取异常处理类, 可以看到默认的异常处理方法为 rest_framework.views.exception_handler , 代码如下:
def exception_handler(exc, context):
"""
Returns the response that should be used for any given exception. By default we handle the REST framework `APIException`, and also
Django's built-in `Http404` and `PermissionDenied` exceptions. Any unhandled exceptions may return `None`, which will cause a 500 error
to be raised.
"""
if isinstance(exc, Http404):
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied() if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait if isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
data = {'detail': exc.detail} set_rollback()
return Response(data, status=exc.status_code, headers=headers) return None
可以看到上面的异常处理只处理三种异常类型: Http404异常, 权限异常和DRF定义的APIException, 而对于其他异常它都返回的是None, 而None能抛出500内部错误
自定义异常处理方法
我们前面自定的Response类中,额外定义了status和msg两个返回值. 由于看了上面默认的异常处理类, 发现其他不处理的异常它都会返回None. 这里我们为了统一返回风格, 就可以再自定义一个异常处理类, 如:
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
from rest_framework import status def exception_handler(exc, context):
# 调用drf内置的异常处理, 返回的结果若为None, 则自定义返回异常
ret = drf_exception_handler(exc, context)
if ret:
return ret
data = {
'detail': f'服务器内部错误:{exc}'
}
return Response(data=data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
1. 导入DRF默认的exception_handler/Response/status, 并重命名 exception_handler 为 drf_exception_handler
2. 模仿DRF默认的exception_handler自定义一个同名同参函数 exception_handler
3. 在自定义的exception_handler中先调用DRF默认的exception_handler并获取其返回值, 若返回值不为None, 说明异常能被DRF正常处理, 若返回值为None, 则再定义一个data字典, 里面带上具体的异常消息, 最后通过创建Response对象返回, 创建时可以赋值http状态为500, status.HTTP_500_INTERNAL_SERVER_ERROR 其实对应的值就是500, DRF只是把500用一个更容易理解的变量包装了一下而已
10种接口的简单实现
在一个视图中可以实现10种接口对图书的增删改查接口, 分别为: 单增, 群增, 单删, 群删, 单局部改, 单整体改, 群局部改, 群整体改, 单查, 群查
路由配置urls.py为:
from django.urls import path
from books import views urlpatterns = [
path('v2/', views.V2BookView.as_view()),
path('v2/<int:pk>/', views.V2BookView.as_view()),
]
视图类views.py为:
class V2BookView(APIView): def get(self, request, *args, **kwargs):
# 获取pk
pk = kwargs.get('pk') if pk:
# 单查
try:
book = models.Book.objects.get(pk=pk, is_delete=False)
except Exception:
return MyResponse(status=1, msg='该书不存在') serializer = V2BookSerializer(book)
else:
# 群查
books = models.Book.objects.filter(is_delete=False).all()
serializer = V2BookSerializer(books, many=True)
return MyResponse(result=serializer.data) # 单增: 传的数据是与model对应的字典
# 群增: 传的是 包含多个model对应字典的 列表或者元组
def post(self, request, *args, **kwargs):
if isinstance(request.data, dict):
# 单增
serializer = V2BookSerializer(data=request.data)
elif isinstance(request.data, list):
# 群增
serializer = V2BookSerializer(data=request.data, many=True)
else:
return MyResponse(status=1, msg='只能为list或列表')
serializer.is_valid(raise_exception=True)
serializer.save()
return MyResponse(result=serializer.data) # 单删 pk
# 群删 pks
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
# 单删
pks = [pk]
else:
# 群删
pks = request.data.get('pks')
# update返回受影响的行
if not models.Book.objects.filter(pk__in=pks, is_delete=False).update(is_delete=True):
return MyResponse(status=1, msg='图书不存在或已被删除')
return MyResponse() # 单整体改 /pk/ dict
# 群整体改 list
# 反序列化的目的是: 将众多数据的校验交给序列化类来处理, 让序列化类扮演反序列化角色
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk')
request_data = request.data
# 将单整体改和群整体改都转化为群整体改
if pk and isinstance(request_data, dict):
# 单整体改
pks = [pk, ]
request_data = [request_data, ]
elif not pk and isinstance(request_data, list):
# 群改, 数据格式: [{"pk":1, "name": "python999"}, {"pk":2, "publish": 4}, {"pk":3, "price": 100}]
pks = []
for item in request_data:
pk = item.get('pk', None)
if pk:
pks.append(pk)
else:
request_data.remove(item)
else:
return MyResponse(status=1, msg='格式必须为list或dict')
# 处理pks, 生成序列化参数instance(books)和data(data_list)
if pks:
books = []
data_list = []
for index, pk in enumerate(pks):
try:
books.append(models.Book.objects.get(pk=pk, is_delete=False))
data_list.append(request_data[index])
except Exception as e:
continue
if books:
serializer = V2BookSerializer(instance=books, data=data_list, many=True,
partial=kwargs.get('partial', False), context={"request": request})
serializer.is_valid(raise_exception=True)
serializer.save()
return MyResponse(result=serializer.data)
return MyResponse(status=1, msg='图书都不存在') # 单局部改 /pk/
# 群局部改
# 局部改和整体改逻辑一样, 只是在序列化时多了一个partial参数
def patch(self, request, *args, **kwargs):
return self.put(request, partial=True, *args, **kwargs)
django-rest-framework-源码解析002-序列化/请求模块/响应模块/异常处理模块/渲染模块/十大接口的更多相关文章
- Django Rest Framework源码剖析(六)-----序列化(serializers)
一.简介 django rest framework 中的序列化组件,可以说是其核心组件,也是我们平时使用最多的组件,它不仅仅有序列化功能,更提供了数据验证的功能(与django中的form类似). ...
- Django Rest Framework源码剖析(八)-----视图与路由
一.简介 django rest framework 给我们带来了很多组件,除了认证.权限.序列化...其中一个重要组件就是视图,一般视图是和路由配合使用,这种方式给我们提供了更灵活的使用方法,对于使 ...
- Django Rest Framework源码剖析(三)-----频率控制
一.简介 承接上篇文章Django Rest Framework源码剖析(二)-----权限,当服务的接口被频繁调用,导致资源紧张怎么办呢?当然或许有很多解决办法,比如:负载均衡.提高服务器配置.通过 ...
- Django Rest Framework源码剖析(五)-----解析器
一.简介 解析器顾名思义就是对请求体进行解析.为什么要有解析器?原因很简单,当后台和前端进行交互的时候数据类型不一定都是表单数据或者json,当然也有其他类型的数据格式,比如xml,所以需要解析这类数 ...
- django之admin源码解析
解析admin的源码 第一步:项目启动,加载settings文件中的 INSTALLED_APPS 里边有几个app就加载几个,按照注册顺序来执行. 第二步:其中加载的是admin.py,加载每一个a ...
- Django Rest Framework源码剖析(二)-----权限
一.简介 在上一篇博客中已经介绍了django rest framework 对于认证的源码流程,以及实现过程,当用户经过认证之后下一步就是涉及到权限的问题.比如订单的业务只能VIP才能查看,所以这时 ...
- Django rest framework源码分析(4)----版本
版本 新建一个工程Myproject和一个app名为api (1)api/models.py from django.db import models class UserInfo(models.Mo ...
- Django rest framework源码分析(3)----节流
目录 Django rest framework(1)----认证 Django rest framework(2)----权限 Django rest framework(3)----节流 Djan ...
- Django rest framework源码分析(1)----认证
目录 Django rest framework(1)----认证 Django rest framework(2)----权限 Django rest framework(3)----节流 Djan ...
- Django rest framework源码分析(2)----权限
目录 Django rest framework(1)----认证 Django rest framework(2)----权限 Django rest framework(3)----节流 Djan ...
随机推荐
- MyBatis学习笔记(2)--缓存
一.什么是缓存 --存在于内存中的临时数据. 为什么使用缓存?--减少和数据库的交互次数,提高执行效率. 适用于缓存的数据: 1.经常查询并且不经常改变的数据. 2.数据的正确与否对最终结果影响较小的 ...
- Python3-设计模式-装饰器模式
装饰器模式 动态的给原有对象添加一些额外的职责,面向切面编程(AOP),多用于和主业务无关,但又必须的业务,如:登录认证.加锁.权限检查等 Python代码实现示例 需求点: 1.在old_func( ...
- Python 偏函数用法全方位解析
Python的functools模块中有一种函数叫“偏函数”,自从接触它以来,发现确实是一个很有用且简单的函数,相信你看完这篇文章,你也有相见恨晚的感觉. 我们都知道,函数入参可以设置默认值来简化函数 ...
- Windows安装 PyCharm
PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试.语法高亮.Project管理.代码跳转.智能提示.自动完成.单元测试.版本控制. ...
- 入门大数据---HDFS-API
第一步:创建一个新的项目 并导入需要的jar包 公共核心包 公共依赖包 hdfs核心包 hdfs依赖包 第二步:将Linux中hadoop的配置文件拷贝到项目的src目录下 第三步:配置windows ...
- 为什么 group by后面 必须跟selecte 后面的除了聚集函数外的所有字段
如:SELECT store_name, SUM(Sales) FROM Store_Information GROUP BY store_name 可以而SELECT store_name, add ...
- VS2017 快捷键
VS2017注释:先CTRL+K 然后CTRL+C (ctrl按住不松,松开k按c) 取消注释:先CTRL+K,然后CTRL+U (ctrl按住不松,松开k按c)
- C# @string $string $@string
@string 保证换行后也属于同一个字符串 (请特别注意\r\n这样也会直接输入,不在起到换行的效果) string execSql = @" SELECT T1.ProcInstID ...
- 服务消费者(Feign-上)
上一篇文章,讲述了Ribbon去做负载请求的服务消费者,本章讲述声明性REST客户端:Feign的简单使用方式 - Feign简介 Feign是一个声明式的Web服务客户端.这使得Web服务客户端的写 ...
- 带大家认识CSS层叠上下文/层叠等级的区别和意义
什么是“层叠上下文” 层叠上下文(stacking context),是HTML中一个三维的概念.在CSS2.1规范中,每个盒模型的位置是三维的,分别是平面画布上的X轴,Y轴以及表示层叠的Z轴.一般情 ...