Django DRF路由与扩展功能的实现


Posted in Python onJune 03, 2020

一. 视图集与路由的使用

使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中:

  • list() 提供一组数据
  • retrieve() 提供单个数据
  • create() 创建数据
  • update() 保存数据
  • destory() 删除数据

ViewSet视图集类不再实现get()、post()等方法,而是实现动作 action 如 list() 、create() 等。

视图集只在使用as_view()方法的时候,才会将action动作与具体请求方式对应上。

1. 常用的视图集父类

1.ViewSet

继承自APIView 与 ViewSetMixin作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。

ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典(如{'get':'list'})的映射处理工作。

在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。

2.GenericViewSet

使用ViewSet通常并不方便,因为list、retrieve、create、update、destory等方法都需要自己编写,而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写。但是Mixin扩展类依赖与GenericAPIView,所以还需要继承GenericAPIView。

GenericViewSet就帮助我们完成了这样的继承工作,继承自GenericAPIView与ViewSetMixin,在实现了调用as_view()时传入字典(如{'get':'list'})的映射处理工作的同时,还提供了GenericAPIView提供的基础方法,可以直接搭配Mixin扩展类使用。

3.ModelViewSet

继承自GenericViewSet,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。

4.ReadOnlyModelViewSet

继承自GenericViewSet,同时包括了ListModelMixin、RetrieveModelMixin。

下面我们还是通过案例,为大家演示吧!

首先,先创建一个子应用。

python3 manage.py startapp collect

5. 在collect下新建序列化器类

# collect下的serializers.py文件

from students.models import Student
from rest_framework import serializers


class StudentModelSerializer(serializers.ModelSerializer):

  class Meta:
    model = Student
    fields = ["id", "name", "age", "sex"]
    extra_kwargs = {
      "name": {"max_length": 10, "min_length": 4},
      "age": {"max_value": 150, "min_value": 0},
    }

  def validate_name(self, data):
    if data == "root":
      raise serializers.ValidationError("用户名不能为root!")
    return data

  def validate(self, attrs):
    name = attrs.get('name')
    age = attrs.get('age')

    if name == "alex" and age == 22:
      raise serializers.ValidationError("alex在22时的故事。。。")

    return attrs


class StudentInfoModelSerializer(serializers.ModelSerializer):
  class Meta:
    model = Student
    fields = ["id", "name"]

6. collect下的urls.py

from django.urls import path, re_path
from collect import views

