Django Rest Framework 源码探究(三 其它部分)

RESTful API 设计指南

restful api规范开发指南:

  1. 协议 建议使用HTTPS
  2. 域名 专用域名
  3. 版本
    建议:https://api.example.com/v1/
  4. 路径
  5. http动词
    对于资源的具体操作类型,由HTTP动词表示。
    常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
    GET(SELECT):从服务器取出资源(一项或多项)。
    POST(CREATE):在服务器新建一个资源。
    PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
    PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
    DELETE(DELETE):从服务器删除资源。
    还有两个不常用的HTTP动词。
    HEAD:获取资源的元数据。
    OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
  6. 过滤信息
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
  1. 状态码
    200 400 404 操作是幂等的 ,幂等
    http状态码之外,可以自己设计code
  2. 错误处理
  3. 返回结果
  4. Hypermedia API

djangorestframework

  • 看源码时要注意的问题
  1. python3默认都是新式类,继承遵循深度优先原则
  2. 执行时先顺杆爬执行所有类的new方法,爬杆时深度优先从左至右 不重复
    不重复的意思是,来的时候路过,再往后找的时候就不走这了
  3. 先执行所有的new方法,然后原路返回(倒序)执行所有的init方法

知乎链接

Python的多重继承正如文档所言是深度优先从左至右不重复
在Python里,当你新构造一个对象时,有两个步骤:首先是自底向上,从左至右调用__new__,然后再依照递归栈依次调用__init__

一、认证

源码流程分析

二、权限

三、节流

  • 认证、权限、节流三者思路一样,明白一个就全通了。

四、版本

版本控制有什么用

  1. 更方便的提取当前请求所使用的版本
  2. 可以通过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[v1|v2]+)/users/$', views.UsersView.as_view(),name='uuu'),
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返回')

源码流程

  1. 获取用户请求
  2. 获取用户请求体
  3. 根据用户请求头 和 parser_classes = [JSONParser,FormParser,] 中支持的请求头进行比较
  4. JSONParser对象去请求体
  5. 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)

序列化源码探究

  1. 序列化应用第一步 --- 类实例化
    实例化时先执行构造方法和new方法,__new__ 比 __init__要早执行
    顺藤往上摸,找父类的 __new__和__init__
  2. 找父类 - 以ModelSerializer为例
    ModelSerializer >>> Serializer >>> BaseSerializer
    在BaseSerializer里发现 __new__和__init__
  3. 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

  1. 第二步:实例化完该调用了
    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 离得更近,它会执行

  1. 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()方法 把两个重要的参数传入

  1. 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_方法来指定自定义字段级验证。

分页

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书写变得很蛋疼,那就再引入个路由器,表面上掩盖一下。

恩,就这样

转载于:https://www.cnblogs.com/jinshen/p/9222313.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值