zoukankan      html  css  js  c++  java
  • drf9

    上节回顾

    1 jwt:重点(跟语言,框架无关)
    	-json web token
        -cookie:客户端浏览器上的键值对,数据不安全
        -session:服务端的键值对(内存,数据库,redis,文件),安全,对服务端压力大
        -token:三段:头.荷载.签名
        	-header(公司信息,加密方式。。。)
            -payload(荷载,真正有用的数据部分,用户id,用户名字。。)
            -signature(签名,把头和荷载部分通过加密算法加密---》得到一个签名)
    2 drf-jwt模块
    	-快速使用:(默认使用的是auth的user表)
        	-1 创建用户
            -2 在路由中配置path('login/', obtain_jwt_token),
        	-3 在postman中测试,用户名密码输入刚刚创建的用户就可以生成token
            -4 让一个视图必须登录以后才能访问
            	-authentication_classes = [JSONWebTokenAuthentication, ]
    			-permission_classes = [IsAuthenticated,]
        	-5 让一个视图可以登录后访问,也可以不登录访问
            	-authentication_classes = [JSONWebTokenAuthentication, ]
            -6 用postman测试,在请求头中加入
            	-Authorization  jwt adfasdf
                
        -自己写基于jwt的认证类(登录了能访问,不登录就不能访问)
        	class JwtAuthentication(BaseJSONWebTokenAuthentication):
                def authenticate(self, request):
                    #token=request.GET.get('token')
                    token=request.META.get('HTTP_Authorization'.upper())
                    try:
                        # 验证token是否正确
                        payload = jwt_decode_handler(token)
                    except jwt.ExpiredSignature:
                        raise AuthenticationFailed('过期了')
                    except jwt.DecodeError:
                        raise AuthenticationFailed('解码错误')
                    except jwt.InvalidTokenError:
                        raise AuthenticationFailed('不合法的token')
                    user=self.authenticate_credentials(payload)
                    return (user, token)
         -自定制认证类的使用方式:
        	-全局使用
            -局部使用
    
    3 base64编码(跟语言无关,跟框架无关)
    	-不同语言的base64可以相互编码解码
        -base64内置模块
        -图片的二进制,有的时候也会以base64的形式编码
        
    4 drf的视图(两个,5个,9个,视图集)+序列化器+自动生成路由
    

    今日内容

    1 基于jwt的多方式登陆

    1 手机号+密码   用户名+密码  邮箱+密码
    2 流程分析(post请求):
    	-路由:自动生成
        -视图类:ViewSet(ViewSetMixin, views.APIView)
        -序列化类:重写validate方法,在这里面对用户名和密码进行校验
    

    路由

    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()  # 由于提交的create认为往表里存,实际上是查,但是表中已经有了,所以需要重写该字段来跟数据库不建立关系
        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('用户名或密码错误')
    
    # 一般都是用框架提供的jwt认证,如果没有才会自己写(以下是例子)
    def get_token(id,salt='123'):
        import hashlib
        md=hashlib.md5()
        md.update(bytes(str(id),encoding='utf-8'))
        md.update(bytes(salt,encoding='utf-8'))
        return md.hexdigest()+'|'+str(id)
    
    def check_token(token,salt='123'):
        ll=token.split('|')
        import hashlib
        md=hashlib.md5()
        md.update(bytes(ll[-1],encoding='utf-8'))
        md.update(bytes(salt,encoding='utf-8'))
        if ll[0]==md.hexdigest():
            return True
        else:
            return False
    
    class TokenAuth():
        def authenticate(self, request):
            token = request.GET.get('token')
            success=check_token(token)
            if success:
                return
            else:
                raise AuthenticationFailed('认证失败')
        def authenticate_header(self,request):
            pass
    class Login(APIView):
        def post(self,reuquest):
            back_msg={'status':1001,'msg':None}
            try:
                name=reuquest.data.get('name')
                pwd=reuquest.data.get('pwd')
                user=models.User.objects.filter(username=name,password=pwd).first()
                if user:
                    token=get_token(user.pk)
                    # models.UserToken.objects.update_or_create(user=user,defaults={'token':token})
                    back_msg['status']='1000'
                    back_msg['msg']='登录成功'
                    back_msg['token']=token
                else:
                    back_msg['msg'] = '用户名或密码错误'
            except Exception as e:
                back_msg['msg']=str(e)
            return Response(back_msg)
    from rest_framework.authentication import BaseAuthentication
    class TokenAuth():
        def authenticate(self, request):
            token = request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if token_obj:
                return
            else:
                raise AuthenticationFailed('认证失败')
        def authenticate_header(self,request):
            pass
    
    class Course(APIView):
        authentication_classes = [TokenAuth, ]
    
        def get(self, request):
            return HttpResponse('get')
    
        def post(self, request):
            return HttpResponse('post')
    

    2 自定义user表,签发token,认证类

    表模型

    class MyUser(models.Model):   # 由于后面使用的payload源代码中使用的username,password,phone,emai的取值,所以如果我们在继续使用payload等的验证,那么表中该字段名就应该一致,否则就需要重写payload等的验证
        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='用户名或密码错误')
    
    # 认证类应该得到的user对象,应该是自己user表的user对象,而之前的类用的是auth的user表
    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('解码错误')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('不合法的token')
            # 得到的user对象,应该是自己user表的user对象,payload就是一个字典
            # user=MyUser.objects.get(id=payload['user_id']) 不这样写的原因是可以不查数据库,直接从payload中拿出数据,优化的代码,提高了效率,因此到达视图类中的时候,直接通过request.user拿出内部的user_id,后续查询其他相关的时候,直接使用user_id查询就可以了
            user=payload
            return (user, token)
    

    3 book,publish,author表关系及抽象表建立

    # 注意:以后所有的数据删除,尽量用软删除,使用一个字段标志是否删除,而不是真正的从数据库中删除
    	-好处: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,如果想设置关系表级联,只能手动定义关系表,DO_NOTHING删除了但是字段不会动
        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)
    
    class XXX:
        def yyy(self)
        	return self.__dict__
    class ppp(xxx):  # 如果这个类继承了xxx,因为xxx里面写了将对象转换成字典的方法,因此只要是ppp中的对象调用函数yyy就会将对象ppp转成字典例如对象ppp1.yyy这个ppp1的对象就被转成了字典
        pass
    

    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()  # 此处是BookSerializer对象的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对象
                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)
            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)
    
    # 序列化类
    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']  # 多写了的字段通过models表中的def来控制,最后让其显示自己的名字,而不是数字序号
            extra_kwargs = {
                'publish': {'write_only': True},
                'authors': {'write_only': True},
                'publish_name': {'read_only': True},
                'author_list': {'read_only': True},
            }
    
    # models表
    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()]  # 列表生成式
    

    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:
                from rest_framework.serializers import ListSerializer
                # ListSerializer的update方法没有写,需要我们自己写,如果不重写ListSerializer的update方法,这是存不进去的,然后在序列化的类中BookSerializer写list_serializer_class=ListBookSerializer指定many=True的时候,生成的ListBookSerializer的对象了
                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='修改%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))
    
    # 重写update方法
    class ListBookSerializer(serializers.ListSerializer):
        def update(self, instance, validated_data):
       instance # book_list:是一堆图书对象,validated_data:列表套字典,是要修改的数据
            return [self.child.update(book, validated_data[i]) for i, book in enumerate(instance)]  # 此处是列表生成式,里面是通过单次修改实现的
    

    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)
                # 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 序列化类

    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 路由

    path('books/', views.BookView.as_view()),
    re_path('books/(?P<pk>d+)', views.BookView.as_view()),
    
  • 相关阅读:
    Druid初步学习
    跨区域的application共享问题。
    jsp系统时间和时间对比(活动结束不结束)
    Intellij Idea中运行tomcat 报內存溢出
    SpringMVC -rest风格修改删除
    Redis服务器的创建服务
    Double和double的区别
    1.Redis安装(转)
    查看Mysql执行计划
    linux查询日志命令总结
  • 原文地址:https://www.cnblogs.com/feiguoguobokeyuan/p/14044119.html
Copyright © 2011-2022 走看看