urlpatterns = [
  # 不要在同一个路由的as_view中书写两个同样的键的http请求,会产生覆盖!!!
  # ViewSet
  path('student1/', views.Student1ViewSet.as_view({"get": "get_5"})),
  path('student1/get_5_girl/', views.Student1ViewSet.as_view({"get": "get_5_girl"})),
  re_path(r'^student1/(?P<pk>\d+)/$', views.Student1ViewSet.as_view({"get": "get_one"})),
  # GenericViewSet
  path('student2/', views.Student3GenericViewSet.as_view({"get": "get_5"})),
  path('student2/get_5_girl/', views.Student3GenericViewSet.as_view({"get": "get_5_girl"})),
  # GenericViewSet,可以和模型类进行组合快速生成基本的API接口
  path("students3/", views.Student4GenericViewSet.as_view({"get": "list", "post": "create"})),
  # ModelViewSet 默认提供了5个API接口
  path("students4/", views.Student5ModelViewSet.as_view({"post": "create", "get": "list"})),
  re_path(r"^students4/(?P<pk>\d+)/$", views.Student5ModelViewSet.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
  # ReadOnlyModelViewSet
  path("students5/", views.Student6ReadOnlyModelViewSet.as_view({"get": "list"})),
  re_path(r"^students5/(?P<pk>\d+)/$", views.Student6ReadOnlyModelViewSet.as_view({"get": "retrieve"})),

  # 一个视图类中调用多个序列化器
  path("student8/", views.Student8GenericAPIView.as_view()),

  # 一个视图集中调用多个序列化器
  path("student9/", views.Student9ModelViewSet.as_view({"get": "list"})),
  re_path(r"^student9/(?P<pk>\d+)/$", views.Student9ModelViewSet.as_view({"get": "retrieve"})),
]


"""
有了视图集以后,视图文件中多个视图类可以合并成一个,但是,路由的代码就变得复杂了,
需要我们经常在as_view方法 ,编写http请求和视图方法的对应关系,
事实上,在路由中,DRF也提供了一个路由类给我们对路由的代码进行简写。
当然,这个路由类仅针对于 视图集 才可以使用。
"""

# 路由类默认只会给视图集中的基本5个API生成地址[ 获取一条,获取多条,添加.删除,修改数据 ]
from rest_framework.routers import DefaultRouter
# 实例化路由类
router = DefaultRouter()
# router.register("访问地址前缀","视图集类","访问别名")
# 注册视图视图集类
router.register("student7", views.Student7ModelViewSet)

# 把路由列表注册到django项目中
urlpatterns += router.urls

7. collect下的views.py

"""ViewSet视图集,继承于APIView,所以APIView有的功能,它都有,APIView没有的功能,它也没有"""
from rest_framework.viewsets import ViewSet
from students.models import Student
from .serializers import StudentModelSerializer
from rest_framework.response import Response


class Student1ViewSet(ViewSet):
  def get_5(self, request):
    student_list = Student.objects.all()[:5]

    serializer = StudentModelSerializer(instance=student_list, many=True)

    return Response(serializer.data)

  def get_one(self, request, pk):
    student = Student.objects.get(pk=pk)

    serializer = StudentModelSerializer(instance=student)

    return Response(serializer.data)

  def get_5_girl(self, request):
    student_list = Student.objects.filter(sex=False)[:5]

    serializer = StudentModelSerializer(instance=student_list, many=True)

    return Response(serializer.data)


"""如果希望在视图集中调用GenericAPIView的功能,则可以采用下面方式"""
from rest_framework.generics import GenericAPIView


class Student2ViewSet(ViewSet, GenericAPIView):
  queryset = Student.objects.all()
  serializer_class = StudentModelSerializer

  def get_5(self, request):
    student_list = self.get_queryset()[:5]

    serializer = StudentModelSerializer(instance=student_list, many=True)

    return Response(serializer.data)

  def get_one(self, request, pk):
    student = self.get_object()

    serializer = StudentModelSerializer(instance=student)

    return Response(serializer.data)

  def get_5_girl(self, request):
    student_list = self.get_queryset().filter(sex=False)[:5]

    serializer = StudentModelSerializer(instance=student_list, many=True)

    return Response(serializer.data)

"""
上面的方式,虽然实现视图集中调用GenericAPIView,但是我们要多了一些类的继承。
所以我们可以直接继承 GenericViewSet
"""
from rest_framework.viewsets import GenericViewSet


class Student3GenericViewSet(GenericViewSet):
  serializer_class = StudentModelSerializer
  queryset = Student.objects.all()

  def get_5(self, request):
    student_list = self.get_queryset()[:5]

    serializer = self.get_serializer(instance=student_list, many=True)

    return Response(serializer.data)

  def get_5_girl(self, request):
    student_list = self.get_queryset().filter(sex=False)[:5]

    serializer = self.get_serializer(instance=student_list, many=True)

    return Response(serializer.data)

"""
在使用GenericViewSet时,虽然已经提供了基本调用数据集(queryset)和序列化器属性,但是我们要编写一些基本的
API时,还是需要调用DRF提供的模型扩展类 [Mixins]
"""
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin, CreateModelMixin


class Student4GenericViewSet(GenericViewSet, ListModelMixin, CreateModelMixin):
  queryset = Student.objects.all()
  serializer_class = StudentModelSerializer


from rest_framework.viewsets import ModelViewSet


class Student5ModelViewSet(ModelViewSet):
  queryset = Student.objects.all()
  serializer_class = StudentModelSerializer


# 只读模型视图集
from rest_framework.viewsets import ReadOnlyModelViewSet


class Student6ReadOnlyModelViewSet(ReadOnlyModelViewSet):
  queryset = Student.objects.all()
  serializer_class = StudentModelSerializer


# 路由的使用
from rest_framework.decorators import action


class Student7ModelViewSet(ModelViewSet):
  queryset = Student.objects.all()
  serializer_class = StudentModelSerializer

  # methods 指定允许哪些http请求访问当前视图方法
  # detail 指定生成的路由地址中是否要夹带pk值,True为需要
  @action(methods=["GET"], detail=False)
  def get_6(self, request):
    serilizer = self.get_serializer(instance=self.get_queryset().get(pk=6))
    return Response(serilizer.data)


"""在多个视图类合并成一个视图类以后,那么有时候会出现一个类中需要调用多个序列化器"""

"""1. 在视图类中调用多个序列化器"""
"""原来的视图类中基本上一个视图类只会调用一个序列化器,当然也有可能要调用多个序列化器"""
from .serializers import StudentInfoModelSerializer


class Student8GenericAPIView(GenericAPIView):
  queryset = Student.objects.all()

  # GenericAPI内部调用序列化器的方法,我们可以重写这个方法来实现根据不同的需求来调用不同的序列化器
  def get_serializer_class(self):
    if self.request.method == "GET":
      # 2个字段
      return StudentInfoModelSerializer
    return StudentModelSerializer

  def get(self, request):
    """获取所有数据的id和name"""
    student_list = self.get_queryset()

    serializer = self.get_serializer(instance=student_list, many=True)

    return Response(serializer.data)

  def post(self, request):
    """添加数据"""
    data = request.data
    serializer = self.get_serializer(data=data)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data)


