DjangoRestFramework开发实践

在这之前我写过一篇关于Django与Drf快速开发实践的博客,Django快速开发实践:Drf框架和xadmin配置指北,粗略说了一下Drf配置和基本使用,不过里面只是涉及到最基本的CRUD,在正常的后端开发中涉及的诸如认证和权限、消息队列、缓存之类的操作,上一篇博客并没有涉及,这次开发我仔细了看了官方文档的这几个部分,把这部分的功能完善了起来。

Drf的设计很有Django的味道,(毕竟就是伴生框架嘛)封装了很多功能,稍微配置一下就可以用,这点在快速开发方面真的好评。

开始进入正题。

认证和授权

任何系统都离不开认证和授权,Drf内置一套强大的鉴权系统,框架提供了几种基本的认证和权限控制方式,小型系统基本够用,还可以自定义权限中间件,很方便就能实现节流这样的功能。

API认证

Drf内置的四种API认证方式,基本信息我做了一个表格:

认证方式 说明
BasicAuthentication 每次提交请求的时候附加用户名和密码来进行认证
TokenAuthentication 每次提交请求的时候在HTTP headers里附加Token进行认证
SessionAuthentication 用户登录之后系统在cookies存入sessionid进行认证
RemoteUserAuthentication 通过web服务器认证(apache/nginx这些)

我选择的是基于Token的认证,客户端登录之后维护一个token,每次请求附加到HTTP headers,还算是方便。

Drf还可以自定义认证方式,只要继承authentication.BaseAuthentication这个类然后重写authenticate方法就好了。这里只简单说下步骤,具体的参考官方文档。

  • 创建认证类:继承BaseAuthentication、重写authenticate方法
  • authenticate()返回值
    1. None:当前认证不管,等下一个认证来执行
    2. raise exceptions.AuthenticationFailed('用户认证失败')
    3. 有返回值元组形式:(元素1,元素2)元素1复制给request.user、元素2复制给request.auth

settings.py中可以配置默认的认证方式,这里我添加了三个:

REST_FRAMEWORK = {
# 身份验证
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}

当然也支持各种第三方的认证框架,比如下面这些:

使用这些认证方式,认证通过后,在views里面request.user就是一个Django用户对象,如果未认证,就是一个AnonymousUser对象。

接下来说说权限

API授权

Drf的接口权限有以下几种:

一般来说小网站用到DjangoModelPermissions就是够用的,或者干脆简单一点,用IsAuthenticatedqueryset限定请求的数据即可。

介绍完了基本概念,来看看代码中是如何操作的。

在viewset中的使用

对于操作用户信息的viewset,我只用了permissions.IsAuthenticated这个权限,然后覆盖了ReadOnlyModelViewSetget_queryset方法,把queryset变成只包括当前用户,这样就保证了一个用户只能操作自己的信息。

from rest_framework import authentication, permissions, viewsets

class UserViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = [permissions.IsAuthenticated]
serializer_class = UserSerializer def get_queryset(self):
return User.objects.filter(pk=self.request.user.pk)

viewset的action同样可以使用权限,加在装饰器的参数上即可:

@action(detail=True, methods=['GET'], permission_classes=[permissions.IsAuthenticated])
def some_actions(self, request, pk=None):
dosomething
return Response(SomeSerializer(some_data, many=True).data)

这里提一下装饰器的detail参数,这个代表了是对列表操作还是对单个对象操作,True就是对单个对象。

从请求的URL上应该可以很好理解。

  • 对列表:http://hostname/viewset/some_action/
  • 对单个对象:http://hostname/viewset/1/some_action/

这部分的参考资料(优先阅读官方文档):

ApiView和ViewSet

Drf的ApiView相当于Django的View,每个ViewSet是一组Restful的接口,看看viewset的代码,其中定义了6个action,对应get、post、put、delete这些http方法。

class UserViewSet(viewsets.ViewSet):
def list(self, request):
pass def create(self, request):
pass def retrieve(self, request, pk=None):
pass def update(self, request, pk=None):
pass def partial_update(self, request, pk=None):
pass def destroy(self, request, pk=None):
pass

除了自带的这些,还可以定义额外的action,这个在前文已经提过了。

