首页 > 其他分享 >drf

drf

时间:2023-02-12 15:55:32浏览次数:43  
标签:self token user 序列化 data class drf

安装

pip3 install djangorestframework 

Restful规范

#规范:我们可以不按照(公司自己的规范,基本上都大差不差)
# REST全称是Representational State Transfer,表征性状态转移,2000年Roy Fielding的博士论文中

# RESTful是一种定义 API接口的设计风格,尤其适用于前后端分离的应用模式中
# 10条
	-1 保证数据安全,通常使用https进行交互(https比http安全,http+ssl=https)
    -2 接口中带,api关键字标,识接口api接口
    	https://api.baidu.com
		https://127.0.0.1/api/books
            
   -3 url链接中带接口版本号
		https://api.baidu.com/v1
		https://api.baidu.com/v2
            
   -4 数据即是资源,均使用名词(可复数)(地址中不带操作资源的动词)
		https://api.baidu.com/v1/books
   -5 资源操作由请求方式决定
		-获取数据  get
    	-新增数据  post
        -删除数据  delete
        -修改数据  put/patch
        
  -6 在url中带搜索,过滤,排序条件
		https://api.example.com/v1/zoos?limit=10
  -7 响应中带状态码
		-http响应,就有状态码
    		-2xx
        	-3xx
            -4xx
            -5xx
         -200,301,302,403,404,500
        -返回的json格式中也带状态码
        	-公司自己定制的
            {code:1000}
            
 -8 响应的json格式中带错误信息
		{code:1000,msg:成功}
    
-9 针对不同操作,服务器向用户返回的结果应该符合以下规范
    GET /books:返回资源对象的列表(数组)
    GET /books/1:返回单个资源对象
    POST /books:返回新生成的资源对象
    PUT /books/2:返回完整的资源对象 (完全修改)
    PATCH /books/2:返回完整的资源对象(局部修改)
    DELETE /books/1:返回一个空文档
-10 响应中带链接
	

序列化

# 数据转换格式
序列化: 把我们识别的数据转换成指定的格式提供给别人
反序列化:把别人提供的数据转换/还原成我们需要的格式。

常用字段

 # 重点的:
    '''
    CharField
    IntegerField
    DecimalField
    DateTimeField
    DateField
    
    ListField
    DictField
    '''

序列化类常用字段参数

## 通用的
read_only	表明该字段仅用于序列化输出,默认False(重点)
write_only	表明该字段仅用于反序列化输入,默认False(重点)
required	表明该字段在反序列化时必须输入,默认True(了解)
default	    反序列化时使用的默认值(了解)
allow_null	表明该字段是否允许传入None,默认False(了解)
validators	该字段使用的验证器(写函数的列表,使用这些函数校验该字段)(了解)
error_messages	包含错误编号与错误信息的字典


## 其他的
## CharField
max_length	最大长度
min_lenght	最小长度
allow_blank	是否允许为空
trim_whitespace	是否截断空白字符

# IntegerField
max_value	最小值
min_value	最大值

反序列化

(重写create和update)
# 重点:
	-如果想序列化和反序列化都用一个序列化类,可以使用如下俩字段控制
	-read_only	表明该字段仅用于序列化输出,默认False(重点)
	-write_only	表明该字段仅用于反序列化输入,默认False(重点)
    -如果写起来比较麻烦,可以使用两个序列化类,一个序列化,一个反序列化
# source的用法
	name1 = serializers.CharField(source='name')--->意思是name1映射成models中的name
    
    
# 如果继承Serializer类,要修改和保存,一定要重写update和create
	BookSerializer---》Serializer---》BaseSerializer--save的核心代码
         if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)
        else:
            self.instance = self.create(validated_data)
	
    
    
# 

SerializerMethodField的使用

class BookSerializer(serializers.Serializer):
    price = serializers.IntegerField()

    name = serializers.CharField()
    # publish = serializers.CharField()

    # 显示出版社的详情

    ## 方式一(在序列化类中写)
    # publish_name = serializers.SerializerMethodField(read_only=True)  # 只能用来做序列化
    # def get_publish_name(self,obj):
    #     # obj 就是当前要序列化的book对象
    #     # return obj.publish.name
    #     return {'name':obj.publish.name,'addr':obj.publish.addr}

    # 方式二(在模型类中写方法,方法返回什么,这个字段就是什么)(在表模型中写用的多)
    # publish_detail=serializers.CharField()
    # publish_detail=serializers.DictField()


    ## 方式三
    publish_detail = serializers.CharField(source='publish.addr')

序列化类之ModelSerializer

from .models import Book, Publish


