day90
1 基于jwt的多方式登陆
2 自定义user表,签发token,认证类
3 book,publish,author表关系及抽象表建立
4 book表对单及对群增删改查
## 1 基于jwt的多方式登陆 ```python 1 手机号+密码 用户名+密码 邮箱+密码 2 流程分析(post请求): -路由:自动生成 -视图类:ViewSet(ViewSetMixin, views.APIView) -序列化类:重写validate方法,在这里面对用户名和密码进行校验 3 代码实现 ``` ### 路由 ```python path('login/', views.LoginViewSet.as_view({'post':'create'})), ``` ### 视图 ```python 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) # 如果失败,不用管了 ``` ### 序列化类 ```python 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('用户名或密码错误') ``` ## 2 自定义user表,签发token,认证类 ### 表模型 ```python 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()), ``` ### 视图 ```python 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='用户名或密码错误') ``` ## 3 book,publish,author表关系及抽象表建立 ```python # 注意:以后所有的数据删除,尽量用软删除,使用一个字段标志是否删除,而不是真正的从数据库中删除 -好处:1 这样删除数据不会影响索引,不会导致索引失效 2 之前存的用户数据还在,以备以后使用 # 表模型如下 # 抽象出一个基表(不在数据库生成,abstract=True),只用来继承 class BaseModel(models.Model): is_delete = models.BooleanField(default=False) create_time = models.DateTimeField(auto_now_add=True) class Meta: # 基表必须设置abstract,基表就是给普通Model类继承使用的,设置了abstract就不会完成数据库迁移完成建表 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) # 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联 # ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表 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) # 有作者可以没有详情,删除作者,详情一定会被级联删除 # 外键字段为正向查询字段,related_name是反向查询字段 author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE) ``` ## 4 book表单增群增 ```python 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) # from rest_framework.serializers import ListSerializer ser.save() # ListSerializer的save return APIResponse(msg='增加%s条成功' % len(request.data)) ``` ## 5 book表单查群查 ```python 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) ``` ## 6 book表单改群改 ```python 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)) ``` ## 7 book表的单删群删 ```python 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('没有要删除的数据') return APIResponse(code=999, msg='没有要删除的数据') ``` ## 8 序列化类 ```python 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) # 列表套字典,是要修改的数据 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) ``` ### 9 路由 ```python path('books/', views.BookView.as_view()), re_path('books/(?P<pk>\d+)', views.BookView.as_view()), ```
# settings.py AUTH_USER_MODEL = 'app01.UserInfo' REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'app01.utils.common_exception_handler', }
# urls.py from django.contrib import admin from django.urls import path, re_path from rest_framework.routers import SimpleRouter from app01 import views router = SimpleRouter() router.register('login', views.LoginView, basename='login') urlpatterns = [ path('admin/', admin.site.urls), path('login1/', views.MyLoginView.as_view()), path('order/', views.OrderView.as_view()), path('book/', views.BookView.as_view()), re_path('book/(?P<pk>\d+)', views.BookView.as_view()), ] urlpatterns += router.urls
# models.py from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): phone = models.CharField(max_length=32, unique=True) # 自定义user表 class MyUser(models.Model): username = models.CharField(max_length=32, unique=True) password = models.CharField(max_length=32) phone = models.CharField(max_length=32) email = models.EmailField() # 抽象出一个基表,仅用来继承,不在数据库中生成表(abstract = True) class BaseModel(models.Model): is_delete = models.BooleanField(default=False) create_time = models.DateField(auto_now_add=True) class Meta: abstract = True # 基表必须设置abstract,基表就是给普通Model类继承使用的,设置了abstract就不会完成数据库迁移完成建表 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) # db_constraint:是否建立外键 # 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联 # ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表 authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False) # related_name:反向查询时的表名 @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) # 有作者可以没有详情,删除作者,详情一定会被级联删除 # 外键字段为正向查询字段,related_name是反向查询字段 author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)
# auth.py import jwt from rest_framework_jwt.utils import jwt_decode_handler from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication from rest_framework import exceptions # 自定义认证类 class JwtAuthentication(BaseJSONWebTokenAuthentication): def authenticate(self, request): token = request.META.get('HTTP_AUTHORIZATION') try: payload = jwt_decode_handler(token) 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('未知错误') user = payload return (user, token)
# utils.py from rest_framework.response import Response from rest_framework.views import exception_handler 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_handler(exc, context): response = exception_handler(exc, context) if response is None: response = Response({'code': 999, 'msg': str(exc)}) return response
# serializer.py from rest_framework import serializers from app01 import models import re from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler from rest_framework.exceptions import ValidationError class LoginSerializer(serializers.ModelSerializer): username = serializers.CharField() # 需要重写username字段,否则会当作新增请求 class Meta: model = models.UserInfo fields = ['username', 'password'] def validate(self, attrs): 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() if user and user.check_password(password): payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) self.context['token'] = token # ser.context为views与serializer进行数据通信的字典 self.context['username'] = username return attrs else: raise ValidationError('用户名或密码错误') class MyListSerializer(serializers.ListSerializer): # many=True时,生成的是ListSerializer对象,其中update方法需要重写,否则数据无法存入 def update(self, instance, 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 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} } list_serializer_class = MyListSerializer # 指定当many=True时,生成MyListSerializer的对象(默认为ListSerializer)
# views.py from django.shortcuts import render # Create your views here. from rest_framework.viewsets import ViewSet from rest_framework.views import APIView from app01.utils import APIResponse from app01 import serializer import re from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler from app01 import models from app01 import auth # 基于jwt的多方式登录 class LoginView(ViewSet): def create(self, request, *args, **kwargs): # 自动生成路由会将post请求对应为create方法,所以这里要写create ser = serializer.LoginSerializer(data=request.data) ser.is_valid(raise_exception=True) token = ser.context['token'] # ser.context为views与serializer进行数据通信的字典 username = ser.context['username'] return APIResponse(token=token, username=username) 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.check_password(password): # check_password()为内置auth中的方法,这里自己写的不能用check_password 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='用户名或密码错误1') class OrderView(APIView): authentication_classes = [auth.JwtAuthentication, ] def get(self, request, *args, **kwargs): return APIResponse(msg='查询订单') class BookView(APIView): def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: # 如果能通过pk取到值,代表访问路径中传入了pk,则代表单查 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 post(self, request, *args, **kwargs): if isinstance(request.data, dict): # 如果request.data是字典对象,那么就是单增 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.is_valid(raise_exception=True) ser.save() return APIResponse(msg='新增%s条数据成功' % len(request.data)) def delete(self, request, *args, **kwargs): pk = kwargs.get('pk') pks = [] if pk: pks.append(pk) else: pks = request.data res = models.Book.objects.filter(id__in=pks).update(is_delete=True) # 软删除 if res >= 1: # res为影响的条数 return APIResponse(msg='删除%s条成功' % res) else: raise Exception('没有要删除的数据') def put(self, request, *args, **kwargs): pk = kwargs.get('pk') pks = [] if pk: book = models.Book.objects.filter(id=pk).first() ser = serializer.BookSerializer(instance=book, data=request.data) ser.is_valid(raise_exception=True) ser.save() return APIResponse(msg='修改成功') else: for item in request.data: pks.append(item['id']) item.pop('id') book_list = models.Book.objects.filter(id__in=pks, is_delete=False).all() ser = serializer.BookSerializer(instance=book_list, data=request.data, many=True) ser.is_valid(raise_exception=True) ser.save() return APIResponse(msg='修改%s条数据成功' % len(book_list))

浙公网安备 33010602011771号