"""2. 在一个视图集中调用多个序列化器"""

class Student9ModelViewSet(ModelViewSet):
  queryset = Student.objects.all()

  """要求:
      列表数据list,返回2个字段,
      详情数据retrieve,返回所有字段,
  """
  def get_serializer_class(self):
    # 本次客户端请求的视图方法名 self.action
    if self.action == "list":
      return StudentInfoModelSerializer
    return StudentModelSerializer

二. 扩展功能

为了方便接下来的学习,我们创建一个新的子应用 opt

python3 manage.py startapp opt

因为接下来的功能中需要使用到登录功能,所以我们使用django内置admin站点并创建一个管理员.

创建管理员以后,访问admin站点,先修改站点的语言配置,在settings里修改

LANGUAGE_CODE = 'zh-hans'

1. 认证Authentication

可以在配置文件中配置全局默认的认证方案

REST_FRAMEWORK = {
  'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework.authentication.SessionAuthentication', # session认证
    'rest_framework.authentication.BasicAuthentication',  # 基本认证
  )
}

也可以在每个视图中通过设置authentication_classess属性来设置

opt下的urls.py

from django.urls import path
from opt import views

urlpatterns = [
  path('auth1/', views.Demo1APIView.as_view()),
  path('auth2/', views.Demo2APIView.as_view()),
]

opt下的views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAdminUser

"""用户的认证和权限识别"""


class Demo1APIView(APIView):
  """只允许登录后的用户访问"""
  permission_classes = [IsAuthenticated]

  def get(self, request):
    """个人中心"""
    return Response("个人中心")


class Demo2APIView(APIView):
  """只允许管理员访问"""
  permission_classes = [IsAdminUser]

  def get(self, request):
    """个人中心2"""
    return Response("个人中心2")

2. 权限Permissions

权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。

  • 在执行视图的dispatch()方法前,会先进行视图访问权限的判断
  • 在通过get_object()获取具体对象时,会进行模型对象访问权限的判断

内置提供的权限:

  • AllowAny 允许所有用户
  • IsAuthenticated 仅通过认证的用户
  • IsAdminUser 仅管理员用户
  • IsAuthenticatedOrReadOnly 已经登陆认证的用户可以对数据进行增删改操作,没有登陆认证的只能查看数据。

