上節(jié)回顧
1、jwt:重點(diǎn)(跟語言套像,框架無關(guān))
-json web token
-cookie:客戶端瀏覽器上的鍵值對(duì)酿联,數(shù)據(jù)不安全
-session:服務(wù)端的鍵值對(duì)(內(nèi)存,數(shù)據(jù)庫,redis贞让,文件)周崭,安全,對(duì)服務(wù)端壓力大
-token:三段:頭喳张,荷載续镇,簽名
-header:公司信息,加密方式销部,類型
-payload:荷載摸航,真正有用的數(shù)據(jù)部分,用戶id舅桩,用戶名字
-signature:簽名酱虎,把頭和荷載部分通過加密算法加密--》得到一個(gè)簽名
2、drf-jwt模塊
-快速使用:默認(rèn)使用的是auth的user表
-1擂涛、創(chuàng)建用戶
-2读串、在路由中配置path('login/',obtain_jwt_token),
-3、在postman中測(cè)試撒妈,用戶名密碼輸入剛剛創(chuàng)建的用戶就可以生成token
-4爹土、讓一個(gè)視圖必須登錄以后才能訪問
-authentication_classes=[JSONWebTokenAuthentication,]
-permission_classes=[IsAuthenticated,]
-5、讓一個(gè)視圖可以登錄后訪問踩身,也可以不登陸訪問就只加上token的認(rèn)證
-authentication_classes=[JSONWebTokenAuthentication,]
-6、用postman測(cè)試社露,在請(qǐng)求頭中加入
-Authorization jwt adasffa
-自己寫基于jwt的認(rèn)證類(登錄了能訪問挟阻,不登錄就不能訪問)
class JwtAuthentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
#token=request.GET.get('token')
token=request.META.get('HTTP_Authorization'.upper())
try:
# 驗(yàn)證token是否正確
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise AuthenticationFailed('過期了')
except jwt.DecodeError:
raise AuthenticationFailed('解碼錯(cuò)誤')
except jwt.InvalidTokenError:
raise AuthenticationFailed('不合法的token')
user=self.authenticate_credentials(payload) # 這是用的自帶的user表的字段取出的user,不適用于自己寫的user表峭弟,自己創(chuàng)建的用戶表需要自己手動(dòng)取
return (user, token)
-自定制認(rèn)證類的使用方式:
-全局使用
-局部使用
3附鸽、base64編碼(跟語言無關(guān),跟框架無關(guān))
-不同語言的base64可以互相編碼解碼
-base64內(nèi)置模塊
-圖片的二進(jìn)制瞒瘸,有時(shí)候也會(huì)以base64的形式編碼
4坷备、drf的視圖(兩個(gè)視圖基類,5個(gè)視圖擴(kuò)展類情臭,9個(gè)視圖子類省撑,視圖集)+序列化器+自動(dòng)生成路由
今日內(nèi)容
1、基于jwt的多方式登錄
-1俯在、手機(jī)號(hào)+密碼竟秫,用戶名+密碼,郵箱+密碼
-2跷乐、流程分析(post請(qǐng)求):
-路由:自動(dòng)生成
-視圖類:ViewSet(ViewSetMixin肥败,Views.APIView) 所有東西自己寫,路由可以自己生成
-序列化類:重寫validate方法,在這里面對(duì)用戶名和密碼進(jìn)行校驗(yàn)馒稍,就是全局鉤子
-3皿哨、代碼實(shí)現(xiàn)
路由
也可以選擇自動(dòng)生成
path('login/', views.LoginViewSet.as_view({'post':'create'})),
視圖
class LoginViewSet(ViewSet):
def create(self, request, *args, **kwargs):
# 實(shí)例化得到一個(gè)序列化類的對(duì)象
# ser=LoginSerializer(data=request.data,context={'request':request}) #context數(shù)據(jù)交流的橋梁
ser = LoginSerializer(data=request.data)
# 序列化類的對(duì)象的校驗(yàn)方法
ser.is_valid(raise_exception=True) # 字段自己的校驗(yàn),局部鉤子校驗(yàn)纽谒,全局鉤子校驗(yàn)证膨,只要走了這個(gè),并且沒有拋異常佛舱,
#就會(huì)走到序列化器的全局鉤子去校驗(yàn)椎例,那么全局鉤子里往序列化器里添加屬性,那么我在這數(shù)據(jù)校驗(yàn)之后就能從序列化對(duì)象里取到添加的屬性
# 如果通過请祖,表示登錄成功订歪,返回手動(dòng)簽發(fā)的token
token = ser.context.get('token')
username = ser.context.get('username')
return APIResponse(token=token, username=username)
# 如果失敗,不用管了
序列化器類
from rest_framework import serializers
from app01.models import UserInfo
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
from rest_framework_jwt.views import obtain_jwt_token
class LoginSerializer(serializers.ModelSerializer):
username = serializers.CharField()
class Meta:
model = UserInfo
fields = ['username', 'password']
def validate(self, attrs):
# username可能是郵箱肆捕,手機(jī)號(hào)刷晋,用戶名
username = attrs.get('username')
password = attrs.get('password')
# 如果是手機(jī)號(hào)
if re.match('^1[3-9]\d{9}$', username):
# 以手機(jī)號(hào)登錄
user = UserInfo.objects.filter(phone=username).first()
elif re.match('^.+@.+$', username):
# 以郵箱登錄
user = UserInfo.objects.filter(email=username).first()
else:
# 以用戶名登錄
user = UserInfo.objects.filter(username=username).first()
# 如果user有值并且密碼正確
if user and user.check_password(password):
# 登錄成功,生成token
# drf-jwt中有通過user對(duì)象生成token的方法
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
# token是要在視圖類中使用慎陵,現(xiàn)在我們?cè)谛蛄谢愔? # self.context.get('request')
# 視圖類和序列化類之間通過context這個(gè)字典來傳遞數(shù)據(jù)
self.context['token'] = token
self.context['username'] = user.username
return attrs
else:
raise ValidationError('用戶名或密碼錯(cuò)誤')
2眼虱、 自定義user表拳喻,簽發(fā)token振愿,認(rèn)證類
表模型
class MyUser(models.Model):
# 這里字段必須用username豁遭,應(yīng)為是生成token的源碼里用的就是這個(gè)字段名
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
phone = models.CharField(max_length=32)
email = models.EmailField()
路由
path('login2/', views.MyLoginView.as_view()),
視圖
# 這是從jwt的配置文件中導(dǎo)出的功能
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from rest_framework_jwt.views import obtain_jwt_token
class MyLoginView(APIView):
def post(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
# 如果是手機(jī)號(hào)
if re.match('^1[3-9]\d{9}$', username):
# 以手機(jī)號(hào)登錄
user = MyUser.objects.filter(phone=username).first()
elif re.match('^.+@.+$', username):
# 以郵箱登錄
user = MyUser.objects.filter(email=username).first()
else:
# 以用戶名登錄
user = MyUser.objects.filter(username=username).first()
# 如果user有值并且密碼正確
if user and user.password == password:
# 登錄成功憾赁,生成token
# drf-jwt中有通過user對(duì)象生成token的方法
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return APIResponse(token=token, username=user.username)
else:
return APIResponse(code=101, msg='用戶名或密碼錯(cuò)誤')
from app01.auth import JWTAuthentication
class OrderAPIView(APIView):
authentication_classes = [JWTAuthentication,]
def get(self,request):
# print(request.user) # 自己的user對(duì)象 {'user_id': 1, 'username': 'lqz', 'exp': 1605545489, 'email': '33@qq.com'}
print(request.user) # user是個(gè)字典肛响,就是認(rèn)證后返回的user质况,就是payload字典威彰。內(nèi)部有user_id,
#后續(xù)要查詢?cè)撚脩舻乃杏唵畏嘌Γ苯痈鶕?jù)user_id查詢即可
return APIResponse(msg='全部訂單')
自定義認(rèn)證類
from rest_framework_jwt.utils import jwt_decode_handler
import jwt
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from app01.models import MyUser
class JWTAuthentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
token = request.META.get('http_authorization'.upper())
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise AuthenticationFailed('過期了')
except jwt.DecodeError:
raise AuthenticationFailed('解碼錯(cuò)誤')
except jwt.InvalidTokenError:
raise AuthenticationFailed('不合法的token')
# 得到user對(duì)象纺铭,應(yīng)該是自己user表中的user對(duì)象
print(payload) # {'user_id': 1, 'username': 'lqz', 'exp': 1605545489, 'email': '33@qq.com'}
# user=MyUser.objects.get(id=payload['user_id']) # 可以這樣取,是訪問數(shù)據(jù)庫寇钉,效率有問題,也可以直接從那個(gè)payload里取舶赔,就是字典里取值扫倡,很快
user = payload # 直接讓uesr就是payload
return (user, token)
3、book竟纳,publish撵溃,author表關(guān)系及抽象表建立
# 注意:以后所有的數(shù)據(jù)刪除,盡量用軟刪除蚁袭,使用一個(gè)字段標(biāo)志是否刪除征懈,而不是真正的從數(shù)據(jù)庫中刪除
-好處:1 這樣刪除數(shù)據(jù)不會(huì)影響索引,不會(huì)導(dǎo)致索引失效
2 之前存的用戶數(shù)據(jù)還在揩悄,以備以后使用
# 表模型如下
# 抽象出一個(gè)基表(不再數(shù)據(jù)庫生成,abstract=True),只用來繼承
class BaseModel(models.Model):
is_delete = models.BooleanField(default=False)
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
# 基表必須設(shè)置abstract卖哎,基表就是給普通Model類繼承使用的,設(shè)置了abstract就不會(huì)完成數(shù)據(jù)庫遷移完成建表
abstract = True
class Book(BaseModel):
name = models.CharField(max_length=16)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish = models.ForeignKey(to='Publish', db_constraint=False, on_delete=models.DO_NOTHING)
# 重點(diǎn):多對(duì)多外鍵實(shí)際在關(guān)系表中,ORM默認(rèn)關(guān)系表中兩個(gè)外鍵都是級(jí)聯(lián)
# ManyToManyField字段不提供設(shè)置on_delete亏娜,如果想設(shè)置關(guān)系表級(jí)聯(lián)焕窝,只能手動(dòng)定義關(guān)系表
authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
# 自定義連表深度,不需要反序列化维贺,因?yàn)樽远x插拔屬性不參與反序列化
# 把他包裝成數(shù)據(jù)屬性它掂,這樣在序列化類中可以顯示序列化后的字段里有這個(gè)屬性,不用重寫字段
@property
def publish_name(self):
return self.publish.name
# 這個(gè)寫到序列化器里后和source一樣溯泣,如果是個(gè)可執(zhí)行的就會(huì)執(zhí)行虐秋,如果是對(duì)象就會(huì)拿過去,包不包裝成數(shù)據(jù)屬性都一樣
@property
def author_list(self):
# ll=[]
# for author in self.authors.all():
# ll.append({'name':author.name,'sex':author.get_sex_display()})
return [{'name': author.name, 'sex': author.get_sex_display()} for author in self.authors.all()]
class Publish(BaseModel):
name = models.CharField(max_length=16)
address = models.CharField(max_length=64)
class Author(BaseModel):
name = models.CharField(max_length=16)
sex = models.IntegerField(choices=[(0, '男'), (1, '女')], default=0)
class AuthorDetail(BaseModel):
mobile = models.CharField(max_length=11)
# 有作者可以沒有詳情垃沦,刪除作者客给,詳情一定會(huì)被級(jí)聯(lián)刪除
# 外鍵字段為正向查詢字段,related_name是反向查詢字段
author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)
4肢簿、book表單增群增
class BookView(APIView):
def post(self, request, *args, **kwargs):
if isinstance(request.data, dict):
# 增一條
ser = serializer.BookSerializer(data=request.data)
ser.is_valid(raise_exception=True)
ser.save()
return APIResponse(data=ser.data)
elif isinstance(request.data, list):
# 增多條
ser = serializer.BookSerializer(data=request.data, many=True)
# 內(nèi)部如何實(shí)現(xiàn)的靶剑?
# many=True,ser不是BookSerializer對(duì)象,而是ListSerializer對(duì)象池充,套了一個(gè)個(gè)的BookSerializer
print(type(ser))
ser.is_valid(raise_exception=True)
#
from rest_framework.serializers import ListSerializer
ser.save() # ListSerializer的save
return APIResponse(msg='增加%s條成功' % len(request.data))
5桩引、book表單查群查
class BookView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk', None)# 這里要設(shè)置個(gè)默認(rèn)值,因?yàn)橛锌赡躪wargs是個(gè)空收夸,空去get就會(huì)報(bào)錯(cuò)
if pk:
# 單查
# 方式一
# book=models.Book.objects.filter(id=pk).filter(is_delete=False).first()
# if not book:
# raise Exception('要查詢的不存在')
# 方式二
book = models.Book.objects.get(id=pk, is_delete=False)
ser = serializer.BookSerializer(instance=book)
else:
# 查所有
book_list = models.Book.objects.all().filter(is_delete=False)
ser = serializer.BookSerializer(instance=book_list, many=True)
return APIResponse(data=ser.data)
6 book表單改群改
class BookView(APIView):
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk', None)
if pk:
# 單條修改
book = models.Book.objects.get(id=pk, is_delete=False)
ser = serializer.BookSerializer(instance=book, data=request.data)
ser.is_valid(raise_exception=True)
ser.save()
return APIResponse(msg='修改成功')
else:
# 分析:ListSerializer的update方法沒有寫坑匠,需要我們自己寫
from rest_framework.serializers import ListSerializer
# pks=[item['id'] for item in request.data] # 需要把id剔除
# 如果不重寫ListSerializer的update方法,這是存不進(jìn)去的
pks = []
for item in request.data:
pks.append(item['id'])
item.pop('id')
print(request.data)
book_list = models.Book.objects.filter(id__in=pks, is_delete=False)
ser = serializer.BookSerializer(instance=book_list, data=request.data, many=True)
print(type(ser))
ser.is_valid(raise_exception=True)
ser.save()
return APIResponse(msg='修改%s條成功')
# 你們能想到的方法
# pks = []
# for item in request.data:
# pks.append(item['id'])
# item.pop('id')
# book_list = models.Book.objects.filter(id__in=pks, is_delete=False)
#
# for i,book in enumerate(book_list):
# ser = serializer.BookSerializer(instance=book, data=request.data[i])
# ser.is_valid(raise_exception=True)
# ser.save()
# return APIResponse(msg='修改%s條成功'%len(book_list))
7 book表的單刪群刪
class BookView(APIView):
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk', None)
pks = []
if pk:
# 單條刪除
# res=models.Book.objects.filter(id=pk).update(is_delete=True)
# print(res)
# return APIResponse(msg='刪除成功')
pks.append(pk)
else:
pks = request.data # 傳過來的就是列表
res = models.Book.objects.filter(id__in=pks).update(is_delete=True)
if res >= 1:
return APIResponse(msg='刪除%s條成功' % res)
else:
# raise Exception('沒有要?jiǎng)h除的數(shù)據(jù)')
return APIResponse(code=999, msg='沒有要?jiǎng)h除的數(shù)據(jù)')
8 序列化類
from app01 import models
# 重寫群改的序列化類,因?yàn)長istSerializer里沒有重寫update方法
class ListBookSerializer(serializers.ListSerializer):
# def create(self, validated_data):
# print('=======',validated_data)
# return '1'
def update(self, instance, validated_data):
print(instance) # book_list:是一堆圖書對(duì)象
print(validated_data) # 列表套字典卧惜,是要修改的數(shù)據(jù)
# self.child就是BookSerializer
return [self.child.update(book, validated_data[i]) for i, book in enumerate(instance)]
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
list_serializer_class=ListBookSerializer # 指定many=True的時(shí)候笛辟,生成的ListBookSerializer的對(duì)象了
fields = ['name', 'price', 'publish', 'authors', 'publish_name', 'author_list']
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True},
'publish_name': {'read_only': True},# 后面兩個(gè)字段是模型類的數(shù)據(jù)屬性,起始也可以重寫兩個(gè)字段也能達(dá)到相同的效果序苏,但是這樣寫更巧妙
'author_list': {'read_only': True},
}
# def create(self, validated_data): # 寫了就不走自己的保存了
# print(validated_data)
9、路由
path('books/', views.BookView.as_view()),
re_path('books/(?P<pk>\d+)', views.BookView.as_view()),