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 相关文章推荐
Linux 发邮件磁盘空间监控(python)
Apr 23 Python
python利用urllib实现爬取京东网站商品图片的爬虫实例
Aug 24 Python
python复制文件到指定目录的实例
Apr 27 Python
Python使用matplotlib实现的图像读取、切割裁剪功能示例
Apr 28 Python
python实现C4.5决策树算法
Aug 29 Python
python调用摄像头拍摄数据集
Jun 01 Python
python 通过可变参数计算n个数的乘积方法
Jun 13 Python
Python替换月份为英文缩写的实现方法
Jul 15 Python
使用Pyhton集合set()实现成果查漏的例子
Nov 24 Python
基于Tensorflow的MNIST手写数字识别分类
Jun 17 Python
pycharm 如何取消连按两下shift出现的全局搜索
Jan 15 Python
python中zip()函数遍历多个列表方法
Feb 18 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
德劲1104的电路分析与改良
2021/03/01 无线电
PHP中动态HTML的输出技术
2006/10/09 PHP
php使用CutyCapt实现网页截图保存的方法
2016/10/03 PHP
php  PATH_SEPARATOR判断当前服务器系统类型实例
2016/10/28 PHP
浅谈laravel框架sql中groupBy之后排序的问题
2019/10/17 PHP
javascript 添加和移除函数的通用方法
2009/10/20 Javascript
Json和Jsonp理论实例代码详解
2013/11/15 Javascript
js实现同一页面多个不同运动效果的方法
2015/04/10 Javascript
Struts2+jquery.form.js实现图片与文件上传的方法
2016/05/05 Javascript
对js eval()函数的一些见解
2016/08/15 Javascript
微信小程序  modal详解及实例代码
2016/11/09 Javascript
如何给ss bash 写一个 WEB 端查看流量的页面
2017/03/23 Javascript
vue2.0中vue-cli实现全选、单选计算总价格的实例代码
2017/07/18 Javascript
推荐10款扩展Web表单的JS插件
2017/12/25 Javascript
JS解惑之Object中的key是有序的么
2019/05/06 Javascript
javascript事件监听与事件委托实例详解
2019/08/16 Javascript
countUp.js实现数字动态变化效果
2019/10/17 Javascript
JavaScript实现音乐导航效果
2020/11/19 Javascript
[31:01]2014 DOTA2国际邀请赛中国区预选赛5.21 CNB VS Orenda
2014/05/23 DOTA
Python类方法__init__和__del__构造、析构过程分析
2015/03/06 Python
在Python中使用lambda高效操作列表的教程
2015/04/24 Python
python itchat实现微信好友头像拼接图的示例代码
2017/08/14 Python
解决Pycharm中恢复被exclude的项目问题(pycharm source root)
2020/02/14 Python
Python实现快速大文件比较代码解析
2020/09/04 Python
html5 application cache遇到的严重问题
2012/12/26 HTML / CSS
印度尼西亚最好的小工具在线商店:Erafone.com
2019/03/26 全球购物
如何转换一个字符串到enum值
2014/04/12 面试题
解释下列WebService名词:WSDL、SOAP、UDDI
2012/06/22 面试题
幼儿园安全责任书范本
2014/07/24 职场文书
群众路线教师自我剖析材料
2014/09/29 职场文书
2014年煤矿安全工作总结
2014/12/04 职场文书
捐资助学感谢信
2015/01/21 职场文书
2015秋季新学期开学寄语
2015/05/28 职场文书
Ajax请求超时与网络异常处理图文详解
2021/05/23 Javascript
基于Java的MathML转图片的方法(示例代码)
2021/06/23 Java/Android
Golang使用Panic与Recover进行错误捕获
2022/03/22 Golang