ApiView的话,使用起来和Django的差不多,一个apiview只能对应一个url,通过重写getpost这些方法可以实现不同的http方法响应。

ApiView和ViewSet同样通过在类字段中加入authentication_classespermission_classes实现认证和授权。

分页 PAGINATION

Drf和Django一样自带分页功能,很好用(当然也支持使用第三方的分页功能)。

首先进行配置(不配置的话使用默认配置),这里我设置每页显示十条记录:

REST_FRAMEWORK = {
# 设置分页
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}

使用得最多的ModelViewSet已经自带分页了,这个我们不用操心,不过如果自己定义了action来返回列表数据的话,就没有分页,这时候要用paginate_queryset方法来处理。

代码如下:

@action(detail=False)
def tag(self, request):
queryset = SomeModel.objects.all().order_by('-add_time')
page = self.paginate_queryset(queryset)
if page is not None:
return self.get_paginated_response(self.get_serializer(page, many=True).data)
return Response(self.get_serializer(queryset, many=True).data)

可以看出Drf自动处理了不同页面的请求,不用像Django一样自己从GET或者POST数据里读取page,分页相关的方法直接在viewset对象里面,非常方便。

API文档设置

Drf支持很多文档插件,这里我用一下比较传统的coreapi文档,首先配置:

REST_FRAMEWORK = {
# 文档
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}

在使用之前还需要使用pip安装coreapi:

pip install coreapi

所有的ViewSet只要用了Router注册的话都默认添加到文档里,不过ApiView的话是要自己添加的。

from rest_framework.schemas.coreapi import AutoSchema

class SomeView(APIView):
schema = AutoSchema() def post(self, request):
data = dosomething()
return Response(data)

这样就可以了,别忘了注册路由,路由我在下文统一介绍。

配置完成之后文档的效果应该是下图这样,这个页面可以测试请求,还可以以各种方式登录认证,比Drf默认的界面丰富一些~

路由

使用Drf框架可以有两种路由方式,一种是Drf的路由,一直是Django的路由,两种搭配使用。

Drf的Router

from rest_framework import routers

router = routers.DefaultRouter()
router.register('users', UserViewSet, basename='api-users')
router.register('user-profiles', UserProfileViewSet, basename='api-user-profiles')
router.register('tags', TagViewSet, basename='api-tags')
router.register('categories', CategoryViewSet, basename='api-categories')
router.register('articles', ArticleViewSet, basename='api-articles')

定于完成之后要添加到Django的urlpatterns里面。

urlpatterns = [
path('api/', include(router.urls)),
]

对于ApiView,路由和Django的ClassBaseView差不多:

urlpatterns = [
path('login/', views.LoginView.as_view()),
path('signup/', views.SignUpView.as_view()),
]

配置认证和文档相关url

from rest_framework.authtoken import views as authtoken_view
from rest_framework.documentation import include_docs_urls urlpatterns = [
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api-token-auth/', authtoken_view.obtain_auth_token),
path('api-docs/', include_docs_urls(title='One Cat Docs')),
]

Serializers

序列化器也是Drf的一个重要概念,和Django的Form很像,作用是将Model对象的数据转换为json、xml之类的结构化数据。

使用起来很简单,我一般都是这么定义

class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'

使用__all__直接把所有模型字段都包括进去,针对外键等关系字段,我们需要做一些其他处理。

这里简单介绍一下我常用的几种关系字段处理方式

  • StringRelatedField:将关系字段显示为一个字符串,这个字符串取决于该模型定义的__str__方法
  • PrimaryKeyRelatedField:显示成该关系字段的主键,这个也是默认的
  • 嵌套序列化:定义一个该字段对应类型的序列化器,嵌套序列化

具体的使用方式可以在官网找到~

下面再介绍几个常用的参数:

  • read_only:一般针对不想被修改的字段可以使用,比如说用户id
  • many:用于多对多关系或者一对多,会将所有引用序列化为一个列表

限流

其实就是一个自定义的认证过程。

Drf内置有BaseThrottle SimpleRateThrottle,后者是前者的之类。

  • BaseThrottle 需要自己写allow_requestwait方法,控制粒度更细
  • SimpleRateThrottle重写get_cache_key和设置scope名称就可以,更简单