# class PublishModelSerializer(serializers.ModelSerializer):
#     # 指定跟表的对应关系
#     class Meta:
#         model = Publish  # 跟哪个表有关系
#         # fields =['name','addr']
#         # fields ='__all__'  # 所有字段,只序列化字段,不序列化方法
#         fields = ['name', 'addr', 'name_detail']
#         # 给字段类,传递参数、属性
#  		read_only_fields = ['depth', ]        
#         extra_kwargs={
#             'addr':{'max_length':8,'min_length':3,'write_only':True},
#             # 'name':{'write_only':True}
#         }
#         # 不重要的
#         # exclude=['name']  # 它跟fields 只能由一个
#         # depth=1
#
#
#     #1 不用重写create和update
#
#     #2  在出版社名字后加 _vip
#     # 方式一
#     # name=serializers.SerializerMethodField()
#     # def get_name(self,obj):
#     #     return obj.name+'_vip'
#
#     # 方式二,在表模型中定义一个name_detail,在fields中写一些字段或方法
#
#     ## 序列化的时候使用name_detail,反序列化的时候使用name
#     # name_detail = serializers.CharField(read_only=True)  如果是个方法,这句可以不写
#     name = serializers.CharField(write_only=True)
#
#
#
#     #3  限制addr,最大长度8,最小长度3

序列化类全局,局部钩子

    # 局部钩子:给某个字段再增加校验(固定用法),定义一个方法,名字为validate_字段名
    def validate_addr(self, item):
        if item.startswith('sb'):
            # 抛异常,不能以sb开头
            raise ValidationError('不能以sb开头')
        else:
            return item

    # 全局钩子
    # 限制出版社的名字不能等于出版社地址
    def validate(self, item):
        name = attrs.get('name')
        addr = attrs.get('addr')
        if name == addr:
            raise ValidationError('name和addr不能一样')
        else:
            return attrs

序列化类源码分析

# 局部和全局钩子在哪执行
	-ser.is_valid--->BaseSerializer的is_valid---》self._validated_data = self.run_validation(self.initial_data)---》是哪个类的run_validation?不是Field类的,而是Serializer类的
    
    -Serializer类
    	-run_validation内部有一句
        	# 字段自己的规则,局部钩子,全局钩子的执行流程
            -value = self.to_internal_value(data) # 局部钩子
            for field in fields:
            	validate_method = getattr(self, 'validate_' + field.field_name, None)
                validated_value = field.run_validation(primitive_value) # 字段自己的规则
                if validate_method is not None:
                    validated_value = validate_method(validated_value) # 字段局部钩子的规则
            -value = self.validate(value)# 全局钩子

drf的请求

# Request类的对象
	-.data:包含了对POST、PUT、PATCH请求方式解析后的数据
    -.query_params:Django标准的request.GET
    
# 通过配置,设置后端接口支持的编码格式
	- 默认情况支持三种
    -全局配置(在项目的settings.py中)
    REST_FRAMEWORK = {
        'DEFAULT_PARSER_CLASSES': [
            'rest_framework.parsers.JSONParser',  # json
            # 'rest_framework.parsers.FormParser',  # urlencoded
            # 'rest_framework.parsers.MultiPartParser'  # form-data文件格式
        ]
    }
    -局部配置(在视图类中写类属性)
        class IndexView(APIView):
            parser_classes = [JSONParser,] # 只支持json格式
    
# 查找顺序是先找视图类局部----》项目配置文件中----》drf的配置文件
    
    
    
# drf有内置的配置文件,在drf源码的settings.py中有个DEFAULT字典‘

  # 默认解析的编码格式
  'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser', # json
        'rest_framework.parsers.FormParser', # urlencoded
        'rest_framework.parsers.MultiPartParser' # form-data文件格式
    ]

响应

# drf的Response类的对象(继承了HttpResponse)
# response对象的属性
	-data:要返回的数据,放到了http响应的响应体中
    -status:http响应的状态码
    	-drf把所有的状态码都定义成了常量
        -rest_framework.status下
    -headers  http响应头,是个字典
    	{'name':'lqz'}
    -content_type响应编码格式,了解
    
    
# 响应显示的格式的局部配置和全局配置
    -全局配置(在项目的settings.py中)
    REST_FRAMEWORK = {
      'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ]
    }
    -局部配置(在视图类中写类属性)
        class IndexView(APIView):
    		renderer_classes = [JSONOpenAPIRenderer,]

视图组件之两个视图基类

APIView

##第一层:继承APIView完全自己写
class BookView(APIView):
    def get(self, request):
        qs = Book.objects.all()
        ser = serializer.BookSerializer(instance=qs, many=True)
        return Response(data=ser.data, status=200)

    def post(self, request):
        ser = serializer.BookSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(data={'code': 100, 'msg': '创建成功'})
        else:
            return Response(data=ser.errors)

