DRF其余

1. 版本 了解*

    1. 可以放在url,这种是比较推荐的。
      它需要配套的路由,一般所有的CBV都要使用,所以放在全局配置中使用。
      全局配置,引入restframework类
      'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
      路由使用,加上正则url或者re_path(r'^(?P[v1|v2]+)/order/$', OrderView.as_view())
      URLPathVersioning
    1. 可以通过get传参携带版本版本信息,不太常用
      rest-framework提供使用的方法类,类似用户认证,权限检查,节流检查。在CBV中的定义静态字段使用,一般使用自带的就足够了
      from rest_framework.versioning import QueryParameterVersioning,BaseVersioning,
      versioning_class = QueryParameterVersioning
      支持三项全局配置DEFAULT_VERSION:默认版本ALLOWED_VERSIONS:允许访问版本,VERSION_PARAM:get参数
      也可以自定义版本类,继承BaseVersioning,一般没必要。
  • 获取时都是通过request.version获取。

  • 源码流程

还是走dispatch()方法,在初始化CBV的时候我们知道在最后执行了用户认证,权限检查,节流检查,而在执行这三种方法之前,执行了这样一行语句
request.version,request.versioning_scheme = self.determine_version(request, *args, **kwargs),我们知道这个方法是返回一个元组,然后赋值,然后查看determine_version()方法,看到这里做了一个判断,如果用户没定义version_class的值,就去全局配置中取这个值,这个值其实就是版本类的对象,然后执行了对象的determine_version方法,这个方法是是版本类中的方法,将版本返回。

注意这两个determine_version()方法,第一个是CBV的,它要返回一个元组,赋值给request.version(版本)和request.versioning_scheme(版本类),在这个方法中得到了版本类的信息,但是不知道version的信息,所以在这个方法中调用了版本类的方法determine_version()将version的信息返回。

restframework反向生成其实就是调用django的reverse,然后将request中的version值取出,然后赋值到参数中{kwargs}

对于这些版本类,他们中除了有determine_version方法将版本返回外,还有一个reverse方法,作用就是反向生成url,需要传入viewname(urls中每句路由匹配都可以加name = 'yourname')和request
使用时,通过request.scheme得到类,
request.versioning_scheme.reverse(viewname='order',request = request)可以反向生成url

通过django内置的reverse也可以实现反向url生成,不过需要传递参数

url2 = reverse(viewname='order',kwargs={'version':'v2'}),这里的键为version,是默认的,也就是传递参数,可以在setting中配置
REST_FRAMEWORK{
'VERSION_PARAM' : 'version'
}

2. 解析器 了解*

  • 前戏:django:request.POST / request.body
    要想获取到request.POST的内容需要两点要求!
    1 请求头中Content-Type:application/x-www-form-urlencoded
    ps 只有当请求头为这个时,request.POST才会去解析request.body的值
    2 数据格式的要求
    ps name=xiaohong&age=18&gender=woman,不是这种格式无法解析,也就没值
    例如:
    a. form表单的提交会将类型设置为application/x-www-form-urlencoded,并且会将数据格式化为指定格式
    b. ajax请求,data虽然是以字典的的形式传入,但还是会格式化为指定格式
  • 但是我们也不一定非要使用request.POST的方式取值
    发送ajax请求时,还可以使用下面的方式
$.ajax({
  url:...
  type: 'POST',
  headers:{'Content-Type':"application/json"},
  data: JSON.stringfy{{ name:'xiaohong',age=18 }}
})

根据headers中的Content-Type,将数据解析为json类型,然后传入的数据也是json类型的,我们在request.POST中取不到值,但是request.body中有值,我们只需要将request.body 值loads一下,就可以拿到了
json.loads(request.body)

  • 总结就是,发送的数据为指定类型,指定格式,reqeust中可以直接拿到,不是指定的格式,request.body也能拿到,若type为json,数据为json类型,我们在POST中无法拿到数据,但是可以将body中的数据用json.loads的方法解析出来。


  • 解析器
    常用的解析器全局定义,
REST_FRAMEWORK  = {
  "DEFAULT_PARSER_CLASSES":'rest_framework.parsers.JSONParser', 'rest_framework.parsers.Formparser'
}

特殊的解析器在CBV中定义,

    parser_classes = ['JSONParser','FileUploadParser']

使用时request.data

他的本质就是拿到request.headers中的Content-Type , 与我们配置的解析器media_type进行比对,相同的就可以解析,不同就换列表中下一个解析器比对,若到最后没有找到,则抛出异常。

  • 源码流程

