DRF知识点总结(3)

简介: DRF知识点总结(3)

九、条件搜索

如果某个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派生类

如果是使用 ListModelMixinModelViewSet ,则只需要配置相关类即可,内部会自动执行相关分页的方法。

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

相关文章
|
网络协议 Ubuntu Linux
linux网络配置文件
Linux网络配置文件因发行版不同而异:Ubuntu使用`/etc/network/interfaces`,Red Hat系列采用`/etc/sysconfig/network-scripts/ifcfg-ethX`,CentOS用`/etc/sysconfig/network`设置主机名和网关,`/etc/resolv.conf`配置DNS。其他文件如`/etc/hostname`和`/etc/nsswitch.conf`也影响网络设置。编辑这些文件时,记得先备份,并可能需重启网络服务以应用更改。具体操作应参考相应Linux发行版的文档。
373 1
|
缓存
npm install 报 npm ERR! cb()never called!的错误
npm install 报 npm ERR! cb()never called!的错误
1776 0
|
人工智能 自然语言处理 算法
几款宝藏级AI阅读工具推荐!论文分析、文档总结必备神器!
【10月更文挑战第8天】几款宝藏级AI阅读工具推荐!论文分析、文档总结必备神器!
1392 1
几款宝藏级AI阅读工具推荐!论文分析、文档总结必备神器!
|
前端开发 文件存储 Python
python之xhtml2pdf: HTML转PDF工具示例详解
python之xhtml2pdf: HTML转PDF工具示例详解
1052 0
|
并行计算 算法 Java
cmake+vs2019编译OpenCV
cmake+vs2019编译OpenCV
380 0
|
存储 Python
dill:Python中增强版的pickle
dill:Python中增强版的pickle
442 4
|
前端开发 JavaScript
饿了么UI中el-tree中的树节点选中高亮的两种常用方式(highlight-current属性)
饿了么UI中el-tree中的树节点选中高亮的两种常用方式(highlight-current属性)
899 0
|
关系型数据库 MySQL 数据库
深入探讨MySQL分表策略与实践
深入探讨MySQL分表策略与实践
704 0
|
存储 监控 Linux
性能监控之Telegraf+InfluxDB+Grafana linux服务器实时监控
【6月更文挑战11天】标题性能监控之 Java Metrics 度量包性能监控之Telegraf+InfluxDB+Grafana linux服务器实时监控
347 2
|
消息中间件 网络协议 算法
流量回放工具之 goreplay 核心源码分析
【6月更文挑战第3天】流量回放工具之 goreplay 核心源码分析
318 3