class BookDetailView(APIView):
    def get(self, request, pk):
        book = Book.objects.all().filter(pk=pk).first()
        ser = serializer.BookSerializer(instance=book)
        return Response(data=ser.data, status=200)

    def put(self, request, pk):
        book = Book.objects.all().filter(pk=pk).first()
        ser = serializer.BookSerializer(data=request.data, instance=book)
        if ser.is_valid():
            ser.save()
            return Response(data={'code': 100, 'msg': '修改成功'})
        else:
            return Response(data=ser.errors)

    def delete(self, request, pk):

        Book.objects.filter(pk=pk).delete()
        return Response(data={'code': 100, 'msg': '删除成功'})

GenericAPIView

###第二层:继承GenericAPIView
'''
有两个类属性
    queryset = Book.objects.all()  # 你要序列化的数据
    serializer_class = serializer.BookSerializer # 你要使用的序列化类
三个方法
    get_queryset() # 获取要序列化的数据
    get_serializer() # 获取要使用的序列化类
    get_object()    # 获取单条数据(一定要用pk,如果你想改,重写类属性lookup_field)

'''
from rest_framework.generics import GenericAPIView


class BookView(GenericAPIView):
    queryset = Book.objects.all()  # 你要序列化的数据
    serializer_class = serializer.BookSerializer  # 你要使用的序列化类

    def get(self, request):
        qs = self.get_queryset()  # GenericAPIView提供的等同于self.queryset
        ser = self.get_serializer(instance=qs, many=True)  # GenericAPIView提供的等同于self.serializer_class
        return Response(data=ser.data, status=200)

    def post(self, request):
        ser = self.get_serializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(data={'code': 100, 'msg': '创建成功'})
        else:
            return Response(data=ser.errors)


class BookDetailView(GenericAPIView):
    queryset = Book.objects.all()  # 你要序列化的数据
    serializer_class = serializer.BookSerializer  # 你要使用的序列化类

    def get(self, request, *args, **kwargs):
        book = self.get_object()  # 获取单条
        ser = self.get_serializer(instance=book)
        return Response(data=ser.data, status=200)

    def put(self, request, pk):
        book = self.get_object()
        ser = self.get_serializer(data=request.data, instance=book)
        if ser.is_valid():
            ser.save()
            return Response(data={'code': 100, 'msg': '修改成功'})
        else:
            return Response(data=ser.errors)

    def delete(self, request, pk):

        self.get_object().delete()

        return Response(data={'code': 100, 'msg': '删除成功'})

视图组件之5个视图扩展类

http://127.0.0.1:8000/media/image/avatar.jpg

### 第三层:继承GenericAPIView+5个视图扩展类(不是视图类,没有继承APIView及其子类)
from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin

from rest_framework.generics import GenericViewSet

# GenericAPIView 创建一条数据 post
# ListModelMixin 获取全部数据 get
# UpdateModelMixin 更新一条数据
# DestroyModelMixin 删除一条数据
# RetrieveModelMixin 获取单独一条数据

class 							BookView(GenericViewSet,ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):
    queryset = Book.objects.all()  # 你要序列化的数据
    serializer_class = serializer.BookSerializer  # 你要使用的序列化类
	lookup_field="id"
    
# http://127.0.0.1:8000/Book/2/  DELETE请求 删除id为2的书籍
# http://127.0.0.1:8000/Book/1   GET请求 获取id为1的书
# http://127.0.0.1:8000/Book/    POST 新增一条数据
# http://127.0.0.1:8000/Book/    GET请求 获取所有的书
# http://127.0.0.1:8000/Book/3/  PUT请求 更新id为3的书
class BookView(GenericViewSet,ListModelMixin,CreateModelMixin):
    queryset = Book.objects.all()  # 你要序列化的数据
    serializer_class = serializer.BookSerializer  # 你要使用的序列化类

    def get(self, request):
        # qs = self.get_queryset()  # GenericAPIView提供的等同于self.queryset
        # ser = self.get_serializer(instance=qs, many=True)  # GenericAPIView提供的等同于self.serializer_class
        # return Response(data=ser.data, status=200)
        return self.list(request)

    def post(self, request):
        return self.create(request)


class BookDetailView(GenericViewSet,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):
    queryset = Book.objects.all()  # 你要序列化的数据
    serializer_class = serializer.BookSerializer  # 你要使用的序列化类

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

视图组件之9个视图子类

