一鬼廓、基于jwt的多方式登陸
1肿仑、要求:手機、郵箱和用戶名都能登陸
2、流程分析(post請求):
-路由:自動生成
-視圖類:ViewSet(ViewSetMixin, views.APIView)
-序列化類:重寫validate方法尤慰,在這里面對用戶名和密碼進行校驗
3馏锡、代碼實現(xiàn)
###路由
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/',views.LoginView.as_view({'post':'post'}))
]
#自動生成路由無法實現(xiàn)。因為action中有對應關(guān)系伟端,只要把視圖函數(shù)中的post函數(shù)改成create就行了
#post:create
#get:list
#get:retrieve
#put:update
#delete:destroy
###表模型
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
phone = models.CharField(max_length=32,unique=True)
###視圖
from rest_framework.viewsets import ViewSet
from app01 import serializer
from app01.utils import APIResponse
class LoginView(ViewSet):
def post(self,request,*args,**kwargs):
#實例化得到一個序列化類的對象杯道,里面加上context來進行與序列化類的數(shù)據(jù)傳遞
ser = serializer.LoginSerializer(data=request.data,context={'request':request})
#序列化類對象的校驗,字段自己的校驗责蝠、局部鉤子校驗以及全局鉤子校驗
if ser.is_valid():
#如果通過校驗党巾,表示登陸成功,返回手動簽發(fā)的token
token = ser.context.get('token')
username = ser.context.get('username')
return APIResponse(token=token,username=username)
else:
return APIResponse(code=101,msg='用戶名或密碼錯誤')
#方式二
#try:
#ser = serializer.LoginSerializer(data=request.data,context={'request':request})
#ser.is_valid(raise_exception=true)
#token = ser.context.get('token')
#username = ser.context.get('username')
#return APIResponse(token=token,username=username)
#except Exception as e:
#return APIResponse(code=101,msg='用戶名或密碼錯誤')
###序列化類
from rest_framework import serializers
from rest_framework_jwt.utils import jwt_encode_handler,jwt_payload_handler
from app01 import models
from rest_framework.exceptions import ValidationError
import re
class LoginSerializer(serializers.ModelSerializer):
# 覆蓋霜医,避免login校驗username有數(shù)據(jù)庫唯一字段約束的限制
username = serializers.CharField()
class Meta:
model = models.UserInfo
fields = ['username','password']
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
#分三種情況齿拂,取出對應的user對象
if re.match('^1[3-9]\d{9}$',username):
user = models.UserInfo.objects.filter(phone=username).first()
elif re.match('^.+@.+$',username):
user = models.UserInfo.objects.filter(email=username).first()
else:
user = models.UserInfo.objects.filter(username=username).first()
#基于auth組件校驗密碼
if user and user.check_password(password):
#登陸成功,生成token
#drf-jwt中有通過user對象生成token的方法支子,中間借助payload轉(zhuǎn)碼
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
# self.context.get('request')
#視圖類和序列化類之間通過context這個字典傳遞數(shù)據(jù)创肥,將token和username傳給視圖
self.context['token'] = token
self.context['username'] = user.username
return attrs
else:
raise ValidationError('用戶名以及密碼錯誤')
###自定義APIResponse以及全局異常
from rest_framework.response import Response
#自己封裝的response對象
class APIResponse(Response):
def __init__(self,code=100,msg='登陸成功', data=None,
status=None, headers=None,
content_type=None,**kwargs):
dic = {'code':code,'msg':msg}
if data:
dic['data'] = data
dic.update(kwargs)
super().__init__(data=dic,status=status,headers=headers,content_type=content_type)
#全局異常的捕獲
from rest_framework.views import exception_handler
from rest_framework import status
def common_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is None:
response = Response({'code':999,'detail': '未知錯誤'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return response
#全局異常捕獲需在settings.py中設置
REST_FRAMEWORK = {
'EXCEPTION_HANDLER':'app01.utils.common_exception_handler'
}
###注意點:1达舒、全局鉤子中要有返回值attrs值朋。2、封裝的response對象中傳入的data是字典巩搏。3昨登、在序列化中要重寫username,因為本身auth表中就帶有username贯底,不重寫相當于保存一樣的就會報錯丰辣。
二、自定義user表禽捆,簽發(fā)token笙什,認證類
1、不借助auth表胚想,自定義user表琐凭,視圖函數(shù)繼承APIView,不借助序列器
2浊服、代碼實現(xiàn)
###路由
path('login2/',views.MyLoginView.as_view())
path('order/',views.OrderAPIView.as_view())
###表模型
class MyUser(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
phone = models.CharField(max_length=32)
email = models.EmailField()
###自定義jwt認證
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication, jwt_decode_handler
from rest_framework_jwt.utils import jwt_decode_handler
import jwt
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('解碼錯誤')
except jwt.InvalidTokenError:
raise AuthenticationFailed('不合法的token')
print(payload) #{'user_id': 1, 'username': 'egon', 'exp': 1605532596, 'email': '11@qq.com'}
#user = models.MyUser.objects.get(id = payload('user_id)) # 正常情況下通過認證统屈,得到當前登陸用戶(需要每次訪問數(shù)據(jù)庫),出于效率考慮,我們沒有查詢數(shù)據(jù)庫牙躺,自己構(gòu)造了一個user實例化對象
#user = models.MyUser(id = payload['user_id'],username = payload['username'])
user = payload
return (user,token)
###視圖
from rest_framework.views import APIView
import re
from app01 import models
from rest_framework_jwt.utils import jwt_encode_handler,jwt_payload_handler
class MyLoginView(APIView):
def post(self,request,*args,**kwargs):
username = request.data.get('username')
password = request.data.get('password')
if re.match('^1[3-9]\d{9}$',username):
user = models.MyUser.objects.filter(phone=username).first()
elif re.match('^.+@.+$',username):
user = models.MyUser.objects.filter(email=username).first()
else:
user = models.MyUser.objects.filter(username=username).first()
if user and user.password == password:
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return APIResponse(token=token,username=user.username)
else:
return APIResponse(code=101,msg='用戶名或密碼錯誤')
from app01 import auth
from app01.auth import JwtAuthentication
class OrderAPIView(APIView):
authentication_classes = [auth.JwtAuthentication, ]
def get(self,request):
print(request.user)
#user是jwt認證中的payload愁憔,是個字典,內(nèi)部有user_id
#后續(xù)要查詢該用戶的所有訂單孽拷,直接根據(jù)user_id查詢即可
return APIResponse(msg='查詢訂單成功')
三吨掌、表關(guān)系以及抽象表的建立
注:以后所有的數(shù)據(jù)刪除,盡量用軟刪除,使用一個字段標志是否刪除思犁,而不是真正的從數(shù)據(jù)庫中刪除
優(yōu)點:1代虾、這樣刪除數(shù)據(jù)不會影響索引,不會導致索引失效
2激蹲、之前存的用戶數(shù)據(jù)還在棉磨,以備以后使用
#表模型
#抽象出一個基表(不在數(shù)據(jù)庫生成,abstract=True)学辱,只用來繼承
class BaseModel(models.Model):
is_delete = models.BooleanField(default=False)
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
#基表必須設置abstract = True,基表就是給普通Model類繼承使用的乘瓤,設置了abstract就不會完成數(shù)據(jù)庫遷移以及建表
abstract = True
class Book(BaseModel):
name = models.CharField(max_length=16)
price = models.DecimalField(max_digits=5, decimal_places=2)
publisher = models.ForeignKey(to='Publisher', db_constraint=False, on_delete=models.DO_NOTHING)
# 重點:多對多外鍵實際在關(guān)系表中,ORM默認關(guān)系表中兩個外鍵都是級聯(lián)
# ManyToManyField字段不提供設置on_delete策泣,如果想設置關(guān)系表級聯(lián)衙傀,只能手動定義關(guān)系表
authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
@property
def publish_name(self):
return self.publish.name
@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)
# 有作者可以沒有詳情,刪除作者萨咕,詳情一定會被級聯(lián)刪除
# 外鍵字段為正向查詢字段统抬,related_name是反向查詢字段
author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)
# 二、表斷關(guān)聯(lián)
# 1危队、表之間沒有外鍵關(guān)聯(lián)聪建,但是有外鍵邏輯關(guān)聯(lián)(有充當外鍵的字段)
# 2、斷關(guān)聯(lián)后不會影響數(shù)據(jù)庫查詢效率茫陆,但是會極大提高數(shù)據(jù)庫增刪改效率(不影響增刪改查操作)
# 3金麸、斷關(guān)聯(lián)一定要通過邏輯保證表之間數(shù)據(jù)的安全
# 4、斷關(guān)聯(lián)
# 5簿盅、級聯(lián)關(guān)系
# 作者沒了挥下,詳情也沒:on_delete=models.CASCADE
# 出版社沒了,書還是那個出版社出版:on_delete=models.DO_NOTHING
# 部門沒了桨醋,員工沒有部門(空不能):null=True, on_delete=models.SET_NULL
# 部門沒了棚瘟,員工進入默認部門(默認值):default=0, on_delete=models.SET_DEFAULT
四、批量操作
###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)部如何實現(xiàn)的喜最?
# many=True,ser不是BookSerializer對象偎蘸,而是ListSerializer對象,套了一個個的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))
###book表的單查與群查
class BookView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk', None)
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)
###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]
# 如果不重寫ListSerializer的update方法禀苦,這是存不進去的
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))
###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('沒有要刪除的數(shù)據(jù)')
return APIResponse(code=999, msg='沒有要刪除的數(shù)據(jù)')
###序列化類
from app01 import models
class ListBookSerializer(serializers.ListSerializer):
# def create(self, validated_data):
# print('=======',validated_data)
# return '1'
def update(self, instance, validated_data):
print(instance) # book_list:是一堆圖書對象
print(validated_data) # 列表套字典,是要修改的數(shù)據(jù)
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的時候遂鹊,生成的ListBookSerializer的對象了
fields = ['name', 'price', 'publish', 'authors', 'publish_name', 'author_list']
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True},
'publish_name': {'read_only': True},
'author_list': {'read_only': True},
}
# def create(self, validated_data):
# print(validated_data)
###路由
path('books/', views.BookView.as_view()),
re_path('books/(?P<pk>\d+)', views.BookView.as_view()),