可以在配置文件中全局设置默认的权限管理类,如:

REST_FRAMEWORK = {
  ....
  
  'DEFAULT_PERMISSION_CLASSES': (
    'rest_framework.permissions.IsAuthenticated',
  )
}

如果未指明,则采用如下默认配置

'DEFAULT_PERMISSION_CLASSES': (
  'rest_framework.permissions.AllowAny',
)

也可以在具体的视图中通过permission_classes属性来设置。

opt下的urls.py

urlpatterns = [
  path('auth1/', views.Demo1APIView.as_view()),
  path('auth2/', views.Demo2APIView.as_view()),
  # 自定义权限
  path('auth3/', views.Demo3APIView.as_view()),
]

opt下的views.py

# 自定义权限
from rest_framework.permissions import BasePermission


class MyPermission(BasePermission):
  def has_permission(self, request, view):
    """
    针对访问视图进行权限判断
    :param request: 本次操作的http请求对象
    :param view: 本次访问路由对应的视图对象
    :return:
    """
    if request.user.username == "xiaoming":
      return True
    return False


class Demo3APIView(APIView):
  permission_classes = [MyPermission]

  def get(self, request):
    """个人中心3"""
    return Response("个人中心3")

3. 限流Throttling

可以对接口访问的频次进行限制,以减轻服务器压力。

一般用于付费购买次数,投票等场景使用.

可以在配置文件中,使用DEFAULT_THROTTLE_CLASSES 和 DEFAULT_THROTTLE_RATES进行全局配置

REST_FRAMEWORK = {
  # 限流
  'DEFAULT_THROTTLE_CLASSES': ( # 对全局进行设置
    'rest_framework.throttling.AnonRateThrottle',
    'rest_framework.throttling.UserRateThrottle'
  ),
  'DEFAULT_THROTTLE_RATES': {
    'anon': '3/hour',
    'user': '3/minute',
  }
}

DEFAULT_THROTTLE_RATES 可以使用 second, minute, hour 或day来指明周期。

也可以在具体视图中通过throttle_classess属性来配置

opt下的urls.py

urlpatterns = [
  path('auth1/', views.Demo1APIView.as_view()),
  path('auth2/', views.Demo2APIView.as_view()),
  # 自定义权限
  path('auth3/', views.Demo3APIView.as_view()),
  # 限流
  path('auth4/', views.Demo4APIView.as_view()),
]

opt下的views.py

# 限流
from rest_framework.throttling import UserRateThrottle, AnonRateThrottle


class Demo4APIView(APIView):
  # throttle_classes = [UserRateThrottle, AnonRateThrottle] # 全局配置后,这里就不用指定

  def get(self, request):
    """投票页面"""
    return Response("投票页面")

4. 过滤Filtering

对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展来增强支持。

pip3 install django-filter

在配置文件里进行注册

INSTALLED_APPS = [
  ...
  'django_filters', # 需要注册应用,
]