### 第4层:9个视图子类
from rest_framework.generics import ListAPIView, CreateAPIView, ListCreateAPIView
from rest_framework.generics import RetrieveAPIView, UpdateAPIView, DestroyAPIView, RetrieveUpdateAPIView, \
    RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView


# ListAPIView:GenericAPIView+ListModelMixin

class BookView(ListCreateAPIView):
    queryset = Book.objects.all()  # 你要序列化的数据
    serializer_class = serializer.BookSerializer  # 你要使用的序列化类


class BookDetailView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()  # 你要序列化的数据
    serializer_class = serializer.BookSerializer  # 你要使用的序列化类

视图组件之视图集

### 第5层:继承视图集(路由的写法特殊)
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = serializer.BookSerializer

drf之路由

基本使用

#### 自动生成路由

# 第一步:导入
from rest_framework.routers import DefaultRouter, SimpleRouter

# 第二步:实例化得到对象
router = SimpleRouter()
# router = DefaultRouter()  # 它自动生成的路由多一条,多个根路径
# 第三步:注册路由
# 第一个参数是路径,第二个参数是视图类(必须继承ViewSet及子类),第三个参数是别名(反向解析使用)
# router.register('books', views.BookView, basename='books')
# router.register('publish', views.PublishView, basename='publish')

router.register('test', views.TestView, basename='testview')

print(router.urls)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),

    ## 第四步:把自动生成的路由添加到总路由中(方式二:路由分发方式)
    path('', include(router.urls)),

]
## 第四步:把自动生成的路由添加到总路由中(方式一:列表相加)
# urlpatterns += router.urls

actions

# 视图函数
class TestView(GenericViewSet):
    '''action参数
    methods:做一个映射,就是http的请求方式,就会映射当前方法
    detail:True或False
        -test/login/
        -^test/pk/login
    url_path:访问的路径,可以不写,如果不写,以方法名作为路径 通过get请求访问这个路径test/login就能触发login的执行
    url_name:别名
    '''
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get_serializer_class(self):
        # 当前视图类的对象中就有action属性,我们可以通过这个属性判断当前请求会执行哪个视图类中的方法
        if self.action=='login':
            return PublishSerializer
        else:
            return self.serializer_class

    # @action(methods=['GET'],detail=False,url_path='login',url_name='login')
    @action(methods=['GET'],detail=True)
    def login(self, request,pk):
        print(self.action)
        print(pk)
        # self.get_serializer()
        return Response('登录成功')
    
    
## 路由
router.register('test', views.TestView, basename='testview')

drf之认证功能

判断用户是否登录
某个接口登录后才能访问,需要使用认证功能

登录功能

#如果全局使用了认证类,登录接口需要局部禁用掉
class LoginView(ViewSet):
    authentication_classes = []  #不使用任何认证类
    @action(methods=['POST'], detail=False)
    def login(self, request):
        name = request.data.get('name')
        password = request.data.get('password')
        user=User.objects.filter(name=name,password=password).first()
        if user:
            # 生成一个随机字符串
            # 通过伪随机数生成一个永不重复的字符串
            token=str(uuid.uuid4())
            # 把随机字符串存入表,UserToken表,如果存在,就更新token,如果不存在,就新增
            # UserToken.objects.create(token=token,user=user)
            UserToken.objects.update_or_create(defaults={'token':token},user=user)
            return Response({'code':100,'msg':'登录成功','token':token})
        else:
            return Response({'code':101,'msg':'用户名或密码错误'})

认证类

###自定义认证类,要继承BaseAuthentication,重写authenticate,如果认证通过,返回两个值,如果认证失败,抛认证失败的异常
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 校验用户是否登录:看它有没有带token来,以及token是不是合法的(是不是我给的)
        token = request.GET.get('token')
        # 校验token是否合法(根据token去库中查询是否存在,如果存在,就是登陆了,放行)
        user_token = UserToken.objects.filter(token=token).first()
        if user_token:
            # 带的token是有效的
            # user_token.user当前登录用户
            return user_token.user, token
        else:
            raise AuthenticationFailed('token不合法或没有迭代token')

认证类的使用

# 局部使用(在视图类中加入)

#认证类的局部配置
from app01.auth import LoginAuth
class PublishView(ModelViewSet):
    #局部配置
    authentication_classes = [LoginAuth,]
    

#全局使用
### 认证类的全局配置
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.LoginAuth",]
}

JWT

介绍

# https://www.cnblogs.com/liuqingzheng/articles/8990027.html

# Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密


# JWT的构成
	-三段式:
    	-header:一般放公司信息,加密方式 (用处不大)
        -payload:用户信息: user_id,user_name,email,expire
        -signature:第一部分和第二部加密得到的
    -通过base64转码
  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