dispatch方法中将request进行了封装,在用户认证中我们已经知道他多出的属性有一个authencators, 现在又用到它另一个属性,parsers,同理,这个属性的值由一个方法获取,get_parsers,这个方法就是一个列表生成式,将解析器类全部实例化出对象,得到一个解析器对象列表,其实已经调用完毕。

  • 当使用request.data时,调用Request类中的data方法,这个方法类似用户认证时的request.user, request.data方法中,判断是否有request._full_data是否有值,没有值再去调用load_data_and_file()方法,多个地方用到request.data时可以避免重复的调用load_data_and_file()方法。
  • 又转到load_data_and_file()方法,里面和request.user里面类似,利用hasattr判断request._data是否有值这个方法调用了reqeust._parse,因为request.parsers中有各种解析器类的对象,request.content_type可以取到请求的Content-Type类型,所以如果匹配,就可以调用合适的解析器对象的parse()方法进行解析。得到data数据。

3. 序列化 最重点,必须安排的明明白白****

  • 可以对请求数据进行校验,可以将Queryset进行序列化
  1. 使用默认的序列化方式

json只能对python自带的数据类型进行序列化,但是不能将自定义的数据类型进行序列化, 例如模型类中的类型。
我们可以先拿到QuerySet.list_value(),然后将元组转换为list,再进行json.loads()和json.dumps(ensure_ascii=False不将汉字转化)

  1. 使用rest_framework中的序列化工具

a.定义序列化类

简单类

class UserInfoSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()
    #这些字段必须和model中保持一致,否则就会报错

b. CVB视图中,创建一个序列化类的对象其中的instance=QuerySet类型的对象,many = True表示列表QuerySet中不止一个值,然后调用这个对象的.data方法获取,json.dumps(sr.data , ensure_ascii=False)json转储。

进阶类

class UserInfoView(APIView):
    def get(self,request,*args,**kwargs):
        # 获取对象
        userinfo = UserInfo.objects.all() 
        # 声明序列化对象
        ser = UserInfoSerializer(instance=userinfo, many=True)
        # ser.data就可以得到数据, 然后将数据json 转储
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

以上的是简单的序列化类,然后进阶!

  • 我们在django的ORM中了解到如果有choice时,可以通过get_字段名_dislpay()的方式获取到字段对应的choice中的值。
  • 在上面的类中,我们必须和定义和模型类中相同字段,这是因为我们没有指定source,如果指定了source,定义什么就没限制了,中文也是可以的,例如:
class UserInfoSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    用户类型  =serializers.CharField(source = 'get_user_type_display')

    group = serializer.Charfield(source = 'group.title')
    # 其中user_type是模型类中的字段名,get_..._display是另一部分。
  • 之所以没有加括号是因为rest_framework会判断source的值是否callable,是就将返回值返回,不是就返回这个字段的值。话说,引号中很少加()
  • source就是instance对象点出来的东西,例如上面的group就是UserInfo中有group属性其实是外键,所以可以直接点出来

进阶二
上面的source只能将choice字段和foreign字段显示,不能将many_to_many字段显示,这时候需要自定义显示

class UserInfoSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    用户类型  =serializers.CharField(source = 'get_user_type_display')
    group = serializer.Charfield(source = 'group.title')

    #################################### 
    roles = serializer.SerializerMethodField()
    def get_roles(self,userinfo):
#  函数名字get开头,字段结尾,接受的参数为该字段
      roles_obj = userinfo.roles.all()
      ret = []
      for item in roles_obj:
        ret.append(
            {'id':item.id,  'title':item.title }
            )
        return ret   

高阶用法,全能用法 ****

  • 继承自ModelSerializer,在里面可以使用source来解决外键和choice的选项,使用serializer.SerializerMethodField()的方式自定义显示。
  • 可以使用在class Meta定义显示的字段,depth定义显示的深度
class UserInfoSerializer(serializers.ModelSerializer):
    # choice字段的用source
    usertype = serializers.CharField(source='get_user_type_display')
    # 外键的也可以直接显示  但是要将要定义deph的值,表示深度

    #  更加复杂的字段使用
    a_field = serializer.SerializerMethodField()
    def get_a_field(self , objs):
      pass

    class Meta:
        model = UserInfo  # 根据这个模型类,自动生成它的字段
        # fields = '__all__'
        fields = ['id','username', 'password', 'usertype', 'roles', 'group']
        depth = 1

部分总结

  • 写类:两种
