多方式登录
DRF - 多方式登录
用户可以以用户名、邮箱、手机号登录
之前写的逻辑处理一般都是在视图类中,现在把逻辑拿到了序列化类的
validate
全局钩子中,在视图函数中 就无需写逻辑了,执行到.is_valid
就会执行validate
方法
- 重点:视图类和序列化类之间交互数据的桥梁:
context={}
(是1个字典)
自定义用户表,登录手动签发token
,自定义的认证类
认证通过,正常情况下查询得到当前登录用户
效果
用户名登录
邮箱登录
手机号登录
代码
models.py
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=16, verbose_name='用户名')
password = models.CharField(max_length=16, verbose_name='密码')
phone = models.CharField(max_length=11, verbose_name='手机号')
email = models.EmailField(verbose_name='邮箱')
class Meta:
verbose_name_plural = '用户信息表'
def __str__(self):
return self.username
serializer.py
import re
from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
from rest_framework.exceptions import ValidationError
from rest_framework import serializers
from multiple_login.models import UserInfo
class LoginSerializer(serializers.ModelSerializer):
username = serializers.CharField()
class Meta:
model = UserInfo
fields = ['username', 'password']
def validate(self, attrs):
# username可能是邮箱,手机号,用户名
username = attrs.get('username')
password = attrs.get('password')
# 如果是手机号
if re.match('^1[3-9]\d{9}$', username):
# 以手机号登录
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对象生成token的方法
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
# token是要在视图类中使用,现在我们在序列化类中
# self.context.get('request')
# 视图类和序列化类之间通过context这个字典来传递数据
self.context['token'] = token
self.context['username'] = user.username
return attrs
else:
raise ValidationError('用户名或密码错误')
utils.py
from rest_framework.views import exception_handler
from rest_framework.response import 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)
def common_exception(exc, context):
# 先调用REST framework默认的异常处理方法获得标准错误响应对象
response = exception_handler(exc, context)
# 在此处补充自定义的异常处理
if response is None:
response = Response(data={'code': 999, 'msg': str(exc)})
return response
auth.py
import jwt
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.utils import jwt_decode_handler
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')
# 得到的user对象,应该是自己user表的user对象
# user=MyUser.objects.get(id=payload['user_id'])
user = payload
return (user, token)
views.py
import re
from rest_framework_jwt.settings import api_settings
from rest_framework.views import APIView
from multiple_login.utils import APIResponse
from multiple_login.models import UserInfo
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_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 = 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.password == password:
# 登录成功,生成token
# drf-jwt中有通过user对象生成token的方法
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return APIResponse(token=token, username=user.username)
else:
return APIResponse(code=101, msg='用户名或密码错误')
urls.py
from django.contrib import admin
from django.urls import path
from multiple_login import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', views.MyLoginView.as_view()),
]
settings.py
REST_FRAMEWORK = {
# 配置全局异常
'EXCEPTION_HANDLER': 'multiple_login.utils.common_exception'
}
DRF - 批量操作
功能:群增、群删、群改、群查
代码
models.py
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=16, verbose_name='用户名')
password = models.CharField(max_length=16, verbose_name='密码')
phone = models.CharField(max_length=11, verbose_name='手机号')
email = models.EmailField(verbose_name='邮箱')
class Meta:
verbose_name_plural = '用户信息表'
def __str__(self):
return self.username
# 抽象出一个基表(不在数据库生成,abstract=True),只用来继承(类似于Mixin的混入思想)
class BaseModel(models.Model):
is_delete = models.BooleanField(default=False)
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
# 基表必须设置abstract = True,基表就是给普通Model类继承使用的,设置了abstract 就不会完成数据库迁移 完成建表
abstract = True
class Book(BaseModel):
name = models.CharField(max_length=16, verbose_name='书名')
price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='图书价格')
publisher = models.ForeignKey(to='Publisher', db_constraint=False, on_delete=models.DO_NOTHING,
verbose_name='图书关联的出版社')
# 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联
# ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表
authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False, verbose_name='图书的作者们')
@property
def publisher_name(self):
return self.publisher.name
@property
def author_list(self):
# ll=[]
# for author in self.authors.all():
# ll.append({'name':author.name,'sex':author.get_gender_display()})
return [{'name': author.name, 'sex': author.get_gender_display()} for author in self.authors.all()]
class Meta:
verbose_name_plural = '图书表'
def __str__(self):
return self.name
class Publisher(BaseModel):
name = models.CharField(max_length=16, verbose_name='出版社名')
address = models.CharField(max_length=64, verbose_name='出版社地址')
class Meta:
verbose_name_plural = '出版社表'
def __str__(self):
return self.name
class Author(BaseModel):
name = models.CharField(max_length=16, verbose_name='作者姓名')
gender = models.IntegerField(choices=[(0, '保密'), (1, '男'), (1, '女')], default=0, verbose_name='作者性别')
class Meta:
verbose_name_plural = '作者表'
def __str__(self):
return self.name
class AuthorDetail(BaseModel):
mobile = models.CharField(max_length=11)
# 有作者可以没有详情,删除作者,详情一定会被级联删除
# 外键字段为正向查询字段,related_name是反向查询字段
author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE,
verbose_name='关联的作者')
class Meta:
verbose_name_plural = '作者详情表'
serializer.py
import re
from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
from rest_framework.exceptions import ValidationError
from rest_framework import serializers
from app01 import models
class LoginSerializer(serializers.ModelSerializer):
username = serializers.CharField()
class Meta:
model = models.UserInfo
fields = ['username', 'password']
def validate(self, attrs):
# username可能是邮箱,手机号,用户名
username = attrs.get('username')
password = attrs.get('password')
# 如果是手机号
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()
# 如果user有值并且密码正确
if user and user.check_password(password):
# 登录成功,生成token
# drf-jwt中有通过user对象生成token的方法
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
# token是要在视图类中使用,现在我们在序列化类中
# self.context.get('request')
# 视图类和序列化类之间通过context这个字典来传递数据
self.context['token'] = token
self.context['username'] = user.username
return attrs
else:
raise ValidationError('用户名或密码错误')
# 图书表序列化类
class ListBookSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
# print(instance) # book_list:是一堆图书对象
# print(validated_data) # 列表套字典,是要修改的数据
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', 'publisher', 'authors', 'publisher_name', 'author_list']
extra_kwargs = {
'publisher': {'write_only': True},
'authors': {'write_only': True},
'publisher_name': {'read_only': True},
'author_list': {'read_only': True},
}
utils.py
from rest_framework.views import exception_handler
from rest_framework.response import 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)
def common_exception(exc, context):
# 先调用REST framework默认的异常处理方法获得标准错误响应对象
response = exception_handler(exc, context)
# 在此处补充自定义的异常处理
if response is None:
response = Response(data={'code': 999, 'msg': str(exc)})
return response
auth.py
import jwt
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.utils import jwt_decode_handler
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')
# 得到的user对象,应该是自己user表的user对象
print(payload)
# user=MyUser.objects.get(id=payload['user_id'])
user = payload
return (user, token)
views.py
import re
from rest_framework_jwt.settings import api_settings
from rest_framework.views import APIView
from app01 import models, serializer
from app01.utils import APIResponse
from app01.models import UserInfo
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_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 = 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.password == password:
# 登录成功,生成token
# drf-jwt中有通过user对象生成token的方法
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return APIResponse(token=token, username=user.username)
else:
return APIResponse(code=101, msg='用户名或密码错误')
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)
# 内部如何实现的?
# many=True,ser不是BookSerializer对象,而是ListSerializer对象,套了一个个的BookSerializer
# print(type(ser))
ser.is_valid(raise_exception=True)
ser.save() # ListSerializer的save
return APIResponse(msg=f'增加{ser}条成功')
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)
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=f'删除{res}条成功')
else:
return APIResponse(code=999, msg='没有要删除的数据')
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方法没有写,需要我们自己写
# pks=[item['id'] for item in request.data]
# 如果不重写ListSerializer的update方法,这是存不进去的
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)
ser = serializer.BookSerializer(instance=book_list, data=request.data, many=True)
ser.is_valid(raise_exception=True)
ser.save()
return APIResponse(msg=f'修改{len(book_list)}条成功')
urls.py
from django.contrib import admin
from django.urls import path, re_path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', views.MyLoginView.as_view()),
path('books/', views.BookView.as_view()),
re_path('books/(?P<pk>\d+)', views.BookView.as_view()),
]
settings.py
REST_FRAMEWORK = {
# 配置全局异常
'EXCEPTION_HANDLER': 'app01.utils.common_exception'
}