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
- APIView 继承了View (django.views.View)
- ModelViewSet 继承View
相关工具
collection 存储多个api,可以批量执行测试
接口测试
fiddler 抓包工具
主要功能
restful规范
前后端分离的项目中,API设计的规范,在一定程度上达成共识,便于理解。
- 使用https进行数据传输,保证数据的安全性
- 在域名或者路径中体现API字样,一看即懂 如https://api.baidu.com/
- 在路径中体现版本 如https://api.baidu.com/v1 https://api.baidu.com/v2
- 数据资源,在路径中均使用名词!!
- 请求方式!!,表示操作类型 GET /api/v1/users/id POST 新增 PUT 更新 DELETE 删除
- 资源过滤,通过查询字符串进行过滤数据 如https://api.baidu.com/v1/users/?sex=male&offset=10&limit=10&sortby=age&order=desc
- 响应状态码 200, 请求成功 201, 创建资源成功 301,永久重定向 302, 临时重定向 404, 请求地址不存在 405, 请求方法不支持 500, 服务端内部错误
- 响应的错误信息error、提示信息msg
- 返回资源结果
- 响应的数据可以有链接
视图
使用rest_framework必须定义class-based-view
- 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
- 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({ })
- 视图集
# 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
重点:
- drf 必须定义视图类,且取消了csrf验证
- request对象是drf封装的(rest_framework.request.Request),原生的在self._request
- request.data POST 请求提交的数据; request.query_params GET 请求查询参数 request.method 请求方法 request.FILES 文件
- 三大认证 用户认证、权限认证、访问频率
序列化
将模型对象,转为字典,经过Response转为json字符串,便于网络传输。
- 小组长,完成如下工作
-
创建一个gitee仓库; 创建一个Django项目,配置跨域、drf、mysql、静态文件; 创建一个users 应用,并注册; 将Django项目推送到gitee的仓库。 登录自己的gitee账号,创建一个组织,在组织内部创建仓库: 添加 开发成员 负责人克隆项目到本地,并创建基本的项目master,然后提交,供组员在master基础上创建分支,并行开发。
注意:如果远程仓库初始化了一个git仓库,则本地git init产生的仓库,在推送时,会有冲突,需要先合并。
- 组员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() …
- 成员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})
- 小组长汇总
-
拉取项目, 合并分支。在本地原来的仓库中拉取组员更新的内容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)
- 自带校验参数: max_length min_length max_value min_value allow_blank = True/False 是否允许空 trim_whitespace 是否截断空白符
- 校验规则不够,自己写规则函数,在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")
- serializers.CharField(validators=[自定义函数地址])
- 限制的参数 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)
- 创建一个users应用,并定义模型类(继承AbstractUser)
- 迁移,并创建一个用户
- 定义LoginAPIView,让用户登录,并生成token
- 定义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中配置,并且
- 关闭局部认证
- 自定义的认证类不能放入views.py 中 每个请求过来都认证用户是否登录
# 配置全局认证 REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [users.auth.MyAuthentication], }