drf
1 web应用模式
1 混合开发
2 前后端分离
api接口
#通过网络,规定前后台信息的交互规则的url链接,也就是前后的交互的媒介
restful规范
'''
-1 数据的安全保障,通常使用https(http+ssl/tsl)协议
-url链接一般都采用https协议进行传输
-采用https协议,可以提高数据交互过程中的安全性
-2 接口中带api标识
-https://api.lqz.com/books
-https://www.lqz.com/api/books 咱们用这个
-3 多版本共存,路径中带版本信息
-https://api.lqz.com/v1/login
-https://www.lqz.com/api/v2/login
-4 数据即是资源,均使用名词,尽量不出现动词(最核心的)
-接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
-接口形式如下
https://api.baidu.com/users
https://api.baidu.com/books
-特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义、 https://api.baidu.com/login
-5 资源操作由请求方式决定(method)
-操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
https://api.baidu.com/books - get请求:获取所有书
https://api.baidu.com/books/1 - get请求:获取主键为1的书
https://api.baidu.com/books - post请求:新增一本书书
https://api.baidu.com/books/1 - put请求:修改主键为1的书
https://api.baidu.com/books/1 - delete请求:删除主键为1的书
-6 在请求地址中带过滤条件
https://api.baidu.com/books?name=红&price=99
-7 响应中状态码:两套
-http响应状态码:1xx:请求正在处理,2xx:成功响应,3xx:重定向,4xx:客户端错误,5xx:服务端错误
-https://blog.csdn.net/li_chunlong/article/details/120787872
-公司内部规定的响应状态码,放在响应体中
{code:0} 咱们后期一般使用100 101 102这种
-8 返回数据中带错误信息
{
code:0
msg: "ok/用户名错误"
}
-9 返回的结果应该符合以下规范---》好多公司不遵循这个
GET 获取所有数据:返回资源对象的列表(数组)[{name:红楼梦,price:99},{name:红楼梦,price:99},{name:红楼梦,price:99}]
GET 单个对象:返回单个资源对象:{name:红楼梦,price:99}
POST 新增对象:返回新生成的资源对象:{name:西游记,price:99}
PUT 修改对象:返回完整的资源对象 :{name:西游记,price:100}
DELETE 删除:返回一个空文档
-10 响应数据中带连接
'''
总结:为了数据安全,一般使用https协议,在url链接中一般带api标识和版本信息来作为区分,
而且url尽量使用名词可以携带参数,不同的请求方式,代表不同的请求内容,在返回数据中,一般带状态码和错误信息,也可以带链接,返回的数据需要安照一定的格式。
序列化和反序列化
序列化:把我们能够识别的数据类型转换为固定的格式提供给别人
反序列化:把别人提供的数据类型转换为我们能够识别的数据类型
接口总结
1 如果是get请求,获取数据,封装为列表套字典,如果带有请求参数,返回一个对象,即一个字典
2 如果是post请求,提交数据,提供序列化,保存到数据库,返回当前保存数据的对象,即一个字典
3 如果是put,修改一条数据,需要得到数据和修改的条件,返回修改后的对象,即一个字典
4 如果是delete,删除一个数据,可以返回一个空文档,也可以返回一个列表套字典
cbv源码分析
'''
当请求来了,路由匹配成功,会执行as_view()(request)===>返回view的内存地址===》执行view(request)==>返回self.dispatch ===>执行通过__getattr__得到当前请求方法的内存地址===》然后通过当前请求方式去执行类中的对应方法
'''
-序列化类:serializer
# 6 cbv源码分析
-路由中:path('api/v1/books/', views.BookView.as_view())---》路由匹配成功就会执行---》views.BookView.as_view()(request)---->看一下BookView.as_view是怎么写的---》View这个类中的as_view类的绑定方法---》执行结果是view的内存地址[as_view中的内存函数,闭包函数]---》本质请求来了,执行view(request)--->return self.dispatch(request, *args, **kwargs)--->对象,谁的对象,View的对象,咱们从BookView---》View---》self是BookView的对象----》发现BookView没有dispatch---》View类的dispatch
def dispatch(self, request, *args, **kwargs):
if request.method.lower() in self.http_method_names:
# self是BookView的对象,通过字符串get找方法或属性,找到了get方法
#handler就是BookView类的get方法
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# 执行 BookView的get(request)---->get请求就会执行BookView的get方法,post请求就会执行BookView的post方法
return handler(request, *args, **kwargs)
APIView的执行流程
'''
当请求来了,匹配成功当前url,会执行当前对应的APIView中的as_view()==>去父类中调用as_view,封装request,执行三大认证,去除了csrf认证,==》执行csrf_exempt,返回view的 内衬地址,==》执行view==>得到self.dispatch==》self是视图类的对象,得到当前请求方式的内存地址,全局异常捕获然后执行当前得到的内存地址
'''
# 总结:APIView的执行流程
1 去除了所有的csrf
2 包装了新的request,以后在视图类中用的request是新的request Request类的对象,不是原生的了
-原生的在:新的requets._request
3 在执行视图类的方法之前,执行了3大认证
4 如果在3大认证或视图函数方法执行过程中出了错,会有异常捕获----》全局异常捕获
5 以后视图类方法中的request都是新的了
request源码
# Request源码
-方法 __getattr__
-在视图类的方法中,执行request.method ,新的request是没有method的,就触发了新的Request的__getattr__方法的执行
def __getattr__(self, attr):
try:
# 从老的request中反射出 要取得属性
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
-request.data--->这是个方法,包装成了数据属性
-以后无论post,put。。放在body中提交的数据,都从request.data中取,取出来就是字典
-无论是那种编码格式
-request.query_params--->这是个方法,包装成了数据属性
-get请求携带的参数,以后从这里面取
-query_params:查询参数--->restful规范请求地址中带查询参数
-request.FILES--->这是个方法,包装成了数据属性
-前端提交过来的文件,从这里取
# Request类总结
-1 新的request用起来,跟之前一模一样,因为新的取不到,会取老的__getattr__
-2 request.data 无论什么编码,什么请求方式,只要是body中的数据,就从这里取,字典
-3 request.query_params 就是原来的request._request.GET
-4 上传的文件从request.FILES
序列化器的介绍
# 因为咱么在写接口时,需要序列化,需要反序列化,而且反序列化的过程中要做数据校验---》drf直接提供了固定的写法,只要按照固定写法使用,就能完成上面的三个需求
# 提供了两个类 Serializer ModelSerializer
-以后咱们只需要写自己的类,继承drf提供的序列化类,使用其中的某些方法,就能完成上面的操作
# 使用APIView+序列化类+Response 完成接口的编写
'''
序列化器使用
1 先实现一个序列化类,对应模型字段
2 通过增加字段的规范来得到对内容的规定
3 通过局部钩子,和全局钩子再一次对内容进行规范
4 因为使用序列化类来实现get,post,put,delete,需要重写create和update方法,来增加和修改数据
5 如果是get方法,需要查询到这个内容的对象,在进行序列化
6 如果是post方法,需要得到当前需要增加的内容,校验数据,保存数据,需要重写create方法,
7 如果是put方法,需要得到当前当前需要修改的对象,和修改的数据内容,实例化这个序列化器对象,验证参数,实现save方法,当需要重写update方法
'''
'''
# 序列化类的使用
1 写一个类,继承serializers.Serializer
2 在类中写字段,要序列化的字段
3 在视图类中使用:(多条,单条)
serializer = BookSerializer(instance=book_list, many=True)
serializer = BookSerializer(instance=book)
'''
常用字段类和参数
常用字段类
字段 | 字段构造方式 |
---|---|
BooleanField | BooleanField() |
NullBooleanField | NullBooleanField() |
CharField | CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) |
EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
RegexField | RegexField(regex, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ |
URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
UUIDField | UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" |
IPAddressField | IPAddressField(protocol=’both’, unpack_ipv4=False, **options) |
IntegerField | IntegerField(max_value=None, min_value=None) |
FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 |
DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) |
DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField() |
ChoiceField | ChoiceField(choices) choices与Django的用法相同 |
MultipleChoiceField | MultipleChoiceField(choices) |
FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | ListField(child=, min_length=None, max_length=None) |
DictField | DictField(child=) |
# IntegerField CharField DateTimeField DecimalField
# ListField和DictField---》比较重要,但是后面以案例形式讲
字段参数
选项参数(CharField,IntegerFild)
参数名称 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
max_value | 最小值 |
min_value | 最大值 |
同用参数
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
# read_only write_only 很重要,后面以案例讲
反序列化校验
# 反序列化,有三层校验
-1 字段自己的(写的字段参数:required max_length 。。。)
-2 局部钩子:写在序列化类中的方法,方法名必须是 validate_字段名
def validate_name(self, name):
if 'sb' in name:
# 不合法,抛异常
raise ValidationError('书名中不能包含sb')
else:
return name
-3 全局钩子:写在序列化类中的方法 方法名必须是 validate
def validate(self, attrs):
price = attrs.get('price')
name = attrs.get('name')
if name == price:
raise ValidationError('价格不能等于书名')
else:
return attrs
# 只有三层都通过,在视图类中:
ser.is_valid(): 才是True,才能保存
反序列化之保存
# 新增接口:
-序列化类的对象,实例化的时候:ser = BookSerializer(data=request.data)
-数据校验过后----》调用 序列化类.save()--->但是要在序列化类中重写 create方法
def create(self, validated_data):
book=Book.objects.create(**validated_data)
return book
# 修改接口
-序列化类的对象,实例化的时候:ser = BookSerializer(instance=book,data=request.data)
-数据校验过后----》调用 序列化类.save()--->但是要在序列化类中重写 update方法
def update(self, book, validated_data):
for item in validated_data: # {"name":"jinping","price":55}
setattr(book, item, validated_data[item])
book.save()
return book
# 研究了一个问题
在视图类中,无论是保存还是修改,都是调用序列化类.save(),底层实现是根据instance做一个判断
ApiView+序列化类案例
'''
class Bookserializers(serializers.Serializer):
print(123)
name = serializers.CharField()
price = serializers.CharField()
publish_id = serializers.CharField(write_only=True)
Authors = serializers.ListField(write_only=True)
Authors_list = serializers.ListField(read_only=True)
publish_dict = serializers.DictField(read_only=True)
# 1 字段自己的校验
# 2 局部钩子
# def validate_字段名(self, 字段名):
#
# # 3 局部钩子
# def validate(self, attr):
def update(self, instance, validated_data):
print(11)
Authors = validated_data.pop("Authors")
print(Authors)
for i in validated_data:
setattr(instance, i, validated_data[i])
instance.Authors.set(Authors)
instance.save()
return instance
def create(self, validated_data):
print(1)
Author = validated_data.pop("Authors")
book = Book.objects.create(**validated_data)
book.Authors.add(*Author)
book.save()
return book
class BookView(APIView):
def get(self, request):
book = Book.objects.all()
ser = Bookserializers(instance=book, many=True)
return Response(ser.data)
def post(self, request):
ser = Bookserializers(data=request.data)
print(ser)
if ser.is_valid():
ser.save()
return Response({"data": ser.data})
return Response({"code": 1001, "msg": "增加失败"})
class BookDealieView(APIView):
def get(self, request, *args, **kwargs):
book = Book.objects.filter(pk=kwargs.get("pk")).first()
ser = Bookserializers(instance=book, many=False)
return Response(ser.data)
def put(self, request, *args, **kwargs):
book = Book.objects.filter(pk=kwargs.get("pk")).first()
ser = Bookserializers(data=request.data, instance=book)
if ser.is_valid():
print(11)
ser.save()
return Response({"data": ser.data})
return Response({"code": 1001, "msg": "增加失败"})
def delete(self, request, *args, **kwargs):
Book.objects.filter(pk=kwargs.get("pk")).delete()
return Response({"code": 200, "msg": "删除成功"})
'''
# 通过装饰器做,装饰器视图函数的,以后都会有request
def wrapper(func):
def inner(request, *args, **kwargs):
# 造个新的requeset
# 如果是 urlencoded,form-data ----》request.POST 就有值
# 如果request.POST 就有值没有值,就是json格式编码
try:
print(request.body)
request.data = json.loads(request.body) # 表示是json格式编码
except Exception as e:
request.data = request.POST
res = func(request, *args, **kwargs)
return res
return inner
常用字段类
#1 BooleanField BooleanField()
#2 NullBooleanField NullBooleanField()
#3 CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
#4 EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
#5 RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
#6 SlugField SlugField(max_length=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+
#7 URLField URLField(max_length=200, min_length=None, allow_blank=False)
#8 UUIDField UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
#9 IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
#10 IntegerField IntegerField(max_value=None, min_value=None)
#11 FloatField FloatField(max_value=None, min_value=None)
#12 DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置
#13 DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
#14 DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
#15 TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
#16 DurationField DurationField()
#17 ChoiceField ChoiceField(choices) choices与Django的用法相同
#18 MultipleChoiceField MultipleChoiceField(choices)
#19 FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
#20 ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
----------重要,后面讲-----------
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)
#记住的:CharField IntegerField DecimalField DateTimeField BooleanField
ListField
DictField
常用字段参数
选项参数:
# CharField及其子类的(EmailField) ---》反序列化的校验,字段自己的规则
max_length 最大长度
min_lenght 最小长度
allow_blank 是否允许为空
trim_whitespace 是否截断空白字符
# IntegerField
max_value 最小值
min_value 最大值
# 所有字段类都有的
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
validators 该字段使用的验证器
----看一眼忘掉-----
error_messages 包含错误编号与错误信息的字典
label 用于HTML展示API页面时,显示的字段名称
help_text 用于HTML展示API页面时,显示的字段帮助提示信息
# 重点:
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
## 反序列化校验执行流程
-1 先执行字段自己的校验规则----》最大长度,最小长度,是否为空,是否必填,最小数字。。。。
-2 validators=[方法,] ----》单独给这个字段加校验规则
name=serializers.CharField(validators=[方法,])
-3 局部钩子校验规则
-4 全局钩子校验规则
序列化高级用法之source
source可以指定序列化字段的名字
-自有字段,直接写字段名字
-name_real = serializers.CharField(max_length=8, source='name')
-关联字段,一对多的关联,直接点
-publish = serializers.CharField(source='publish.name')
-多对多,搞不了,source不能用
-authors=serializers.CharField(source='authors.all')
序列化高级用法之定制字段的两种方式
SerializerMethodField定制
'''自定义一个字段名,字段类是SerializerMethodField,如果这个写在序列化类中,需要使用get_字段名,需要传入一个obj对象'''
# 定制关联字段的显示形式
-一对多的,显示字典
-多对多,显示列表套字典
# 代码
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=8)
price = serializers.CharField()
# 定制返回格式---》方式一
publish_detail = serializers.SerializerMethodField()
def get_publish_detail(self, obj):
return {'name': obj.publish.name, 'addr': obj.publish.addr}
author_list = serializers.SerializerMethodField()
def get_author_list(self, obj):
l = []
for author in obj.authors.all():
l.append({'name': author.name, 'phone': author.phone})
return l
在表模型中定制
'''
1 在序列化类中写好字段名,这个字段类是当前数据展示的形式,如ListField,DictField,author_list = serializers.ListField()
2 在模型类中写这两个方法,如def author_list(self):
'''
#### 表模型
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE) # 留住,还有很多
authors = models.ManyToManyField(to='Author')
def publish_detail(self):
return {'name': self.publish.name, 'addr': self.publish.addr}
def author_list(self):
l = []
for author in self.authors.all():
l.append({'name': author.name, 'phone': author.phone})
return l
# 序列化类
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=8)
price = serializers.CharField()
# publish_detail = serializers.CharField()
publish_detail = serializers.DictField()
author_list = serializers.ListField()
多表关联反序列化保存
# 视图类
class BookView(APIView):
def post(self, request):
ser = BookSerializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '新增成功'})
else:
return Response({'code': 101, 'msg': ser.errors})
# 序列化类
class BookSerializer(serializers.Serializer):
# name和price 既用来序列化,又用来反序列化 即写又读 ,不用加read_only,write_only
name = serializers.CharField(max_length=8)
price = serializers.CharField()
# 只用来做序列化 只读 read_only
publish_detail = serializers.DictField(read_only=True)
author_list = serializers.ListField(read_only=True)
# 只用来做反序列化 只写 write_only
publish = serializers.CharField(write_only=True)
authors = serializers.ListField(write_only=True)
# 新增要重写create方法
def create(self, validated_data):
# validated_data 校验过后的数据,{name:红楼梦,price:19,publish:1,authors:[1,2]}
# 新增一本图书
book = Book.objects.create(name=validated_data.get('name'), price=validated_data.get('price'),
publish_id=validated_data.get('publish'))
# 作者也要关联上
# book.authors add remove set clear....
book.authors.add(*validated_data.get('authors'))
# book.authors.add(1,2)
return book
###视图类
class BookDetailView(APIView):
def put(self, request, pk):
book = Book.objects.filter(pk=pk).first()
ser = BookSerializer(data=request.data, instance=book)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '修改成功'})
else:
return Response({'code': 101, 'msg': ser.errors})
# 序列化类
#### 反序列化的多表关联的保存
class BookSerializer(serializers.Serializer):
# name和price 既用来序列化,又用来反序列化 即写又读 ,不用加read_only,write_only
name = serializers.CharField(max_length=8)
price = serializers.CharField()
# 只用来做反序列化 只写 write_only
publish = serializers.CharField(write_only=True)
authors = serializers.ListField(write_only=True)
# 修改要重写update
def update(self, instance, validated_data):
# validated_data 校验过后的数据,{name:红楼梦,price:19,publish:1,authors:[1,2]}
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.publish_id = validated_data.get('publish')
# 先清空,再add
authors = validated_data.get('authors')
instance.authors.clear()
instance.authors.add(*authors)
instance.save()
return instance
ModelSerializer使用
# ModelSerializer 继承自Serializer,帮咱们完成了很多操作
-跟表模型强关联
-大部分请求,不用写create和update了
# 如何使用
class BookSerializer(serializers.ModelSerializer):
# 跟表有关联
class Meta:
model = Book # 跟book表建立了关系 序列化类和表模型类
# fields = '__all__' # 序列化所有Book中的字段 id name price publish authors
fields = ['name', 'price', 'publish_detail', 'author_list', 'publish', 'authors'] # 序列化所有Book中的name和price字段字段
# 定制name反序列化时,最长不能超过8 给字段类加属性---方式一
extra_kwargs = {'name': {'max_length': 8},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True},
'publish': {'write_only': True},
'authors': {'write_only': True},
}
### ModelSerializer的使用
class BookSerializer(serializers.ModelSerializer):
# 跟表有关联
class Meta:
model = Book # 跟book表建立了关系 序列化类和表模型类
# fields = '__all__' # 序列化所有Book中的字段 id name price publish authors
fields = ['name', 'price', 'publish_detail', 'author_list', 'publish', 'authors'] # 序列化所有Book中的name和price字段字段
# 定制name反序列化时,最长不能超过8 给字段类加属性---方式一
extra_kwargs = {'name': {'max_length': 8},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True},
'publish': {'write_only': True},
'authors': {'write_only': True},
}
# 如果Meta写了__all__ ,就相当于,复制了表模型中的所有字段,放在了这里,做了个映射
# name = serializers.CharField(max_length=32)
# price = serializers.CharField(max_length=32)
# 定制name反序列化时,最长不能超过8 给字段类加属性---方式二,重写name字段
# name = serializers.CharField(max_length=8)
# 同理,所有的read_only和wirte_only都可以通过重写或使用extra_kwargs传入
# 终极,把这个序列化类写成跟之前一模一样项目
# publish_detail = serializers.SerializerMethodField(read_only=True)
# def get_publish_detail(self, obj):
# return {'name': obj.publish.name, 'addr': obj.publish.addr}
# author_list = serializers.SerializerMethodField(read_only=True)
# def get_author_list(self, obj):
# l = []
# for author in obj.authors.all():
# l.append({'name': author.name, 'phone': author.phone})
# return l
# 局部钩子和全局钩子跟之前完全一样
def validate_name(self, name):
if name.startswith('sb'):
raise ValidationError('不能sb')
else:
return name
反序列化类校验部分源码解析
# 反序列化校验,什么时候,开始执行校验
-视图类中的 ser.is_valid(),就会执行校验,校验通过返回True,不通过返回False
# 入口:ser.is_valid() 是序列化类的对象,假设序列化类是BookSerializer---》is_valid---》找不到,找到父类BaseSerializer中有 :【raise_exception:先注意】
def is_valid(self, *, raise_exception=False):
if not hasattr(self, '_validated_data'):
try:
# self序列化类的对象,属性中没有_validated_data,一定会走这句【核心】
self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
else:
self._errors = {}
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors)
# self._validated_data = self.run_validation(self.initial_data) 核心--》self序列化类的对象
-切记一定不要按住ctrl键点击
-真正的执行顺序是,从下往上找,找不到,再往上
-最终从Serializer类中找到了run_validation,而不是Field中的run_validation
def run_validation(self, data=empty):
# 字段自己的,validates方法
(is_empty_value, data) = self.validate_empty_values(data)
if is_empty_value:
return data
# 局部钩子----【局部钩子】
value = self.to_internal_value(data)
try:
self.run_validators(value)
# 全局钩子--》如果在BookSerializer中写了validate,优先走它,非常简单
value = self.validate(value)
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc))
return value
# 局部钩子 self.to_internal_value(data) ---》self是BookSerializer的对象,从根上找
def to_internal_value(self, data):
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
# fields写在序列化类中一个个字段类的对象
for field in fields:
# self BookSerializer的对象,反射validate_name
validate_method = getattr(self, 'validate_' + field.field_name, None)
try:
# 在执行BookSerializer类中的validate_name方法,传入了要校验的数据
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
-ser.is_valid---》走局部钩子的代码---》是通过反射获取BookSerializer中写的局部钩子函数,如果写了,就会执行----》走全局钩子代码---》self.validate(value)--->只要序列化类中写了,优先走自己的
断言
关键字assert
drf之请求
3.1 Request能够解析的前端传入的编码格式
# 需求是该接口只能接收json格式,不能接收其他格式
# 方式一,在继承自APIView及其子类的的视图类中配置(局部配置)
# 总共有三个:from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
class BookView(APIView):
parser_classes = [JSONParser,]
# 方式二:在配置文件中配置(影响所有,全局配置)
-django有套默认配置,每个项目有个配置
-drf有套默认配置,每个项目也有个配置---》就在django的配置文件中
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
# 'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser',
],
}
# 方式三:全局配了1个,某个视图类想要3个,怎么配?
-只需要在视图类,配置3个即可
-因为:先从视图类自身找,找不到,去项目的drf配置中找,再找不到,去drf默认的配置找
Request类有哪些属性和方法
Request 类是 Python 的 urllib 库中的一个类,用于生成 HTTP 请求。它有以下属性和方法:
属性:
full_url:Request 对象请求的完整 URL。
data:请求主体内容,可以是字节或字符串类型。
headers:一个字典,表示请求头信息。
method:HTTP 请求方法,例如 "GET" 或 "POST" 等。
url:请求的地址。
origin_req_host:原始请求地址中的主机名。
host:请求地址中的主机名。
type:URL 的协议类型,例如 "http" 或 "https" 等。
unverifiable:如果响应是不可验证的,则为 True。
方法:
add_header(header, value):在请求头中添加新的键值对。
get_header(header_name, default=None):获取请求头中指定键的值,默认值为空。
has_header(header_name):检查给定请求头是否存在。
get_method():返回请求的 HTTP 方法。
is_unverifiable():如果响应是不可验证的,则返回 True。
set_proxy(url, type):设置代理服务器的地址和类型。
drf之响应
4.1 Response能够响应的编码格式
# drf 是djagno的一个app,所以要注册
# drf的响应,如果使用浏览器和postman访问同一个接口,返回格式是不一样的
-drf做了个判断,如果是浏览器,好看一些,如果是postman只要json数据
# 方式一:在视图类中写(局部配置)
-两个响应类---》找---》drf的配置文件中找--》两个类
-from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
renderer_classes=[JSONRenderer,]
# 方式二:在项目配置文件中写(全局配置)
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
# 方式三:使用顺序(一般就用内置的即可)
优先使用视图类中的配置,其次使用项目配置文件中的配置,最后使用内置的
Resposne的源码属性或方法
# drf 的Response 源码分析
-from rest_framework.response import Response
-视图类的方法返回时,retrun Response ,走它的__init__,init中可以传什么参数
# Response init可以传的参数
def __init__(self,
data=None,
status=None,
template_name=None,
headers=None,
exception=False,
content_type=None)
-data:之前咱们写的ser.data 可以是字典或列表,字符串---》序列化后返回给前端---》前端在响应体中看到的就是这个
-status:http响应的状态码,默认是200,你可以改
-drf在status包下,把所有http响应状态码都写了一遍,常量
-from rest_framework.status import HTTP_200_OK
-Response('dddd',status=status.HTTP_200_OK)
-template_name:了解即可,修改响应模板的样子,BrowsableAPIRenderer定死的样子,后期公司可以自己定制
-headers:响应头,http响应的响应头
-考你,原生djagno,如何像响应头中加东西
# 四件套 render,redirect,HttpResponse,JsonResponse
obj = HttpResponse('dddd')
obj['xxc'] = 'yyc'
return obj
-content_type :响应编码格式,一般不动
# 重点:data,status,headers
视图组件介绍及两个视图基类
# drf 视图,视图类,学过APIView,drf的基类,drf提供的最顶层的类-
# GenericAPIView:GenericAPIView继承了APIView
# APIView跟之前的View区别
-传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;
-视图方法可以返回REST framework的Response对象-
-任何APIException异常都会被捕获到,并且处理成合适的响应信息;
-在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制
# 两个视图基类
APIVIew
-类属性:
renderer_classes # 响应格式
parser_classes #能够解析的请求格式
authentication_classes#认证类
throttle_classes#频率类
permission_classes#权限类
-基于APIView+ModelSerializer+Resposne写5个接口
APIView+ModelSerializer+Resposne写5个接口
class Bookserializers(serializers.ModelSerializer):
# name = serializers.CharField()
# price = serializers.CharField()
# publish_id = serializers.CharField(write_only=True)
# Authors = serializers.ListField(write_only=True)
Authors_list = serializers.ListField(read_only=True)
publish_dict = serializers.DictField(rea d_only=True)
class Meta:
model = Book
# feilds = "__all__"
fields = ['name', 'price', 'publish', 'Authors', 'Authors_list', 'publish_dict']
extra_kwargs = {
}
# 1 字段自己的校验
# 2 局部钩子
# def validate_字段名(self, 字段名):
#
# # 3 局部钩子
# def validate(self, attr):
# def update(self, instance, validated_data):
# print(11)
# Authors = validated_data.pop("Authors")
# print(Authors)
# for i in validated_data:
# setattr(instance, i, validated_data[i])
# instance.Authors.set(Authors)
# instance.save()
# return instance
#
# def create(self, validated_data):
# print(1)
# Author = validated_data.pop("Authors")
# book = Book.objects.create(**validated_data)
# book.Authors.add(*Author)
# book.save()
# return book
class BookView(APIView):
def get(self, request):
book = Book.objects.all()
ser = Bookserializers(instance=book, many=True)
return Response(ser.data)
def post(self, request):
ser = Bookserializers(data=request.data)
print(ser)
if ser.is_valid():
ser.save()
return Response({"data": ser.data})
return Response({"code": 1001, "msg": "增加失败"})
class BookDealieView(APIView):
def get(self, request, *args, **kwargs):
book = Book.objects.filter(pk=kwargs.get("pk")).first()
ser = Bookserializers(instance=book, many=False)
return Response(ser.data)
def put(self, request, *args, **kwargs):
book = Book.objects.filter(pk=kwargs.get("pk")).first()
ser = Bookserializers(data=request.data, instance=book)
if ser.is_valid():
ser.save()
return Response({"data": ser.data})
return Response({"code": 1001, "msg": "增加失败"})
def delete(self, request, *args, **kwargs):
Book.objects.filter(pk=kwargs.get("pk")).delete()
return Response({"code": 200, "msg": "删除成功"})
基于GenericAPIView+5个视图扩展类
from rest_framework.generics import GenericAPIView
from rest_framework.generics import ListAPIView, CreateAPIView, UpdateAPIView, RetrieveAPIView, DestroyAPIView
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView, RetrieveUpdateAPIView, \
RetrieveDestroyAPIView
# 基于GenericAPIView+5个视图扩展类
# 5个视图扩展类CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin
class BookView(ListAPIView, CreateAPIView):
queryset = Book.objects.all()
serializer_class = Bookserializers
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
# class BookView(ListCreateAPIView):
# queryset = Book.objects.all()
# serializer_class = Bookserializers
#
# def get(self, request):
# return self.list(request)
#
# def post(self, request):
# return self.create(request)
class BookDealieView(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
queryset = Book.objects.all()
serializer_class = Bookserializers
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
模块与包的使用
# 模块与包
-模块:一个py文件,被别的py文件导入使用,这个py文件称之为模块,运行的这个py文件称之为脚本文件
-包:一个文件夹下有__init__.py
# 模块与包的导入问题
'''
0 导入模块有相对导入和绝对导入,绝对的路径是从环境变量开始的
1 导入任何模块,如果使用绝对导入,都是从环境变量开始导入起
2 脚本文件执行的路径,会自动加入环境变量
3 相对导入的话,是从当前py文件开始计算的
4 以脚本运行的文件,不能使用相对导入,只能用绝对导入
'''
视图之两个视图基类
# 视图类:
-APIView:之前用过
-GenericAPIView:GenericAPIView继承了APIView
# GenericAPIView
-类属性:
queryset:要序列化的所有数据
serializer_class:序列化类
lookup_field = 'pk' :查询单条时的key值
-方法:
-get_queryset():获取所有要序列化的数据【后期可以重写】
-get_serializer : 返回序列化类
-get_object :获取单个对象
#总结:以后继承GenericAPIView写接口
1 必须配置类属性
queryset
serializer_class
2 想获取要序列化的所有数据
get_queryset()
3 想使用序列化类:
get_serializer
4 想拿单条
get_object
drf之视图--两个视图基类
# 6 drf之视图--两个视图基类
-APIView:drf最顶层的视图类,继承了原生djagno的View
-1 去除了csrf
-2 包装了新的request
-3 在执行视图类的方法之前,执行了三大认证
-4 处理了全局异常
-GenericAPIView:继承了APIView
-类属性:
-queryset:要序列化的数据
-serializer_class:指定序列化类
-lookup_field:获取单挑数据的查询条件
-filter_backends:过滤类
-pagination_class:分页类
-如何使用:
self:一个对象 等同于下面写的p
self.queryset--->先找对象自己,而自己没有,才用了类的
-方法:绑定给对象的方法(写成方法的目的是解耦合,提高扩展性)
-get_queryset:返回所有要序列化的数据
-get_object:获取单条数据,路由必须:path('/<int:pk>/', PublishDetailView.as_view()),
-get_serializer:真正的实例化得到序列化类
ser=get_serializer(instance=qs,many=True)
-get_serializer_class:仅仅是返回序列化类,不能传任何参数
serializer_class=self.get_serializer_class()
serializer_class(instance=qs,many=True)
-以后,如果涉及到数据库操作,尽量用GenericAPIView
5个视图扩展类
# 写5个类(不叫视图类,视图扩展类,需要配合GenericAPIView一起用),每个类有一个方法,以后想写哪个接口,就继承哪个类即可
from rest_framework.response import Response
##### 咱们自己封装的
# class ListModelMixin:
# def list(self, request, *args, **kwargs):
# qs = self.get_queryset()
# ser = self.get_serializer(qs, many=True)
# return Response({'code': 100, 'msg': '成功', 'results': ser.data})
#
#
# class CreateModelMixin:
# def create(self, request, *args, **kwargs):
# ser = self.get_serializer(data=request.data)
# if ser.is_valid():
# ser.save()
# return Response({'code': 100, 'msg': '成功'})
# else:
# return Response({'code': 100, 'msg': ser.errors})
#
#
# class RetrieveModelMixin:
# def retrieve(self, request, *args, **kwargs):
# book = self.get_object()
# ser = self.get_serializer(book)
# return Response({'code': 100, 'msg': '成功', 'results': ser.data})
#
#
# # DestroyModelMixin,UpdateModelMixin
# class DestroyModelMixin:
# def destroy(self, request, *args, **kwargs):
# self.get_object().delete()
# return Response({'code': 100, 'msg': '删除成功'})
#
#
# class UpdateModelMixin:
# def update(self, request, *args, **kwargs):
# book = self.get_object()
# ser = self.get_serializer(data=request.data, instance=book)
# if ser.is_valid():
# ser.save()
# return Response({'code': 100, 'msg': '更新成功'})
# else:
# return Response({'code': 100, 'msg': ser.errors})
from .models import Book
from .serializer import BookSerialzier
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, \
RetrieveModelMixin
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
# 5 个视图扩展类--->不是视图类---》必须配合GenericAPIView及其子类使用---》不能配合APIView使用
# 5个视图扩展类,每一个类中只有一个方法,完成5个接口中的其中一个,想写多个接口,就要继承多个
9个视图子类
# ModelViewSet:
-继承它后,只需要在视图类中写两行
queryset = Book.objects.all()
serializer_class = BookSerialzier
-配置路由,5个接口都有了
path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
# ModelViewSet 源码分析
-继承了:
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin
GenericViewSet
-ViewSetMixin :没有见过,重写了 as_view
-GenericAPIView
-只要继承了ModelViewSet,路由写法变了,谁控制它变的:ViewSetMixin
# ViewSetMixin 如何控制路由写法变了?
-BookView.as_view 是在执行,其实是ViewSetMixin的as_view
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
#我们传入的 actions={'get': 'list', 'post': 'create'}
def view(request, *args, **kwargs):
self = cls(**initkwargs)
for method, action in actions.items():
# method:get
# action:list
# 通过反射,self是BookView的对象,取BookView对象中反射list
handler = getattr(self, action)
# 通过反射:setattr(BookView的对象,'get',list方法)
setattr(self, method, handler)
# APIViwe的dispatch
return self.dispatch(request, *args, **kwargs)
return csrf_exempt(view)
# 总结:
'''
1 只要继承了ViewSetMixin及其子类,路由写法就变了,必须传actions参数
2 变成映射关系了:
path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
3 以后,只要是books路由匹配成功,的get请求,就会执行视图类BookView的list方法
4 以后视图类中的方法名,可以随意命名
5 这个类,必须配合视图类使用(APIView,GenericAPIView,9个视图子类),必须放在视图类之前
'''
视图集
# 两个视图基类
-APIView
-GenericAPIView
# 5 个视图扩展类(不是视图类,需要配合GenericAPIView及其子类使用)
# 9个视图子类
# 视图集:
ModelViewSet:5个接口的
ReadOnlyModelViewSet:两个接口,list和retrieve
ViewSetMixin:魔法,不能单独使用,必须配合视图类用,路由写法变了
ViewSet:ViewSetMixin+APIView,以后想继承APIView,但是想路由写法变化,视图类中方法可以任意命名
GenericViewSet:ViewSetMixin+GenericAPIView,以后想继承GenericAPIView,但是想路由写法变化,视图类中方法可以任意命名
drf之路由
# 路由写法有多种
-原始写法
-映射的写法:继承ModelViewSet,path('books/', BookView.as_view({'get': 'list', 'post': 'create'}))
-自动生成路由
# 自动生成路由
-必须要继承ViewSetMixin及其子类的视图类,才能用
-继承了 5个视图扩展类+ViewSetMixin的视图类,能自动生成路由
-跟咱们写的这个是一样的
-path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
-path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
-自己写的视图类的方法,如何映射
-映射的方式我们会
-自动生成的方式
# 自动生成路由
1 继承了 5个视图扩展类+ViewSetMixin的视图类,能自动生成路由(get:list,get:retrieve..)
2 我们自己命名的: 方法名:login send_sms,需要使用装饰器来做
# 视图类:
class SMSView(ViewSet):
@action(methods=['GET'], detail=False, url_path='lqz', url_name='lqz')
def lqz(self, request):
# 路由
router.register('lqz',SMSView,'lqz')
# 路径是:http://127.0.0.1:8000/api/v1/lqz/lqz/
3 action装饰器的参数
methods:请求方式
detail:一个True,一个False,用True,表示生成详情的路径 <int:pk>==》前端查询单条,要设detail
# True,books/1/方法名/
# False,books/方法名/
url_path:路径名字,需要加上前面的路径一起,如果不加,默认以函数名作为路径名
url_name:反向解析使用的名字(用的不多)
# 路由类,有两个,用法完全一致,区别是DefaultRouter生成的路径多
SimpleRouter :用的最多
DefaultRouter
# DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据。
基于两个视图基类GenericAPIView,APIView
class Bookserializers(serializers.ModelSerializer):
# name = serializers.CharField()
# price = serializers.CharField()
# publish_id = serializers.CharField(write_only=True)
# Authors = serializers.ListField(write_only=True)
Authors_list = serializers.ListField(read_only=True)
publish_dict = serializers.DictField(read_only=True)
class Meta:
model = Book
# feilds = "__all__"
fields = ['name', 'price', 'publish', 'Authors', 'Authors_list', 'publish_dict']
extra_kwargs = {
}
class BookAPIView(APIView):
def get(self, request):
book = Book.objects.all()
ser = Bookserializers(instance=book, many=True)
return Response({"code": 200, "msg": "查询成功", "result": ser.data})
def post(self, request):
ser = Bookserializers(data=request.data)
if ser.is_valid():
ser.save()
return Response({"code": 200, "msg": "增加成功", "result": ser.data})
return Response({"code": 200, "msg": "增加失败", "result": ser.errors})
class BookDeaileAPIView(APIView):
def get(self, request, *args, **kwargs):
book = Book.objects.filter(pk=kwargs.get("pk")).first()
ser = Bookserializers(instance=book, many=True)
return Response({"code": 200, "msg": "查询成功", "result": ser.data})
def put(self, request, *args, **kwargs):
book = Book.objects.filter(pk=kwargs.get("pk")).first()
ser = Bookserializers(instance=book, many=False, data=request.data)
if ser.is_valid():
ser.save()
return Response({"code": 200, "msg": "修改成功", "result": ser.data})
return Response({"code": 200, "msg": "修改败", "result": ser.errors})
def delete(self, request, *args, **kwargs):
Book.objects.filter(pk=kwargs.get("pk")).delete()
return Response({"code": 200, "msg": "删除成功"})
class BookGenericAPIView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = Bookserializers
def get(self, request):
book = self.get_queryset()
ser = self.get_serializer(book, many=True)
return Response({"code": 200, "msg": "查询成功", "result": ser.data})
def post(self, request):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({"code": 200, "msg": "增加成功", "result": ser.data})
return Response({"code": 200, "msg": "增加失败", "result": ser.errors})
class BookDeleailGenericAPIView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = Bookserializers
def get(self, request, *args, **kwargs):
ser = self.get_serializer(self.get_object(), many=False)
return Response({"code": 200, "msg": "查询成功", "result": ser.data})
def put(self, request, *args, **kwargs):
ser = self.get_serializer(self.get_object(), data=request.data)
if ser.is_valid():
ser.save()
return Response({"code": 200, "msg": "修改成功", "result": ser.data})
return Response({"code": 200, "msg": "修改失败", "result": ser.errors})
def delete(self, request, *args, **kwargs):
self.get_object().delete()
return Response({"code": 200, "msg": "删除成功"})
5个视图扩展类
5个视图扩展类
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, \
DestroyModelMixin
class ListModelMixin:
def list(self, request):
queryset = self.get_queryset()
ser = self.get_serializer(queryset, many=True)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
class CreateModelMixin:
def create(self, request):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
return Response({'code': 100, 'msg': '失败', 'results': ser.errors})
class UpdateModelMixin:
def update(self, request, *args, **kwargs):
queryset = self.get_object()
ser = self.get_serializer(queryset, many=False, data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
return Response({'code': 100, 'msg': '失败', 'results': ser.errors})
class RetrieveModelMixin:
def retrieve(self, request, *args, **kwargs):
queryset = self.get_object()
ser = self.get_serializer(queryset, many=False)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
class DestroyModelMixin:
def destroy(self, request, *args, **kwargs):
self.get_object().delete()
return Response({'code': 100, 'msg': '成功'})
9个视图子类
# 9个视图子类
class BookListGenericAPIView(GenericAPIView, ListModelMixin):
queryset = Book.objects.all()
serializer_class = Bookserializers
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
class BookCreateGenericAPIView(GenericAPIView, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = Bookserializers
def post(self, request):
return self.create(request)
class BookRetrieveGenericAPIView(GenericAPIView, RetrieveModelMixin):
queryset = Book.objects.all()
serializer_class = Bookserializers
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
class BookUpdateGenericAPIView(GenericAPIView, UpdateModelMixin):
queryset = Book.objects.all()
serializer_class = Bookserializers
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
class BookDestroyGenericAPIView(GenericAPIView, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = Bookserializers
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class BookUpdateRetrieveDestroyGenericAPIView(GenericAPIView, DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin):
queryset = Book.objects.all()
serializer_class = Bookserializers
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
class BookListCreateGenericAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = Bookserializers
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
视图集
from rest_framework.viewsets import ModelViewSet
# 继承五个视图扩展类,需要自己写路由
class BookModelViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = Bookserializers
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
path('book/', BookModelViewSet.as_view({'get': 'list', 'post': 'create'})),
path('book/<int:pk>/', BookModelViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
自动生产路由
# ReadOnlyModelViewSet:两个接口,list和retrieve
from rest_framework.viewsets import ReadOnlyModelViewSet
class BookLIstretrieveView(ReadOnlyModelViewSet):
queryset = Book.objects.all()
serializer_class = Bookserializers
# ViewSetMixin:魔法,不能单独使用,必须配合视图类用,路由写法变了
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
class BookViewSet(ViewSet): # ViewSetMixin, views.APIView
queryset = Book.objects.all()
serializer_class = Bookserializers
''''
- 1 写在视图类方法上
class SendView(ViewSet):
# methods指定请求方法,可以传多个
# detail:只能传True和False
-False,不带id的路径:send/send_sms/
-True,带id的路径:send/2/send_sms/
#url_path:生成send后路径的名字,默认以方法名命名
# url_name:别名,反向解析使用,了解即可
@action(methods=['POST'], detail=False)
def send_sms(self, request):
'''
# @action(methods=['get'], detail=False) # http://127.0.0.1:8000/lyx/lyx/
@action(methods=['get'], detail=True) # http://127.0.0.1:8000/lyx/lyx/
def lyx(self, request, *args, **kwargs): # http://127.0.0.1:8000/lyx/1/lyx/
print(kwargs.get('pk'))
return Response("你好")
from django.urls import path, include
from index.views import *
from rest_framework import routers
'''
SimpleRouter
DefaultRouter===>多生产一个跟路由
'''
router = routers.SimpleRouter() # 创建一个 SimpleRouter 实例
router.register("book", BookLIstretrieveView) # 注册视图集
router.register("lyx", BookViewSet) # 注册视图集
'''
prefix 该视图集的路由前缀
viewset 视图集
base_name 路由别名的前缀
'''
urlpatterns = [
添加路由
# 方式2
path("", include(router.urls))
]
# 添加路由
# 方式一
# urlpatterns += router.urls
# http://127.0.0.1:8000/book/
# http://127.0.0.1:8000/book/<int:pk>/
drf视图
DRF(Django Rest Framework)是一个用于构建Web API的强大框架,提供了许多视图类来帮助我们快速构建API。下面是DRF中常用的视图关系:
GenericAPIView:这是一个视图类,它提供了一组通用的方法(如get、post、put、delete等),可以方便地处理HTTP请求。
ListAPIView:继承自GenericAPIView,用于返回查询集合的数据列表。
RetrieveAPIView:继承自GenericAPIView,用于返回单个对象实例的详细信息。
CreateAPIView:继承自GenericAPIView,用于创建新的对象实例。
UpdateAPIView:继承自GenericAPIView,用于更新现有的对象实例。
DestroyAPIView:继承自GenericAPIView,用于删除现有的对象实例。
ListCreateAPIView:继承自ListAPIView和CreateAPIView,用于同时处理列表和创建对象的请求。
RetrieveUpdateAPIView:继承自RetrieveAPIView和UpdateAPIView,用于同时处理获取单个对象实例和更新该实例的请求。
RetrieveDestroyAPIView:继承自RetrieveAPIView和DestroyAPIView,用于同时处理获取单个对象实例和删除该实例的请求。
RetrieveUpdateDestroyAPIView:继承自RetrieveAPIView、UpdateAPIView和DestroyAPIView,用于同时处理获取单个对象实例、更新该实例和删除该实例的请求。
ModelViewSet:继承自ViewSetMixin和GenericAPIView,用于提供CRUD操作的视图类。
ReadOnlyModelViewSet:继承自ViewsetMixin和GenericAPIView,用于提供只读操作(get、list、head等)的视图类。
ViewSet:继承自ViewSetMixin和GenericAPIView,用于实现非常定制化的API行为。
ModelViewset+ReadOnlyModelViewSet:继承自ModelViewSet和ReadOnlyModelViewSet,用于同时提供CRUD和只读操作的视图类。
认证
# 认证,在执行视图之前执行了三大认证
# 认证的使用方法
'''
1 写一个类,继承BaseAuthentication
2 在类中写authenticate方法
3 在方法中,完成登录认证,如果不是登录,抛异常
token = request.GET.get("token")
user_taken = UserTaken.objects.filter(token=token).first()
if not user_taken:
raise AuthenticationFailed("你暂时没有登录")
4 如果登录成功,返回登录用户和token
return user_taken.user, token
5 在视图类中使用,(局部使用)
authentication_classes = [UserAuthentication] # 局部
6 全局使用
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'应用名.模块名.类名', # 认证
],
}
7 全局使用后,局部禁用
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'应用名.模块名.类名', # 认证
],
}
局部禁用
authentication_classes = []
8 认证类的使用顺序
-先使用局部
-在使用项目配置文件
-使用drf默认配置
重点,一旦通过了认证,在request中就有当前登录用户
'''''
权限
'''
# 权限类使用
## 1 写一个类,继承BasePermission
### 2 在类中写方法:has_permission
1 如果有权限,返回True
if request.user.is_super == 1:
return True
2 如果没有权限,返回False
return False
3 错误信息是self.message=""
self.message = ""
return False
### 3 局部使用
permission_classes = [UThrottle]
### 4 全局使用
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'index.UserAuthentication.UserAuthentication', # 认证
],
'DEFAULT_PERMISSION_CLASSES': [
'index.userthrottle.BasePermission'
],
}
### 5 局部禁用
permission_classes = []
频率
'''
频率,按ip地址,用户id,限制
### 1 写一个类,继承SimpleRateThrottle
### 2 写一个方法,get_cache_key
-可以返回ip或者用户id
return request.META.get("REMOTE_ADDR")
-返回什么,就用什么做频率限制
### 3 写一个类属性
scope='lqz'
### 4 在配置文件中配置
'DEFAULT_THROTTLE_RATES': {
'lqz': '3/m' # 一分钟访问3次
},
### 5 全局使用
'DEFAULT_THROTTLE_CLASSES': [
],
### 6 局部使用
class BookView(APIView):
throttle_classes = [MyThrottle]
'''
# 1 权限源码
-继承了APIView----》dispatch----》407行左右---》 self.initial(request, *args, **kwargs)---》414行 ---》self.check_permissions(request)
-def check_permissions(self, request):
# 自己写的配置的权限类的对象
for permission in self.get_permissions(): # [咱们写的权限了的对象,]
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
# 2 认证类的源码
-继承了APIView----》dispatch----》407行左右---》 self.initial(request, *args, **kwargs)---》414行 ---》self.perform_authentication(request)---》
# perform_authentication
def perform_authentication(self, request):
request.user
# request是新的Request----》from rest_framework.request import Request----》220行
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
# request是新的Request--->self._authenticate()---》373行
def _authenticate(self):
# self.authenticators配置再视图类上的认证类对象列表[认证类对象1,认证类对象2]
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException: # drf中所有的异常都继承了APIException
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple # 解压赋值
return
self._not_authenticated()
# request.user---->能拿到当前登录用户
# 3 排序---》查询所有---》继承GenericAPIView及其子类的
-filter_backends=[排序类] # 内置有,不需要自己写
- ordering_fields = ['price','id']
-http://127.0.0.1:8000/api/v1/books/?ordering=-price,-id
# 4 过滤
-内置过滤类
-第三方过滤类:django-filter
-自定义过滤类:
-写一个类,继承BaseFilterBackend
-重写:filter_queryset,在里面完成过滤,返回qs对象
-我们猜:ListModelMixin---》list,执行了配置的过滤类的filter_queryset方法
def list(self, request, *args, **kwargs):
# queryset = self.filter_queryset(所有数据)
# filter_queryset肯定执行了我们自己写的过滤类
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
# 5 分页
-PageNumberPagination, LimitOffsetPagination, CursorPagination
# 上下文管理器
-__enter__
-__exit__
# 面向对象封装---》
-__开头,隐藏了,
-内部使用self.__属性/方法
-外部使用:person._类名__字段名
-别人,公司内部,不太使用 __
-约定俗称,类内部使用 _开头,本意就是隐藏,不给外部使用
全局异常处理
# 只要三大认证,视图类的方法出了异常,都会执行一个函数:rest_framework.views import exception_handler
### 注意:exception_handler
# 如果异常对象是drf的APIException对象,就会返回Response
# exception_handler只处理了drf的异常,其它的异常需要我们自己处理
# 如果异常对象不是drf的APIException对象,就会返回None
# 补充:
# isinstance() 判断一个对象是不是某个类的对象 isinstance(对象,类)
# issubclass() 判断一个类,是不是另一个类的子类
def common_exception_handler(exc, context):
# 只要走到这里,一定出异常了,我们正常的项目要记录日志(后面讲)
# 两种可能:一个是Response对象,一个是None
res = exception_handler(exc, context)
if res:
# 说明是drf的异常,它处理了
if isinstance(res.data, dict):
detail = res.data.get('detail')
else:
detail = res.data
return Response({'code': 998, 'msg': detail})
else:
# 说明是其它异常,它没有处理
# return Response({'code': 999, 'msg': '系统异常,请联系系统管理员'})
return Response({'code': 999, 'msg': str(exc)})
### 配置文件
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.excepitons.common_exception_handler',
}
接口文档
# 后端把接口写好后
-登录接口
-注册接口
-查询所有图书带过滤接口
# 前端人员需要根据接口文档,进行前端开发
# 前后端需要做对接----》对接第一个东西就是这个接口文档---》前端照着接口文档开发
# 公司3个人,每个人开发了10个接口,3个人都要同时写接口文档
# 接口文档的编写形式
-1 world,md,编写,大家都可以操作,写完放在git,公司的文档管理平台上
-2 第三方的接口文档平台(收费)
https://www.showdoc.com.cn/
-3 公司自己开发接口文档平台
-4 公司使用开源的接口文档平台,搭建
-YAPI:百度开源的
-https://zhuanlan.zhihu.com/p/366025001
-5 项目自动生成接口文档
-coreapi
-swagger
# 使用coreapi自动生成接口文档
-使用步骤:
-1 安装:pip3 install coreapi
-2 加一个路由
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='站点页面标题'))
]
-3 在视图类上加注释
-4 配置文件中配置:
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
-5 表模型或序列化类的字段上写 help_text--->会显示在接口文档的字段介绍上
-6 访问地址:
http://127.0.0.1:8000/docs/
jwt介绍和原理
# cookie,session,token发展历史
-会话管理
-cookie:客户端浏览器的键值对
-session:服务的的键值对(djangosession表,内存中,文件,缓存数据库)
-token:服务的生成的加密字符串,如果存在客户端浏览器上,就叫cookie
-三部分:头,荷载,签名
-签发:登录成功,签发
-认证:认证类中认证
# jwt:Json web token (JWT),web方向的token认证
base64编码和解码
# base64并不是一种加密反射,只是编码解码方式
# 字符串,可以转成base64编码格式:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 解码成base64
import json
import base64
d = {'user_id': 1, 'username': "lqz"}
d_str = json.dumps(d)
# print(d_str)
# # 对字符串进行bashe64 编码
# res=base64.b64encode(bytes(d_str,encoding='utf-8'))
# print(res) # eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImxxeiJ9
# 解码
# res=base64.b64decode('TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ=')
# print(res)
# 记住: base64 编码,长度一定是4的倍数。如果不够,用 = 补齐
# base64的用途
'''
1 互联网中,前后端数据交互,使用base64编码
2 jwt 字符串使用base64编码
3 互联网中一些图片,使用base64编码
'''
import json
import base64
l = {"code": 111, "msg": "你好你好"}
l_str = json.dumps(l)
l_str = l_str.encode('utf-8')
res = base64.b64encode(l_str)
print(res) # b'eyJjb2RlIjogMTExLCAibXNnIjogIlx1NGY2MFx1NTk3ZFx1NGY2MFx1NTk3ZCJ9'
m = base64.b64decode(res)
print(m) # b'{"code": 111, "msg": "\\u4f60\\u597d\\u4f60\\u597d"}'
print(json.loads(m)) # {'code': 111, 'msg': '你好你好'}
print(m.decode('utf-8')) # {"code": 111, "msg": "\u4f60\u597d\u4f60\u597d"}
jwt原理
https://www.cnblogs.com/liuqingzheng/articles/17413681.html
三段式
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
#1 header jwt的头部承载两部分信息:
声明类型,这里是jwt
声明加密的算法 通常直接使用 HMAC SHA256
公司信息
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64编码,构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
# 2 payload 载荷就是存放有效信息的地方。
当前用户信息,用户名,用户id
exp: jwt的过期时间,这个过期时间必须要大于签发时间
定义一个payload:
{
"exp": "1234567890",
"name": "John Doe",
"user_id":99
}
然后将其进行base64编码,得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
# signature JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
jwt开发流程
# 使用jwt认证的开发流程,就是两部分
-第一部分:签发token的过程,登录做的
-用户携带用户名和密码,访问我,我们校验通过,生成token串,返回给前端
-第二部分:token认证过程,登录认证时使用,其实就是咱们之前讲的认证类,在认证类中完成对token的认证操作
-用户访问我们需要登陆后才能访问的接口,必须携带我们签发的token串(请求头)
-我们取出token,验证该token是否过期,是否被篡改,是否是伪造的
-如果正常,说明荷载中的数据,就是安全的,可以根据荷载中的用户id,查询出当前登录用户,放到request中即可
drf-jwt
# djagno+drf框架中,使用jwt来做登录认证
# 使用第三方:
-django-rest-framework-jwt:https://github.com/jpadilla/django-rest-framework-jwt
-djangorestframework-simplejwt:https://github.com/jazzband/djangorestframework-simplejwt
# 我们可以自己封装 :https://gitee.com/liuqingzheng/rbac_manager/tree/master/libs/lqz_jwt
# 安装
pip3 install djangorestframework-jwt
# 补充:
- 密码明文一样,每次加密后都不一样,如何做,动态加盐,动态的盐要保存,跟密码保存在一起
- 有代码,有数据库,没有登录不进去的系统
# 解决不了:
token被获取,模拟发送请求
不能篡改
不能伪造
# 快速使用
-签发过程(快速签发),必须是auth的user表
-登录接口--》基于auth的user表签发的
-认证过程
-登录认证,认证类
# 总结:
-签发:只需要在路由中配置
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
-认证:视图类上加
class BookView(APIView):
authentication_classes = [JSONWebTokenAuthentication] # 认证类,drf-jwt提供的
permission_classes = [IsAuthenticated] # 权限类,drf提供的
-访问的时候,要在请求头中携带,必须叫
Authorization:jwt token串
drf-jwt定制返回格式
# 登录签发token的接口,要返回code,msg,username,token等信息
# 对返回格式进行定制
-1 写个函数,函数返回字典格式,返回的格式,会被序列化,前端看到
def common_response(token, user=None, request=None):
return {
'code': '100',
'msg': '登录成功',
'username': user.username,
'token': token,
}
-2 写的函数配置一下
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.jwt_response.common_response',
}
drf-jwt自定义用户签发
# 1 创建一个用户表
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
age = models.IntegerField()
email = models.CharField(max_length=64)
# 2 登录接口
class UserView(ViewSet):
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
if user:
# 登录成功,签发token--->先背过,
# 1 通过user,获取payload
payload = jwt_payload_handler(user)
print(payload)
# 2 通过payload,得到token
token = jwt_encode_handler(payload)
return Response({'code': 100, 'msg': '登录成功', 'username': user.username, 'token': token})
else:
return Response({'code': 101, 'msg': '用户名或密码错误'})
# 3 登录接口签发token返回给前端
drf-jwt自定义认证类
'''
认证,成功返回用户和token,否则抛异常
'''
# drf的认证类定义方式,之前学过
# 在认证类中,自己写逻辑
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
# 背过
# 校验token是否过期,合法,如果都通过,查询出当前用户,返回
# 如果不通过,抛异常
try:
payload = jwt_decode_handler(token)
# 如果认证通过,payload就可以认为是安全的,我们就可以使用
user_id = payload.get('user_id')
# 每个需要登录后,才能访问的接口,都会走这个认证类,一旦走到这个认证类,机会去数据库查询一次数据,会对数据造成压力?
user = User.objects.get(pk=user_id)
# 优化后的
# user = User(username=payload.get('username'), id=user_id)
# user = {'username':payload.get('username'), 'id':user_id}
except jwt.ExpiredSignature:
raise AuthenticationFailed('token过期')
except jwt.DecodeError:
raise AuthenticationFailed('解码失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('token认证异常')
except Exception:
raise AuthenticationFailed('token认证异常')
return user, token
# 全局使用,局部使用
class Auth(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
try:
payload = jwt_decode_handler(token)
user = {"username": payload.get("username"), "id": payload.get("user_id")}
except Exception:
raise AuthenticationFailed("认证失败")
return user, token
drf-jwt的签发源码分析
# from rest_framework_jwt.views import obtain_jwt_token
# obtain_jwt_token就是ObtainJSONWebToken.as_view()---》视图类.as_view()
# 视图类
class ObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
# 父类:JSONWebTokenAPIView
class JSONWebTokenAPIView(APIView):
# 局部禁用掉权限和认证
permission_classes = ()
authentication_classes = ()
def post(self, request, *args, **kwargs):
# serializer=JSONWebTokenSerializer(data=request.data)
serializer = self.get_serializer(data=request.data)
# 调用序列化列的is_valid---》字段自己的校验规则,局部钩子和全局钩子
# 读JSONWebTokenSerializer的局部或全局钩子
if serializer.is_valid(): # 全局钩子在校验用户,生成token
# 从序列化类中取出user
user = serializer.object.get('user') or request.user
user = serializer.object.get('user') or request.user
# 从序列化类中取出token
token = serializer.object.get('token')
# 咱么定制返回格式的时候,写的就是这个函数
response_data = jwt_response_payload_handler(token, user, request)
response = Response(response_data)
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# JSONWebTokenSerializer的全局钩子
class JSONWebTokenSerializer(Serializer):
def validate(self, attrs):
# attrs前端传入的,校验过后的数据 {username:lqz,password:lqz12345}
credentials = {
'username': attrs.get('usernme'),
'password': attrs.get('password')
}
if all(credentials.values()): # 校验credentials中字典的value值是否都不为空
# user=authenticate(username=前端传入的,password=前端传入的)
# auth模块的用户名密码认证函数,可以传入用户名密码,去auth的user表中校验用户是否存在
# 等同于:User.object.filter(username=username,password=加密后的密码).first()
user = authenticate(**credentials)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
认证源码
# from rest_framework_jwt.authentication import JSONWebTokenAuthentication
# JSONWebTokenAuthentication
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
def get_jwt_value(self, request):
# auth=['jwt','token串']
auth = get_authorization_header(request).split()
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1] # token串
# 父类中找:BaseJSONWebTokenAuthentication---》authenticate,找到了
class BaseJSONWebTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
# 拿到前端传入的token,前端传入的样子是 jwt token串
jwt_value = self.get_jwt_value(request)
# 如果前端没传,返回None,request.user中就没有当前登录用户
# 如果前端没有携带token,也能进入到视图类的方法中执行,控制不住登录用户
# 所以咱们才加了个权限类,来做控制
if jwt_value is None:
return None
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
# 通过payload拿到当前登录用户
user = self.authenticate_credentials(payload)
return (user, jwt_value)
# 如果用户不携带token,也能认证通过
# 所以我们必须加个权限类来限制
class IsAuthenticated(BasePermission):
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
django-simpleui使用
安装
pip install django-simpleui
豆瓣:http://pypi.douban.com/simple/
中科大:https://pypi.mirrors.ustc.edu.cn/simple/
清华:https://pypi.tuna.tsinghua.edu.cn/simple/
settings配置
INSTALLED_APPS = [
'simpleui',
]
升级simpleui
pip install django-simpleui --upgrade
修改主题
SIMPLEUI_DEFAULT_THEME = 'admin.lte.css'#默认的==》有Simpleui-x,layui等
图标
SIMPLEUI_DEFAULT_ICON = False
True 开启默认图标,默认为True
False 关闭默认图标
#自定义图标
SIMPLEUI_ICON = {
'系统管理': 'fab fa-apple',
'员工管理': 'fas fa-user-tie'
}
菜单
system_keep 保留系统菜单 默认为False,不保留。 如果改为True,自定义和系统菜单将会并存
menu_display 过滤显示菜单和排序功能
dynamic 开启动态菜单功能
例子
import time
SIMPLEUI_CONFIG = {
'system_keep': False,
'menu_display': ['Simpleui', '测试', '权限认证', '动态菜单测试'], # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示.
'dynamic': True, # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
'menus': [{
'name': 'Simpleui',
'icon': 'fas fa-code',
'url': 'https://gitee.com/tompeppa/simpleui',
# 浏览器新标签中打开
'newTab': True,
}, {
'app': 'auth',
'name': '权限认证',
'icon': 'fas fa-user-shield',
'models': [{
'name': '用户',
'icon': 'fa fa-user',
'url': 'auth/user/'
}]
}, {
# 自2021.02.01+ 支持多级菜单,models 为子菜单名
'name': '多级菜单测试',
'icon': 'fa fa-file',
# 二级菜单
'models': [{
'name': 'Baidu',
'icon': 'far fa-surprise',
# 第三级菜单 ,
'models': [
{
'name': '爱奇艺',
'url': 'https://www.iqiyi.com/dianshiju/'
# 第四级就不支持了,element只支持了3级
}, {
'name': '百度问答',
'icon': 'far fa-surprise',
'url': 'https://zhidao.baidu.com/'
}
]
}, {
'name': '内网穿透',
'url': 'https://www.wezoz.com',
'icon': 'fab fa-github'
}]
}, {
'name': '动态菜单测试' ,
'icon': 'fa fa-desktop',
'models': [{
'name': time.time(),
'url': 'http://baidu.com',
'icon': 'far fa-surprise'
}]
}]
}
模板
修改模板修改模板
重写首页,在templates目录中新建admin文件夹,然后添加index.html 如果选择继承方式,就只能采用block
{% extends 'admin/index.html' %}
{% load static %}
{% block head %}
{{ block.super }}
..此处写你的代码
{% endblock %}
{% block script %}
{{ block.super }}
..此处写你的代码
{% endblock %}
全部重写
<html>
<head>
<title>完全自定义</title>
</head>
<body>
这里你是自定义的html代码
</body>
</html>
头部添加自定义代码
{% extends 'admin/index.html' %}
{% load static %}
{% block head %}
{{ block.super }}
..此处写你的代码
{% endblock %}
底部添加自定义代码
{% extends 'admin/index.html' %}
{% load static %}
{% block script %}
{{ block.super }}
..此处写你的代码
{% endblock %}
自定义按钮
@admin.register(Employe)
class EmployeAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'gender')
# 增加自定义按钮
actions = ['make_copy', 'custom_button']
def custom_button(self, request, queryset):
pass
# 显示的文本,与django admin一致
custom_button.short_description = '测试按钮'
# icon,参考element-ui icon与https://fontawesome.com
custom_button.icon = 'fas fa-audio-description'
# 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
custom_button.type = 'danger'
# 给按钮追加自定义的颜色
custom_button.style = 'color:black;'
def make_copy(self, request, queryset):
pass
make_copy.short_description = '复制员工'
字段参数
字段 说明
icon 按钮图标,参考https://element.eleme.cn/#/zh-CN/component/icon与https://fontawesome.com,把class 复制进来即可
type 按钮类型,参考:https://element.eleme.cn/#/zh-CN/component/button
style 自定义css样式
confirm 弹出确认框,在3.4或以上版本中生效
def message_test(self, request, queryset):
messages.add_message(request, messages.SUCCESS, '操作成功123123123123')
# 给按钮增加确认
message_test.confirm = '你是否执意要点击这个按钮?'
链接按钮
action_type 按钮动作类型,0=当前页内打开,1=新tab打开,2=浏览器tab打开
action_url 按钮访问链接
# 增加自定义按钮
actions = ['custom_button']
def custom_button(self, request, queryset):
pass
# 显示的文本,与django admin一致
custom_button.short_description = '测试按钮'
# icon,参考element-ui icon与https://fontawesome.com
custom_button.icon = 'fas fa-audio-description'
# 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
custom_button.type = 'danger'
# 给按钮追加自定义的颜色
custom_button.style = 'color:black;'
# 链接按钮,设置之后直接访问该链接
# 3中打开方式
# action_type 0=当前页内打开,1=新tab打开,2=浏览器tab打开
# 设置了action_type,不设置url,页面内将报错
# 设置成链接类型的按钮后,custom_button方法将不会执行。
custom_button.action_type = 0
custom_button.action_url = 'http://www.baidu.com'
layer对话框按钮
#对话框按钮是在admin中进行配置action,可以自定义输入的字段,然后通过ajax请求到action中进行业务的处理。
#需要继承AjaxAdmin 在from simpleui.admin import AjaxAdmin包中simplepro也会同步支持对话框按钮功能
字段说明
下列字段是指action
的layer
属性
字段 | 说明 |
---|---|
title | 对话框标题 |
tips | 对话框提示 |
confirm_button | 确认按钮文本 |
cancel_button | 取消按钮文本 |
width | 对话框宽度,百分比,例如:50% |
labelWidth | 表格的label宽度,例如:80px |
params | 对话框表格中的字段,array |
params字段
字段 | 说明 |
---|---|
type | 类型,取值为:input原生属性,和elementui的:select、date、datetime、rate、color、slider、switch、input_number、checkbox、radio |
key | 参数名,post参数中获取的名称 |
value | 默认值,数组或文本 |
label | 字段在表格中显示的名称 |
size | 组件的大小,取值为:medium / small / mini |
require | 是否必选,取值为:True/False |
width | 输入框宽度,例如:200px |
options | 选项,数组,type为select、checkbox、radio的时候可用 |
字段options字段
字段 | 说明 |
---|---|
key | 值 |
label | 显示文本 |
例子
class RecordAdmin(ImportExportActionModelAdmin, AjaxAdmin):
resource_class = ProxyResource
list_display = ('id', 'name', 'type', 'money', 'create_date')
list_per_page = 10
actions = ('layer_input',)
def layer_input(self, request, queryset):
# 这里的queryset 会有数据过滤,只包含选中的数据
post = request.POST
# 这里获取到数据后,可以做些业务处理
# post中的_action 是方法名
# post中 _selected 是选中的数据,逗号分割
if not post.get('_selected'):
return JsonResponse(data={
'status': 'error',
'msg': '请先选中数据!'
})
else:
return JsonResponse(data={
'status': 'success',
'msg': '处理成功!'
})
layer_input.short_description = '弹出对话框输入'
layer_input.type = 'success'
layer_input.icon = 'el-icon-s-promotion'
# 指定一个输入参数,应该是一个数组
# 指定为弹出层,这个参数最关键
layer_input.layer = {
# 弹出层中的输入框配置
# 这里指定对话框的标题
'title': '弹出层输入框',
# 提示信息
'tips': '这个弹出对话框是需要在admin中进行定义,数据新增编辑等功能,需要自己来实现。',
# 确认按钮显示文本
'confirm_button': '确认提交',
# 取消按钮显示文本
'cancel_button': '取消',
# 弹出层对话框的宽度,默认50%
'width': '40%',
# 表单中 label的宽度,对应element-ui的 label-width,默认80px
'labelWidth': "80px",
'params': [{
# 这里的type 对应el-input的原生input属性,默认为input
'type': 'input',
# key 对应post参数中的key
'key': 'name',
# 显示的文本
'label': '名称',
# 为空校验,默认为False
'require': True
}, {
'type': 'select',
'key': 'type',
'label': '类型',
'width': '200px',
# size对应elementui的size,取值为:medium / small / mini
'size': 'small',
# value字段可以指定默认值
'value': '0',
'options': [{
'key': '0',
'label': '收入'
}, {
'key': '1',
'label': '支出'
}]
}, {
'type': 'number',
'key': 'money',
'label': '金额',
# 设置默认值
'value': 1000
}, {
'type': 'date',
'key': 'date',
'label': '日期',
}, {
'type': 'datetime',
'key': 'datetime',
'label': '时间',
}, {
'type': 'rate',
'key': 'star',
'label': '评价等级'
}, {
'type': 'color',
'key': 'color',
'label': '颜色'
}, {
'type': 'slider',
'key': 'slider',
'label': '滑块'
}, {
'type': 'switch',
'key': 'switch',
'label': 'switch开关'
}, {
'type': 'input_number',
'key': 'input_number',
'label': 'input number'
}, {
'type': 'checkbox',
'key': 'checkbox',
# 必须指定默认值
'value': [],
'label': '复选框',
'options': [{
'key': '0',
'label': '收入'
}]
}]
}
action 返回结果
{
'status': 'error',
'msg': '请先选中数据!'
}
layer 文件上传
@admin.register(Layer)
class LayerAdmin(AjaxAdmin):
actions = ('upload_file',)
def upload_file(self, request, queryset):
# 这里的upload 就是和params中配置的key一样
upload= request.FILES['upload']
print(upload)
pass
upload_file.short_description = '文件上传对话框'
upload_file.type = 'success'
upload_file.icon = 'el-icon-upload'
upload_file.enable = True
upload_file.layer = {
'params': [{
'type': 'file',
'key': 'upload',
'label': '文件'
}]
}
关闭登录动画
SIMPLEUI_LOGIN_PARTICLES = False
首页-修改默认
首页配置
SIMPLEUI_HOME_PAGE = 'https://www.baidu.com'
首页标题
SIMPLEUI_HOME_TITLE = '百度一下你就知道'
首页图标,支持element-ui和fontawesome的图标,参考https://fontawesome.com/icons图标
SIMPLEUI_HOME_ICON = 'fa fa-user'
首页-跳转地址
# 设置simpleui 点击首页图标跳转的地址
SIMPLEUI_INDEX = 'https://www.baidu.com'
修改LOGO
SIMPLEUI_LOGO = 'https://avatars2.githubusercontent.com/u/13655483?s=60&v=4'
服务器信息
隐藏:
SIMPLEUI_HOME_INFO = False
显示:
SIMPLEUI_HOME_INFO = True
快速操作
隐藏:
SIMPLEUI_HOME_QUICK = False
显示:
SIMPLEUI_HOME_QUICK = True
最近动作
隐藏:
SIMPLEUI_HOME_ACTION = False
显示:
SIMPLEUI_HOME_ACTION = True
离线模式
SIMPLEUI_STATIC_OFFLINE = True
标签:return,get,self,request,data,def,drf
From: https://www.cnblogs.com/liyuanxiangls/p/17445504.html