快速使用

# 使用第三方模块:django-rest-framework-jwt(作者好多年不改了)
# 新的,用起来,跟它差不多(名字忘了)
# 安装:pip install djangorestframework-jwt

# base64的使用(转码方式)


# 快速使用,签发token给前端
	-用户表使用auth的user表
    -登录功能不用写,djangorestframework-jwt帮你写好了

内置认证类使用

签发

# 在路由中配置
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('login/', obtain_jwt_token),  # 登录接口好了,可以签发token,依赖了auth的user表
]

认证

from rest_framework_jwt.authentication import JSONWebTokenAuthentication  # drf_jwt内置的认证类
from rest_framework.permissions import IsAuthenticated 
class BookView(APIView):
    # 使用drf_jwt内置的认证,必须加这两个
    authentication_classes = [JSONWebTokenAuthentication, ]
    permission_classes = [IsAuthenticated]

自定义token签发认证

from rest_framework.response import Response
from .models import UserInfo
from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class LoginView(APIView):
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = UserInfo.objects.filter(username=username, password=password).first()
        if user:  # 用户名和密码正确,登录成功,签发token
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return Response({'code': '100', 'msg': '登录成功', 'token': token, 'usrename': user.username})
        else:
            return Response({'code': '101', 'msg': '用户名或密码错误'})
        
        
        
        
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
import jwt
from .models import UserInfo
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class JwtAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 取出前端传入的token串
        token=request.META.get('HTTP_TOKEN')
        # 通过token获得payload
        try:
            payload = jwt_decode_handler(token) # 通过token串获得payload,验签(是否被篡改),检查过期时间
        # except Exception:
        #     raise exceptions.AuthenticationFailed('token认证失败')
        except jwt.ExpiredSignature:
            msg = '签名过期'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg ='解码错误'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()
        # 通过payload获得当前用户(自己的表,自己拿)
        # user=UserInfo.objects.filter(pk=payload['user_id']).first()
        
        
        # 稍微优化一下,不是每次都取查询当前登录用户
        # user={'id':payload['user_id'],'username':payload['username']}
        user=UserInfo(id=payload['user_id'],username=payload['username'])
        # 返回当前用户
        return user,token        

simplekjwt

settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
}

INSTALLED_APPS = [
    ...
    'rest_framework_simplejwt'
    ...
]

views

from rest_framework.permissions import IsAuthenticated
permission_classes = [IsAuthenticated]

获取token

 from rest_framework_simplejwt.tokens import RefreshToken
    def _get_tokens_for_user(self, user):   # user 是一个用户对象
        refresh = RefreshToken.for_user(user)
        return str(refresh.access_token)

验证

请求头
key                         value
Authorization             Bearer  token值

案例

# settings
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
}

INSTALLED_APPS = [
    ...
    'rest_framework_simplejwt'
    ...
]

# views.py
class UserViews(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request):
        res = UserSerializer(data=request.data)
        if res.is_valid():
            token = res.context.get('token')
            user = res.context.get('user')
            msg = {
                "token": token,
                "username": user.username
            }
            return Response({"code": "登录成功", "msg": msg})
        return Response(res.errors)
    
   # Serializer.py
  
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework_simplejwt.tokens import RefreshToken
from . import models


class UserSerializer(serializers.ModelSerializer):
    username = serializers.CharField(max_length=32)

    class Meta:
        model = models.UserInfo
        fields = ["username", "password"]

    def validate(self, attrs):
        user = self._get_user(attrs)
        self.context['user'] = user
        self.context['token'] = self._get_tokens_for_user(user)
        return attrs

    def _get_user(self, attrs):
        username = attrs.get("username")
        password = attrs.get("password")
        user = models.UserInfo.objects.filter(username=username).first()
        if user and user.check_password(password):
            return user
        else:
            raise ValidationError({'detail': '用户名或密码错误'})

    def _get_tokens_for_user(self, user):
        refresh = RefreshToken.for_user(user)

        return str(refresh.access_token)

 # 其它视图
from rest_framework.permissions import IsAuthenticated
 permission_classes = [IsAuthenticated] 

配置