实现1分钟内只能访问3次的限流

SimpleRateThrottle代码如下:

from rest_framework.throttling import SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
'''匿名用户60s只能访问三次(根据ip)'''
scope = 'throttle' #这里面的值,自己随便定义,settings里面根据这个值配置throttle def get_cache_key(self, request, view):
#通过ip限制节流
return self.get_ident(request) class UserThrottle(SimpleRateThrottle):
'''登录用户60s可以访问10次'''
scope = 'userThrottle' #这里面的值,自己随便定义,settings里面根据这个值配置userThrottle def get_cache_key(self, request, view):
return request.user.user_id

BaseThrottle 代码如下:

from rest_framework.throttling import BaseThrottle
import time
VISIT_RECORD = {} #保存访问记录 class VisitThrottle(BaseThrottle):
'''60s内只能访问3次''' def __init__(self):
self.history = None #初始化访问记录 def allow_request(self,request,view):
#获取用户ip (get_ident)
remote_addr = self.get_ident(request)
ctime = time.time()
#如果当前IP不在访问记录里面,就添加到记录
if remote_addr not in VISIT_RECORD:
VISIT_RECORD[remote_addr] = [ctime,] #键值对的形式保存
return True #True表示可以访问
#获取当前ip的历史访问记录
history = VISIT_RECORD.get(remote_addr)
#初始化访问记录
self.history = history #如果有历史访问记录,并且最早一次的访问记录离当前时间超过60s,就删除最早的那个访问记录,
#只要为True,就一直循环删除最早的一次访问记录
while history and history[-1] < ctime - 60:
history.pop()
#如果访问记录不超过三次,就把当前的访问记录插到第一个位置(pop删除最后一个)
if len(history) < 3:
history.insert(0,ctime)
return True def wait(self):
'''还需要等多久才能访问'''
ctime = time.time()
return 60 - (ctime - self.history[-1])

配置节流

#全局
REST_FRAMEWORK = {
# 设置全局节流
"DEFAULT_THROTTLE_CLASSES":['api.utils.throttle.UserThrottle'], #全局配置,登录用户节流限制(10/m)
# 设置访问频率
"DEFAULT_THROTTLE_RATES":{
'throttle':'3/m', #没登录用户3/m,throttle就是scope定义的值,通过IP地址
'userThrottle':'10/m', #登录用户10/m,userThrottle就是scope定义的值, 通过user_id
}
} # 局部:在类视图中添加
throttle_classes = [VisitThrottle,]

大概就这些吧,官方文档真的很详细,看官方文档可以解决95%的疑问,加上Django和python自带的快速开发生产力,写起来太爽啦~

欢迎交流

我整理了一系列的技术文章和资料,在公众号「程序设计实验室」后台回复 linux、flutter、c#、netcore、android、java、python 等可获取相关技术文章和资料

