Django Rest Framework 源码探究(三 其它部分)
RESTful API 设计指南
restful api规范开发指南:
- 参考链接:阮一峰 博客 十条
- 协议 建议使用HTTPS
- 域名 专用域名
- 版本
建议:https://api.example.com/v1/
- 路径
- http动词
对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。
还有两个不常用的HTTP动词。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。 - 过滤信息
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
- 状态码
200 400 404 操作是幂等的 ,幂等
http状态码之外,可以自己设计code - 错误处理
- 返回结果
- Hypermedia API
djangorestframework
- 看源码时要注意的问题
- python3默认都是新式类,继承遵循深度优先原则
- 执行时先顺杆爬执行所有类的new方法,爬杆时深度优先从左至右 不重复
不重复的意思是,来的时候路过,再往后找的时候就不走这了 - 先执行所有的new方法,然后原路返回(倒序)执行所有的init方法
Python的多重继承正如文档所言是深度优先从左至右不重复
在Python里,当你新构造一个对象时,有两个步骤:首先是自底向上,从左至右调用__new__,然后再依照递归栈依次调用__init__
- 安装:pip3 install djangorestframework
- 使用示例 - 博客
一、认证
源码流程分析
二、权限
三、节流
- 认证、权限、节流三者思路一样,明白一个就全通了。
四、版本
版本控制有什么用
- 更方便的提取当前请求所使用的版本
- 可以通过restful 内置的reverse反向生成url
使用
单个视图使用:
class 类中指定:
versioning_class = QueryParameterVersioning
全局使用:
settings中配置生效,全局使用
REST_FRAMEWORK = {
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
"DEFAULT_VERSION":'v1',
"ALLOWED_VERSIONS":['v1','v2'],
"VERSION_PARAM":'version',
"DEFAULT_PARSER_CLASSES":['rest_framework.parsers.JSONParser','rest_framework.parsers.FormParser']
}
E:\space_env\env_for_blog\Lib\site-packages\rest_framework\versioning.py
一个基类,5个版本控制类:
BaseVersioning
QueryParameterVersioning(BaseVersioning)
URLPathVersioning(BaseVersioning)
AcceptHeaderVersioning(BaseVersioning)
HostNameVersioning(BaseVersioning)
NamespaceVersioning(BaseVersioning)
源码探究
BaseVersioning 中有三个函数
def determine_version
返回值是 version 子类覆写的钩子
def reverse
反向生成url,每个子类生成的方式不一样
def is_allowed_version
给后边子类提供可支持的版本号啊
class URLPathVersioning(BaseVersioning):
# 版本错误信息
invalid_version_message = _('Invalid version in URL path.')
def determine_version()
···
return version
def reverse():
return父类的reverse方法
探究
APIView -- dispatch -- self.initial(request, *args, **kwargs)
# Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
def determine_version(self, request, *args, **kwargs):
"""
返回元组: (version, versioning_scheme)
"""
if self.versioning_class is None:
return (None, None)
scheme = self.versioning_class()
return (scheme.determine_version(request, *args, **kwargs), scheme)
self.initial传入的是新request
在APIView - def determine_version里边:获取了version版本, versioning_scheme版本类对象
然后把这俩值封装到request参数里边
# 获取版本
print(request.version)
# 获取处理版本的对象
print(request.versioning_scheme)
reverse
利用restful - reverse
# 反向生成URL(rest framework)
u1 = request.versioning_scheme.reverse(viewname='uuu',request=request)
print(u1)
利用django自带的reverse
# 反向生成URL
u2 = reverse(viewname='uuu',kwargs={'version':2})
print(u2)
这个需要自己指定kwargs
传参方式 - 解析方式
- 基于url传参 如:/users?version=v1
- 基于url的正则方式 如:/v1/users/
- 基于 accept 请求头方式 如:Accept: application/json; version=1.0
- 基于主机名方法 也就是请求域名 如:v1.example.com
- 基于django路由系统的namespace 如:example.com/v1/users/
基于url传参
对应的类是:versioning_class = QueryParameterVersioning
http://127.0.0.1:8000/api/users/?version=v2
基于url路径正则
对应类:URLPathVersioning(BaseVersioning)
url(r'^(?P
http://127.0.0.1:8000/api/users/?version=v2
- 其它几种使用类似,基本也用不到
【注意】
配置或测试URL时,需要保证所输入的URL符合规则
也就是说,变量得是那个v1/v2 才能顺利匹配版本/返回invalid_version_message
如果连URL开头都没匹配上的话,那只能404了,压根走不到版本控制的代码处
五、解析器
HTTP协议须知
后端要想成功的解析出数据,有两点要求:
1. 请求头要写对,比如
Content-Type: application/x-www-form-urlencoded
2. 数据格式要与请求头标识的一致
form提交时:Content-Type: application/x-www-form-urlencoded
后端:request.POST从中取值
application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值
其余情况,需要request.body取值
啥用
rest_framework 解析器,对请求体数据进行解析
BaseParser 基类 没卵用
JSONParser
media_type = 'application/json'
FormParser
media_type = 'application/x-www-form-urlencoded'
MultiPartParser
media_type = 'multipart/form-data'
Parser for multipart form data, which may include file data.
文件传输,也用这个解析
FileUploadParser
media_type = '/'
这个貌似是上传数据用的,用到再看
使用
class ParserView(APIView):
parser_classes = [JSONParser,FormParser,]
"""
JSONParser:表示只能解析content-type:application/json头
JSONParser:表示只能解析content-type:application/x-www-form-urlencoded头
"""
def post(self,request,*args,**kwargs):
print(request.data)
return HttpResponse('ParserView返回')
源码流程
- 获取用户请求
- 获取用户请求体
- 根据用户请求头 和 parser_classes = [JSONParser,FormParser,] 中支持的请求头进行比较
- JSONParser对象去请求体
- request.data 真正调用
封装参数
dispatch - self.initialize_request - parsers=self.get_parsers()
request.data
Request() 类 >>> def data()方法 >>> _load_data_and_files
is_form_media_type 直接copy
或者 self._parse()
media_type = self.content_type - 获取content_type
stream = self.stream - 调用Request/stream()获取body数据
parser = self.negotiator.select_parser(self, self.parsers) - 获取设置的序列化类
parsed = parser.parse(stream, media_type, self.parser_context) - 序列化
try:
return (parsed.data, parsed.files)
except AttributeError:
empty_files = MultiValueDict()
return (parsed, empty_files)
六、序列化Serializer
序列化是什么
【概念】:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),
我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象传输于网络必须进行流化)!
在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!
序列化器允许将诸如查询集和模型实例之类的复杂数据转换为原生 Python 数据类型,然后可以将它们轻松地呈现为 JSON,XML 或其他内容类型。
序列化器还提供反序列化,在首次验证传入数据之后,可以将解析的数据转换回复杂类型(并做验证)。
REST framework 中的序列化类与 Django 的 Form 和 ModelForm 类非常相似。
我们提供了一个 Serializer 类,它提供了一种强大的通用方法来控制响应的输出,以及一个 ModelSerializer 类,
它为创建处理模型实例和查询集的序列化提供了有效的快捷方式。
序列化在django-rest-framework中的典型应用就是:
从数据库中取数据,然后将其序列化为支持网络传输的表述(如json)
验证,对请求发来的数据进行验证.对发来的json串解析为Serializer对应的字段
ser.is_valid()验证,然后ser.validated_data取数据进行操作,或者ser.errors提取错误信息
我们可以通过声明序列来完成,这些序列与Django的表单(forms)工作相似。
forms是DB(后端)与HTML的桥梁,serializers是DB(后端)与传输的转换器
两者在设计思路和使用方式上有很多雷同,源码流程也很相似
初步使用
原始方式
def get(self,request,*args,**kwargs):
roles = models.Role.objects.all().values('id','title')
roles = list(roles)
ret = json.dumps(roles,ensure_ascii=False)
return HttpResponse(ret)
【注意】ensure_ascii=False 表示不转码,就按照数据库原始数据形式来发
方式二,使用serializers
class RolesSerializer(serializers.Serializer):
# 字段名与数据库名称一致
id = serializers.IntegerField()
title = serializers.CharField()
class RolesView(APIView):
def get(self,request,*args,**kwargs):
roles = models.Role.objects.all()
ser = RolesSerializer(instance=roles,many=True)
ret = json.dumps(ser.data, ensure_ascii=False)
# 对于单个对象,many=False
role = models.Role.objects.all().first()
ser = RolesSerializer(instance=role, many=False)
# ser.data 已经是转换完成的结果
ret = json.dumps(ser.data, ensure_ascii=False)
return HttpResponse(ret)
print(ser.data,type(ser.data))
{'title': '开发', 'id': 1} <class 'rest_framework.utils.serializer_helpers.ReturnDict'>
ser.data 是字典(有序字典)
自定义字段
user_type = serializers.CharField() 等价于 xxxxx = serializers.CharField(source="user_type")
转换完的字段名 user_type 变为 xxxxx
所以可以这样写:
user_type = serializers.CharField(source="user_type") 显示存储的代号
user_type = serializers.CharField(source="get_user_type_display") 显示对应的名称
外键关联时:
gp = serializers.CharField(source="group.title")
内部可以根据 . 来split,一直向后取值
内部:
源码会判断传入的值是否callable,如果可调用,自动加()执行
source="user_type" >>> row.user_type
source="get_user_type_display" >>> row.get_user_type_display()
def fun(arg):
if callable(arg):
ret = arg()
else:
ret = arg
print(ret)
def add(a,b):
return a + b
fun('测试')
fun(add(2,6))
真正的自定义显示,钩子
rls = serializers.SerializerMethodField() # 自定义显示
def get_rls(self,row):
role_obj_list = row.roles.all()
ret = []
for item in role_obj_list:
ret.append({'id':item.id,'title':item.title})
return ret
# my_field = serializer.SerializerMethodField(method_name='get_my_field')
# default_method_name = 'get_{field_name}'.format(field_name=field_name)
自定义filed
class MyField(serializers.CharField):
def to_representation(self, value):
print(value)
return "真正的返回值"
使用:
class UserInfoSerializer(serializers.Serializer):
my_field = MyField(source='username')
返回时,显示的就是return内容
继承ModelSerializer
也可以支持自定义字段。
指定字段时能省点事,仅此而已
class UserInfoSerializer(serializers.ModelSerializer):
oooo = serializers.CharField(source="get_user_type_display")
rls = serializers.SerializerMethodField() # 自定义显示钩子
gp = serializers.CharField(source="group.title")
def get_rls(self,row):
return ({'name':'jinshen'})
class Meta:
model = models.UserInfo
# fields = "__all__"
fields = ['id','username','password','group','roles']
class UserInfoView2(APIView):
def get(self, request, *args, **kwargs):
users = models.UserInfo.objects.all()
ser = UserInfoSerializer2(instance=users, many=True, context={'request': request})
ret = json.dumps(ser.data, ensure_ascii=False)
return HttpResponse(ret)
自动序列化连表/深度控制
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
# fields = "__all__"
fields = ['id','username','password','group','roles']
depth = 1 # 0 ~ 10
生成链接
serializers.HyperlinkedIdentityField
# group字段对应生成url链接 - "group": "http://127.0.0.1:8000/api/v1/group/1",
class UserInfoSerializer3(serializers.ModelSerializer):
group = serializers.HyperlinkedIdentityField(view_name='gp',lookup_field='group_id',lookup_url_kwarg='pk')
'''
view_name='gp' url别名
lookup_field='group_id' 对应的字段列,默认pk
lookup_url_kwarg='pk' url中的分组名,可以自己改
没有source参数!
'''
class Meta:
model = models.UserInfo
fields = ['id','username','password','group','roles']
depth = 0 # 0 ~ 10
class UserInfoView3(APIView):
def get(self, request, *args, **kwargs):
users = models.UserInfo.objects.all()
'''如果要生成链接,必须加上context参数'''
ser = UserInfoSerializer3(instance=users, many=True, context={'request': request})
ret = json.dumps(ser.data, ensure_ascii=False)
return HttpResponse(ret)
序列化源码探究
- 序列化应用第一步 --- 类实例化
实例化时先执行构造方法和new方法,__new__ 比 __init__要早执行
顺藤往上摸,找父类的__new__和__init__
- 找父类 - 以ModelSerializer为例
ModelSerializer >>> Serializer >>> BaseSerializer
在BaseSerializer里发现__new__和__init__
- new和init
kwargs.pop('many', False) 取many值,缺省默认False,
def __new__(cls, *args, **kwargs):
# We override this method in order to automagically create
# `ListSerializer` classes instead when `many=True` is set.
if kwargs.pop('many', False):
# many = True时执行,对queryset进行处理
return cls.many_init(*args, **kwargs)
# many缺省或为False时执行,对obj进行处理
return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)
def __init__():
pass 一坨赋值语句封装参数
分析
many = False
return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)
返回的是自己,啥都没做。new就完事了 ,接下来执行init方法去了
many = True
执行 many_init() 方法
这个方法做了一件事 把处理序列化的类指定为 ListSerializer,并返回
小结
ser = RolesSerializer(instance=role, many=False)
类实例化做的事:做区分
对象, Serializer类处理; self.to_representation
QuerySet,ListSerializer类处理; self.to_representation
- 第二步:实例化完该调用了
ret = json.dumps(ser.data, ensure_ascii=False)
ser.data 根据继承关系找 data方法
Serializer - data方法
@property
def data(self):
ret = super(Serializer, self).data #调用父类data方法
return ReturnDict(ret, serializer=self)
BaseSerializer - data方法
if not hasattr(self, '_data'):
if self.instance is not None and not getattr(self, '_errors', None):
self._data = self.to_representation(self.instance)
elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
self._data = self.to_representation(self.validated_data)
else:
self._data = self.get_initial()
return self._data
to_representation方法,
依然是从下往上找,Serializer和BaseSerializer里都有此方法
但是Serializer-to_representation 离得更近,它会执行
- Serializer-to - representation
def to_representation(self, instance):
"""
Object instance -> Dict of primitive datatypes.
"""
ret = OrderedDict() #这就是最后返回给我们的那个有序字典实例
fields = self._readable_fields
for field in fields:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
···
field 是我们在自定义序列化类时指定的 Charfiled IntegerField 这些字段对象
Charfiled 等,里边没有get_attribute() 方法
Charfiled继承自Filed
class Filed() - get_attribute()
def get_attribute(self, instance):
"""
Given the *outgoing* object instance, return the primitive value
that should be used for this field.
"""
try:
return get_attribute(instance, self.source_attrs)
instance 是传入的对象
self.source_attrs就是参数source,
在类的init里可以看到它的封装
在Filed类下的bind()方法,可以看到split . 取值
source_attrs 可以是:group.title get_user_type_display roles.all等等
通过后边的一系列骚操作,不管它是什么,都能从里边拿出想要的东西
上来就跳了,返回单独的get_attribute()方法 把两个重要的参数传入
- E:\space_env\env_for_blog\Lib\site-packages\rest_framework\fields.py
py文件下的get_attribute()方法
def get_attribute(instance, attrs):
for attr in attrs:
# 循环所有的attr(source参数)
try:
if isinstance(instance, collections.Mapping):
instance = instance[attr]
else:
instance = getattr(instance, attr)
except ObjectDoesNotExist:
return None
if is_simple_callable(instance):
try:
instance = instance()
except (AttributeError, KeyError) as exc:
# If we raised an Attribute or KeyError here it'd get treated
# as an omitted field in `Field.get_attribute()`. Instead we
# raise a ValueError to ensure the exception is not masked.
raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc))
return instance
通过在这个函数里遍历,根据source取出各层的instance实例并逐层返回
序列化中的自定义验证
字段级别的验证① 自定义validator类
serializers.py 文件在开头引入包的时候,就引入了fields.py的所有字段
example_field = serializers.CharField(...) 用的其实是fileds里边的Charfiled
class CharField(Field): 继承自 class Field(object):
我们看看Field支持哪些参数:
class Field(object):
default_error_messages = {
'required': _('This field is required.'),
'null': _('This field may not be null.')
}
default_validators = []
def __init__(self, read_only=False, write_only=False,
required=None, default=empty, initial=empty, source=None,
label=None, help_text=None, style=None,
error_messages=None, validators=None, allow_null=False):
default_error_messages 两条默认的错误提示
分别对应的参数是:required和allow_null
validators参数对应的就是验证方法(基于类),默认空列表
class MultipleOf(object):
def __init__(self, base):
self.base = base
def __call__(self, value):
# __call__()方法能够让类的实例对象,像函数一样被调用;
# XXValidator(12)(34) 第一个()实例化,第二个()内的34会被识别为参数
if value % self.base != 0:
message = 'This field must be a multiple of %d.' % self.base
raise serializers.ValidationError(message)
使用:
title = serializers.CharField(error_messages={'required': '标题不能为空'}, validators=[MultipleOf('老男人'), ])
上边这种方法,实现了对单个字段的验证。哪个需要验,就在哪写validators=[这个类]
【小总结】
自定义validator类,与form里边自定义validator类用法完全一致
这个validator是filed参数,也就是只能作为参数对单个字段进行验证
具体调用的地方在···\rest_framework\fields.py - class Field() - def run_validators()
try:
validator(value)
except ValidationError as exc:
pass
【小总结over】
字段级别的验证② 自定义字段
另外,还可以通过自定义字段的方法实现验证。
django-form中
所有字段对象都继承自class Field ,在这个类里边留了一些钩子方法
如:to_python() validate()
自定义一个字段类,覆写以上方法
当调用表单的 is_valid() 方法时,
is_valid()>>> self.errors>>>self.full_clean()>>>self._clean_fields()>>>value = field.clean(value)
调用自定义字段的clean()
def clean(self, value):
"""
Validates the given value and returns its "cleaned" value as an
appropriate Python object.
Raises ValidationError for any errors.
"""
value = self.to_python(value) # 字段里自定义to_python,返回value
self.validate(value) # 字段里自定义validate,返回对象
self.run_validators(value) # 自定义validator类
return value
restful-serializer中
字段继承自 ···\rest_framework\fields.py class Field()
没留那么多钩子,但是可以通过覆写to_representation方法实现
需要注意的是,每个Filed的这个方法做的事情不一样
class MyField(serializers.CharField):
# 自定义filed
def to_representation(self, value):
验证或者其它操作
return str(value)
这个没应用过,但原理是通的。
基于函数的自定义验证:
找钩子函数,定义序列化类时,将钩子函数写进去
这个钩子方法写在序列化类,而非某个字段。
Serializer 为例:
Serializer内部可以找到以下3个方法,不用去父类找
is_valid >>> run_validation >>> to_internal_value
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
...
if validate_method is not None:
validated_value = validate_method(validated_value)
...
所以钩子方法就是 validate_ + 字段名 + ()
传入的是value,return的也是value,中间怎么搞自己发挥
整个钩子方法都在try语句里边,后边有except ValidationError跟着
所以不通过,直接raise ValidationError就行了
【类比form】
也是form级调用is_valid方法时:
is_valid()调用self.is_bound 和 se lf.errors
self.errors >>> self.full_clean() >>>
① self.clean_fields()
② self.clean_form() >>> clean()#纯钩子
③ self.post_clean() #纯钩子
2和3是纯钩子,不做讨论
clean_fields() 下 for循环内部
hasattr(self, 'clean%s' % name):#这里的name形参其实就是form的字段名
value = getattr(self, 'clean%s' % name)()
self.cleaned_data[name] = value
钩子方法:clean + 字段名 + ()
validate_title() clean_title()
两者都是在验证器/表单级别的类里边,提供了针对具体字段验证的钩子方法
就是这么简单的事情,官方文档的解释也太草率了,还给了个不知所云的even_number
验证器可以是任何可调用对象,在失败时引发 serializers.ValidationError。
def even_number(value):
if value % 2 != 0:
raise serializers.ValidationError('This field must be an even number.')
你可以通过向 Serializer 子类添加 .validate_<field_name>方法来指定自定义字段级验证。
分页
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
分页相关的类:···\rest_framework\pagination.py
BasePagination 基类
PageNumberPagination 普通分页
LimitOffsetPagination 按照Limit、Offset分页,此时page参数失效
CursorPagination 游标/加密分页
分页里边唯一的亮点:在于游标分页,大幅度提升性能,但一定程度上限制了用户的查询便利性。
class MytestSerialiser(serializers.ModelSerializer):
class Meta:
model = models.Role
fields = "__all__"
class MyPageNumberPagination(PageNumberPagination):
# 默认每页显示的数据条数
page_size = 4
# 获取URL参数中设置的每页显示数据条数
page_size_query_param = 'page_size'
# 获取URL参数中传入的页码key
page_query_param = 'page'
# 最大支持的每页显示的数据条数
max_page_size = 10
class MyLimitOffsetPagination(LimitOffsetPagination):
# 默认每页显示的数据条数
default_limit = 5
# URL中传入的显示数据条数的参数
limit_query_param = 'limit'
# URL中传入的数据位置的参数
offset_query_param = 'offset' #第几条
# 最大每页显得条数
max_limit = None
class MyCursorPagination(CursorPagination):
# URL传入的游标参数
cursor_query_param = 'cursor'
# 默认每页显示的数据条数
page_size = 2
# URL传入的每页显示条数的参数
page_size_query_param = 'page_size'
# 每页显示数据最大条数
max_page_size = 500
# 根据ID从大到小排列
ordering = "id"
class PagerView2(APIView):
'''
http://127.0.0.1:8000/api/v1/page/02/ 获取数据
http://127.0.0.1:8000/api/v1/page/02/?page=2 获取具体页码数据
http://127.0.0.1:8000/api/v1/page/02/?page=2&page_size=3 用户还能自己指定每页条数
'''
def get(self, request, *args, **kwargs):
roles = models.Role.objects.all()
# 实例化分页对象,获取数据库中的分页数据
paginator = MyPageNumberPagination()
page_user_list = paginator.paginate_queryset(roles, self.request, view=self) # 分页的方法
# 序列化分页后的对象
serializer = MytestSerialiser(page_user_list, many=True)
# 生成分页和数据
response = paginator.get_paginated_response(serializer.data) # 内部执行Response方法
return response
视图
以GenericAPIView为例,继承自APIView。与django的GenericView思路及其相似(鸡肋)
个人认为类通用视图比较鸡肋,而且耦合性太强,能不用就不用
但是在使用视图集时,这东西就必须用了
class View1View(GenericAPIView): # APIView
queryset = models.Role.objects.all()
serializer_class = PagerSerialiser
pagination_class = PageNumberPagination
def get(self,request,*args,**kwargs):
# 获取数据
roles = self.get_queryset() # models.Role.objects.all()
# [1, 1000,] [1,10]
pager_roles = self.paginate_queryset(roles)
# 序列化
ser = self.get_serializer(instance=pager_roles,many=True)
return Response(ser.data)
视图集
- 官网解释
Django REST framework 允许将一组相关视图的逻辑组合到一个称为 ViewSet 的类中
ViewSet 类只是一种基于类的 View,它不提供任何处理方法,如 .get() 或 .post(),而是提供诸如 .list() 和 .create() 之类的操作。
ViewSet 只在用 .as_view() 方法绑定到最终化视图时做一些相应操作。
通常,不是在 urlconf 中的视图集中明确注册视图,而是使用路由器类注册视图集,这会自动为您确定 urlconf。
- 说人话就是视图集使用的路由方式改变了
而且,把路由方法由 put delete post get 改为 creat destroy update retrieve捎带还赠送了一个List查询
也就是把增删改查对应的方法名变了,而且每个方法写在一个单独的类里边
映射关系需要自己写在urlpatterns里:
'get': 'retrieve',
'get': 'list',
'post':'create'
'delete': 'destroy',
'put': 'update',
'patch': 'partial_update',
最全的类继承,就是继承ModelViewSet,包含则增删改查所有的方法
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
增删改查全继承:
class View2View(ModelViewSet):
少来点:
class View3View(ListModelMixin,CreateModelMixin,GenericViewSet):
对应路由的写法:
# 如果不使用router ,get的映射要自己写两遍,不带pk参数映射list 带pk映射retrieve
# 因为视图集把get方法拆开了,在视图集里对应两个类
url(r'^(?P<version>[v1|v2]+)/viewset/$', views.View2View.as_view({'get': 'list','post':'create'})),
url(r'^(?P<version>[v1|v2]+)/viewset/(?P<pk>\d+)/$',
views.View2View.as_view({'get': 'retrieve',
'delete': 'destroy',
'put': 'update',
'patch': 'partial_update',
})),
url(r'^(?P<version>[v1|v2]+)/viewset3/$', views.View3View.as_view({'get': 'list','post':'create'})),
genericviewset的第一个父类 ViewSetMixin 改写了as_view方法
路由规则改变了,所以要自己在url后边加{'get','xxx'}参数
Modelviewset,它继承6个类,功能最全
【小结】
使用的时候 APIView - Genericviewset - modelviewset
功能越来越全,代码越来越少,耦合越来越强
Genericviewset 与增删改查 联合作为父类 也行,灵活应用
补充了一个更细粒度的权限管理,之前是全局权限,引入这个之后可以做对象级别的权限管理
Genericviewset唯一的好处:获取列表/单个对象 做出了区分
路由 和 渲染器
这俩东西是掺和在一块的
- 自己写路由
看下边这4种路由的写法:
urlpatterns = [
url(r'^test/$', views.TestView.as_view()),
url(r'^test/(?P<pk>[^/.]+)/$', views.TestView.as_view()),
url(r'^test\.(?P<format>[a-z0-9]+)$', views.TestView.as_view()),
url(r'^test/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)$', views.TestView.as_view())
]
前边两个是 为列表/对象查询做的区分
后边两个引入的那个format / pk+format 纯粹是为了支持渲染器写的
如果公司项目仅适用json,那渲染器就没什么用了。
- 视图集中写路由(半自动)
视图集的部分已经写过了
urlpatterns = [
url(r'^test/$', views.UserViewSet.as_view({'get': 'list', 'post': 'create'})),
url(r'^test/(?P<pk>\d+)/$', views.UserViewSet.as_view(
{'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
]
- 全自动模式
from django.conf.urls import url, include
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
视图函数中:
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
from .. import models
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = "__all__"
class UserViewSet(ModelViewSet):
queryset = models.UserInfo.objects.all()
serializer_class = UserSerializer
include(router.urls) 一句帮我们生成对应的4种url,省点事
这基本就是restful官网教程的quickstart了,写的是爽了,但在生产环境中完全没意义。
渲染器
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
···\site-packages\rest_framework\settings.py 默认的渲染器
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
)
自定义:
全局settings里设置RENDERER_CLASSES 值
或者 视图里自己指定 renderer_classes = [JSONRenderer,BrowsableAPIRenderer]
默认就有json啊:这三种方式都能访问
http://127.0.0.1:8000/test/?format=json
http://127.0.0.1:8000/test.json
http://127.0.0.1:8000/test/
JSONRenderer 对应的
media_type = 'application/json'
format = 'json'
BrowsableAPIRenderer 对应的
media_type = 'text/html'
format = 'api'
一共有10来种渲染器,每种对应的format字段名称都不一样
路由和渲染器从根上说是两码事,
引入渲染器会导致url书写变得很蛋疼,那就再引入个路由器,表面上掩盖一下。
恩,就这样