# ACCESS_TOKEN_LIFETIME 用处较大 其他用不到
from datetime import timedelta
  
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # 访问令牌的有效时间 
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),    # 刷新令牌的有效时间
  
    'ROTATE_REFRESH_TOKENS': False,     # 若为True,则刷新后新的refresh_token有更新的有效时间
    'BLACKLIST_AFTER_ROTATION': True,   # 若为True,刷新后的token将添加到黑名单中, 
                                        # When True,'rest_framework_simplejwt.token_blacklist',should add to INSTALLED_APPS
  
    'ALGORITHM': 'HS256',       # 对称算法:HS256 HS384 HS512  非对称算法:RSA
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,      # if signing_key, verifying_key will be ignore.
    'AUDIENCE': None,
    'ISSUER': None,
  
    'AUTH_HEADER_TYPES': ('Bearer',),           # Authorization: Bearer <token>
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',   # if HTTP_X_ACCESS_TOKEN, X_ACCESS_TOKEN: Bearer <token>
    'USER_ID_FIELD': 'id',                      # 使用唯一不变的数据库字段,将包含在生成的令牌中以标识用户
    'USER_ID_CLAIM': 'user_id',
  
    # 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),   # default: access
    # 'TOKEN_TYPE_CLAIM': 'token_type',         # 用于存储令牌唯一标识符的声明名称 value:'access','sliding','refresh'
    #
    # 'JTI_CLAIM': 'jti',
    #
    # 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',     # 滑动令牌是既包含到期声明又包含刷新到期声明的令牌
    # 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),       # 只要滑动令牌的到期声明中的时间戳未通过,就可以用来证明身份验证
    # 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),  # path('token|refresh', TokenObtainSlidingView.as_view())
}

drf之权限

# 继承BasePermission,重写has_permission,如果有权限,就返回True,没有权限就返回False
from rest_framework.permissions import BasePermission


class PermissionUser(BasePermission):
    def has_permission(self, request, view):
        # 如果有权限,返回True
        if request.user.user_type == 1:
            return True  # 超级用户允许访问
        else:
            return False

局部和全局使用

#认证类的局部配置
from app01.auth import LoginAuth
class PublishView(ModelViewSet):
     # 权限类:publish的5个接口,必须超级用户才能访问
    permission_classes = [PermissionUser]
    

#全局使用
REST_FRAMEWORK={
    "DEFAULT_PERMISSION_CLASSES":["app01.auth.PermissionUser",]
}

频率限制

# 限制用户的访问次数:根据用户ip地址限制
# 访问者IP地址
	-request.META.get('REMOTE_ADDR')
    
 

#####频率类的使用
# 1 写一个类,继承SimpleRateThrottle,重写get_cache_key,返回ip就以ip限制
# 2 在类中写一个类属性:scope = 'ip_m_3'
# 3 在settings.py中写
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        # key值是频率类中scop字段对应的值,value是访问次数限制
        'ip_m_3': '3/m',
    }
}



### 局部使用和全局使用
	-配置在视图类中
    	class IndexView(APIView):
            throttle_classes = [MyThrotting, ]
    -配置在settings.py中
    	REST_FRAMEWORK = {
    	'DEFAULT_THROTTLE_CLASSES': ['app01.auth.MyThrotting',],
		}
class MyThrotting(SimpleRateThrottle):
    scope = 'ip_m_3'

    def get_cache_key(self, request, view):
        return self.get_ident(request)  #返回访问者的ip:BaseThrottle的访问

过滤排序

# 请求地址中带过滤条件
# 在视图函数中配置 filter_backends  内置,第三方,自己写
# 继承哪个视图类,才能使用filter_backends,必须继承GenericAPIView及其子类

内置 过滤使用

from rest_framework.generics import ListAPIView
from .models import Book
from .serializer import BookSerializer
from rest_framework.viewsets import ViewSetMixin, GenericViewSet
from rest_framework.mixins import ListModelMixin

