drf框架----django-rest-framework

安装

drf 是django的一个扩展应用, windows为例:

python -m pip install django-rest-framework
#或者
python -m pip install djangorestframework

使用时,需在django项目中安装该应用。

#settings.py
INSTALLED_APP = [
...,
rest_framework, # 若不注册, 会templateNotExists错误
]

然后就可以在项目中使用

#视图
from rest_framework.views import APIView
#响应
from rest_framework.response import Response
#序列化,将python对象----转为字典
from rest_framework import serializers
serializers.ModelSerializer
serializers.Serializer

# 视图集
from rest_framework.viewsets import ModelViewSet
  1. APIView 继承了View (django.views.View)
  2. ModelViewSet 继承View

相关工具

collection 存储多个api,可以批量执行测试

接口测试

fiddler 抓包工具

主要功能

restful规范

前后端分离的项目中,API设计的规范,在一定程度上达成共识,便于理解。

  1. 使用https进行数据传输,保证数据的安全性
  2. 在域名或者路径中体现API字样,一看即懂 如https://api.baidu.com/
  3. 在路径中体现版本 如https://api.baidu.com/v1 https://api.baidu.com/v2
  4. 数据资源,在路径中均使用名词!!
  5. 请求方式!!,表示操作类型 GET /api/v1/users/id POST 新增 PUT 更新 DELETE 删除
  6. 资源过滤,通过查询字符串进行过滤数据 如https://api.baidu.com/v1/users/?sex=male&offset=10&limit=10&sortby=age&order=desc
  7. 响应状态码 200, 请求成功 201, 创建资源成功 301,永久重定向 302, 临时重定向 404, 请求地址不存在 405, 请求方法不支持 500, 服务端内部错误
  8. 响应的错误信息error、提示信息msg
  9. 返回资源结果
  10. 响应的数据可以有链接

视图

使用rest_framework必须定义class-based-view

  1. django的View源码
path("/users/login", Login.as_view()) # 类方法, 闭包
@classonlymethod
def as_view(cls, **initkwargs):
   """Main entry point for a request-response process."""
   # 省略不执行部分

   def view(request, *args, **kwargs):
       # 视图类的实例对象
       self = cls(**initkwargs)
       
       if hasattr(self, get) and not hasattr(self, head):
           self.head = self.get
       
       # setup给self对象进行属性赋值
       #self.request = request
       #self.args = args
       self.setup(request, *args, **kwargs)
       
       if not hasattr(self, request):
           raise AttributeError(
               "%s instance has no request attribute. Did you override "
               "setup() and forget to call super()?" % cls.__name__
           )

	   # 提交给对应的视图类的方法, 返回响应
       return self.dispatch(request, *args, **kwargs)
   view.view_class = cls
   view.view_initkwargs = initkwargs

   # take name and docstring from class
   update_wrapper(view, cls, updated=())

   # and possible attributes set by decorators
   # like csrf_exempt from dispatch
   update_wrapper(view, cls.dispatch, assigned=())
   return view
  1. APIView视图 继承django的View,取消CSRF验证。 @csrf_exampt def xxx 或者csrf_exampt(函数)