REST_FRAMEWORK = {
  ...
  'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}

在视图中添加filter_fields属性,指定可以过滤的字段。

opt下的urls.py

urlpatterns = [
  path('auth1/', views.Demo1APIView.as_view()),
  path('auth2/', views.Demo2APIView.as_view()),
  # 自定义权限
  path('auth3/', views.Demo3APIView.as_view()),
  # 限流
  path('auth4/', views.Demo4APIView.as_view()),
  # 过滤
  path('data5/', views.Demo5APIView.as_view()),
]

opt下的views.py

# 过滤
from rest_framework.generics import GenericAPIView, ListAPIView
from students.models import Student
from .serializers import StudentModelSerializer
from django_filters.rest_framework import DjangoFilterBackend


class Demo5APIView(ListAPIView):
  queryset = Student.objects.all()
  serializer_class = StudentModelSerializer
  filter_backends = [DjangoFilterBackend] # 全局配置后,这里就不用指定了。
  filter_fields = ['age', "id"] # 声明过滤字段

5. 排序Ordering

对于列表数据,REST framework提供了OrderingFilter过滤器来帮助我们快速指明数据按照指定字段进行排序。

使用方法:

在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter过滤器,REST framework会在请求的查询字符串参数中检查是否包含了ordering参数,如果包含了ordering参数,则按照ordering参数指明的排序字段对数据集进行排序。

前端可以传递的ordering参数的可选字段值需要在ordering_fields中指明。

opt下的urs.py

urlpatterns = [
  path('auth1/', views.Demo1APIView.as_view()),
  path('auth2/', views.Demo2APIView.as_view()),
  # 自定义权限
  path('auth3/', views.Demo3APIView.as_view()),
  # 限流
  path('auth4/', views.Demo4APIView.as_view()),
  # 过滤
  path('data5/', views.Demo5APIView.as_view()),
  # 排序
  path('data6/', views.Demo6APIView.as_view()),
]

opt下的views.py

# 排序
from rest_framework.filters import OrderingFilter


class Demo6APIView(ListAPIView):
  queryset = Student.objects.all()
  serializer_class = StudentModelSerializer
  filter_backends = [DjangoFilterBackend, OrderingFilter] # 局部配置会覆盖全局配置
  filter_fields = ['id', "sex"]
  ordering_fields = ['id', "age"]

6. 分页Pagination

REST framework提供了分页的支持。

我们可以在配置文件中设置全局的分页方式,如:

REST_FRAMEWORK = {
  'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
  'PAGE_SIZE': 100 # 每页数目
}

也可通过自定义Pagination类,来为视图添加不同分页行为。在视图中通过pagination_clas属性来指明。

opt下的urls.py

urlpatterns = [
  path('auth1/', views.Demo1APIView.as_view()),
  path('auth2/', views.Demo2APIView.as_view()),
  # 自定义权限
  path('auth3/', views.Demo3APIView.as_view()),
  # 限流
  path('auth4/', views.Demo4APIView.as_view()),
  # 过滤
  path('data5/', views.Demo5APIView.as_view()),
  # 排序
  path('data6/', views.Demo6APIView.as_view()),
  # 分页
  path('data7/', views.Demo7APIView.as_view()),
]

opt下的views.py

# 分页
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination

"""1. 自定义分页器,定制分页的相关配置"""
"""
# 页码分页 PageNumberPagination
前端访问形式:GET http://127.0.0.1:8000/opt/data7/?page=4

page=1  limit 0,10
page=2  limit 10,20

# 偏移量分页 LimitOffsetPagination
前端访问形式:GET http://127.0.0.1:8000/opt/data7/?start=4&size=3

start=0 limit 0,10
start=10 limit 10,10
start=20 limit 20,10
"""


class StandardPageNumberPagination(PageNumberPagination):
  """分页相关配置"""
  page_query_param = "page"     # 设置分页页码关键字名
  page_size = 3           # 设置每页显示数据条数
  page_size_query_param = "size"   # 设置指定每页大小的关键字名
  max_page_size = 5         # 设置每页显示最大值


class StandardLimitOffsetPagination(LimitOffsetPagination):
  default_limit = 2         # 默认限制,默认值与PAGE_SIZE设置一致
  limit_query_param = "size"     # limit参数名
  offset_query_param = "start"    # offset参数名
  max_limit = 5           # 最大limit限制


class Demo7APIView(ListAPIView):
  queryset = Student.objects.all()
  serializer_class = StudentModelSerializer
  # 分页
  # 页码分页类
  pagination_class = StandardPageNumberPagination
  # 偏移量分页类
  # pagination_class = StandardLimitOffsetPagination

注意:如果在视图内关闭分页功能,只需在视图内设置

pagination_class = None

到此这篇关于Django DRF路由与扩展功能的实现的文章就介绍到这了,更多相关Django DRF路由与扩展内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python按照多个字符对字符串进行分割的方法
Mar 17 Python
Python fileinput模块使用实例
Jun 03 Python
python字符串对其居中显示的方法
Jul 11 Python
python&amp;MongoDB爬取图书馆借阅记录
Feb 05 Python
使用apidoc管理RESTful风格Flask项目接口文档方法
Feb 07 Python
python3 读写文件换行符的方法
Apr 09 Python
Python实现绘制双柱状图并显示数值功能示例
Jun 23 Python
基于python3实现socket文件传输和校验
Jul 28 Python
对python中矩阵相加函数sum()的使用详解
Jan 28 Python
Python中函数的基本定义与调用及内置函数详解
May 13 Python
Python实现最常见加密方式详解
Jul 13 Python
Python+Matplotlib图像上指定坐标的位置添加文本标签与注释
Apr 11 Python
Django中使用Json返回数据的实现方法
Jun 03 #Python
浅谈numpy中np.array()与np.asarray的区别以及.tolist
Jun 03 #Python
基于Python绘制美观动态圆环图、饼图
Jun 03 #Python
利用OpenCV中对图像数据进行64F和8U转换的方式
Jun 03 #Python
浅谈python opencv对图像颜色通道进行加减操作溢出
Jun 03 #Python
python golang中grpc 使用示例代码详解
Jun 03 #Python
Opencv图像处理:如何判断图片里某个颜色值占的比例
Jun 03 #Python
You might like
smtp邮件发送一例
2006/10/09 PHP
Linux下进行MYSQL编程时插入中文乱码的解决方案
2007/03/15 PHP
php下判断数组中是否存在相同的值array_unique
2008/03/25 PHP
使用ThinkPHP自带的Http类下载远程图片到本地的实现代码
2011/08/02 PHP
PHP实现一维数组转二维数组的方法
2015/02/25 PHP
JavaScript 加号(+)运算符号
2009/12/06 Javascript
使用JavaScript库还是自己写代码?
2010/01/28 Javascript
JavaScript动态创建div属性和样式示例代码
2013/10/09 Javascript
javascript读取Xml文件做一个二级联动菜单示例
2014/03/17 Javascript
javascript随机之洗牌算法深入分析
2014/06/07 Javascript
jQuery验证插件 Validate详解
2014/11/20 Javascript
jQuery实现的多级下拉菜单效果代码
2015/08/24 Javascript
js点击返回跳转到指定页面实现过程
2020/08/20 Javascript
JavaScript Promise 用法
2016/06/14 Javascript
快速移动鼠标触发问题及解决方法(ECharts外部调用保存为图片操作及工作流接线mouseenter和mouseleave)
2016/08/29 Javascript
小程序兼容安卓和IOS数据处理问题及坑
2018/09/18 Javascript
react同构实践之实现自己的同构模板
2019/03/13 Javascript
js中offset,client , scroll 三大元素知识点总结
2019/09/11 Javascript
node读写Excel操作实例分析
2019/11/06 Javascript
[04:10]2018年度CS GO玩家最喜爱的主播-完美盛典
2018/12/16 DOTA
Python中XlsxWriter模块简介与用法分析
2018/04/24 Python
python批量修改ssh密码的实现
2019/08/08 Python
PyQt5+python3+pycharm开发环境配置教程
2020/03/24 Python
Python如何批量获取文件夹的大小并保存
2020/03/31 Python
Python Django搭建网站流程图解
2020/06/13 Python
opencv 阈值分割的具体使用
2020/07/08 Python
Python爬虫小例子——爬取51job发布的工作职位
2020/07/10 Python
matplotlib基础绘图命令之errorbar的使用
2020/08/13 Python
css3的@media属性实现页面响应式布局示例代码
2014/02/10 HTML / CSS
优秀民警事迹材料
2014/01/29 职场文书
学习经验交流会主持词
2014/04/01 职场文书
门前三包责任书
2014/04/15 职场文书
学生吸烟检讨书
2014/09/14 职场文书
检察院对照“四风”认真查找问题落实整改措施
2014/09/26 职场文书
2014年个人技术工作总结
2014/12/08 职场文书
Golang 如何实现函数的任意类型传参
2021/04/29 Golang