##### 过滤功能的使用#####
from rest_framework.filters import SearchFilter,OrderingFilter
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    # 配置过滤类
    filter_backends = [SearchFilter,] 可以有多个过滤类
    #配置要过滤的字段
    search_fields=['name',]  # http://127.0.0.1:8000/books/?search=红 只要是name中带红都会查出来
    search_fields=['name','price'] # http://127.0.0.1:8000/books/?search=红 只要是name中或者price中带红都会查出来
    # 重写
    class SelfFilterBackend(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        return queryset.filter(mtb_user_id=request.user.user_id)

内置排序使用

from rest_framework.filters import SearchFilter, OrderingFilter


class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    # 配置排序类
    filter_backends = [OrderingFilter, ]
    # 配置要排序的字段
    # ordering_fields = ['price'] # http://127.0.0.1:8000/books/?ordering=-price 按price降序排列
    ordering_fields = ['price','id'] #http://127.0.0.1:8000/books/?ordering=-price,-id 按price降序排列,如果price一样,再按id的降序排列

排序和过滤同时使用

# 既有排序,又有过滤
from rest_framework.filters import SearchFilter, OrderingFilter
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    # 配置排序类
    filter_backends = [SearchFilter,OrderingFilter, ]
    #配置要过滤的字段
    search_fields=['name',]
    # 配置要排序的字段
    ordering_fields = ['price']  #http://127.0.0.1:8000/books/?search=记&ordering=-price  查询名字中带记的并且按价格降序排列

第三方过滤类的使用

# http://127.0.0.1:8000/books/?name=红楼梦&price=12
# django-filter--基本使用(更高级,后面项目讲)
# pip install django-filter
-配置在类属性上:filter_backends = [DjangoFilterBackend]
    -配置字段:filterset_fields=['name','price']
    -http://127.0.0.1:8000/books/?name=红楼梦&price=12

分页

自定义分页类

############## 自定义分页类
##基本分页
from rest_framework.pagination import  PageNumberPagination,LimitOffsetPagination,CursorPagination
##基本分页
class MyPageNumberPagination(PageNumberPagination):
    # 重写4个类属性即可
    page_size = 2  # 每页显示两条
    page_query_param = 'page'  # 查询条件  http://127.0.0.1:8000/books/?page=2
    page_size_query_param = 'size'  # http://127.0.0.1:8000/books/?page=2&size=4 获取第二页数据,返回4条
    max_page_size = 5               # http://127.0.0.1:8000/books/?page=2&size=400 获取第二页数据,最多返回5条


class MyLimitOffsetPagination(LimitOffsetPagination):
    # 4个类属性
    default_limit = 2   #每页默认显示多少条
    limit_query_param = 'limit' # http://127.0.0.1:8000/books/?limit=3
    offset_query_param = 'offset'# http://127.0.0.1:8000/books/?limit=3&offset=1  # 从2位置取3条数据
    max_limit = 5   # 限制limit最大条数


class MyCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 查询条件,无用
    page_size = 2  # 每页显示多少条
    ordering = 'id' #按谁排序

自定义全局异常

#1  定义一个函数
def common_exception_handler(exc, context):
    # 加入日志的逻辑
    response = exception_handler(exc, context)
    if response:
        return Response(data={'code': 9998, 'msg': response.data})
    else:
        return Response(data={'code': 9999, 'msg': '服务器异常,请联系系统管理员'})
    
 # 2 在配置文件中配置
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler', 
}

自动生成接口文档

# 前后的分离
	-前端一批人
    	-根本不知道你写了什么接口,请求参数什么样,响应数据什么样
        -使用什么编码都不知道
    -后端一批人
    	-我们写了很多接口
        
        
# 需要写接口文档(不同公司有规范)
	-1 公司有接口文档平台,后端在平台上录入接口
    -2 使用第三方接口文档平台,后端写了在平台录入
    	-Yapi:开源
    -3 使用md,word文档写,写完传到git上
    -4 自动生成接口文档(swagger,coreapi)
    	-swagger自动导入,导入到Yapi中

        
# coreapi
	-pip3 install coreapi
    -在路由中配置
    from rest_framework.documentation import include_docs_urls
    path('docs/', include_docs_urls(title='路飞项目接口文档平台'))
    -在配置文件中配置
    	REST_FRAMEWORK = {
    	'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
    
    -访问地址http://127.0.0.1:8000/docs(只要路由中有的,都能看到)

simple-ui

pip3 install django-simpleui
# 使用
INSTALLED_APPS = [
    "simpleui",
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'home.apps.HomeConfig',
    'corsheaders',
    'user.apps.UserConfig'
]

跨域问题

使用django-cors-headers

# 1 下载模块
pip install django-cors-headers
# 2、添加到setting的app中
INSTALLED_APPS = (
	...
	'corsheaders',
	...
)
# 3、添加中间件
MIDDLEWARE = [  # Or MIDDLEWARE_CLASSES on Django < 1.10
	...
	'corsheaders.middleware.CorsMiddleware',
	'django.middleware.common.CommonMiddleware',
	...
]
# setting下面添加下面的配置

 # 允许的请求
CORS_ALLOW_CREDENTIALS = True

CORS_ORIGIN_ALLOW_ALL = True  
# 如果为True,则将不使用白名单,并且将接受所有来源。默认为False

CORS_ORIGIN_WHITELIST = (
	'*'
)
CORS_ALLOW_METHODS = (
	'DELETE',
	'GET',
	'OPTIONS',
	'PATCH',
	'POST',
	'PUT',
	'VIEW',
)

# 允许的请求头
CORS_ALLOW_HEADERS = (
	'XMLHttpRequest',
	'X_FILENAME',
	'accept-encoding',
	'authorization',
	'content-type',
	'dnt',
	'origin',
	'user-agent',
	'x-csrftoken',
	'x-requested-with',
	'Pragma',
    'token'
)

redis连接

pip install django-redis

# 在配置文件中写
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}
            # "PASSWORD": "123",
        }
    }
}