from rest_framework.views import APIView
from rest_framework.response import Response
class Query(APIView): #APIView 继承View
	# 查看请求能否过来,以及其发过来的数据
    # 前端发送post的数据--> request.data
    # put/delete请求   --> request.data
    # 前端get 查询字符串--> request.query_params
    # 
   
    print(dir(request)

	return Response({
          
   })
  1. 视图集
# views.py
from rest_framework.viewsets import ModelViewSet
from users.models import User
from users.serializers import UserSer

# 定义一个视图集
class UserViewSet(ModelViewSet):
	queryset = User.objects.all()
	serializer_class = UserSer

# 主路由urls.py
 from rest_framework.routers import DefaultRouter
 router = DefaultRouter()
 router.register("/users/test", UserViewSet) #对应视图集

urlpatterns += router.urls

重点:

  1. drf 必须定义视图类,且取消了csrf验证
  2. request对象是drf封装的(rest_framework.request.Request),原生的在self._request
  3. request.data POST 请求提交的数据; request.query_params GET 请求查询参数 request.method 请求方法 request.FILES 文件
  4. 三大认证 用户认证、权限认证、访问频率

序列化

将模型对象,转为字典,经过Response转为json字符串,便于网络传输。

  1. 小组长,完成如下工作
    创建一个gitee仓库; 创建一个Django项目,配置跨域、drf、mysql、静态文件; 创建一个users 应用,并注册; 将Django项目推送到gitee的仓库。 登录自己的gitee账号,创建一个组织,在组织内部创建仓库: 添加 开发成员 负责人克隆项目到本地,并创建基本的项目master,然后提交,供组员在master基础上创建分支,并行开发。

注意:如果远程仓库初始化了一个git仓库,则本地git init产生的仓库,在推送时,会有冲突,需要先合并。

  1. 组员A,克隆项目主分支master,并完成如下工作
    配置仓库签名,创建分支branch_username,并切换分支 在users应用中,定义一个User模型类,继承django内建的AbstractUser 创建一个serializers.py, 并定义User模型类的局部序列化器(序列化username、mobile) 提交本地仓库
# 模型类 局部属性序列化
# models.py
class User(AbstractUser):
	mobile = models.CharField("手机号", max_length=11)

	def __str__(self):
		return self.username
	
	class Meta:
		db_table = "user_tb"
		verbose_name_plural = "用户表"
# 注意配置 AUTH_USER_MODEL = users.User

# 序列化器
# serializers.py
from rest_framework import serializers
class UserSerializer(serializers.Serializer): # 局部序列化
	username = serializers.CharField()   # 只序列化部分属性
	mobile11 = serializers.CharField(source=mobile) #source标识模型类中的字段/外键.属性/ 模型类中的方法,如test
	# 需要重写create & update方法
	
	# 序列化方法字段, 搭配一个函数get_字段名
	authors = serializers.SerializerMethodField()
	def get_authors(self, instance):
		data = instance.authors.all()
		return data   # 返回什么 authors就是什么数据

	
# 模型类的序列化器,所有类属性或者指定
class UserSer(serializers.ModelSerializer):
	password = serializers.CharField(max_length=64, write_only=True)
	class Meta:
		model = User
		fields = "__all__"
		#fields = (xx,xx,..., "password")
		#exclude = (xx,...) 排除 不与fields一起使用
		#read_only_fields = (xx,xx,)有效
		#write_only_fields = (,) 无效
		extra_kwargs = {
          
   
			mobile:{
          
   read_only: True}
		}
# 反序列化不需要重写create& update

其他字段类型 serializers.BooleanField() serializers.IntegerField() serializers.DecimalField() serializers.DateField() serializers.DateTimeField() serializers.ChoiceField() serializers.ImageField() serializers.FileField() serializers.EmailField() serializers.SlugField() 正则 serializers.URLField() …

  1. 成员B,克隆项目主分支master,并完成如下工作
    配置签名,创建分支(vscode会自动切换到该分支),并发布 在users应用中,配置路由/users/user/用户id 定义视图类UserAPIView,接收GET 请求,实现查询用户,序列化并返回响应 用户模型类User, 序列化器UserSerializer 发布分支,即推送到远程仓库git push origin branch_worker_b
path(user/<int:uid>, UserAPIView.as_view()),

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class UserAPIView(APIView):
	def get(self, request, uid):
		# 查询一个对象
		user = User.objects.filter(id=uid).first()
		# 序列化一个对象
		user_ser = UserSerializer(user)
		#查询多个
		#user_queryset = User.objects.all()
		#user_queryset_ser = UserSerializer(user_queryset, many=True)
		
		# 返回响应
		return Response(user_ser.data)
		# return Response({"code": 200, "msg": 成功, user: user_ser.data})
  1. 小组长汇总
    拉取项目, 合并分支。在本地原来的仓库中拉取组员更新的内容git pull origin branch_worker,自动合并到master主分支。(git pull origin,默认拉取master分支) 本地创建mysql数据库,并完成模型类迁移 插入测试数据 API接口测试, 浏览器输入地址http://host/users/user/1 成功!!

反序列化

将json字符串经过request转为字典,然后使用序列化器转为模型对象,还可以进行数据校验,如数据类型是否满足要求。

class UserSerializer(serializers.Serializer):
	# 需要反序列化的属性
	username = serializers.CharField(max_length=10, min_length=5) # 参数用于校验
	mobile = serializers.CharField(max_length=11, min_length=11)

	#更新时 必须重写update方法   implement
	def update(self, instance, data):
		instance.username = data.get("username")
		instance.mobile = data.get("mobile")
		instance.save()
	# 新增时,必须重写create方法
	def create(self, data):
		user = User.objects.create(**data)
		return user

# 视图类中 put更新
def put(self, request, uid):
	user = User.objects.filter(id=uid).first()
	user_ser = UserSerializer(instance=user, data=request.data)  # 使用data进行更新
	# 校验
	if user_ser.is_valid():
		user_ser.save()
		return Response(user_ser.data)
	else:
		return Response(user_ser.errors)
# 新增
def post(self, request):
	user_ser = UserSerializer(data=request.data)
	if user_ser.is_valid():
		user_ser.save()
		return Response(user_ser.data)
	else:
		return Response(user_ser.errors)
  1. 自带校验参数: max_length min_length max_value min_value allow_blank = True/False 是否允许空 trim_whitespace 是否截断空白符
  2. 校验规则不够,自己写规则函数,在UserSerializer类中
from rest_framework.exceptions import ValidationError
class UserSerializer(serializers.Serializer):
	...
	# 局部验证
	def validate_mobile(self, mobile): # validate_字段
		if not mobile.startswith("185"):
			raise ValidationError("xxx")
		else:
			return mobile
	# 全局验证
	def validate(self, data):
		username = data.get("username")
		mobile = data.get("mobile")
		if mobile.startswith("1"):
			return data
		else:
			return ValidationError("xxx")
  1. serializers.CharField(validators=[自定义函数地址])
  2. 限制的参数 readonly,字段仅仅用于序列化输出 writeonly,字段仅仅用于反序列化输入 required=True,反序列化时必须传入 default, 反序列化时的默认值 allow_null, 反序列化时是否允许None validators,该字段使用的验证器

响应

对响应处理,返回带有web界面的响应

from rest_framework.response import Response

return Response(data, content_type, status, headers,...)
data, 返回的数据字典
content_type, 内容类型
status, 状态码, 可以使用rest_framework.status中的常量
headers, 响应头字典 , 如{
          
   "token":"xx"}

GenericAPIView

GenericAPIView 和 5个扩展类

from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin # 查看所有
from rest_framework.mixins import CreateModelMixin # 新增一个
from rest_framework.mixins import UpdateModelMixin # 更新一个
from rest_framework.mixins import DestroyModelMixin #删除一个
from rest_framework.mixins import RetrieveModelMixin

# 两个路由
# 查看所有、新增一个
path("GenericAPIView/", UserView1.as_view()),
# 查看一个、更新一个、删除一个, 必须是pk
path("GenericAPIView/<int:pk>/", UserView2.as_view()),

# 基于GenericAPIView 和5个扩展类的视图
class UserView1(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = User.objects.all()
    serializer_class = UserSer

    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)

class UserView2(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = User.objects.all()
    serializer_class = UserSer

    def get(self, request, pk):
        return self.retrieve(request, pk)

    def put(self, request, pk):
        return self.update(request, pk)

    def delete(self, request, pk):
        return self.destroy(request, pk)

# 浏览器中测试即可

进一步改进 GenericAPIView 9个视图子类

from rest_framework.generics import ListAPIView, CreateAPIView, UpdateAPIView, DestroyAPIView,RetrieveAPIView
#前端只需发送相应的请求即可
#path("/books/", BookAPIView1.as_view())
class BookAPIView1(ListAPIView, CreateAPIView):
	#查看所有, 新增一个  ListCreateAPIView
    queryset = Book.objects.all()
    serializer_class = BookSer  #新增时,注意与序列化器匹配

#path(/books/book/<int:pk>, BookAPIView2.as_view())
class BookAPIView2(UpdateAPIView, DestroyAPIView, RetrieveAPIView):
	#更新一个、删除一个、查看一个 RetrieveUpdateDestroyAPIView
	queryset = Book.objects.all()
	serializer_class = BookSer

视图集

path("v1/users/", UserViewSet.as_view({
          
   "get":"list", post:create})),

# 必须是pk
path("v1/users/user/<int:pk>/",  UserViewSet.as_view({
          
   "get":"retrieve", "put":"update", delete:"destroy"})),

# 视图集合
from rest_framework.viewsets import ModelViewSet
class UserViewSet(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSer

自定义action 将请求定向到自定义的函数,则需继承ViewSetMixin, APIView

# 路由,实现get请求 对应到自己定义的action
path("ViewSetMixin/", UserViewSet.as_view({
          
   "get":"my_func", post: "my_func2"}))

# 视图类
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework.viewsets import ViewSetMixin
class UserViewSet(ViewSetMixin, APIView):
	# 自定义的函数
    def my_func(self, request):

       print("自定义的action")

       return Response({
          
   "code": 200, "msg": "自定义的action"})

	def my_func2(self, request):
		return Response(xxx)

传入一个pk, 也是可以的;

路由

自动生成路由

# 主路由中urls.py
from rest_framework.routers import DefaultRouter
from users.views import UserViewSet

router = DefaultRouter()
router.register("user", UserViewSet)

urlpatterns = [
    path(admin/, admin.site.urls),

    # 图形验证码
    path("v1/imageCode/<str:uuid_>/", gen_image_code),

    # 用户注册
    path(v1/users/, include("users.urls")),
]

urlpatterns += router.urls

# 对应到视图集
from rest_framework.viewsets import ModelViewSet
from users.models import User
from users.serializers import UserSer
class UserViewSet(ModelViewSet):
	queryset = User.objects.all()
	serializer_class = UserSer

会自动生成路由 /user/ 查看所有 /user/ 新增一个 /user/id 查看一个、更新一个、 删除一个

认证

通过请求头中的Authorization检查用户是否登录 定义认证类,并继承BaseAuthentication,重写authenticate方法; 认证通过,返回user对象,auth口令 认证失败,raise AuthenticationFailed

print(“认证结果:”, request.user, request.auth, request.authenticators)

  1. 创建一个users应用,并定义模型类(继承AbstractUser)
  2. 迁移,并创建一个用户
  3. 定义LoginAPIView,让用户登录,并生成token
  4. 定义CheckUserAPIView,get请求携带token,认证

使用APIpost认证时,自己添加Authorization头部时,会报错,如下: 此时应用使用下面这种方式: 服务端要对token (Bearer abcxxx)分割一下

核心代码:

from django.contrib.auth.hashers import check_password
import jwt
from django.conf import settings
from datetime import timedelta
from datetime import datetime
# 用户登录
class LoginAPIView(APIView):

    def post(self, request):
        print("请求体数据:", request.data, type(request.data))
        username = request.data.get("username")
        password = request.data.get("password")

        # 查询用户对象
        user = User.objects.filter(username=username).first()
        # print("user:", user)
        if user:
            validate = check_password(password, user.password)

            if validate:
                payload = {
          
   
                    "uid": user.id,
                    uname: user.username
                }
                expire = 300
                token = self.generate_token(payload, expire)

                res = Response({
          
   "code": 200, "msg": "登录成功"})

                # 设置响应头
                res[token] = token
                res[author] = laufing

                return res

        return Response({
          
   "code": 204, "msg": 用户名或密码错误})

    @staticmethod
    def generate_token(payload, expire, secret=None):
        _payload = {
          
   
            "exp": time.time() + expire  # 当前时间戳加 s
        }
        # 将用户信息字典更新到自己内部
        _payload.update(payload)

        if secret:
            ...
        else:
            secret = settings.SECRET_KEY

        return jwt.encode(payload=_payload, key=secret, algorithm="HS256")

对get等其他请求,开启认证

# 用户的认证
from rest_framework.authentication import BaseAuthentication
from jwt.exceptions import ExpiredSignature, DecodeError
from rest_framework.exceptions import AuthenticationFailed

# 自定义认证类
class MyAuthentication(BaseAuthentication):
	# 必须重写authenticate方法
    def authenticate(self, request):
    	# 获取请求头中的Authorization  即jwt token
        token = request.headers.get("Authorization")
        print("token:", token)
        # apipost 开启认证时,token以Bearer开头
        if token.startswith("Bearer"):
            token = token.split(" ")[-1]
        try:
            payload = jwt.decode(token, key=settings.SECRET_KEY, algorithms=[HS256])
        except ExpiredSignature:
        	# 认证失败  抛出异常
            raise AuthenticationFailed("token已过期")

        except DecodeError:
            raise AuthenticationFailed("非法的token")

        user = User.objects.filter(id=payload.get("uid")).first()
        # 认证通过,返回user对象, auth口令
        return user, token

# 处理需认证的请求
class CheckUserAPIView(APIView):
	# 认证的类  ---局部认证
    authentication_classes = [MyAuthentication,]
	
	# 在进入get视图前,先认证
    def get(self, request):
		# request.user
		# request.auth
		# request.authenticators
        if request.user:

            return Response({
          
   "code": 200, "msg": "验证通过"})

        else:
            return Response({
          
   "code": 204, "msg": "验证不通过"})

自定义action的方式,必须继承ViewSetMixin, APIView

class CheckUserAPIView(ViewSetMixin, APIView):
    authentication_classes = [MyAuthentication]
    queryset = User.objects.all()
    serializer_class = UserSer

    @action(methods=["GET"], detail=False)  # detail 是否允许传入pk
    def myget(self, request):
        print(request.user)
        print(request.auth)
        print(request.authenticators)

        if request.user:

            return Response({
          
   "code": 200, "msg": "验证通过"})

        else:
            return Response({
          
   "code": 204, "msg": "验证不通过"})

全局认证 在项目的settings.py中配置,并且

  1. 关闭局部认证
  2. 自定义的认证类不能放入views.py 中 每个请求过来都认证用户是否登录
# 配置全局认证

REST_FRAMEWORK = {
          
   
	"DEFAULT_AUTHENTICATION_CLASSES": [users.auth.MyAuthentication],
}

权限

经验分享 程序员 微信小程序 职场和发展