day 74
day 74 drf
01接口;接口的概念、数据接口文档、接口规范(restful)
- django restfarmework
- drf是django的插件
- 安装
- pip3 install djangorestfarmework
- 使用drf时要在settings中app注册
01接口
- 什么是接口;规定了提交请求参数的请求方式、访问器可以获取响应的反馈数据的url连接
- 四部分;url连接+请求方式+请求参数+响应数据
- 满足了同样的接口规范,无论前后端是什么语言,和数据处理方式,都可以进行交互
02文档
03restful接口规范
-
url连接
- 接口都是操作前后台数据,所以需要保证数据的安全性,最好采用https协议
- 接口用来操作数据,与网址有区别,所以用特定的关键字表示接口,api
https://api.baidu.com
https://www.baidu.com/api
- 接口操作的数据称之为资源,在url中只体现资源名词,不体现操作资源的方式 动词
- 常规资源接口;
https://api.baidu.com/books/
https://api.baidu.com/books/(pk)/
- 非常规接口;和某资源不是特别密切或是不是一种资源
https://api.baidu.com/login/
https://api.baidu.com/place/search/
- 常规资源接口;
- 如果一个资源存在多版本结果,在url连接中要用特定符号来兼容多版本共存
v1/v2
;https://api.baidu.com/v1/books/
https://api.baidu.com/v2/books/
- 群资源操作一般还有额外的限制条件,如排序、限制条数、分页等等
- 采用?限制条件;
https://api.baidu.com/books/?ordering=-price&limit=3
- 采用?限制条件;
-
请求方式
- 五大请求方式;get、post、put、patch、delete
- get;获取单个或多个资源
https://api.baidu.com/books/
https://api.baidu.com/books/(pk)/
- post;新增单个或多个资源
https://api.baidu.com/books/
- 单增,提交单个数据字典,完成单增,返回单个结果对象
- 群增,提交多个数据字典,完成多增,返回多个结果对象
- put;整体修改单个或多个资源
- 整体修改多个,提供多个数据字典的数组(数据字典中要包含主键),完成修改,返回多个结果对象
- 整体修改单个,提供单个数据字典(主键在url中体现)完成单改,返回单个结果对象
- patch;局部修改单个或多个资源
- 方式与put请求相同吗,不同点;操作的资源如果有5个key-value键值对,put请求提供的字典必须报包含所有的键值资源,patch只需要携带需要修改的资源
- delete;删除单个或多个资源
- 多删,提供多个资源的主键数据,完成群删,
- 单删,不许要提供额外的数据资源,
- 不需要返回资源,但一般返回结果消息
- get;获取单个或多个资源
- 五大请求方式;get、post、put、patch、delete
-
响应结果
-
响应对象中要包含网络状态吗(网络状态信息与网络状态码是捆绑出先的)
- 1XX;基本信息
- 2XX;成功 200基本 201新增成功
- 3XX;重定向
- 4XX;客户端错误
- 5XX;服务端错误
-
数据状态码(一般是后台规定的)
- 0;成功
- 1;失败 - 1XX;具体失败信息(要在接口文档中写明)
- 2;无数据 - 2XX;具体无数据信息(要在接口文档中写明)
- 数据状态吗的信息(一般不仅仅是对数据状态吗的解释,更多是对结果的描述,给前台开发者阅读)
-
数据结果(常量,数组,字典),如果有子资源(图片,音频,视频),返回资源的url连接
{ "status": 0, 'msg':'ok', 'results':[{ 'name': '西游记', 'img': 'https://api.baidu.com/media/book/xyj.png' },{}] }
-
02drf生命周期 - CBV
- (项目启动)APIView的as_view覆盖掉View的方法(在基础上局部禁用csrf)=> (接收到请求)调用as_view的返回值view,调用dispatch分发请求 => APIView重写了dispatch方法 => 在分发之前,对request进行了二次封装、对数据进行解析 => 三大认证 => 请求对实际响应,视图类的返回值 => 出现异常,就会交给异常处理模块进行处理 => 响应模块完成响应,渲染模块可以json或浏览器模式进行渲染
03drf的基础组件;请求、响应、渲染、解析、异常
- APIView 类继承了View类,重写了as_view 和 dispatch 方法
- 重写的as_view方法,主体还是View的as_view,只是在返回函数地址时,局部禁用了csrf认证
- 重写的dispatch方法,
01请求模块
- 将wsgi的request对象转化为drf的Requeset类的对象
- 封装后的request对象完全兼容wsgi的request对象,并且将原来的request保存在request._request
- 重新格式化请求数据存放位置
- 拼接参数;request.query_params
- 数据包参数;request.data
02解析模块
- 指粗粒数据包参数 - form-data,urlencoded,json
- 全局配置所有视图类的解析方式,配置三种
- 局部配置当前视图类的解析方式,配置三种
03响应模块
- data;响应数据
- status;响应的网络状态码
- template_name;drf完成前后台不分离,不需要了解
- headeers;响应头
- exception;一般异常响应,会将其设置为True,默认False
- content_type;默认就是application/json 不需要处理
04渲染模块
- 可以配置返回结果为json数据或页面数据
- postman请求结果是json,浏览器请求结果是页面
05异常模块
-
自定义异常处理函数;异常先交给drf处理,如果处理结果为空代表是服务端错误,再手动处理,不管哪种异常都要记录到日志文件当中
# 导入drf的异常处理函数exceptioin_handler并重命名方便调用 from rest_framework.views import exception_handler as drf_exception_handler # 对自定义的服务端错误信息进行封装 from rest_framework.response import Response # 导入drf内置响应状态码 from rest_framework import status # 重写异常处理函数 def exception_handler(exc, context): response = drf_exception_handler(exc, context) # 交给drf自带处理客户端错误 # 对错误信息进行格式化封装 发生错误的视图类 错误请求的请求方式 错误信息 detail = '%s - %s - %s' % (context.get('view'), context.get('request').method, exc) if not response: # response为空表示为,服务端错误 response = Response({'detail': detail}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # status为给客户端返回的响应状态码 else: response.data = {'detail': detail} # 核心:要将response.data.get('detail')信息记录到日志文件 # logger.waring(response.data.get('detail')) import sys # 将异常信息以错误流的形式打印到终端 sys.stderr.write('异常:%s\n' % response.data.get('detail')) return response # 将处理好的响应数据返回出去
-
在settings中配置执行自定义异常处理函数
# drf框架自定义配置 REST_FRAMEWORK = { # 异常模块:异常处理函数 # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', drf自带异常处理函数 'EXCEPTION_HANDLER': 'api.exception.exception_handler', }
04drf的序列化(核心);序列化、模型序列化、群操作序列化
-
Serializer序列化
- 设置序列化字段,字段名于字段类型要与处理的model类属性名对应(值参与序列化的类型不需要设置条件)
- model类中有的字段,但在序列化中没有对应字段的不会参加序列化
- 自定义序列化方法
- 字段类型为
SerializerMethodField()
,值由 get_自定义字段名(self,model_obj)方法提供(一般自定义字段的值都与要序列化的model对象有关
- 字段类型为
class UserSerializer(serializers.Serializer): username = serializers.CharField() # model中字段 gender = serializers.SerializerMethodField() # 自定义字段 def get_gender(self, obj): # 自定义字段gender的值 return obj.get_sex_display() # 注:在高级序列化与高级视图类中,drf默认帮我们处理图片等子资源 icon = serializers.SerializerMethodField() def get_icon(self, obj): return '%s%s%s' % (settings.BASE_URL, settings.MEDIA_URL, obj.img)
-
Serializer反序列化
-
系统校验字段与自定义校验字段定义没有区别;
字段 = serializers.字段类型(条件)
password = serializers.CharField(min_length=3, max_length=16)
-
有自定义字段时是不能直接入库的,需要设置入库规则,或将字段移除(自定义反序列化字段一般是用作全局校验用)
re_password = serializers.CharField(min_length=3, max_length=16)
-
局部校验钩子函数
validate_字段名(self,字段值value)
,所有字段都可以设置局部校验。规则;成功直接返回value,失败则抛出异常ValidationError('错误信息')
def validate_username(self, value): if 'g' in value.lower(): raise serializers.ValidationError('名字中不能有g') return value
-
全局校验钩子函数
validate(self, 所有字段字典attrs)
,可以在内部进行需要多字段参与的校验。规则成功直接返回attrs,失败则抛出异常ValidationError({'异常字段': '错误信息'})
def validate(self, attrs): password = attrs.get('password') re_password = attrs.pop('re_password') if password != re_password: raise serializers.ValidationError({'re_password': '两次密码不一致'}) return attrs
-
重写create方法实现增入库,返回入库成功对象
def create(self, validated_data): return models.User.objects.create(**validated_data)
-
重写update方法实现改入库,返回入库成功对象
def update(self, instance: models.User, validated_data): # 用户名不能被修改 validated_data.pop('username') models.User.objects.filter(pk=instance.id).update(**validated_data) return instance
class UserDeSerializer(serializers.Serializer): # 系统校验字段 username = serializers.CharField(min_length=3, max_length=16, error_messages={ 'min_length': '太短', 'max_length': '太长' }) password = serializers.CharField(min_length=3, max_length=16) # 不写,不参与反序列化,写就必须参与反序列化(但可以设置required=False取消必须) # required=False的字段,前台不提供,走默认值,提供就一定进行校验;不写前台提不提供都采用默认值 sex = serializers.BooleanField(required=False) # 自定义校验字段:从设置语法与系统字段没有区别,但是这些字段不能参与入库操作,需要在全局钩子中,将其取出 re_password = serializers.CharField(min_length=3, max_length=16) # 局部钩子: # 方法就是 validate_校验的字段名(self, 校验的字段数据) # 校验规则:成功直接返回value,失败抛出校验失败信息 def validate_username(self, value): if 'g' in value.lower(): raise serializers.ValidationError('名字中不能有g') return value # 全局钩子: # 方法就是 validate(self, 所有的校验数据) # 校验规则:成功直接返回attrs,失败抛出校验失败信息 def validate(self, attrs): password = attrs.get('password') re_password = attrs.pop('re_password') if password != re_password: raise serializers.ValidationError({'re_password': '两次密码不一致'}) return attrs # 在视图类中调用序列化类的save方法完成入库,Serializer类能做的增入库走create方法,改入库走update方法 # 但Serializer没有提供两个方法的实现体 def create(self, validated_data): return models.User.objects.create(**validated_data) # instance要被修改的对象,validated_data代表校验后用来改instance的数据 def update(self, instance: models.User, validated_data): # 用户名不能被修改 validated_data.pop('username') models.User.objects.filter(pk=instance.id).update(**validated_data) return instance
-
-
ModelSerializer类;模型序列化类 - 核心类
-
单表;
-
序列化类继承ModelSerializer,所以需要在配置类Meta中进行配置
class UserModelSerializer(serializers.ModelSerializer): class Meta:
-
model配置:绑定序列化相关的model表
model = models.User
-
fields配置;采用插拔式,设置所有参与序列化与反序列化字段
fields = ('username', 'gender', 'icon', 'password', 'sex', 're_password') # 所有参与序列化与反序列化的(系统或自定义)字段都要到内部进行注册
-
extra_kwargs配置;
extra_kwargs = { 'username': { # 系统字段不设置read_only和write_only,默认都参加 'min_length': 3, # 配置简单的校验信息 'max_length': 10, 'error_messages': { # 配置不符合校验规则的返回信息 'min_length': '太短', 'max_length': '太长' } }, 'gender': { 'read_only': True, # 自定义的序列化字段默认就是read_only,且不能修改,可以省略 }, 'password': { 'write_only': True, # 配置为只写,反序列化,用于入库但不给客户端展示的字段 }, 'sex': { # 像sex有默认值的字段,为选填字段('required': True可以将其变为必填字段) 'write_only': True, # 'required': True } }
-
自定义反序列化字段
re_password = serializers.CharField(min_length=3, max_length=16, write_only=True) # 自定义反序列化字段一般不需要返回给前端,要设置 write_only = True
-
自定义序列化字段(推荐使用)
# 在model类中配置 @property # 序列化是通过反射的方法获取值 def gender(self): return self.get_sex_display()
-
局部校验钩子函数
validate_字段名(self,字段值value)
,所有字段都可以设置局部校验。规则;成功直接返回value,失败则抛出异常ValidationError('错误信息')
def validate_username(self, value): if 'g' in value.lower(): raise serializers.ValidationError('名字中不能有g') return value
-
全局校验钩子函数
validate(self, 所有字段字典attrs)
,可以在内部进行需要多字段参与的校验。规则成功直接返回attrs,失败则抛出异常ValidationError({'异常字段': '错误信息'})
def validate(self, attrs): password = attrs.get('password') re_password = attrs.pop('re_password') if password != re_password: raise serializers.ValidationError({'re_password': '两次密码不一致'}) return attrs
-
ModelSerializer内部写好了create和update方法,所以不需要重写
-
-
-
ListSerializer类;群操作序列化类 - 辅助类
- 重点;辅助完成单表多表群增群改操作
- 需要自定义辅助类继承ListSerializer
- 重写update方法,(ListSerializer没有写update方法,有create方法)
class BookListSerializer(serializers.ListSerializer): def update(self, instance_list, validated_data_list): return [ self.child.update(instance_list[index], attrs) for index, attrs in enumerate(validated_data_list) # self.child 为自定义的序列化模型类(BookModelSerializer) ]
01单表操作
class UserV3APIView(APIView):
# 单查群查
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
# 获取数据
user_obj = models.User.objects.filter(is_delete=False, pk=pk).first()
if not user_obj:
return Response({
'status': 1,
'msg': 'pk error',
}, status=400)
# 将数据交给序列化类处理
user_ser = serializers.UserModelSerializer(user_obj, many=False)
return Response({
'status': 0,
'msg': 'ok',
'results': user_ser.data # 将序列化好的数据取出
})
else:
user_query = models.User.objects.filter(is_delete=False).all()
user_ser = serializers.UserModelSerializer(user_query, many=True)
return Response({
'status': 0,
'msg': 'ok',
'results': user_ser.data
})
# 单增
def post(self, request, *args, **kwargs):
user_ser = serializers.UserModelSerializer(data=request.data)
if user_ser.is_valid():
# 入库
user_obj = user_ser.save()
return Response({
'status': 0,
'msg': 'ok',
'results': serializers.UserModelSerializer(user_obj).data
})
else:
return Response({
'status': 1,
'msg': user_ser.errors,
})
02多表操作
-
基表;为抽象表,专门用来继承的,为表提供公有字段的,自身不会在数据库中创建
class BaseModel(models.Model): class Meta: abstract = True
-
断关联表关系
- 不会影响连表查询操作
- 会提升增删改的效率
- 易于后期数据库表的重构
- 缺点在于,数据库本身没有连表检测,容易出现脏数据,需要通过严格的逻辑避免脏数据的产生(必要时管理脏数据)
- A依赖于B,先插入A,该记录对应的B记录没有产生,在没有关联的情况下该数据可以产生,该数据就是脏数据
- 接着再将B数据添加,脏数据就得到了处理,反过来先产生B再产生A,更符合逻辑,通过逻辑将表进行关联
- 连表查询,不会有任何异常
- related_name 在外键中,设置外键反向查询的字段名,正向找字段名,反向找related_name值
- on_delete在外键中必须设置,表示级联关系,在Django 1.X 下系统默认提供值为 models.CASCADE,Django 2.X 下必须手动声明,
- CASCADE;默认值,级联
- DO_NOTHING;外键不会被级联,假设A表依赖于B表,删除B表,A表的外键字段不做任何处理
- SET_DEFAULT;假设A表依赖于B表,B记录删除,A表的外键字段设置为default属性设置的值,必须配合default使用
- SET_NULL;假设A表依赖于B表,B记录删除,A表的外键字段设置为null,所以必须配合
null = True
使用
- db_constraint在外键中控制表关联,默认为True表示关联,设施False表示断开关联(该字段只能给ForeignKey)
-
子查询
- 外键字段默认显示的是外键值(int类型)