# 使用的位置
from django_redis import get_redis_connection
def test(request):
    conn = get_redis_connection()  #从连接池中拿一个连接
    res=conn.get('name')
    print(res)
    return HttpResponse('ok')


# 一旦使用了它,后续的djagno缓存,都缓存到redis中
cache.set('name','xxx')
# django的缓存很高级,可以缓存python中所有的数据类型,包括对象---》把对象通过pickle序列化成二进制,存到redis的字符串中
    

日志

# 日志配置
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {  # 日志的处理格式
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            # 日志位置,日志文件名,日志保存目录必须手动创建
            'filename': os.path.join(os.path.dirname(BASE_DIR), "logs/fuguang.log"),
            # 单个日志文件的最大值,这里我们设置300M
            'maxBytes': 300 * 1024 * 1024,
            # 备份日志文件的数量,设置最大日志数量为10
            'backupCount': 10,
            # 日志格式:详细格式
            'formatter': 'verbose'
        },
    },
    # 日志对象
    'loggers': {
        'django': {  # 固定,将来django内部也会有异常的处理,只会调用django下标的日志对象
            'handlers': ['console', 'file'],
            'propagate': True,  # 是否让日志信息继续冒泡给其他的日志处理系统
        },
    }
}

Response的封装

简化代码

# 在utils/response下
from rest_framework.response import Response
class APIResponse(Response):
    def __init__(self, status=None, headers=None, **kwargs):
        data = {'code': 100, 'msg': '成功'}
        if kwargs:
            data.update(kwargs)

        super(APIResponse, self).__init__(data=data, status=status, headers=headers)
      
# 以后再用	返回对象,使用自己定义的APIResponse对象
return APIResponse(username='lqz',token='ssss')	

回顾

1 drf入门规范
	-前端后开发模式
    -API接口
    -postman使用
    	-做接口测试
    -restful规范:10条
    -djangorestframework
	-APIVIew的执行流程
    -Request对象的源码

2 序列化
	-序列化和反序列化
    	-instance,many=True,ser.data
        -data,ser.is_valid--->ser.save(updata,create)
	-Serializer
    	-写一个个字段
        -字段类型
        -字段属性
        -重写update,create(跟表模型没有直接联系)
        -局部钩子,全局钩子
        
    -ModelSerializer
    	-重写字段
        -SerializerMethodField+配套一个函数get_字段名
    	-class Meta:
            表模型
            序列化和反序列化的字段
        -局部和全局钩子
        -重写update和create
3 请求与响应
	-Request类
    -解析的编码:局部和全局配置
    
    -Response类:data,status,header
    	response.data--->
    -响应的格式:局部和全局配置
4 视图
	-两个视图基类
    	-APIView
        	-认证类
            -权限类
        -GenericAPIView
        	-两个类属性
            -获取单个
            -获取所有
            -获取序列化的类
    -5个视图扩展类
    	-5个接口:list,retrieve,destory,update,create
    -9个视图子类
    	-写两个类属性
    -视图集
        -ViewSetMixin:重写了as_view,路由配置变了,自动生成路由
        -ViewSet:ViewSetMixin+APIView
        GenericViewSet:ViewSetMixin+GenericAPIView
        ModelViewSet:5个视图扩展类+ViewSetMixin+GenericAPIView
        ReadOnlyModelViewSet:
        
5 路由
	-自动生成路由:继承自ViewSetMixin的视图类
    -action装饰器
    	-在视图类对象中存在:self.action
6 认证,权限,频率
	-源码:为什么认证类,配置到视图类中就会执行
    -写一个类,继承基类,重写某个方法,全局配置局部配置
    
7 过滤,排序(查询所有)
	-继承了GenericAPIView+ListModelMixin
    -在视图类属性中配置 filter_backends=[内置,第三方,自己写的]

    -自定义过滤类,继承:BaseFilterBackend,重写filter_queryset
8 分页
	-三种分页方式
    -配置在继承了GenericAPIView+ListModelMixin的视图类的pagination_class类属性
    
    -继承APIView,需要自己写

9 全局异常处理
	-写一个函数
    -配置文件配置
    
    
10 自动生成接口文档
	-接口文档有规范
    -yapi:如何使用
    
11 jwt
12 simple-ui的使用
13 跨域问题

http://localhost:3000/api/goods/?pageSzie=3&page=0

http://localhost:3000/api/goods/?pageSize=3&page=0

标签:self,token,user,序列化,data,class,drf
From: https://www.cnblogs.com/yangyucai/p/17113938.html

相关文章