昨日回顾
1 restful规范
-只是一个规范,规范了前后端交互的接口(api接口)格式
-10条
-https部署
-请求地址中又接口标识
-https://api.baidu.com
-https://www.baidu.com/api/
-多版本共存
-https://www.baidu.com/api/v1/
-请求资源名词表示,可以复数
-一切皆资源
-https://www.baidu.com/api/v1/books/
-请求方式表示操作资源的方式
-get:获取资源
-post:新增资源
-put:修改
-patch:修改
-delete:删除资源
-响应中带状态码
-{code:100}
-响应中带错误信息
-{code:100,msg:'错误'}
-响应中带链接地址
-响应数据遵循以下格式
-多条数据 []
-单条 {}
-新增 新增的数据返回
-修改 修改的数据返回
-删除 空文档
-请求中带过滤条件
2 序列化器
-把对象----》序列化器---》字典-----》Response--->json格式字符串给前端
-把前端传过来的数据---Request-》字典---》序列化器----》对象---》保存
-数据校验
-如何使用
-写一个序列化类 继承Serializer
-在类中写字段
-字段类型:CharField...
-字段属性参数:required...
-在视图类中使用
-实例化得到对象
-序列化(对象--》字典)ser=XXSerialier(instance=对象,many=True)----》ser.data
-反序列化(新增)(字典---》对象)ser=XXSerialier(data=字典)
-反序列化的校验(三种方式)
-字段自己的校验
-validators=[函数内存地址,]
-局部和全局钩子
-validate_字段名(self,data)
-validate(self,attrs)
-wirte_only和read_only
今日内容
1 修改,删除接口
views.py
def put(self, request, id):
# 通过id取到对象
res = {'code': 100, 'msg': ''}
try:
book = models.Book.objects.get(id=id)
ser = BookSerializer(instance=book, data=request.data)
ser.is_valid(raise_exception=True) # 如果写上了raise_exception=True那么就不用写if,校验通过继续往下执行,校验不通过会主动抛异常
ser.save()
res['msg'] = '修改成功'
res['result'] = ser.data # 比较规范的写法,修改成功后,返回的数据有状态码,信息,以及修改后的数据
except Exception as e:
res['code'] = 101
res['msg'] = str(e) # 一般用户看的话错误信息应该直接是未知异常错误,而不是哪行代码错误的信息,这个只是给自己内部排查看的。
return Response(res)
def delete(self,request,id):
response = {'code': 100, 'msg': '删除成功'}
res = models.Book.objects.filter(id=id).delete() # 返回的结果是一个元组,第一个参数是影响的行数,第二个参数是字典,里面的key是表模型,value是影响了多少行,一般我们需要知道影响的行数来进行判断,if res[0]>0,那么代表修改数据成功,否则失败
return Response(response)
serializer.py
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
title = serializers.CharField(max_length=32,min_length=2)
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish = serializers.CharField(max_length=32)
def create(self, validated_data):
res=models.Book.objects.create(**validated_data)
print(res)
return res
# 要修改数据必须重写update方法
def update(self, instance, validated_data): # 不管里面有的数据改没改,都应该把数据给相对应
instance.title=validated_data.get('title')
instance.price=validated_data.get('price')
instance.publish=validated_data.get('publish')
instance.save() # 此处是对象调用的save不是drf中的save,也就是说是book.save(),即此方法中所有的instance在此处全不可以换成对应的对象book
return instance
2 高级用法之source(看源码)
用法一 # 修改返回到前端的字段名,在序列化器中对应的字段处加上以下字段常数,加上什么名字,返回前端的就叫什么名字
# source=title 字段名就不能再叫title
name = serializers.CharField(max_length=32,min_length=2,source='title')
2 # 如果在序列化器中写下了表中没有的字段xxx,而且source对应了方法的内存地址test,那么就会执行表模型中的对应内存地址(test)的方法,并且把返回值赋值给xxx
在序列器中 xxx=serializers.CharField(source='test')
在表模型models中 例 def test(self):
……
return self.title+'aaa'
3 source支持跨表操作例建了另一张表publish,里面有字段name和addr,此处通过表名.字段名拿到了另一张表中的字段,在返回的数据中显示多了一条是以字典显示key为对应的addr,value为publish.adddr拿到另一张表的数据的类,要想显示名字,则应该重写该表的__str__方法(最后以json数据显示);# 或者在已有的字段中通过source='publish.name',将另一张表中字段的数据给该序列化中已有的字段,也能拿到另一张表中的数据
addr=serializers.CharField(source='publish.addr')
# python是强类型语言,不支持字符串和数字类型直接相加,必须强转之后才行,类似的go语言也是强类型语言,而js是弱类型语言,会默认的将某一方自动转成另一个类型来进行运算,所以js是支持字符串和数字类型直接相加。
# 脏数据,一般是因为公司在设计表的时候,不会建立外键约束,因此有的数据因为前面的代码没有考虑周全,导致了脏数据进入了数据表中,要想将这些脏数据删除,其中的办法有例如通过拿出关联的另一张表中的id,看它在不在(in)脏数据的哪个关联字段中。
3 模型类序列化器
1 # 原来用的Serilizer跟表模型没有直接联系, 模型类序列化器ModelSerilizer跟表模型有对应关系
2 使用
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
model=models.Book # 表示跟哪个表模型建立关系该处必须是model
fields=[字段,字段] # 表示序列化Book中的字段和反序列化的字段,该处必须是fields,如果某些字段在表中没有,那么必须要在上面添加字段,例如表中没有publish,但fields中要写该字段,那么就需要像下面一样重写某些字段
fields='__all__' # 对应表中所有字段都序列化,反序列化
exclude=[字段,字段] # 排除哪些字段(不能跟fields同时使用),或者写成exclude=(字段,字段)元组的形式也可以
read_only_fields=['price','publish'] # 序列化显示的字段
write_only_fields=['title'] # 反序列化需要传入的字段,可能有的版本不能用
extra_kwargs ={'title':{'max_length':32,'write_only':True}} # 给字段添加额外的参数,可以用于反序列化的校验
depth=1 # 了解,跨表1次查询,最多建议写3,表示拿出跨表的所有字段
# 重写某些字段,通过这种方法,能拿到其它关联表的字段,那么上面fields里就能拿到重写字段对应的值
publish = serializers.CharField(max_length=32,source='publish.name')
# 局部钩子,全局钩子,跟原来完全一样
3 用模型类序列化器新增,修改不用重写create和update方法了,因为这些方法在ModelSerializer源码内部已经重写了create和update,但是如果像保存到多个不同的表,而有的表就需要重写create方法
4 高级用法之SerializerMethodField
# 把出版社所有的信息取出
class BookSerializer(serializers.Serializer): # 此处用的是Serializer
id = serializers.IntegerField(required=False)
name = serializers.CharField(max_length=32,min_length=2,source='title')
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish = serializers.SerializerMethodField() # 写了SerializerMethodField后面就必须写def get_字段
def get_publish(self,obj): # 上面是啥字段,get后面跟的就是啥字段,obj是前面查询所传过来的对象,此处是Book的对象
dic={'name':obj.publish.name,'addr':obj.publish.addr}
return dic # 返回是什么,前面publish接收的就是返回的结果
class BookModelSerializer(serializers.ModelSerializer): # 此处用的是ModelSerializer
class Meta:
model = models.Book
fields = '__all__'
publish = serializers.SerializerMethodField() # 写了SerializerMethodField后面就必须写def get_字段
def get_publish(self,obj):
dic={'name':obj.publish.name,'addr':obj.publish.addr}
return dic
## 第三中方案,使用序列化类的嵌套(这种方法用的比较多)
class PublishSerializer(serializers.ModelSerializer): # 首先直接序列化另一个表取对应的字段信息
class Meta:
model = models.Publish
# fields = '__all__'
fields = ['name','addr']
class BookModelSerializer(serializers.ModelSerializer):
publish = PublishSerializer() # 调用另一个表中的字段信息
class Meta:
model = models.Book
fields = '__all__' # 自己表中的字段信息(另一个表中的字段优先使用自己的)
5 drf的请求与相应
# Request
-data :前端以post请求提交的数据都在它中
-FILES :前端提交的文件
-query_params:就是原来的request.GET
-重写了 __getattr__
-使用新的request.method其实取得就是原生request.method(通过反射实现)
# Response
-from rest_framework.response import Response
-data:响应的字典
-status:http响应的状态码
-drf提供了所有的状态码,以及它的意思
from rest_framework.status import HTTP_201_CREATED
-template_name:模板名字(一般不动),了解
-headers:放响应头,字典
-content_type:响应的编码方式,了解
# 自己封装一个Response对象
class CommonResponse:
def __init__(self):
self.code=100
self.msg=''
@property # 因为调用的时候不想加(),所以加了属性装饰器
def get_dic(self):
return self.__dict__ # 去对象中找到所有变量并将其转换为字典
# 自己封装一个response,继承drf的Response
# 通过配置,选择默认模板的显示形式(浏览器方式,json方式)
-配置文件方式(全局)
-如果没有配置,默认有浏览器和json(因为drf有默认配置文件,自己没有配,会使用drf默认),默认文件的地址在from rest_framework.settings import DEFAULTS
-自己配置,在django的setting空白地方写下,还可以写其他的响应
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器
'rest_framework.renderers.BrowsableAPIRenderer', # 浏览器API渲染器
)
}
-在视图类中配置(局部)只控制某类的渲染
导入from rest_framework.renderers import JSONRenderer
-class BookDetail(APIView):
renderer_classes=[JSONRenderer,] # 在需要的类中写下所需要的渲染器,那么该类显示输出的就是类中所配置的,其他的类中的不变
# 改模板可以进drf中的templates的模板文件中修改,修改了之后显示就会变成自己修改的样式
7 many=True源码分析,局部全局钩子源码解析
1 many=True 生成的是listserializer,many=False生成的对象是单个的bookserializer
-__init__----->一路找到了BaseSerializer---》__new__决定了生成的对象是谁(在源码中通过pop出many,如果是False,调用了父类的__new__方法,创建了父类的对象,如果是True,调用了cls.many_init方法,在many_init中生成了列表,把类的对象一个一个的放入列表中,然后返回了list_serializer_class)
2 局部钩子和全局钩子入口是is_valid()---》BaseSerializer--》is_valid---》self._validated_data = self.run_validation(self.initial_data)
-Serializer这个类的:self.run_validation
def run_validation(self, data=empty):
value = self.to_internal_value(data) # 局部字段自己的校验和局部钩子校验
try:
self.run_validators(value)
value = self.validate(value) # 全局钩子的校验
except (ValidationError, DjangoValidationError) as exc: # 捕获了两个异常
raise ValidationError(detail=as_serializer_error(exc))
return value
拓展
1 接口幂等性,是什么,如何弄?
2 接口:统一子类的行为
抛异常限制
abc模块限制
人为限制(鸭子类型)
'''
1、如果子类在继承后一定要实现的方法,可以在父类中指定metaclass为abc模块的ABCMeta类,并在指定的方法上标准abc模块的@abcstractmethod来达到目的。
2、一旦定义了这样的父类(有抽象的方法),父类就不能实例化了,否则会抛出TypeError异常。如果没有抽象类的方法,那么可以实例化。
3、继承的子类如果没有实现@abcstractmethod标注的方法,在实例化使也会抛出TypeError异常,如果没有。
继承有抽象方法的接口类但没有重写抽象方法=》报错,继承有抽象方法的接口类必须重写抽象方法,继承有抽象方法的非接口类呢==》不报错
'''
from abc import ABCMeta,abstractmethod
class Tester(metaclass=ABCMeta): # 此是基类,可以实例化
@abstractmethod
def test(self):
pass
class FunctionTester(Tester): # 正常
def test(self):
print("功能测试")
class PerformanceTester(Tester): # 错误
def notest(self):
print("因为没有定义test方法,故实例化会报错")
# 源码中是通过以下的形式来要求必须继承父类的某一类方法,子类不重写该方法就主动抛错(重写的方法名必须一样)
class Test:
def test(self):
raise NotImplementedError("必须重写该方法,否则抛错")