安装
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
相关工具
postman 模拟发送http请求
collection 存储多个api,可以批量执行测试
APIpost工具 接口测试
解析Json数据
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 clone https://gitee.com/laufing_org/django_2022_3_19.git
cd 仓库
# 配置签名,否则在提交时,会限制,每一个开发者克隆项目后都必须配置
git config user.name laufing
git config user.email 944582529@qq.com
# 创建项目
django-admin startproject django_laufing
# 项目其他的配置
# 提交本地仓库,并推送到远程仓库
git status
git add ./
git commit -m '项目主分支master, 起始'
git remote -v
# 没有远程地址时,需要添加
git remote add origin url
# 推送
git push origin 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'],
}