1. 版本 了解
*
- 可以放在url,这种是比较推荐的。
它需要配套的路由,一般所有的CBV都要使用,所以放在全局配置中使用。
全局配置,引入restframework类:
'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
路由使用,加上正则:url或者re_path(r'^(?P<version>[v1|v2]+)/order/$', OrderView.as_view())
URLPathVersioning
- 可以放在url,这种是比较推荐的。
- 可以通过get传参携带版本版本信息,不太常用
rest-framework提供使用的方法类,类似用户认证,权限检查,节流检查。在CBV中的定义静态字段使用,一般使用自带的就足够了
from rest_framework.versioning import QueryParameterVersioning,BaseVersioning
,
versioning_class = QueryParameterVersioning
支持三项全局配置DEFAULT_VERSION
:默认版本ALLOWED_VERSIONS
:允许访问版本,VERSION_PARAM
:get参数
也可以自定义版本类,继承BaseVersioning,一般没必要。
- 可以通过get传参携带版本版本信息,不太常用
获取时都是通过
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进行序列化
- 使用默认的序列化方式
json只能对python自带的数据类型进行序列化,但是不能将自定义的数据类型进行序列化, 例如模型类中的类型。
我们可以先拿到QuerySet.list_value(),然后将元组转换为list,再进行json.loads()和json.dumps(ensure_ascii=False不将汉字转化
)
- 使用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<version>[v1|v2]+)/userinfo/$', UserInfoView.as_view(),name='userinfo'),
re_path(r'^(?P<version>[v1|v2]+)/group/(?P<pk>\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程序的时候继承的都是View
from django.views.generic import View
,然后接触到了APIViewfrom rest-framework.views import APIView
。
还有一个不太怎么用的GenericAPIView,
from 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)
然后就到了GenericViewSet
from rest_framework.viewsets import GenericViewSet
它继承自(ViewSetMixin, generics.GenericAPIView
) , 这个类主要的功能就是将as_view()方法重写了,在你调用as_view()方法时,你要给他传一个字典,例如{'get':'list1', 'post':'list2'}
,get请求时,调用类中的list1方法,post请求时,调用类中的list2方法。
功能最强大的ModelViewSet,
from 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<version>[v1|v2]+)/view/(?P<pk>\d+)/$', ViewView.as_view({'get':'retrieve','post':'create',
'patch':'partial_update','put':'update',
'delete':'destroy'})),
re_path(r'^(?P<version>[v1|v2]+)/view/$', ViewView.as_view({'get': 'list'}))
PS:
APIView继承了View的所有优点,并通过很多组件掩盖了View的很多缺点,它是纯洁中最骚的那个,比较实用。
GenericViewSet这个类也是比较纯洁的,继承自ViewSetMixin,重写了as_view方法,是最好用的增删改查类。相比APIView,它能够自动的根据你的请求来分配对应的执行方法。
某某某ModelMixin也是不太纯洁的,这些类主要是在我们自定义类的时候用到,例如,只继承CreateModelMixin
和GenericViewSet
,之类的。
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: <class 'app01.models.Role'> QuerySet.
paginator = self.django_paginator_class(queryset, page_size)
这个的原因是因为返回的是一个无序的序列,可以在序列化类中,指定class Meta:ordering = 'id'得到有序的序列后就不会报错了,之前遇到这个错误是因为CursorPanination对象没有ordering = 'id'。
6. 路由一般重要
**
a. 一般路由
re_path(r'^(?P<version>[v1|v2]+)/order/$', OrderView.as_view(),name='order')
b. 重写了as_view()方法的路由
re_path(r'^(?P<version>[v1|v2]+)/view/$', ViewView.as_view({'get': 'list'}))
c.带有渲染器的路由(这种路由使用?format=json,也可以得到,format=admin,format=form...)
re_path(r'^(?P<version>[v1|v2]+)/view\.(?P<format>\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<version>[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 过滤类
- 假设在查询过程中,想要实现过滤
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]。
- 使用orderingfilter
backends = [orderingfilter]
ordering_fields = ['price','pk']
请求的url127.0.0.1/books/?ordering=-price,pk