class UserInfoSerializer1(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()
class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserInfo
        # fields = '__all__'
        fields = ['id','username', 'password']
       # depth = 1
  • 字段source, serializer.SerializerMethod

source是使用传入的instance对象,然后 . 出来
serializer.SerializerMethod是自定义字段,要有配套的自定义方法,获取这个值。

  • 额外的,还可以定义Field
class MyField(serializer.CharFiled):
     def to_reprement(self , value):
        print(value) # value是数据库中取到的值
        return 'xxxx' # 返回值
  • restframework之序列化生成HyperMediaLink字段

定义一个HyperlinkedIdentityField字段,可以反向生成url,
例如用户表中有组表的外键group。

    re_path(r'^(?P[v1|v2]+)/userinfo/$', UserInfoView.as_view(),name='userinfo'),
    re_path(r'^(?P[v1|v2]+)/group/(?P\d+)$', GroupView.as_view(),name='gp'),

通过group的id来获取某个group的详细信息,参数为pk随便定义

class UserInfoView(APIView):
    def get(self,request,*args,**kwargs):
        userinfo = UserInfo.objects.all()

        ser = UserInfoSerializer(instance=userinfo, many=True,context={'request': request})

        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

class GroupView(APIView):
    def get(self,request,*args,**kwargs):
        pass

我么们看生成的url是否正确,所以就没必要显示group的详细信息了。
这里注意的一点就是,当使用带链接的字段时,必须保证request参数,所以要在创建序列化对象的时候加上context={'request': request}

class UserInfoSerializer(serializers.ModelSerializer):
    # choice字段的用source
    usertype = serializers.CharField(source='get_user_type_display')
    # 外键的也可以直接显示  但是要将要定义deph的值,表示深度
    group = serializers.HyperlinkedIdentityField(view_name='gp',lookup_url_kwarg='pk',lookup_field='group_id')
    class Meta:
        model = UserInfo
        # fields = '__all__'
        fields = ['id','username', 'password', 'usertype', 'roles', 'group']
        depth = 1

group =serializers.HyperlinkedIdentityField(view_name='gp', lookup_url_kwarg='pk',lookup_field='group_id')最重要的一句,其中pk是默认的,在url中配置pk,在这里就无需设置。
1.其中包括反向生成需要的viewname参数,
2.反向生成的url中的参数lookup_url_kwarg='pk'就是我们之前在url中的参数,必须和前者保持一致,
3.根据哪个字段作为这个参数,我们要获取group,肯定就是group_id作为参数,
3 - 用lookup_field='group.id'会报错,说UserInfo表中无group。id属性,连点都变成句号了?对于ORM,尤其是关乎外键时,点不出来的属性可以杠出来。group.id不可以,那就group_id

序列化之请求数据的校验
对于只参与反序列化的数据使用write_only字段设置,默认是参与序列化,,read_only为True

  • 简单的校验,判空操作
ser = UserSerializer(data=request.data)
#创建一个序列化对象,将data传进去
# 然后作判断,类似于form表单
if ser.is_valid():
  # print(ser.validated_data['key'])
  print(ser.validated_data)
else:
  print(ser.error_messages)

  • 复杂校验,钩子函数(钩子函数中有全局钩子和局部钩子:)

局部钩子的定义:
def validate_字段(self,value): return value值或者抛出异常

def validate_name(self,value):
   if 'j' in value.lower():
        raise exceptions.ValidationError('名字中不能有j')
        #  raise exceptions.ValidationError({' 自定义键 ':' 自定义错误信息 '})
   else:
        return value

全局钩子的定义
def validate(self,attrs): return attrs值或者抛异常

def validate(self,attrs):
    if attrs.get('pwd').lenght < 8:
          raise exceptions.ValidationError('太短')
    else:
        return attrs

4. 分页 一般重要**

分页其实会有问题,当向后查看200页数据以上时,速度会变得非常慢,而且越往后越慢

a. 一般分页, 看第n页,每页显示n条数据。
b. 基于偏移量分页, 在n个位置,向后查看n条数据光标
c. 基于游标分页不让你跳转到具体页,上一页和下一页。记录当前id的最大值和最小值,当你向后翻页时,比最大值小的数据就不想数据库中查了,向前翻页时,比最小值id大的数据就不查了,这让就解决了越往后响应速率变慢的问题,因为查询的数据量小了
进入正题之--- 一般分页

class Pager1View(APIView):
    def get(self,request,*args,**kwargs):
        # 获取数据
        roles = models.Role.objects.all()
        # 创建对象
        from rest_framework.pagination import PageNumberPagination
        pg = PageNumberPagination()

        # 在数据库中获取分页数据
        page_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
        # 上面的语句是获得分页之后的第一条数据,所以需要在settings中指定PAGE_SIZE,否则将没有数据


        # 对分页数据进行序列化
        ser = PagerSerializer(instance=page_roles, many=True)

        # 自动生成上一页和下一页,会有多个数据,很少用到
        # return pg.get_paginated_response()
        return Response(ser.data)

可以通过参数page去查看某页的数据,例如http://127.0.0.1:8000/api/v1/pager1/?page=1‘page’是默认的,当然你也可以自定义参数,但是需要自定义分页类。

  • 我们呢 还可以对以上自定义分页类
class MyPagiantion(PageNumberPagination):
    page_size = 2                # 小于这个数,就是这个数
    page_size_query_param = 'size'    #可以通过size参数指定这一页的大小
    max_page_size = 5                # 超过这个数,就是这个数
# 将size固定在了2-5,因为太大的话会把数据库搞崩。

    page_query_param = 'page'   # 上面提到的参数,你也可以设置为p

http://127.0.0.1:8000/api/v1/pager1/?page=1&size=3
page = 1 第一页
size = 3 每页大小为3

进入正题之--- 基于偏移量分页

  • 自定义分页的方式其实和前面是相同的
class My2Paginator(LimitOffsetPagination):
    
    default_limit = 2      #  limit表示光标向后取几条数据
    limit_query_param = 'limit'        # 偏移量,虽然默认是2,但是可以通过这个参数取修改
    offset_query_param = 'offset'      # 初始位置,从零开始
    max_limit = 5

http://127.0.0.1:8000/api/v1/pager1/?&limit=3&offset=1
limit=3 向后取3条数据
offset=1 从第条数据开始

进入正题之--- 基于游标分页

class My3Pagination(CursorPagination):
    cursor_query_param = 'cursor'    # 光标参数
    page_size = 2        # 默认每页的数据量
    ordering = 'id'          #按照什么进行排序

    page_size_query_param = 'size'    # 指定每一页的大小
    max_page_size = 5      # 每页的最大值

# 某些为None的字段,其实可以不配置,防止用户的错误操作

注意1:如果使用这种方式,cursor的值是加密的,所以用户就不能自己指定去第几页了,所以返回值时,你要给他提供翻页的数据。
注意2:这种方式必须指定一个排序方式,即ordering = 'id',否则会报缺少create时间戳之类的错误,一般重写类属性,或者在视图中将原有属性默认值覆盖:
pg = CursorPagination()
pg.ordering = 'id'

        return pg.get_paginated_response(ser.data)

返回的响应有上一页,下一页。


5. 视图一般重要**

开始学Django程序的时候继承的都是Viewfrom django.views.generic import View,然后接触到了APIViewfrom rest-framework.views import APIView

还有一个不太怎么用的GenericAPIViewfrom rest_framework.generics import GenericAPIView它继承自APIView,它的内部有很多get方法,当我们在类中设置 queryset, serializer_class, pagination_class的值后,就可以通过get()方法获取queryset,获取序列化对象,获取分页类等,但是它并没有简化操作,所以没啥用啊。但还是说说它

class LastTest(GenericAPIView):
    # 想要使用它就给他配置   下列三项
    queryset = models.Role.objects.all()
    serializer_class = HumanSerializer
    pagination_class = PageNumberPagination

    def get(self,request,*args,**kwargs):
        roles = self.get_queryset() # 封装到方法里面,更加的清让操更加的条理化,但是并没有简化操作。
        page_roles = self.paginate_queryset(roles)
        ser = self.get_serializer(page_roles,many=True)
        return Response(ser.data)

然后就到了GenericViewSetfrom rest_framework.viewsets import GenericViewSet它继承自(ViewSetMixin, generics.GenericAPIView) , 这个类主要的功能就是将as_view()方法重写了,在你调用as_view()方法时,你要给他传一个字典,例如{'get':'list1', 'post':'list2'},get请求时,调用类中的list1方法,post请求时,调用类中的list2方法。

功能最强大的ModelViewSetfrom rest_framework.viewsets import ModelViewSet它继承的比较多,有6个,
(mixins.CreateModelMixin, ------>>>>>> 添加一条数据,post请求,它里面有create方法,所以可以在as_view()中传入字典{'post':'create'}
mixins.RetrieveModelMixin, ------>>>>>> 获取一条数据,get请求,url中需要参数,这个类中有retrieve方法,可以在as_view中传入字典{'get':'retrieve'}
mixins.UpdateModelMixin, ------>>>>>> 更新数据,url同样需要参数,这类中有partial_update和update方法,一个局部更新patch,一个全部更新put,可以在as_view()中传入字典{'patch':'partial_update','put':'update'}
mixins.DestroyModelMixin, ------>>>>>>删除数据,需要参数,这类中有destroy方法,可以在as_view中传入字典{'delete':'destroy'}
mixins.ListModelMixin, ------>>>>>> 查询数据,也是get请求,url中无需参数,上面的url可以写成一个,然后传入一个字典,而这个不需要传参数,所以和上面的不属于一个url,但是要在as_view中传入字典{'get':'list'}
GenericViewSet) ------>>>>>>之前的GenericViewSet

urls.py
re_path(r'^(?P[v1|v2]+)/view/(?P\d+)/$', ViewView.as_view({'get':'retrieve','post':'create',
                                                                'patch':'partial_update','put':'update',
                                                                'delete':'destroy'})),
re_path(r'^(?P[v1|v2]+)/view/$', ViewView.as_view({'get': 'list'}))

PS:
APIView继承了View的所有优点,并通过很多组件掩盖了View的很多缺点,它是纯洁中最骚的那个,比较实用。

GenericViewSet这个类也是比较纯洁的,继承自ViewSetMixin,重写了as_view方法,是最好用的增删改查类。相比APIView,它能够自动的根据你的请求来分配对应的执行方法。

某某某ModelMixin也是不太纯洁的,这些类主要是在我们自定义类的时候用到,例如,只继承CreateModelMixinGenericViewSet,之类的。

ModelViewSet是View中最骚的,但是它的功能很局限,不能实现比较复杂的操作,所以,遇到比较简单的增删改查时,用这个类就超级省事。
用这个的时候报了一个算是警告,但是无碍
D:\WorkPlace\pycharm\stu_drf\venv\lib\site-packages\rest_framework\pagination.py:200: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: QuerySet.
paginator = self.django_paginator_class(queryset, page_size)

这个的原因是因为返回的是一个无序的序列,可以在序列化类中,指定class Meta:ordering = 'id'得到有序的序列后就不会报错了,之前遇到这个错误是因为CursorPanination对象没有ordering = 'id'。

6. 路由一般重要**

a. 一般路由
re_path(r'^(?P[v1|v2]+)/order/$', OrderView.as_view(),name='order')
b. 重写了as_view()方法的路由
re_path(r'^(?P[v1|v2]+)/view/$', ViewView.as_view({'get': 'list'}))
c.带有渲染器的路由(这种路由使用?format=json,也可以得到,format=admin,format=form...)
re_path(r'^(?P[v1|v2]+)/view\.(?P\w+)$', ViewView.as_view({'get': 'list'}))
d.自动生成的路由继承视图集的类可使用,也就是as_view()中需要传入字典的

from . import views
from rest_framework import routers
router = routers.DefaultRouter()
router.register('xxx',views.ViewView)

urlpatterns[
    url(r'^(?P[v1|v2]+)/', include(router.urls))
]

  • 上面的xxx就是原来路由中的参数,版本信息需要自己加上。

7. 渲染器了解*

  • 局部
    from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
    renderer_classes = [JSONRenderer, BrowsableAPIRenderer]
  • 全局
REST_FRAMEWORK = {
    "DEFAULT_RENDERER_CLASSES":['rest_framework.renderers.JSONRenderer','rest_framework.renderers.BrowsableAPIRenderer']
}
  • 这个render是有模板的,可以自定义, 继承它的BrowsableAPIRenderer。

8 过滤类

  1. 假设在查询过程中,想要实现过滤
class BooksListAPIView(ListAPIView):
    queryset = models.Books.objects  # .all()也可以,但是没必要
    serializer_class = serializers.BooksSerializer

    # 重点
    filter_backends = [filters.SearchFilter,]
    search_fields = ['name','price']

当请求127.0.0.1/books/?search=1时,name中或price中含有1的都会查找出来

源码流程:在ListAPIView中,get请求执行list方法,自己没有去父类ListModelMixin找,然后queryset = self.filter_queryset(self.get_queryset()),将query取出,然后进行过滤在交给queryset,所以去filter_queryset,自己没有去第二父类GenericAPIView找,里面通过循环的方式将filter_backends取出,实例化,执行对应的filter_queryset,在DRF内部提供了3个filter类,Base作为抽象类,还有search搜索及order排序,使用已有的searchfilter,即将filter_backends = [search_filter]。

  1. 使用orderingfilter
    backends = [orderingfilter]
    ordering_fields = ['price','pk']
    请求的url127.0.0.1/books/?ordering=-price,pk

你可能感兴趣的:(DRF其余)