九、条件搜索
如果某个API需要传递一些条件进行搜索,其实就在是URL后面通过GET传参即可,例如:
/api/users?age=19&category=12
在drf中也有相应组件可以支持条件搜索。
1. 自定义filter
# urls.py from django.urls import path from app01 import views urlpatterns = [ path('api/users/', views.UserView.as_view( {"get": "list", "post": "create"} )), path('api/users/<int:pk>/', views.UserView.as_view( {"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"} )), ]
# views.py from rest_framework import serializers from rest_framework.viewsets import ModelViewSet from rest_framework.filters import BaseFilterBackend from app01 import models class UserModelSerializer(serializers.ModelSerializer): level_text = serializers.CharField( source="get_level_display", read_only=True ) extra = serializers.SerializerMethodField(read_only=True) class Meta: model = models.UserInfo fields = ["username", "age", "email", "level_text", "extra"] def get_extra(self, obj): return 666 class Filter1(BaseFilterBackend): def filter_queryset(self, request, queryset, view): age = request.query_params.get('age') if not age: return queryset return queryset.filter(age=age) class Filter2(BaseFilterBackend): def filter_queryset(self, request, queryset, view): user_id = request.query_params.get('id') if not user_id: return queryset return queryset.filter(id__gt=user_id) class UserView(ModelViewSet): filter_backends = [Filter1, Filter2] queryset = models.UserInfo.objects.all() serializer_class = UserModelSerializer def perform_create(self, serializer): """ 序列化:对请求的数据校验成功后,执行保存。""" serializer.save(depart_id=1, password="123")
流程:假如访问127.0.0.1/api/user?age=18&id=12
queryset = models.UserInfo.objects.all()获取queryset
将queryset传给Filter1,Filter1返回queryset.filter(age=age)再将这个返回的queryset当作Filter2的queryset参数进行筛选,返回queryset.filter(id__gt=user_id)。
filter_backends里的都走完以后才是我们真正开始使用的queryset
当然如果访问带参数的url如127.0.0.1/api/user/100/?age=18&id=12则条件100会在最后得到的queryset获取查找。
2. 第三方filter
在drf开发中有一个常用的第三方过滤器:DjangoFilterBackend。
pip install django-filter
注册app:
INSTALLED_APPS = [
...
'django_filters',
...
]示例1
# views.py from rest_framework import serializers from rest_framework.viewsets import ModelViewSet from django_filters.rest_framework import DjangoFilterBackend from app01 import models class UserModelSerializer(serializers.ModelSerializer): level_text = serializers.CharField( source="get_level_display", read_only=True ) extra = serializers.SerializerMethodField(read_only=True) class Meta: model = models.UserInfo fields = ["username", "age", "email", "level_text", "extra"] def get_extra(self, obj): return 666 class UserView(ModelViewSet): filter_backends = [DjangoFilterBackend, ] filterset_fields = ["id", "age", "email"] queryset = models.UserInfo.objects.all() serializer_class = UserModelSerializer def perform_create(self, serializer): """ 序列化:对请求的数据校验成功后,执行保存。""" serializer.save(depart_id=1, password="123")
此时get: 127.0.0.1:8000/api/user?id=2 和 127.0.0.1:8000/api/user/2/效果基本是一样
这个就相当于直接url内部不带参数的get请求,直接返回一个queryset,不过这次的queryset经过filter筛选并覆盖后的queryset了。
示例2
from rest_framework import serializers from rest_framework.viewsets import ModelViewSet from django_filters.rest_framework import DjangoFilterBackend from django_filters import FilterSet, filters from app01 import models class UserModelSerializer(serializers.ModelSerializer): level_text = serializers.CharField( source="get_level_display", read_only=True ) depart_title = serializers.CharField( source="depart.title", read_only=True ) extra = serializers.SerializerMethodField(read_only=True) class Meta: model = models.UserInfo fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"] def get_extra(self, obj): return 666 class MyFilterSet(FilterSet): # 传入Char ?dd=it 则筛选通过跨表depart__title=it dd= filters.CharFilter(field_name="depart__title", lookup_expr="exact") # 传入Number ?min_id=10 则筛选成为id大于等于10的queryset min_id = filters.NumberFilter(field_name='id', lookup_expr='gte') class Meta: model = models.UserInfo fields = ["min_id", "dd"] class UserView(ModelViewSet): filter_backends = [DjangoFilterBackend, ] filterset_class = MyFilterSet queryset = models.UserInfo.objects.all() serializer_class = UserModelSerializer def perform_create(self, serializer): """ 序列化:对请求的数据校验成功后,执行保存。""" serializer.save(depart_id=1, password="123")
示例3
其实就是示例2字段的拓展
from rest_framework import serializers from rest_framework.viewsets import ModelViewSet from django_filters.rest_framework import DjangoFilterBackend, OrderingFilter from django_filters import FilterSet, filters from app01 import models class UserModelSerializer(serializers.ModelSerializer): level_text = serializers.CharField( source="get_level_display", read_only=True ) depart_title = serializers.CharField( source="depart.title", read_only=True ) extra = serializers.SerializerMethodField(read_only=True) class Meta: model = models.UserInfo fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"] def get_extra(self, obj): return 666 class MyFilterSet(FilterSet): # /api/users/?min_id=2 -> id>=2 min_id = filters.NumberFilter(field_name='id', lookup_expr='gte') # /api/users/?name=wupeiqi -> not ( username=wupeiqi ) name = filters.CharFilter(field_name="username", lookup_expr="exact", exclude=True) # /api/users/?depart=xx -> depart__title like %xx% depart = filters.CharFilter(field_name="depart__title", lookup_expr="contains") # /api/users/?token=true -> "token" IS NULL # /api/users/?token=false -> "token" IS NOT NULL token = filters.BooleanFilter(field_name="token", lookup_expr="isnull") # /api/users/?email=xx -> email like xx% email = filters.CharFilter(field_name="email", lookup_expr="startswith") # /api/users/?level=2&level=1 -> "level" = 1 OR "level" = 2(必须的是存在的数据,否则报错-->内部有校验机制) # level = filters.AllValuesMultipleFilter(field_name="level", lookup_expr="exact") level = filters.MultipleChoiceFilter(field_name="level", lookup_expr="exact", choices=models.UserInfo.level_choices) # /api/users/?age=18,20 -> age in [18,20] age = filters.BaseInFilter(field_name='age', lookup_expr="in") # /api/users/?range_id_max=10&range_id_min=1 -> id BETWEEN 1 AND 10 range_id = filters.NumericRangeFilter(field_name='id', lookup_expr='range') # /api/users/?ordering=id -> order by id asc # /api/users/?ordering=-id -> order by id desc # /api/users/?ordering=age -> order by age asc # /api/users/?ordering=-age -> order by age desc ordering = filters.OrderingFilter(fields=["id", "age"]) # /api/users/?size=1 -> limit 1(自定义搜索) size = filters.CharFilter(method='filter_size', distinct=False, required=False) class Meta: model = models.UserInfo fields = ["id", "min_id", "name", "depart", "email", "level", "age", 'range_id', "size", "ordering"] def filter_size(self, queryset, name, value): int_value = int(value) return queryset[0:int_value] class UserView(ModelViewSet): filter_backends = [DjangoFilterBackend, ] filterset_class = MyFilterSet queryset = models.UserInfo.objects.all() serializer_class = UserModelSerializer def perform_create(self, serializer): """ 序列化:对请求的数据校验成功后,执行保存。""" serializer.save(depart_id=1, password="123")
lookup_expr有很多常见选择:
'exact': _(''), 大小写不用区分
'iexact': _(''), 大小写要区分
'contains': _('contains'),
'icontains': _('contains'),
'startswith': _('starts with'),
'istartswith': _('starts with'),
'endswith': _('ends with'),
'iendswith': _('ends with'),
'gt': _('is greater than'),
'gte': _('is greater than or equal to'),
'lt': _('is less than'),
'lte': _('is less than or equal to'),
'in': _('is in'),
'range': _('is in range'),
'isnull': _(''),
'regex': _('matches regex'),
'iregex': _('matches regex'),
全局配置
# settings.py 全局配置 REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend',] }
3. 内置filter
drf源码中内置了2个filter,分别是:
- OrderingFilter,支持排序。
from rest_framework import serializers from rest_framework.viewsets import ModelViewSet from app01 import models from rest_framework.filters import OrderingFilter class UserModelSerializer(serializers.ModelSerializer): level_text = serializers.CharField( source="get_level_display", read_only=True ) depart_title = serializers.CharField( source="depart.title", read_only=True ) extra = serializers.SerializerMethodField(read_only=True) class Meta: model = models.UserInfo fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"] def get_extra(self, obj): return 666 class UserView(ModelViewSet): filter_backends = [OrderingFilter, ] # ?order=id # ?order=-id # ?order=age ordering_fields = ["id", "age"] queryset = models.UserInfo.objects.all() serializer_class = UserModelSerializer def perform_create(self, serializer): """ 序列化:对请求的数据校验成功后,执行保存。""" serializer.save(depart_id=1, password="123")
- SearchFilter,支持模糊搜索。
from rest_framework import serializers from rest_framework.viewsets import ModelViewSet from app01 import models from rest_framework.filters import SearchFilter class UserModelSerializer(serializers.ModelSerializer): level_text = serializers.CharField( source="get_level_display", read_only=True ) depart_title = serializers.CharField( source="depart.title", read_only=True ) extra = serializers.SerializerMethodField(read_only=True) class Meta: model = models.UserInfo fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"] def get_extra(self, obj): return 666 class UserView(ModelViewSet): # ?search=马冬%梅 filter_backends = [SearchFilter, ] search_fields = ["id", "username", "age"] queryset = models.UserInfo.objects.all() serializer_class = UserModelSerializer def perform_create(self, serializer): """ 序列化:对请求的数据校验成功后,执行保存。""" serializer.save(depart_id=1, password="123")
十、分页
在查看数据列表的API中,如果 数据量 比较大,肯定不能把所有的数据都展示给用户,而需要通过分页展示。
我们在前面看源码时也接触到了GenericAPIView的分页功能,比如当我们get时走ListModelView,其内部便调用了这个功能。
在drf中为我们提供了一些分页相关类:
BasePagination,分页基类
PageNumberPagination(BasePagination) 支持 /accounts/?page=4&page_size=100 格式的分页
LimitOffsetPagination(BasePagination) 支持 ?offset=100&limit=10 格式的分页
CursorPagination(BasePagination) 支持 上一下 & 下一页 格式的分页(不常用)
1. APIView视图
如果编写视图是直接继承APIView,那么在使用分页时,就必须自己手动 实例化 和 调用相关方法。
a.PageNumberPagination
127.0.0.1:8000/api/user?page=3
注:此时于filter无关,我们也没在filter定义这个page字段,这是其内部规定?page
# settings.py 有优先级 REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', "PAGE_SIZE": 2 } # views.py from rest_framework.pagination import PageNumberPagination class UserView(APIView): filter_backends = [DjangoFilterBackend, ] filterset_class = MyFilterSet def get(self, request, *args, **kwargs): queryset = models.UserInfo.objects.all().order_by('id') pager = PageNumberPagination() paginate_queryset = pager.paginate_queryset(queryset, request, self) ser = UserModelSerializer(instance=paginate_queryset, many=True) return Response(ser.data)
注意要在settings中设置每页几条数据,不然得不到结果。
或者:
class MyPageNumberPagination(PageNumberPagination): page_size_query_param = 'size' page_size = 2 max_page_size = 100 class UserView(APIView): def get(self, request, *args, **kwargs): queryset = models.UserInfo.objects.all().order_by('id') pager = MyPageNumberPagination() paginate_queryset = pager.paginate_queryset(queryset, request, self) ser = UserModelSerializer(instance=paginate_queryset, many=True) return Response(ser.data)
我们规定了size,以后再去get默认就只显示一个size的数据了。
当然如果127.0.0.1:8000/api/user?page=1&size=4 则一页显示4条数据
2.LimitOffsetPagination
与上面一样可以在settings中直接设置或者自己写
class MyPageNumberPagination(LimitOffsetPagination): page_size_query_param = 'size' page_size = 3 max_page_size = 100 class UserView(APIView): def get(self, request, *args, **kwargs): queryset = models.UserInfo.objects.all().order_by('id') pager = MyPageNumberPagination() paginate_queryset = pager.paginate_queryset(queryset, request, self) ser = UserModelSerializer(instance=paginate_queryset, many=True) return Response(ser.data)
3.CursorPagination
要注意这里要加上ordering 否则报错
class MyPageNumberPagination(CursorPagination): ordering = 'id' page_size_query_param = 'size' page_size = 3 max_page_size = 100 class UserView(APIView): def get(self, request, *args, **kwargs): queryset = models.UserInfo.objects.all().order_by('id') pager = MyPageNumberPagination() paginate_queryset = pager.paginate_queryset(queryset, request, self) ser = UserModelSerializer(instance=paginate_queryset, many=True) return Response(ser.data)
2. GenericAPIView派生类
如果是使用 ListModelMixin
或 ModelViewSet
,则只需要配置相关类即可,内部会自动执行相关分页的方法。
a.PageNumberPagination
同时也会生成:上一页、下一页链接,根据你写的页码和每页个数。
class MyPageNumberPagination(PageNumberPagination): page_size_query_param = 'size' page_size = 3 max_page_size = 100 class UserView(ModelViewSet): queryset = models.UserInfo.objects.all() serializer_class = UserModelSerializer pagination_class = MyPageNumberPagination
2.LimitOffsetPagination
class MyLimitOffsetPagination(LimitOffsetPagination): page_size_query_param = 'size' page_size = 3 max_page_size = 100 class UserView(ModelViewSet): queryset = models.UserInfo.objects.all() serializer_class = UserModelSerializer pagination_class = MyLimitOffsetPagination
3.CursorPagination
class MyCursorPagination(CursorPagination): ordering = 'id' page_size_query_param = 'size' page_size = 3 max_page_size = 100 class UserView(ModelViewSet): queryset = models.UserInfo.objects.all() serializer_class = UserModelSerializer pagination_class = MyCursorPagination
全局配置
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', #'DEFAULT_PAGINATION_CLASS': 'xx.xxx.MyPxxxPagination', "PAGE_SIZE": 2 }
十一、路由
在之前进行drf开发时,对于路由我们一般进行两种配置:
- 视图继承APIView
from django.urls import path from app01 import views urlpatterns = [ path('api/users/', views.UserView.as_view()), ]
- 视图继承
ViewSetMixin
(GenericViewSet、ModelViewSet)
from django.urls import path, re_path, include from app01 import views urlpatterns = [ path('api/users/', views.UserView.as_view({"get":"list","post":"create"})), path('api/users/<int:pk>/', views.UserView.as_view({"get":"retrieve","put":"update","patch":"partial_update","delete":"destory"})), ]
对于这种形式的路由,drf中提供了更简便的方式:
from rest_framework import routers from app01 import views router = routers.SimpleRouter() router.register(r'api/users', views.UserView) #这里users后面不要加/了 urlpatterns = [ # 其他URL # path('xxxx/', xxxx.as_view()), ] urlpatterns += router.urls
此时users后面不要加/了,且这句话会生成多个链接,比如带着pk的:
也可以利用include,给URL加前缀:
from django.urls import path, include from rest_framework import routers from app01 import views router = routers.SimpleRouter() router.register(r'users', views.UserView) urlpatterns = [ path('api/', include((router.urls, 'app_name'), namespace='instance_name')), # 其他URL # path('forgot-password/', ForgotPasswordFormView.as_view()), ]
十二、解析器
之前使用 request.data
获取请求体中的数据。这个 reqeust.data
的数据怎么来的呢?其实在drf内部是由解析器,根据请求者传入的数据格式 + 请求头来进行处理。
1.JSONParser
2.FormParser
3.MultiPartParser
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="http://127.0.0.1:8000/test/" method="post" enctype="multipart/form-data"> <input type="text" name="user" /> <input type="file" name="img"> <input type="submit" value="提交"> </form> </body> </html>
4.FileUploadParser
解析器可以设置多个,默认解析器:MultiPartParser, JSONParser, FormParser