DRF之路由、分页、搜索、过滤、排序、异常处理
路由、分页、搜索、过滤、排序、异常处理
一 五大接口准备
说在前面
五大接口里面没有群增 群删 群查 ,那么问题来了怎么实现呢?
答案:自己根据实际需求去写
urls.py
from django.conf.urls import url
from django.contrib import admin
from . import views
# 五大街口路由
urlpatterns = [
url(r'^cars/$', views.CarModelViewSet.as_view({
'get':'list',
'post':'create'
})),
url(r'^cars/(?P<pk>.*)/$', views.CarModelViewSet.as_view({
'get':'retrieve',
'put':'update',
'patch':'partial_update',
'delete':'destroy'
}))
]
urlpatterns.extend(router.urls)
models.py
from django.db import models
# Create your models here.
class BaseModel(models.Model):
is_delete = models.BooleanField(default=0)
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class Car(BaseModel):
name = models.CharField(max_length=32)
# 小数点后两位
price = models.DecimalField(max_digits=5, decimal_places=2)
brand = models.ForeignKey('Brand', db_constraint=False, on_delete=models.DO_NOTHING, related_name='cars')
@property
def brand_name(self):
return self.brand.name
def __str__(self):
return f'<{self.name} obj>'
class Meta:
db_table = 'zhefu_car'
verbose_name = '汽车'
verbose_name_plural = verbose_name
class Brand(BaseModel):
name = models.CharField(max_length=16)
class Meta:
db_table = 'zhefu_brand'
verbose_name = '品牌'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
serializers.py
from rest_framework.serializers import ModelSerializer
from . import models
class CarModelSerializer(ModelSerializer):
class Meta:
model = models.Car
# 第三方字段只支持读
fields = ('pk','name', 'price', 'brand', 'brand_name')
extra_kwargs = {
'brand': {
'write_only': True
},
'brand_name': {
'read_only': True
}
}
views.py
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from . import models,serializers
# Create your views here.
class CarModelViewSet(ModelViewSet):
queryset = models.Car.objects.filter(is_delete=False)
serializer_class = serializers.CarModelSerializer
def destroy(self, request, *args, **kwargs):
pk = kwargs.get('pk')
# print(pk,type(pk))
try:
# print('123123123123')
car_obj = models.Car.objects.get(pk=int(pk),is_delete=False)
car_obj.is_delete = True
car_obj.save()
return Response({
'status':1,
'msg':'ok'
})
except:
return Response({
'status':0,
'msg':'删除失败'
})
二 路由组件(没啥用)
from django.conf.urls import url
from . import views
from rest_framework.routers import SimpleRouter
# 初始化路由对象
router = SimpleRouter()
# 注册各种接口路由
# 反向解析用的没啥卵用
router.register('cars', views.CarModelViewSet, base_name='cars')
# router.register('brands', views.BrandModelViewSet, base_name='brand')
urlpatterns = [
# url(r'^cars/$', views.CarModelViewSet.as_view({
# 'get': 'list',
# 'post': 'create'
# })),
# url(r'^cars/(?P<pk>.*)/$', views.CarModelViewSet.as_view({
# 'get': 'retrieve',
# 'put': 'update',
# 'patch': 'partial_update',
# 'delete': 'destroy'
# })),
]
urlpatterns.extend(router.urls) # 内部帮你做好了访问的方法对应的视图方法,名言胜于暗喻不推荐使用这个组件
三 搜索、排序、筛选(分类)、分页
都是针对群查询=> list 的 filter 们
基于上面的五大接口
views.py
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from . import models,serializers
from rest_framework.filters import SearchFilter, OrderingFilter
# 筛选组件 ---------------------------------------------
from django_filters.rest_framework import DjangoFilterBackend
from .filtersets import CarFilterSet
# -------------------------------------------------------
# 自定义的分页组件
from . import pagination
# 走的apiview的dispach
from rest_framework.views import APIView
class CarModelViewSet(ModelViewSet):
queryset = models.Car.objects.filter(is_delete=False)
serializer_class = serializers.CarModelSerializer
def destroy(self, request, *args, **kwargs):
pk = kwargs.get('pk')
# print(pk,type(pk))
try:
# print('123123123123')
car_obj = models.Car.objects.get(pk=int(pk),is_delete=False)
car_obj.is_delete = True
car_obj.save()
return Response({
'status':1,
'msg':'ok'
})
except:
return Response({
'status':0,
'msg':'删除失败'
})
# 搜索、排序、筛选(分类)、分页 => 针对群取list 的 filter 们
###### 搜索组件(要在有list方法的基础上)
# 接口:/cars/?search=秦 || /cars/?search=1 在name和price两个字段中模糊搜索
# filter_backends = [SearchFilter]
# 默认模糊查询,等价$,
# 以什么开头^name,
# 精确匹配=name,
search_fields = ['name', 'price']
'''
访问:http://127.0.0.1:8000/api/cars/?search=奔
返回:[
{
"pk": 3,
"name": "奔驰G60",
"price": "23.10",
"brand_name": "奔驰"
},
{
"pk": 4,
"name": "奔驰300",
"price": "12.40",
"brand_name": "奔驰"
}
]
'''
###### 排序组件
# 排序组件
# 访问规则 http://127.0.0.1:8000/api/cars/?search=奔&ordering=-price 可以同时和搜索组件使用
# 可以指定根据某个字段进行排序
# filter_backends = [SearchFilter, OrderingFilter]
# ordering_fields = ['pk', 'price']
####### 筛选(分类、区间)(待测试)
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
# 分类:一般都是可以分组的字段
# filter_fields = ['brand']
# 区间,也可以包含分类,提倡 筛选 就采用该方式
filter_class = CarFilterSet
###### 分页
# 普通分页(最常用)
# pagination_class = pagination.CarPageNumberPagination
# 偏移分页
# pagination_class = pagination.CarLimitOffsetPagination
# 加密分页(使用的时候详见CarCursorPagination注释)
# pagination_class = pagination.CarCursorPagination
filtersets.py(自定义的筛选组件)
from django_filters.rest_framework import FilterSet
from django_filters import filters
from . import models
class CarFilterSet(FilterSet):
min_price = filters.NumberFilter(field_name='price', lookup_expr='gte')
max_price = filters.NumberFilter(field_name='price', lookup_expr='lte')
class Meta:
model = models.Car
# brand还是实现分类
# min_price,max_price可以定义区间
fields = ['brand', 'min_price', 'max_price']
'''
访问规则:
http://127.0.0.1:8000/api/cars/?brand=2&min_price=8&max_price=10
'''
pagination.py(自定义的分页组件)
# 大逻辑就是 源码已经给你写好了但是没有具体的参数。我们需要继承源码的分页组件类,然后重写一些参数来配置分页组件
from rest_framework.pagination import PageNumberPagination
# 普通分页
class CarPageNumberPagination(PageNumberPagination):
# 默认一页的条数
page_size = 3
# 用户可以自定义选择一页的条数,但是最多显示5条
page_size_query_param = 'page_size'
max_page_size = 5
# 默认条数访问 /cars/?page=页面号
# eg:/cars/?page=1
# 自定义条数访问 /cars/?page=页面号&page_size=一页的条数
# eg:/cars/?page=1&page_size=5
# eg:http://127.0.0.1:8000/api/cars/?ordering=-price&page=1&page_size=6
from rest_framework.pagination import LimitOffsetPagination
# 偏移分页
# 偏移分页不是按照页号计算的,是告诉我从头开始偏移的数量,告诉我每页显示几条
class CarLimitOffsetPagination(LimitOffsetPagination):
# 默认一页的条数
default_limit = 3
# limit控制一页显示的条数,offset控制偏移的条数(从头开始计数)
limit_query_param = 'limit'
offset_query_param = 'offset'
# 限制limit可以设置的最大显示条数
max_limit = 5
# 接口 /cars/?limit=一页的条数&offset=偏移的条数
# eg:/cars/?limit=5&offset=2 # 显示3~7条
# eg:http://127.0.0.1:8000/api/cars/?ordering=-price&limit=4&offset=5 #显示6~9条
from rest_framework.pagination import CursorPagination
# 加密分页
# 自己有个默认排序,一级页码是秘文。
class CarCursorPagination(CursorPagination):
# 默认一页的条数
page_size = 3
# 用户可以自定义选择一页的条数,但最多显示5条
page_size_query_param = 'page_size'
max_page_size = 5
# 默认排序规则,这个必须写
# 如果外面定义了排序组件 那么访问时ordering=字段 必须写
ordering = 'pk'
# 采用默认排序访问 /cars/?cursor=加密串
# eg:/cars/?cursor=cD0z
# 结合视图类实现OrderingFilter自定义排序规则
# /cars/?cursor=加密串&ordering=排序字段
# eg:/cars/?cursor=cD0z&ordering=-price
# 下一页的页码,会自动返回回来
'''
访问:
第一次访问:http://127.0.0.1:8000/api/cars/?ordering=-price&page_size=6
下一页访问:http://127.0.0.1:8000/api/cars/?ordering=-price&page_size=6&cursor=cD0xMC40MA%3D%3D
返回:
{
下一页的页码
"next": "http://127.0.0.1:8000/api/cars/?cursor=cD0xMC40MA%3D%3D&ordering=-price&page_size=6",
"previous": null,
"results": [
{
"pk": 3,
"name": "奔驰G60",
"price": "23.10",
"brand_name": "奔驰"
},
'''
四 自定义异常处理
思路
没多复杂
1 settings.py修改drf处理异常的功能函数exception_handler位置
2 自定义exception_handler处理django处理不了异常
settings.py
REST_FRAMEWORK = {
# 自定义异常的位置
'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}
api/exception.py
# rest_framework.views 下的 exception_handler 处理了所有 drf可控范围内的异常
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
def exception_handler(exc, context):
# 首先调用原生drf处理异常的机制,如果原生的处理不了会返回none
response = drf_exception_handler(exc, context)
# 如果返回none 我们自己自行进行处理
if response is None:
print(context)
return Response({
'error':'服务器异常',
'views':str(context['view']) # 视图函数的位置
})
return response