Day 29 jwt自定义登录+多表操作
一、基于JWT的多方式登录
1 手机号+密码 用户名+密码 邮箱+密码
2 流程分析(post请求):
-路由:自动生成
-视图类:ViewSet(ViewSetMixin, views.APIView)
-序列化类:重写validate方法,在这里面对用户名和密码进行校验
3 代码实现
路由
path('login/', views.LoginViewSet.as_view({
'post':'create'})),
视图
class LoginViewSet(ViewSet):
def create(self, request, *args, **kwargs):
# 实例化得到一个序列化类的对象
# ser=LoginSerializer(data=request.data,context={'request':request})
ser = LoginSerializer(data=request.data)
# 序列化类的对象的校验方法
ser.is_valid(raise_exception=True) # 字段自己的校验,局部钩子校验,全局钩子校验
# 如果通过,表示登录成功,返回手动签发的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可能是邮箱,手机号,用户名
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('用户名或密码错误')
二、自定义user表,签发token,认证类
表模型
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()
路由
path('login2/', views.MyLoginView.as_view()),
视图
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')
# 如果是手机号
if re.match('^1[3-9]\d{9}$', username):
# 以手机号登录
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对象生成token的方法
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return APIResponse(token=token, username=user.username)
else:
return APIResponse(code=101, msg='用户名或密码错误')
三、多表的增删查改
1 路由
urlpatterns = [
path('book/', views.BookView.as_view()),
re_path('^book/(?P<pk>\d+)', views.BookView.as_view()),
]
2 表模型
#抽象出一个基表(不再数据库生成,abstract=True),只用来继承
class Base(models.Model):
is_delete = models.BooleanField(default=False)
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
# 基表必须设置abstract,基表就是给普通Model类继承使用的,
# 设置了abstract就不会完成数据库迁移完成建表
abstract = True # 抽象=True
class Book(Base):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
# 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联
# ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表
authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
def publish_name(self):
return self.publish.name
def authors_info(self):
authors = ','.join([i.name for i in self.authors.all()])
return {
'authors': authors, 'sex': [author.get_sex_display() for author in self.authors.all()]}
class Publish(Base):
name = models.CharField(max_length=32)
address = models.CharField(max_length=64)
class Author(Base):
name = models.CharField(max_length=32)
sex = models.IntegerField(choices=((1, '男'), (2, '女')), default=0)
def get_info(self):
return self.detail.age
class AuthorDetail(Base):
age = models.IntegerField()
# 有作者可以没有详情,删除作者,详情一定会被级联删除
# 外键字段为正向查询字段,related_name是反向查询字段
author = models.OneToOneField(to='Author', related_name='detail', on_delete=models.CASCADE)
注意:以后所有的数据删除,尽量用软删除,使用一个字段标志是否删除,而不是真正的从数据库中删除
-好处:1 这样删除数据不会影响索引,不会导致索引失效
2 之前存的用户数据还在,以备以后使用
3 序列化器
class ListBookSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
print(instance) # 这里面也是一个列表 包含着书对象
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', 'publish', 'authors', 'publish_name', 'authors_info']
extra_kwargs = {
'publish': {
'write_only': True, },
'authors': {
'write_only': True, },
'publish_name': {
'read_only': True, },
'authors_info': {
'read_only': True, },
}
4 视图函数
class BookView(APIView):
def get(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)
else:
books = models.Book.objects.filter(is_delete=False)
ser = serializer.BookSerializer(instance=books, many=True)
return APIResponse(ser.data)
def post(self, request, *args, **kwargs):
if isinstance(request.data, dict):
# 新增单条
ser = serializer.BookSerializer(data=request.data)
ser.is_valid()
i = request.data.get('name')
elif isinstance(request.data, list):
ser = serializer.BookSerializer(data=request.data, many=True)
ser.is_valid(raise_exception=True)
i = ','.join([j.get('name') for j in request.data])
msg = f'书籍 {i} 添加成功!'
ser.save()
return APIResponse(msg=msg, data=ser.data)
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk', None)
if pk: # 存在pk说明单个修改 从路由中获取
# 找书
book = models.Book.objects.get(id=pk)
# 序列化存储 默认的update
ser = serializer.BookSerializer(instance=book, data=request.data)
msg = '修改{}成功'.format(request.data.get('name'), )
else: # 说明多条数据在put的data中
pks = []
# data是一个 存放书籍修改信息的列表
for item in request.data:
pks.append(item.get('id'))
# 反序列化的时候用不到id 也无法修改id 多以弹出来
item.pop('id')
# id__in sql的高级操作!
books = models.Book.objects.filter(id__in=pks, is_delete=False)
# 分发到序列器 然后进行many判断 使用 ListBookSerializer
ser = serializer.BookSerializer(instance=books, data=request.data, many=True)
ser.is_valid(raise_exception=True)
ser.save()
i = ','.join([j.get('name') for j in request.data])
msg = f'书籍 {i} 修改成功!'
print(msg)
print(ser.data)
return APIResponse(msg=msg, data=ser.data)
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk', None)
pks = []
if pk:
pks.append(pk)
# 请求为一个索引的列表
pks = request.data
# 进行软删除,返回的是一个整形 操作的个数
res = models.Book.objects.filter(id__in=pks).update(is_delete=True)
if res > 0: # 说明操作了数据
msg = '删除{}条数据成功!'.format(res, )
else: # 说明数据不存在
msg = '找不到该书籍哦!'
return APIResponse(msg=msg)