一小时完成后台开发:DjangoRestFramework开发实践的更多相关文章

  1. 20145205 《Java程序设计》实验报告三:敏捷开发与XP实践

    开发与XP实践 实验要求 1.XP基础 2.XP核心实践 3.相关工具 实验内容 敏捷开发与XP 软件工程是把系统的.有序的.可量化的方法应用到软件的开发.运营和维护上的过程.软件工程包括下列领域:软 ...

  2. 20145213《Java程序设计》实验三敏捷开发与XP实践

    20145213<Java程序设计>实验三敏捷开发与XP实践 实验要求 1.XP基础 2.XP核心实践 3.相关工具 实验内容 1.敏捷开发与XP 软件工程是把系统的.有序的.可量化的方法 ...

  3. JAVA课程实验报告 实验三 敏捷开发与XP实践

    北京电子科技学院(BESTI) 实     验    报     告 课程:Java程序设计  班级:1353  姓名:韩玉琪  学号:20135317 成绩:             指导教师:娄嘉 ...

  4. 20145215实验三 敏捷开发与XP实践

    20145215实验三 敏捷开发与XP实践 实验内容 XP基础 XP核心实践 相关工具 实验步骤 (一)敏捷开发与XP 软件工程是把系统的.有序的.可量化的方法应用到软件的开发.运营和维护上的过程.软 ...

  5. 20145211 《Java程序设计》实验报告三:敏捷开发与XP实践

    实验内容 使用 git上传代码 使用 git相互更改代码 实现代码的重载 XP基础 XP核心实践 相关工具 一.git上传代码 这一部分是与我的partner合作的,详见他的博客- 20145326蔡 ...

  6. 杨学明老师推出全新课程--《敏捷开发&IPD和敏捷开发结合的实践》

    课时:13小时(2天) 敏捷开发&IPD和敏捷开发结合的实践 讲  师:杨学明 [课程背景] 集成产品开发(IPD).集成能力成熟度模型(CMMI).敏捷开发(Agile Developmen ...

  7. 20155324 《Java程序设计》实验三 敏捷开发与XP实践

    20155324 <Java程序设计>实验三 敏捷开发与XP实践 实验内容 XP基础 1.XP核心实践 2.相关工具 实验步骤 敏捷开发与XP 1.敏捷开发(Agile Developme ...

  8. 20165220 实验三 敏捷开发与XP实践 实验报告

    实验三 敏捷开发与XP实践-1 实验要求: 实验三 敏捷开发与XP实践 http://www.cnblogs.com/rocedu/p/4795776.html, Eclipse的内容替换成IDEA ...

  9. 20155228 实验三 敏捷开发与XP实践

    20155228 实验三 敏捷开发与XP实践 实验内容 1. XP基础 2. XP核心实践 3. 相关工具 实验要求 1.没有Linux基础的同学建议先学习<Linux基础入门(新版)>& ...

随机推荐

  1. hadoop知识整理(1)之HDFS

    一.HDFS是一个分布式文件系统 体系架构: hdfs主要包含了3部分,namenode.datanode和secondaryNameNode namenode主要作用和运行方式: 1)管理hdfs的 ...

  2. JavaScript触发器

    感谢:链接(视频讲解很详细) JavaScript触发器 一.功能 顾名思义就是操控鼠标或键盘触发(实现)一些特定功能. 二.功能实现 <script type="text/javas ...

  3. CentOS8.1安装Docker及Docker-compose

    使用 Docker 仓库进行安装 在新主机上首次安装 Docker Engine-Community 之前,需要设置 Docker 仓库.之后,您可以从仓库安装和更新 Docker. 设置仓库 安装所 ...

  4. UI 自动化环境搭建

    1,pip install selenium 2,驱动放在放在 Python 的根目录下

  5. IP组网实验(使用Cisco Packet Tracer路由器模拟软件)

    最近计网课讲到了以太网,第二个计网实验就是IP组网实验.这个实验主要使用了netsim这个路由器模拟软件.怎奈mac上没有,于是用Cisco Packet Tracer进行了一次模拟(其实就是实验中的 ...

  6. 8.实战交付一套dubbo微服务到k8s集群(1)之Zookeeper部署

    1.基础架构 主机名 角色 ip HDSS7-11.host.com K8S代理节点1,zk1 10.4.7.11 HDSS7-12.host.com K8S代理节点2,zk2 10.4.7.12 H ...

  7. 【spring】循环依赖 Java Vs Spring

    菜瓜:水稻,这次我特意去看了java的循环依赖 水稻:哟,有什么收获 菜瓜:两种情况,构造器循环依赖,属性循环依赖 构造器循环依赖在逻辑层面无法通过.对象通过构造函数创建时如果需要创建另一个对象,就会 ...

  8. [转]IP地址和MAV地址——区别和联系

    [转载]http://wenda.tianya.cn/question/27f9476d1e86f6b6 一.IP地址  对于IP地址,相信大家都很熟悉,即指使用TCP/IP协议指定给主机的32位地址 ...

  9. 图像处理中的valid卷积与same卷积

    valid卷积 在full卷积的卷积过程中,会遇到\(K_{flip}\)靠近I的边界(K矩阵与I矩阵),就会有部分延申到I之外,这时候忽略边界,只考虑I完全覆盖\(K_{flip}\)内的值情况,这 ...

  10. 获取系统的IP地址

    获取linux主机的IP地址 问题描述 在很多软件配置过程中,都需要设置ID信息,通常我选择使用系统配置的eth0网卡的IP地址信息,比如salt-minion-id,在通过cobbler批量安装操作 ...