DRF

DRF

(1)API接口

  • 前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。

  • 为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少双方之间的合作成本。

  • 通过网络,规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介

  • Web API接口和一般的url链接还是有区别的,Web API接口简单概括有下面四大特点

  • url:长得像返回数据的url链接

  • 请求方式:get、post、put、patch、delete

    • 采用get方式请求上方接口
  • 请求参数:json或xml格式的key-value类型数据

    • ak:6E823f587c95f0148c19993539b99295
    • region:上海
    • query:肯德基
    • output:json
  • 响应结果:json或xml格式的数据

    • 上方请求参数的output参数值决定了响应数据的格式

    • 数据

      # xml格式
      https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295&region=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=xml
      #json格式
      https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295&region=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=json
      {
          "status":0,
        	"message":"ok",
          "results":[
              {
                  "name":"肯德基(罗餐厅)",
                  "location":{
                      "lat":31.415354,
                      "lng":121.357339
                  },
                  "address":"月罗路2380号",
                  "province":"上海市",
                  "city":"上海市",
                  "area":"宝山区",
                  "street_id":"339ed41ae1d6dc320a5cb37c",
                  "telephone":"(021)56761006",
                  "detail":1,
                  "uid":"339ed41ae1d6dc320a5cb37c"
              }
            	...
      		]
      }
      
  • API接口案例,继承View

class TaskView(View):
    def get(self, request):
        res = Task.objects.all()
        list = []
        for i in res:
            data = {
                'id': i.id,
                'task_id': i.task_id,
                'task_name': i.task_name,
                'task_time': i.task_time.strftime('%Y-%m-%d %H:%M:%S'),
                'task_desc': i.task_desc,
            }
            list.append(data)
        return JsonResponse({'code': 200, 'msg': '查询所有数据成功', 'data': list},
                            json_dumps_params={'ensure_ascii': False})

    def post(self, request):
        res = json.loads(request.body)
        task_id = res.get('task_id')
        task_name = res.get('task_name')
        task_time = res.get('task_time')
        task_desc = res.get('task_desc')
        data = {
            'task_id': task_id,
            'task_name': task_name,
            'task_time': task_time,
            'task_desc': task_desc,
        }
        Task.objects.create(task_id=task_id, task_name=task_name, task_time=task_time, task_desc=task_desc)
        return JsonResponse({'code': 200, 'msg': '添加数据成功', 'data': data},
                            json_dumps_params={'ensure_ascii': False})


class TaskDetailView(View):
    def get(self, request, pk):
        res = Task.objects.filter(id=pk).first()
        if not res:
            return JsonResponse({'code': 404, 'msg': '查询失败,数据不存在'}, json_dumps_params={'ensure_ascii': False})
        else:
            data = {
                'id': res.id,
                'task_id': res.task_id,
                'task_name': res.task_name,
                'task_time': res.task_time.strftime('%Y-%m-%d %H:%M:%S'),
                'task_desc': res.task_desc,
            }
            return JsonResponse({'code': 200, 'msg': '查询单条数据成功', 'data': data},
                                json_dumps_params={'ensure_ascii': False})

    def delete(self, request, pk):
        task_obj = Task.objects.filter(id=pk)
        if not task_obj:
            return JsonResponse({'code': 404, 'msg': '删除失败,数据不存在'}, json_dumps_params={'ensure_ascii': False})
        else:
            task_obj.delete()
            return JsonResponse({'code': 200, 'msg': '删除数据成功'},
                                json_dumps_params={'ensure_ascii': False})

    def put(self, request, pk):
        res = json.loads(request.body)
        task_id = res.get('task_id')
        task_name = res.get('task_name')
        task_time = res.get('task_time')
        task_desc = res.get('task_desc')
        task_obj = Task.objects.filter(id=pk)
        task_obj.update(task_id=task_id, task_name=task_name, task_time=task_time, task_desc=task_desc)
        return JsonResponse({'code': 200, 'msg': '修改数据成功'}, json_dumps_params={'ensure_ascii': False})

(2)body的编码格式

# 1 get 请求,可以在地址栏中带数据---》也能在body体中带数据
	-注意:可以提交到后端,放在request.body中取
    
# 2 urlencoded 编码格式---》请求体中--》name=heart&age=38

# 3 form-data格式:携带文件和数据
	-文件从:request.FILES.get('myfile')
    -数据:request.POST.get()
    -request.body 能不能用,取决于文件大小,一般不用打印body(会报错)
# 4 json格式
	-request.POST 是没有数据的
    -request.body中有---》需要自己转

(3)restful API规范

  • REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征性状态转移)。 它首次出现在2000年Roy Fielding的博士论文中。

  • RESTful是一种定义Web API接口的设计风格,尤其适用于前后端分离的应用模式中。

  • 这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。

  • 事实上,我们可以使用任何一个框架都可以实现符合restful规范的API接口。

(1)数据的安全保障

  • url链接一般都采用https协议进行传输

    注:采用https协议,可以提高数据交互过程中的安全性

(2)接口特征表现

(3)多数据版本共存

(4)数据即是资源,均使用名词(可复数)

(5)资源操作由请求方式决定(method)

(6)过滤,通过在url上传参的形式传递搜索条件

(7)响应状态码

[1]正常响应

  • 响应状态码2xx
    • 200:常规请求
    • 201:创建成功

[2]重定向响应

  • 响应状态码3xx
    • 301:永久重定向
    • 302:暂时重定向

[3]客户端异常

  • 响应状态码4xx
    • 403:请求无权限
    • 404:请求路径不存在
    • 405:请求方法不存在

[4]服务器异常

  • 响应状态码5xx
    • 500:服务器异常

(8)错误处理,应返回错误信息,error当做key

{
    error: "无权限操作"
}

(9)返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范

GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档

(10)需要url请求的资源需要访问资源的请求链接

# Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
{
  	"status": 0,
  	"msg": "ok",
  	"results":[
        {
            "name":"肯德基(罗餐厅)",
            "img": "https://image.baidu.com/kfc/001.png"
        }
      	...
		]
}
  • 比较好的接口返回
# 响应数据要有状态码、状态信息以及数据本身
{
  	"status": 0,
  	"msg": "ok",
  	"results":[
        {
            "name":"肯德基(罗餐厅)",
            "location":{
                "lat":31.415354,
                "lng":121.357339
            },
            "address":"月罗路2380号",
            "province":"上海市",
            "city":"上海市",
            "area":"宝山区",
            "street_id":"339ed41ae1d6dc320a5cb37c",
            "telephone":"(021)56761006",
            "detail":1,
            "uid":"339ed41ae1d6dc320a5cb37c"
        }
      	...
		]
}

(4)APIView

  • 只要继承了APIView,就不用处理csrf
  • 多一个request.data
  • request.data:
    • request.data 是 DRF 中的一个特殊属性,它是一个类字典对象,用于表示解析后的请求数据。
    • DRF 会根据请求的 Content-Type 自动解析请求体中的数据,并将其转换为适当的 Python 数据类型(如字典、列表等),然后存储在 request.data 中。
    • request.data 中的数据已经被 DRF 处理和验证过,可以直接在视图中使用。
    • 通常在编写 DRF 视图时,我们更倾向于使用 request.data 来访问请求数据,因为它提供了更高级的抽象和处理。
  • request.body:
    • request.body 是 Django HttpRequest 对象的属性,用于获取原始的请求体数据,即未经过任何解析的原始字节字符串。
    • 它是一个字节字符串,包含了 HTTP 请求中的原始数据,不论是 JSON、XML、Form 表单数据还是其他格式。
    • request.body 中的数据是未经处理的原始数据,需要手动进行解析和处理。在处理非标准的请求格式时,可能会使用 request.body

总结

request.data 是 DRF 提供的对请求数据进行解析和处理后的高级抽象request.body 则是原始的请求体数据,需要手动解析。

(1)基于APIView编写接口

  • 基于APIView编写接口,继承APIView类
from rest_framework.views import APIView


class TaskView(APIView):
    def format_data(self, request):
        dic = {}
        for k, v in request.data.items():
            dic[k] = v
        return dic

    def get(self, request):
        res = Task.objects.all()
        list = []
        for i in res:
            data = {
                'id': i.id,
                'task_id': i.task_id,
                'task_name': i.task_name,
                'task_time': i.task_time.strftime('%Y-%m-%d %H:%M:%S'),
                'task_desc': i.task_desc,
            }
            list.append(data)
        return JsonResponse({'code': 200, 'msg': '查询所有数据成功', 'data': list},
                            json_dumps_params={'ensure_ascii': False})

    def post(self, request):
        data = self.format_data(request)
        Task.objects.create(**data)
        return JsonResponse({'code': 200, 'msg': '添加数据成功'}, json_dumps_params={'ensure_ascii': False})


class TaskDetailView(APIView):
    def format_data(self, request):
        dic = {}
        for k, v in request.data.items():
            dic[k] = v
        return dic

    def get(self, request, pk):
        res = Task.objects.filter(id=pk).first()
        if not res:
            return JsonResponse({'code': 404, 'msg': '查询失败,数据不存在'}, json_dumps_params={'ensure_ascii': False})
        else:
            data = {
                'id': res.id,
                'task_id': res.task_id,
                'task_name': res.task_name,
                'task_time': res.task_time.strftime('%Y-%m-%d %H:%M:%S'),
                'task_desc': res.task_desc,
            }
            return JsonResponse({'code': 200, 'msg': '查询单条数据成功', 'data': data},
                                json_dumps_params={'ensure_ascii': False})

    def delete(self, request, pk):
        task_obj = Task.objects.filter(id=pk)
        if not task_obj:
            return JsonResponse({'code': 404, 'msg': '删除失败,数据不存在'}, json_dumps_params={'ensure_ascii': False})
        else:
            task_obj.delete()
            return JsonResponse({'code': 200, 'msg': '删除数据成功'},
                                json_dumps_params={'ensure_ascii': False})

    def put(self, request, pk):
        dic = self.format_data(request)
        Task.objects.filter(id=pk).update(**dic)
        return JsonResponse({'code': 200, 'msg': '修改数据成功'}, json_dumps_params={'ensure_ascii': False})

(2)APIView的执行流程

# 1 APIView继承了 Django的View---》 class APIView(View)

# 2 请求来了,路由匹配成功后---》执行流程
	2.1 路由配置 path('books/', BookView.as_view()),
    2.2 BookView.as_view()(request)-->BookView中没有as_view--》找父类APIView的as_view
    	BookView-->APIView-->View
        
    2.3 APIView的as_view
        @classmethod # 绑定给类的方法,类来调用
        def as_view(cls, **initkwargs):
            # super代指父类--》父类是View--》之前读过--》self.dispatch()
            # 这个view 还是原来View的as_view的执行结果--》as_view中有个view内部函数
            view = super().as_view(**initkwargs)
            # 只要继承了APIView,不需要处理csrf
            '''
            @csrf_exempt
            def index(request):
            	pass
           	等同于  index=csrf_exempt(index)
           	以后调用index,其实调用的 是csrf_exempt(index)()
            '''
            return csrf_exempt(view)
     2.4 请求来了,真正执行的是:csrf_exempt(view)(request)-->去除了csrf的view(request)--》self.dispatch()
     2.5 请求来了,真正执行的是 self.dispatch(request)--->self 是 视图类的对象
         BookView的对象--》自己没有--》APIView中
     2.6 现在要看 APIView的dispatch
     def dispatch(self, request, *args, **kwargs):
        # 1 包装了新的request对象---》现在这个requets对象,已经不是原来django的request对象了
        request = self.initialize_request(request, *args, **kwargs)
        try:
            # 2 APIView的initial--》三件事:三大认证:认证,频率,权限
            self.initial(request, *args, **kwargs)
            # 3 就是执行跟请求方式同名的方法
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)
			# 4 如果在三大认证或视图类的方法中出了异常,会被统一捕获处理
        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

执行流程总结

  1. 只要继承了APIView,就没有csrf限制了
  2. 只要继承了APIView,request就是新的request了,有request.data
  3. 在执行跟请求方式同名的方法之前,执行了三大认证:认证,频率,权限
  4. 只要在三大认证或者视图类的方法中出了异常,都会被捕获,统一处理

(3)Request对象

  • APIView的request对象变成了新的

  • 老的是django.core.handlers.wsgi.WSGIRequest类的对象

  • 现在是rest_framework.request.Request类的对象

  • 新的request支持之前所有老request的操作,多了一个request.datarequest.query_params(等同于老的request.GET)

  • request._request 就是老的request

  • request.user 通过了认证类,当前登录用户

(5)序列化

  • api接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把数据转换格式,序列化可以分两个阶段:

  • 序列化: 把我们识别的数据转换成指定的格式提供给别人。

  • 例如:我们在django中获取到的数据默认是模型对象,但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人。

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

  • 例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中。

(6)序列化类

  • 借助drf提供的序列化类
    • 快速序列化
    • 反序列化之前的数据校验
    • 反序列化

(1)使用步骤

  • 写一个类,继承Serialier
from rest_framework import serializers
class TaskSerializer(serializers.Serializer):
  • 在类中写字段(instance/data/many),字段就是要序列化的字段
from rest_framework import serializers

class TaskSerializer(serializers.Serializer):
    task_id = serializers.CharField()
    task_name = serializers.CharField()
    task_time = serializers.DateTimeField()
    task_desc = serializers.CharField()
  • 在视图函数中,序列化类,实例化得到对象,传入该传的参数

  • 两个重要参数

    • instance 里面放要序列化的queryset对象,如果是多条数据,必须加many=True
    • data 里面放要校验的数据(request.data)
  • 使用序列化类对象的 serializer.data 方法完成序列化

def get(self, request):
    res = Task.objects.all()
    serializer = TaskSerializer(instance=res,many=True)
    return Response({'data': serializer.data})

(2)校验数据

  • serializer.is_valid()

  • 要校验前端传入的数据

  • 要加入字段参数,控制数据校验

# 最大长度为8,最小长度为3
task_id = serializers.CharField(max_length=8,min_length=3)
  • 校验信息serializer.errors
return Response({'code': 200, 'msg': serializer.errors})

image

  • 如果要显示中文,先汉化,在settings里配置
INSTALLED_APPS = [
    ...
    'rest_framework'
]

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_TZ = False

image

  • 局部钩子函数
  • validate_字段名(self,字段名)
from rest_framework.exceptions import ValidationError
def validate_task_name(self, task_name):
    if task_name.startswith('sb'):
        raise ValidationError('task_name不能以sb开头')
    else:
        return task_name
  • 全局钩子

  • validate(self,attrs)

  • attrs是前端传入,经过字段自己校验和局部钩子校验都通过的数据(字典)

def validate(self, attrs):
    if attrs.get('task_name') == attrs.get('task_desc'):
        raise ValidationError('task_name和task_desc不能相同')
   	else:
        return attrs

image

(3)反序列化数据保存

  • serializer.save()需要在序列化类中重写create才能存进去
  • 一定要返回
def post(self, request):
    serializer =TaskSerializer(data=request.data)
    if serializer.is_valid():
        serializer.save() # 重写create 触发序列化中的create
        return Response({'code': 200, 'msg': '保存成功'})
    else:
        return Response({'code': 200, 'msg': serializer.errors})
def create(self, validated_data):
    # validated_data : 前端传入,所有校验通过的数据(字典)
    task = Task.objects.create(**validated_data)
    return task

(4)反序列化数据修改

  • 必须传datainstance
  • serializer类会判断instance是否为None,如果是,就会调用创建,如果不是就会调用修改
def put(self, request, pk):
    obj = Task.objects.filter(id=pk).first()
    serializer = TaskSerializer(instance=obj, data=request.data)
    if not obj:
        return Response({'code': 200, 'msg': '修改数据失败'})
    if serializer.is_valid():
        serializer.save()  # 重写update 触发序列化中的update
        return Response({'code': 200, 'msg': '修改数据成功'})
    else:
        return Response({'code': 200, 'msg': serializer.errors})
def update(self, instance, validated_data):
    # instance : 要修改的对象
    # validated_data : 前端传入,数据
    # 使用反射将前端传入的数据赋值给instance对象
    for k, v in validated_data.items():
        setattr(instance, k, v)
    instance.save()  # 更改保存到数据库中
    return instance

(5)常用字段类型

  • 其他都和models中的一一对应
  • 多出了ListField()DictField()
字段 字段构造方式
BooleanField BooleanField()
NullBooleanField NullBooleanField()
CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+
URLField URLField(max_length=200, min_length=None, allow_blank=False)
UUIDField UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose'"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex'"5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
IntegerField IntegerField(max_value=None, min_value=None)
FloatField FloatField(max_value=None, min_value=None)
DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置
DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
DurationField DurationField()
ChoiceField ChoiceField(choices) choices与Django的用法相同
MultipleChoiceField MultipleChoiceField(choices)
FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)

(6)常用选项参数

  • 选项参数:
参数名称 作用
max_length 最大长度
min_lenght 最小长度
allow_blank 是否允许为空
trim_whitespace 是否截断空白字符
max_value 最小值
min_value 最大值
  • 通用参数:
参数名称 说明
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
validators 该字段使用的验证器,字段自己的校验
error_messages 包含错误编号与错误信息的字典
label 用于HTML展示API页面时,显示的字段名称
help_text 用于HTML展示API页面时,显示的字段帮助提示信息
  • validate案例
from rest_framework.exceptions import ValidationError
def validate_id(id):
    if id.startswith('gg'):
        raise ValidationError('不能以gg开头')
    else:
        return id

class TaskSerializer(serializers.Serializer):
    task_id = serializer.CharField(max_length=32,validators=[validate_id])
  • error_messages案例
class TaskSerializer(serializers.Serializer):
    task_id = serializer.CharField(max_length=32,error_messages={'max_length':'太长了'})

(7)多表关联序列化

(1)定制返回字段名之source

  • serializer.py
class BookSerializer(serializers.Serializer):
    price = serializers.IntegerField(source='book_price')
    publish = serializers.CharField(source="publish.pub_name")
    publish_id = serializers.CharField(source='publish.id')
    
# 注意:这里有个坑,前后不能一致,会报错,如下所示:
# book_price = serializers.IntegerField(source='book_price')
  • models.py
class Book(models.Model):
    book_price = models.IntegerField()
    
class Publish(models.Model):
    pub_name = models.CharField(max_length=32)

(2)定制返回字段

后期想实现如下格式返回的数据:

{name:书名,price:价格,publish:{name:出版社名,addr:地址},authors:[{},{}]}

DictField()ListField()

  • 方式一:在表模型中定义方法

serializer.py

class BookSerializer(serializers.Serializer):
    publish_detail = serializers.DictField()
    author_detail = serializers.ListField()

models.py

class Book(models.Model):
    publish = models.ForeignKey('Publish', on_delete=models.CASCADE)
    author = models.ManyToManyField('Authors')

    def publish_detail(self):
        return {"name": self.publish.pub_name, "pub_city": self.publish.pub_city}

    def author_detail(self):
        l = []
        for author in self.author.all():
            l.append({"name": author.author_name, "age": author.author_age})
        return l
    
	# 也可以直接写
    def publish_name(self)
    	return self.name

image

(3)定制返回格式之SerializerMethodField

serializers.SerializerMethodField()
  • 一定要配合一个方法,必须叫get_字段名

serializer.py

class BookSerializer(serializers.Serializer):
    publish_detail = serializers.SerializerMethodField()
    def get_publish_detail(self, obj):
        # obj 就是当前序列化到的book对象
        return {
            'pub_name': obj.publish.pub_name,
            'pub_city': obj.publish.pub_city,
            'id': obj.pk
        }

    author_detail = serializers.SerializerMethodField()
    def get_author_detail(self, obj):
        # obj 就是当前序列化到的book对象
        l = []
        for author in obj.author.all():
            l.append({"id": author.pk, 'author_name': author.author_name, 'author_age': author.author_age})
        return l

(4)定制返回格式之子序列化

serializer.py

如果是多条字段的话,要加many=True

class PublishSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    pub_name = serializers.CharField()
    pub_city = serializers.CharField()
    pub_email = serializers.EmailField()

class AuthorSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    author_name = serializers.CharField()
    author_age = serializers.IntegerField()

class BookSerializer(serializers.Serializer):
    publish_detail = PublishSerializer(source='publish')
    author_detail = AuthorSerializer(source='author', many=True)

models.py

class Publish(models.Model):
    pub_name = models.CharField(max_length=32)
    pub_city = models.CharField(max_length=32)
    pub_email = models.EmailField()
  • 如果要改返回前端的字段名,需要加上source
publish_detail = PublishSerializer(source='publish')

image

(8)多表关联反序列化保存

  • 反序列化字段可以随意命名,和表字段没关系,但是后续保存和修改要对应好

serializer.py

class BookSerializer(serializers.Serializer):
    publish = serializers.IntegerField(write_only=True)
    author = serializers.ListField(write_only=True)

    def create(self, validated_data):
        publish_id = validated_data.pop('publish')  # 1
        author = validated_data.pop('author')
        book = Book.objects.create(**validated_data, publish_id=publish_id)
        book.author.add(*author)
        return book
    
    def update(self, instance, validated_data):
        publish_id = validated_data.pop('publish')
        author = validated_data.pop('author')
        book_obj = Book.objects.filter(id=instance)
        book_obj.update(**validated_data, publish_id=publish_id)
        book_obj.first().author.set(author)
        return book_obj.first()

views.py

class BookDetailView(APIView):
    def post(self, request):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({'code': 200, 'msg': '数据保存成功'})
        else:
            return Response({'code': 200, 'msg': serializer.errors})

    def put(self, request, pk):
        serializer = BookSerializer(instance=pk,data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({'code': 200, 'msg': '修改数据成功', 'data': serializer.data})

(9)ModelSerializer使用

  • model 是要传入的表
  • fields 是要序列化的字段
    • fields='__all__' 是模型表中所有的字段
    • 如果要指定字段序列化 应该改成 fields=['','']
  • extra_kwargs里传入的是需要添加的限制条件
  • 如果序列化缺少字段,自己手动添加read_only
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'
        
		# 需要反序列化的字段
        extra_kwargs = {
            'book_name':{'max_length':8},
            'publish': {'write_only': True},
            'author': {'write_only': True},
        }

    # 序列化缺少的字段
    publish_detail = PublishSerializer(source='publish', read_only=True)
    author_detail = AuthorSerializer(source='author', many=True, read_only=True)

总结

  1. 不用重写create和update了,但是所有的字段名必须一一对应
  2. 局部钩子和全局钩子和之前一模一样,要写在序列化类中
  3. 要修改模型表中的字段也是可以的,在子序列化中直接修改即可,然后添加上参数source=''

(10)DRF之请求

  • 继承了APIView之后,请求可以是urlencoded,form-data,json格式的数据
    • request.data 都能取出请求体的数据(QueryDict)
  • 那么要只让取一种格式的数据,怎么做?解决方法如下

(1)方式一

  • from rest_framework.parsers import ...

  • parser_classes = []

  • 在视图类中控制

from rest_framework.parsers import JSONParser, FormParser, MultiPartParser


class BookView(APIView):
    parser_classes = [JSONParser]

    def post(self, request):
        print(request.data)
        return Response('ok')

(2)方式二

  • settings.py中控制
  • 全局生效,所有视图类都受影响
  • 如果全局配置了,还想局部再控制,就直接在视图类中定义parser_classes = [JSONParser,...],会优先以视图类为准
REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
    ],
}

image

(11)DRF之响应

  • 响应对象:DRF提供的Response,以后返回都用它。
  • 必须注册app
INSTALLED_APPS = [
    ...
    'rest_framework'
]

(1)Response参数分析

  • data 数据
    • return Response(data={})
  • status 状态码 也可以直接写数字
    • from rest_framework.status import HTTP_200_OK
  • headers 响应头数据

(2)响应格式

  • from rest_framework.renderers import ...

  • renderer_classes = []

  • DRF接口会根据客户端类型自动返回不同格式,浏览器返回HTML,其他返回JSON格式

  • 统一返回json格式,类似与解析

from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer


class BookView(APIView):
    renderer_classes = [JSONRenderer]
  • 在配置文件中配置,局部再使用也是再在里面写,优先视图类中的配置
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer',
    ]
}

(12)2个视图基类

(1)APIView

from rest_framework.views import APIView

(2)GenericAPIView

  • 继承了APIView

  • 需要定义两个属性

    • queryset = 模型表.objects.all() 查询所有的数据
    • serializer_class = 序列化类 定义序列化类
  • 三个方法,获取数据和实例化序列化对象

    • self.get_queryset() 获取所有
    • self.get_object() 获取单条
    • self.get_serializer() 实例化序列化类
    • self.get_serializer_class() 确定视图应该使用哪个序列化器类来序列化或反序列化数据
from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
    queryset = Book.objects.all()  # 查询所有的数据
    serializer_class = BookSerializer  # 序列化类
    
    def get(self, request):
        obj = self.get_queryset()  # 使用 get_queryset()方法获取所有数据,而不要使用self.queryset属性获取
        serializer = self.get_serializer(instance=obj, many=True)  # 使用序列化类 直接使用方法
        return Response({'code': 200, 'msg': '查询所有数据成功', 'data': serializer.data})

(13)5个视图扩展类

  • 超一形态

  • 必须继承GenericAPIView

from rest_framework.mixins import CreateModelMixin,RetrieveModelMixin,DestroyModelMixin,ListModelMixin,UpdateModelMixin
  • ListModelMixin : 查询所有

  • CreateModelMixin : 新增一条

  • RetrieveModelMixin : 查询一条

  • DestroyModelMixin : 删除一条

  • UpdateModelMixin : 更新一条

from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, ListModelMixin,UpdateModelMixin


class BookView(GenericAPIView, CreateModelMixin, ListModelMixin):
    queryset = Book.objects.all()  # 查询所有的数据
    serializer_class = BookSerializer  # 序列化类

    def get(self, request):
        return super().list(request)  # ListModelMixin的list

    def post(self, request):
        return super().create(request)  # CreateModelMixin的create


class BookDetailView(GenericAPIView, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin):
    queryset = Book.objects.all()  # 查询所有的数据
    serializer_class = BookSerializer  # 序列化类

    def get(self, request, pk):
        return super().retrieve(request)  # RetrieveModelMixin的retrieve

    def post(self, request, pk):
        return super().destroy(request)  # DestroyModelMixin的destroy

    def put(self, request, pk):
        return super().update(request)  # UpdateModelMixin的update

(1)9个视图子类

  • 超二形态

  • 等于是把基类和扩展类结合起来了,每个继承的都是视图扩展类和基类

  • 写了就不用再写get post方法了

  • 五大类

from rest_framework.generics import ListAPIView,RetrieveAPIView,UpdateAPIView,DestroyAPIView,CreateAPIView
  • ListAPIView : 查询所有

    • (继承mixins.ListModelMixin,GenericAPIView)
  • RetrieveAPIView : 查询一条

    • (继承mixins.RetrieveModelMixin,GenericAPIView)
  • UpdateAPIView : 修改一条

    • (继承mixins.UpdateModelMixin,GenericAPIView)
  • DestroyAPIView : 删除一条

    • (继承mixins.DestroyModelMixin,GenericAPIView)
  • CreateAPIView : 增加一条

    • (继承mixins.CreateModelMixin,GenericAPIView)
  • 四小类

from rest_framework.generics import ListCreateAPIView,RetrieveUpdateDestroyAPIView,RetrieveDestroyAPIView,RetrieveUpdateAPIView
  • ListCreateAPIView : 查询所有和创建一条

    • (继承mixins.ListModelMixin,mixins.CreateModelMixinGenericAPIView)
  • RetrieveUpdateDestroyAPIView : 查询一条、修改、删除

    • (继承mixins.RetrieveModelMixin,mixins.UpdateModelMixin,mixins.DestroyModelMixin,GenericAPIView)
  • RetrieveDestroyAPIView : 查询一条、删除

    • (继承mixins.RetrieveModelMixin,mixins.DestroyModelMixin,GenericAPIView)
  • RetrieveUpdateAPIView : 查询一条、修改

    • (继承mixins.RetrieveModelMixin,mixins.UpdateModelMixin,GenericAPIView)
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView, RetrieveDestroyAPIView,RetrieveUpdateAPIView

class BookView(ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

class BookDetailView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

(14)视图集

  • 后期如果要写五个接口,通过一个类实现

  • 一旦使用视图集,路由写法就变了

  • from rest_framework.viewsets import ModelViewSet

urls.py

urlpatterns = [
    path('book/', BookView.as_view({'get': 'list', 'post': 'create'})),
    path('book/<int:pk>', BookView.as_view({'get': 'retrieve', 'delete': 'destroy', 'put': 'update'})),
]

views.py

from rest_framework.viewsets import ModelViewSet

class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

(1)定制返回格式

  • 重写对应方法
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def list(self, request, *args, **kwargs):
        res = super().list(request, *args, **kwargs)
        print(res)  # <Response status_code=200, "text/html; charset=utf-8">
        print(
            res.data)  # [{'id': 1, 'name': '蝴蝶2', 'price': '222', 'publish': '浙江出版社'}, {'id': 2, 'name': 'talktalkendless', 'price': '333', 'publish': '福建出版社'}]
        return Response({'code': 200, 'msg': '查询多条成功', 'data': res.data})
    
"""
{
    "code": 200,
    "msg": "查询多条成功",
    "data": [
        {
            "id": 1,
            "name": "蝴蝶2",
            "price": "222",
            "publish": "浙江出版社"
        },
        {
            "id": 2,
            "name": "talktalkendless",
            "price": "333",
            "publish": "福建出版社"
        }
    ]
}
"""

(2)ModelViewSet源码

  1. 视图类:继承了APIView--> GenericAPIView
  2. 继承了五个扩展类
  3. 继承了GenericViewSet
  4. GenericViewSet : ViewSetMixin+GenericAPIView
  5. ViewSetMixin : 核心,只要继承它,路由写法就变了,必须加action做映射,视图类中,可以写任意名的方法,只要做好映射,就能执行

views.py

from rest_framework.viewsets import ViewSetMixin

class BookView(ViewSetMixin,GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def login(self,request):
        return Response('ok')

# "ok"

urls.py

urlpatterns = [
    path('book/', BookView.as_view({'get': 'login'})),
]

(3)ViewSetMixin源码

  • 通过重写as_view使得路由写法变了
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
	# 0 跟APIView的as_view差不多
    # 1 actions must not be empty,如果为空抛异常
	# 2 通过反射把请求方式同名的方法放到了视图类中--》对应咱们的映射
    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        # actions 咱们传入的字典--》映射关系
        # {'get': 'list', 'post': 'create'}
        for method, action in actions.items():
            # method=get    action=list
            # method=post   action=create
            # 视图类对象中反射:list 字符串--》返回了 list方法
            # handler就是list方法
            handler = getattr(self, action)
            # 把handler:list方法 通过反射--》放到了视图类的对象中、
            # method:get
            # 视图类的对象中有个get--》本质是list
            setattr(self, method, handler)
        return self.dispatch(request, *args, **kwargs) # APIView的dispatch
    return csrf_exempt(view)

(4)viewsets下常用的类

  • ViewSetMixin : 视图类继承它,路由写法变了,一定要放在视图类前面

  • ViewSet : APIView+ViewSetMixin

  • GenericViewSet : GenericAPIView+ViewSetMixin

  • ReadOnlyModelViewSet : 视图类继承它,只有两个接口

  • ModelViewSet : 视图类继承它,五个接口都有了

    • 继承五个视图扩展类加GenericViewSet

(5)总结

"""
from rest_framework.views import APIView
最初写接口的方法
"""

"""
from rest_framework.generics import GenericAPIView
继承了APIView 要写两个方法
queryset =
serializer_class =
还给了三个重要的查数据方法
get_queryset()
get_serializer()
get_object()
"""

"""
mixin 五个视图扩展类 要基于GenericAPIView一起使用 无继承,但是封装好了方法供我们使用 super().xxx(request) xxx:对应
from rest_framework.mixins import CreateModelMixin,UpdateModelMixin,RetrieveModelMixin,DestroyModelMixin,ListModelMixin
class BookView(GenericAPIView,ListModelMixin):
def get(self,request):
return super().list(request)
"""

"""
generic 九个视图子类 继承了各自的扩展方法,又继承了GenericAPIView,其实就是进一步封装了方法
from rest_framework.generics import CreateAPIView,ListAPIView,RetrieveAPIView,DestroyAPIView,UpdateAPIView
from rest_framework.generics import ListCreateAPIView,RetrieveDestroyAPIView,RetrieveUpdateDestroyAPIView,RetrieveUpdateAPIView
class BookView(ListAPIView):
queryset =
serializer_class =
"""

"""
导入的类都在viewsets中
视图集 最核心类是ViewSetMixin,是基类,一旦继承了这个类,就要重写url内容,必须传参数action
其实要使用ViewSetMixin这个类的话,就必须要搭配另外一个类一起使用
from rest_framework.viewsets import ViewSetMixin

ViewSet 是 APIView+ViewSetMixin组合而成 最初写接口方式+重写url
from rest_framework.viewsets import ViewSet

from rest_framework.viewsets import GenericViewSet
GenericViewSet 是 GenericView+ViewSetMixin组合而成 可以少写代码,比如:
class BookView(GenericViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get_all(self,request):
obj = self.get_queryset()
serializer = self.get_serializer(instance=obj,many=True)
return Response({'code':200,"msg":'查询所有数据成功','result':serializer.data})

ModelViewSet 继承了五个扩展类,又继承了GenericViewSet,集大成为一体,非常强大
因为他封装了mixin中的五个扩展方法,又能重写路由,不用我们自己写五个方法

from rest_framework.viewsets import ModelViewSet
urls.py:
router = SimpleRouter()
router.register('book', BookView, 'book')
urlpatterns = []
urlpatterns += router.urls

views.py:
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
"""

(15)路由

  • 继承ViewSetMixin,路由写法就变了

  • 还可以自动生成路由

views.py

class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

urls.py

from rest_framework.routers import SimpleRouter

router=SimpleRouter()
# prefix : 前缀
# viewset : 视图类
# basename : 别名
router.register('book',BookView,'book')

urlpatterns = []

urlpatterns += router.urls

**第二种写法 **可以添加前缀

from django.urls import path,include
from app01.views import User
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('users',User,'users')

urlpatterns = [
    path('api/v1/',include(router.urls))
]
# http://127.0.0.1:8000/app01/api/v1/users/...

(1)action装饰器

from rest_framework.decorators import action
from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet
from .serializer import LoginSerializer, PassWordSerializer, RegisterSerializer
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Users

class User(GenericViewSet):
    queryset = Users.objects.all()
    serializer_class = LoginSerializer
    lookup_field = 'phone'

    def get_serializer_class(self):
        if self.action == 'edit_password':
            return PassWordSerializer
        if self.action == 'register':
            return RegisterSerializer
        else:
            return self.serializer_class

    @action(methods=['POST'], detail=False)
    def login(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            return Response({'code': 200, 'msg': '登录成功!'})
        else:
            return Response({'code': 404, 'msg': serializer.errors})

    @action(methods=['POST'], detail=False)
    def register(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({'code': 200, 'msg': '注册成功!'})
        else:
            return Response({'code': 404, 'msg': serializer.errors})

    @action(methods=['POST'], detail=False)
    def edit_password(self, request):
        mobile = request.data.get('mobile')
        obj = Users.objects.filter(mobile=mobile).first()
        if not obj:
            return Response({'code': 404, 'msg': '手机号不存在!'})
        serializer = self.get_serializer(instance=obj, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({'code': 200, 'msg': '修改成功!'})
        else:
            return Response({'code': 404, 'msg': serializer.errors})

最主要的是self.action方法

这个方法会返回当前请求路由中映射的方法名

比如我现在请求http://127.0.0.1:8000/app01/users/register/这个地址,self.action=register

def get_serializer_class(self):
    if self.action == 'edit_password':
        return PassWordSerializer
    if self.action == 'register':
        return RegisterSerializer
    else:
        return self.serializer_class

装饰器有四个属性

methods : 定义了这个自定义动作所允许的 HTTP 请求方法列表。比如,methods=['POST', 'GET']表示这个自定义动作允许 POST 和 GET 请求。默认为['GET']

detail : 一个布尔值,指示这个自定义动作是针对单个对象还是整个集合。如果设置为True,则这个自定义动作将在单个对象上执行,如果设置为False,则将在整个集合上执行。默认为True,表示针对单个对象。

传参数的情况:

views.py

from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

class UserViewSet(viewsets.ViewSet):
    # ...其他代码...

    @action(methods=['POST'], detail=True)
    def deactivate(self, request, pk=None):
        user = self.get_object()
        user.is_active = False
        user.save()
        return Response({'status': 'User deactivated'})

urls.py

from django.urls import path
from .views import UserViewSet

urlpatterns = [
    path('users/<int:pk>/deactivate/', UserViewSet.as_view({'post': 'deactivate'})),
]

url_path : 用于定义自定义动作的 URL 路径。可以将其设置为自定义的路径,以覆盖默认的 URL 路径。

url_name : 用于定义自定义动作的 URL 名称。默认情况下,URL 名称与方法名称相同,但你可以通过这个参数自定义名称。

(16)三大认证

(1)认证类

其实就是所谓的登录认证,用户登录成功,就签发一个token,以后需要登录才能访问的接口,必须携带当时签发的token过来,后端验证通过,继续后续操作。

使用步骤

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
1 写个类,继承BaseAuthentication
2 重写 authenticate 方法
3 在 authenticate 中完成用户登录的校验
	-用户携带token --> 能查到,就是登录了
    -用户没带,查不到,就是没登录
4 如果校验通过,返回当前登录用户和token
5 如果没校验通过,抛异常AuthenticationFailed

authentication.py 认证类

# -*- coding: utf-8 -*-
# author : heart
# blog_url : https://www.cnblogs.com/ssrheart/
# time : 2024/4/16
import uuid
from rest_framework.authentication import BaseAuthentication
from .models import Users, UserToken
from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_TOKEN')
        res = UserToken.objects.filter(token=token).first()
        if res:
            user = res.user
            return user, token # 这个uesr存入request.user中
        else:
            raise AuthenticationFailed('请先登录!')

使用方式

# 1 局部使用
class BookView(GenericViewSet):
    # 写个认证类,在视图类上配置即可
    authentication_classes = [LoginAuth]
    
# 2 全局使用 settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'app01.authentication.LoginAuth'
    ],
}

# 3 局部禁用
class UserView(ViewSet):
    authentication_classes = []

(2)权限类

其实就是所谓的权限认证,用户注册完成之后,会有一个权限的字段,在权限类中可以定义只有某个权限的才可以访问数据。

from rest_framework.permissions import BasePermission
权限类的编写
	-1 写个类,继承BasePermission
    -2 重写 has_permission 方法
    -3 在方法内部进行权限校验
    	-有权限返回True
        -没权限返回False
    -4 定制返回提示:self.message

permissions.py

from rest_framework.permissions import BasePermission

class UserPermission(BasePermission):
    def has_permission(self, request, view):
        # 判断权限,如果有权限,返回True,如果没有,返回False
        # 认证完之后,会把返回的那个user存入request.user中,直接可以取出对象
        if request.user.user_type == 3:
            return True
        else:
            user_type = request.user.get_user_type_display()
            # 这地方可以自定制返回的信息
            self.message=f'您是:{user_type},您没有权限访问'
            return False

image

使用方式

# 1 局部使用
class BookView(GenericViewSet):
    # 写个权限类,在视图类上配置即可
    permission_classes = [UserPermission]
    
# 2 全局使用 settings.py
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'app01.permissions.UserPermission'
    ],
}

# 3 局部禁用
class BookView(GenericViewSet):
    permission_classes = []

(3)频率类

其实就是限制访问的频率

from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
频率类的编写
	-1 写个类,继承SimpleRateThrottle
    -2 重写 get_cache_key ,返回唯一标识,用来限制
    -3 在类中写类属性:rate = '3/m' 做限制

throttling.py

from rest_framework.throttling import BaseThrottle, SimpleRateThrottle

class CommonThrottle(SimpleRateThrottle):
    rate = '3/m'  # 一分钟三次

    def get_cache_key(self, request, view):
        # 返回什么,就会以什么做限制
        return request.META.get('REMOTE_ADDR')

使用方式

# 局部使用
class PublishView(GenericViewSet):
    throttle_classes = [CommonThrottle]
    
# 全局使用
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'app01.throttling.CommonThrottle'
    ],
}

# 局部禁用
class PublishView(GenericViewSet):
    throttle_classes = []

(17)排序、过滤、分页

(1)排序

from rest_framework.filters import OrderingFilter
filter_backends = [OrderingFilter] # 过滤
ordering_fields = ['price','name'] # 按哪个字段过滤 可以有多个字段
class BookView(GenericViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    filter_backends = [OrderingFilter]  # 过滤
    ordering_fields = ['book_price']  # 按哪个字段过滤

    def list(self, request):
        obj = self.filter_queryset(self.get_queryset())
        serializer = BookSerializer(instance=obj,many=True)
        return Response({'code': 200, 'msg': '查询所有数据成功', 'data': serializer.data})

(2)过滤

  • 三种过滤
    • drf内置的
    • 第三方 django-filter
    • 自定义 比较复杂的用它

过滤和排序不冲突

from rest_framework.filters import SearchFilter

filter_backends = [SearchFilter]  # 过滤
search_fields=['name','publish'] # 按字段模糊过滤 也可以多个字段
http://127.0.0.1:8000/books/bookview/?search=ssr
http://127.0.0.1:8000/books/bookview/?search=ssr&ordering=-book_price # 可以两个一起过滤,不冲突

image

第三方过滤 按名字精准匹配

安装第三方模块

pip install django-filter
from django_filters.rest_framework import DjangoFilterBackend
# 查询价格为222的图书
http://127.0.0.1:8000/books/bookview/?book_price=222
filter_backends = [DjangoFilterBackend]  # 过滤
filterset_fields=['book_price','book_name']

image

自定义过滤类

# 查询价格为222 或 名字中包含ssr的数据
from rest_framework.filters import BaseFilterBackend
使用的步骤
	-1 定义一个类 继承 BaseFilterBackend
    -2 在类中重写 filter_queryset
    -3 返回qs对象

要取字段的话,使用request.query_params.get()

from rest_framework.filters import BaseFilterBackend
from django.db.models import Q


class CommonFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 完成过滤,返回qs对象
        book_price = request.query_params.get('book_price', None)
        book_name = request.query_params.get('book_name', None)
        # queryset 其实就是 Book.objects.all()
        queryset =queryset.filter(Q(book_price=book_price) | Q(book_name__contains=book_name))
        return queryset
filter_backends = [CommonFilter]  # 过滤

image

(3)分页

PageNumberPagination

pagination.py

page_size : 每页显示多少条

page_query_param : 分页关键字

page_size_query_param : 每页显示条数

max_page_size : 最多显示

from rest_framework.pagination import LimitOffsetPagination,PageNumberPagination

class CommonPagination(PageNumberPagination):
    page_size = 2 # 每页显示多少条
    page_query_param = 'page' # http://127.0.0.1:8000/book/bookview/?page=2 分页
    page_size_query_param = 'size' # http://127.0.0.1:8000/book/bookview/?page=1&size=3 每页显示条数
    max_page_size = 10 # 不管size输入多少,最多显示10条
pagination_class = CommonPagination

image

LimitOffsetPagination

class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 2 # 每页显示两条
    # http://127.0.0.1:8008/app01/api/v1/books1/?limit=4  一页显示四条
    limit_query_param = 'limit' # 控制每页显示多少条
    # http://127.0.0.1:8008/app01/api/v1/books1/?offset=3  从第3条开始,取两条
    # http://127.0.0.1:8008/app01/api/v1/books1/?offset=3&limit=1  从第3条开始,取1条
    offset_query_param = 'offset' # 偏移量
    max_limit = 10  # 最大每页取10条
pagination_class = LimitOffsetPagination

CursorPagination

# 游标分页-->必须要排序--》只能取上一页和下一页,不能直接跳转到某一页 效率高, 大数据量 app端使用
class CommonCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 查询条件  http://127.0.0.1:8008/app01/api/v1/books1/?cursor=asfasf
    page_size = 2
    ordering = 'id'
pagination_class = CursorPagination

(18)全局异常处理

from rest_framework.exceptions import ValidationError,AuthenticationFailed,APIException
from rest_framework.views import exception_handler
# 1 回顾:APIView的dispatch的时候--》三大认证,视图类的方法中--》出了异常--》被异常捕获--》都会执行一个函数:
    -只要出了异常,都会执行response = self.handle_exception(exc)
    -handle_exception 源码分析
     def handle_exception(self, exc):
        # 拿到一个函数内存地址--》配置文件中配置了
        # self.settings.EXCEPTION_HANDLER
        # 拿到这个函数:rest_framework.views.exception_handler
        exception_handler = self.get_exception_handler()
        # 执行这个函数,传入俩参数
        response = exception_handler(exc, context)
        # drf的 Response的对象
        return response
   	-rest_framework.views.exception_handler 逻辑分析
    	-判断异常是不是 drf内 exceptions.APIException的异常
        -如果是--》组装了Response返回了
        -如果不是--》返回了None
        -总结:默认drf只处理了自己的异常,django的异常,它没处理,直接前端能看到

全局异常处理使用

使用方法
	1 写个函数
    2 在函数中处理异常,统一返回格式
    3 在配置文件中配置:只要出了异常,执行我们自己的
    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
    }

异常函数

# -*- coding: utf-8 -*-
# author : heart
# blog_url : https://www.cnblogs.com/ssrheart/
# time : 2024/4/18

from rest_framework.views import exception_handler
from rest_framework.response import Response

# 自定义异常类
class PasswordException(Exception):
    def __init__(self, msg):
        self.msg = msg

import time

# 记录错误日志:时间,请求方式,请求的地址,客户端ip,用户id
def common_exception_handler(exc, context):
    """
    :param exc:  错误的原因 division by zero
    :param context: 触发错误的各种参数
    {
      'view': <app01.views.testExc object at 0x000002058BF2A740>,
      'args': (), 'kwargs': {},
      'request': <rest_framework.request.Request: GET '/app01/testexc/'>
      }
    """
    request = context.get('request')
    create_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    method = request.method
    addr = request.get_full_path()
    ip = request.META.get('REMOTE_ADDR')
    id = request.user
    view = context.get('view')
    log = [create_time, method, addr, ip, id,view]
    with open('log.log', 'a', encoding='utf-8') as f:
        f.write('\n' + str(log))

    response = exception_handler(exc, context)
    if response:
        if isinstance(response.data, dict):
            error = response.data.get('detail', '系统错误,请联系heartt')
            return Response({"code": 991, 'msg': f"{error}"})
        elif isinstance(response.data, list):
            error = response.data[0]
        else:
            error = '系统错误,请联系heartt'
        return Response({"code": 992, 'msg': f"{error}"})
    else:
        if isinstance(exc, ZeroDivisionError):
            response = Response({"code": 993, 'msg': f"{str(exc)}"})
        elif isinstance(exc, PasswordException):
            response = Response({"code": 994, 'msg': f"密码错误"})
        elif isinstance(exc, Exception):
            response = Response({"code": 995, 'msg': f"{str(exc)}"})
        else:
            response = Response({"code": 999, 'msg': f"{str(exc)}"})
    return response

自定义异常类

# 自定义异常类
class PasswordException(Exception):
    def __init__(self, msg):
        self.msg = msg
        
        
# 视图函数
class testExc(APIView):

    def get(self, request):
        raise PasswordException('asdzxvqweqr')

(19)coreapi

自动生成接口文档

pip install coreapi
from rest_framework.documentation import include_docs_urls

urlpatterns = [
    path('docs/', include_docs_urls(title='DRFTEST'))
]

# 3 配置文件配置
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
    
# 4 正常写视图类
	-在方法中加的注释,会在接口文档中体现
    -比如在models表中写help_text
    -在序列化类中写的
    	-required

(20)jwt

JWT(json web token)是一种前后端登录认证的方案,区别于之前的cookie和session。

它有签发阶段,登录成功后签发。

它有认证阶段,需要登录后才能访问的接口,通过认证token之后,才能继续操作。

全程没有在数据库中进行存储,性能高,安全。

  • JWT开发重点
    • 签发token
    • 校验token

image

签发的token是典型的三段式,用.分割,每一段使用base64编码,字符串长度一定是4的倍数,如果不是,用=补齐,但是=只是占位符

eyJuYW1lIjogImhlYXJ0IiwgImFnZSI6IDE4fQ==.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  • 第一部分我们称它为头部(header),一般放公司信息,加密方式,jwt,一般都是固定的
  • 第二部分我们称其为载荷(payload),一般放登录用户信息,用户名,用户id,过期时间,签发时间,是否是超级用户...
  • 第三部分是签证(signature),是加密后的串二进制数据,是前两段数据拼接之后加密而成
    • 签发阶段:通过头和荷载 使用 某种加密方式[HS256,md5,sha1]得到
    • 校验阶段:拿到token,取出第一和第二部分,通过之前同样的加密方式得到新的第三段签名,用新签名和老签名比较,如果一样说明没被篡改过,就信任,直接去拿数据就可以了

base64

它不是加密方案,而是编码方案,没有加密

  1. 它可以在网络中传输,字符串编码成base64
  2. 图片使用base64编码,传给前端
  3. jwt中使用
import base64
import json

userinfo = {'name': 'heart', 'age': 18}
userinfo_str = json.dumps(userinfo)

# base64 编码
res = base64.b64encode(userinfo_str.encode(encoding='utf-8'))
print(res)  # b'eyJuYW1lIjogImhlYXJ0IiwgImFnZSI6IDE4fQ=='

# base64 解码
res1 = base64.b64decode(res)
res2 = json.loads(res1)
print(res1)  # b'{"name": "heart", "age": 18}'
print(res2)  # {'name': 'heart', 'age': 18}

# 图片
s = 'data:image/jpg;base64,iVBORw0KGgoAAAANSUh...'
ss = s.split(',')[-1]
sss = base64.b64decode(ss)
with open('12306.png','wb')as f:
    f.write(sss)

(1)simple-jwt使用

pip install djangorestframework-simplejwt

快速体验

登录签发:默认使用auth的user表,创建个用户就能登录了

认证:需要加两句话,而且必须是使用auth的user表使用

from rest_framework_simplejwt.views import token_obtain_pair

urlpatterns = [
    path('login/',token_obtain_pair)
]

给这个地址发送post请求登录http://127.0.0.1:8000/app01/login/,就能获取到JWT签发下来的token了

image

然后需要拿着这个token中的access对应的内容,带到请求头中访问需要登录的接口,就可以进行接下来的操作了。

接口中需要定义认证类和权限类,这是jwt中必须要携带的两个参数

from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated

authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]

views.py

from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
from .serializer import UserTokenSerializer

class UserTokenView(GenericViewSet, CreateModelMixin):
    queryset = UserToken.objects.all()
    serializer_class = UserTokenSerializer


    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

(2)simple-jwt配置文件

# JWT配置
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # Access Token的有效期
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),  # Refresh Token的有效期
    
    # 对于大部分情况,设置以上两项就可以了,以下为默认配置项目,可根据需要进行调整
    
    # 是否自动刷新Refresh Token
    'ROTATE_REFRESH_TOKENS': False,  
    # 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为False,则旧的刷新令牌仍然可以用于获取新的访问令牌。需要将'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
    'BLACKLIST_AFTER_ROTATION': False,  
    'ALGORITHM': 'HS256',  # 加密算法
    'SIGNING_KEY': settings.SECRET_KEY,  # 签名密匙,这里使用Django的SECRET_KEY
    # 如为True,则在每次使用访问令牌进行身份验证时,更新用户最后登录时间
    "UPDATE_LAST_LOGIN": False, 
    # 用于验证JWT签名的密钥返回的内容。可以是字符串形式的密钥,也可以是一个字典。
    "VERIFYING_KEY": "",
    "AUDIENCE": None,# JWT中的"Audience"声明,用于指定该JWT的预期接收者。
    "ISSUER": None, # JWT中的"Issuer"声明,用于指定该JWT的发行者。
    "JSON_ENCODER": None, # 用于序列化JWT负载的JSON编码器。默认为Django的JSON编码器。
    "JWK_URL": None, # 包含公钥的URL,用于验证JWT签名。
    "LEEWAY": 0, # 允许的时钟偏差量,以秒为单位。用于在验证JWT的过期时间和生效时间时考虑时钟偏差。
    # 用于指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer"
    "AUTH_HEADER_TYPES": ("Bearer",), 
    # 包含JWT的HTTP请求头的名称。默认为"HTTP_AUTHORIZATION"
    "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", 
     # 用户模型中用作用户ID的字段。默认为"id"。
    "USER_ID_FIELD": "id",
     # JWT负载中包含用户ID的声明。默认为"user_id"。
    "USER_ID_CLAIM": "user_id",
    
    # 用于指定用户身份验证规则的函数或方法。默认使用Django的默认身份验证方法进行身份验证。
    "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
    #  用于指定可以使用的令牌类。默认为"rest_framework_simplejwt.tokens.AccessToken"。
    "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
    # JWT负载中包含令牌类型的声明。默认为"token_type"。
    "TOKEN_TYPE_CLAIM": "token_type",
    # 用于指定可以使用的用户模型类。默认为"rest_framework_simplejwt.models.TokenUser"。
    "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
    # JWT负载中包含JWT ID的声明。默认为"jti"。
    "JTI_CLAIM": "jti",
    # 在使用滑动令牌时,JWT负载中包含刷新令牌过期时间的声明。默认为"refresh_exp"。
    "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
    # 滑动令牌的生命周期。默认为5分钟。
    "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
    # 滑动令牌可以用于刷新的时间段。默认为1天。
    "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
    # 用于生成access和refresh的序列化器。
    "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
    # 用于刷新访问令牌的序列化器。默认
    "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
    # 用于验证令牌的序列化器。
    "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
    # 用于列出或撤销已失效JWT的序列化器。
    "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
    # 用于生成滑动令牌的序列化器。
    "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
    # 用于刷新滑动令牌的序列化器。
    "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}

项目中配置

from datetime import timedelta
SIMPLE_JWT ={
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # Access Token的有效期
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),  # Refresh Token的有效期
    "AUTH_HEADER_TYPES": ("HEART",),  # 自定义请求头前面携带 (Bearer)
}

(3)定制jwt自带的login返回格式

流程

1 编写(重写)序列化类,给登录接口用 simple-jwt 写的登录接口默认使用: rest_framework_simplejwt.serializers.TokenObtainPairSerializer
2 自己写个序列化类,定制序列化的字段
	-重写全局钩子 用来校验用户名和密码
	-校验通过:把校验通过的user放到self.user中
	-定制返回格式返回
3 在settings中配置生效

自己写序列化类

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CommonToken(TokenObtainPairSerializer):

    # 重写全局钩子
    def validate(self, attrs):
        # 校验还用原来的--》校验用户名和密码
        # 校验通过:把校验通过的user放到self.user 中
        # 签发token
        dic = super().validate(attrs)
        data = {
            "code": 100,
            "msg": '登录成功!',
            "username": self.user.username,
            "refresh": dic.get('refresh'),
            "access": dic.get('access')
        }
        return data

路由配置

from rest_framework_simplejwt.views import token_obtain_pair
urlpatterns = [
    path('login/',token_obtain_pair)
]

settings.py

SIMPLE_JWT ={
    'TOKEN_OBTAIN_SERIALIZER':'app01.serializer.CommonToken'
}

image

(4)定制jwt自带的login返回的payload格式

流程

1 编写(重写)序列化类,给登录接口用 simple-jwt 写的登录接口默认使用: rest_framework_simplejwt.serializers.TokenObtainPairSerializer
2 自己写个序列化类,定制序列化的字段
	-在调用get_token后往里面添加值
	-重写全局钩子 用来校验用户名和密码
	-校验通过:把校验通过的user放到self.user中
	-定制返回格式返回
3 在settings中配置生效

自己重写序列化类

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class CommonTokenObtainSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        # super()--->代指父类对象--》
        # 对象调用类的绑定方法--->会自动把对象的类传入
        token = super().get_token(user)  # 这个地方在拿token
        token['name'] = user.username
        return token

    def validate(self, attrs):
        super().validate(attrs)
        token = self.get_token(self.user)
        data = {'code': 100,
                'msg': '登录成功',
                'username': self.user.username,
                'refresh': str(token),
                'access': str(token.access_token)
                }
        return data

路由配置

from rest_framework_simplejwt.views import token_obtain_pair
urlpatterns = [
    path('login/',token_obtain_pair)
]

settings.py

SIMPLE_JWT = {
    'TOKEN_OBTAIN_SERIALIZER': 'app01.serializer.CommonTokenObtainSerializer'
}

(5)多方式登录

# 1 使用auth的user表 只能传用户名 ,密码校验

# 2 项目中:手机号/用户名/邮箱 + 密码 也可以登录成功 但是simple-jwt就不行了

# 3 自己定制登陆接口--》使用auth的user表
	-签发自己签发
    -认证继续用 simple-jwt的认证即可
    
# 4 编写一个多方式登陆接口
	- 扩写auth的user表---》加入mobile字段
    - 编写登陆接口

这个地方可以使用 serializer.context 进行视图函数和序列化之间的数据传输

serializer

签发token使用 RefreshToken.for_user(对象)

from rest_framework_simplejwt.tokens import RefreshToken

from rest_framework import serializers
from .models import UserInfo
import re
from rest_framework.exceptions import ValidationError
from rest_framework_simplejwt.tokens import RefreshToken


class UserSerializer(serializers.Serializer):
    username = serializers.CharField()  # 可能是 用户名  手机号  邮箱
    password = serializers.CharField()

    def _get_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        if re.match(r'^1[3-9][0-9]{9}$', username):
            user = UserInfo.objects.filter(mobile=username).first()
        elif re.match('^.+@.+$', username):
            user = UserInfo.objects.filter(email=username).first()
        else:
            user = UserInfo.objects.filter(username=username).first()
        if user and user.check_password(password):
            return user
        else:
            raise ValidationError('用户名或密码错误')

    def validate(self, attrs):
        user = self._get_user(attrs)
        token = RefreshToken.for_user(user)
        self.context['refresh'] = str(token)
        self.context['access'] = str(token.access_token)
        return attrs

views

class UserView(GenericViewSet):
    serializer_class = UserSerializer

    @action(methods=['POST'], detail=False)
    def login(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            refresh = serializer.context.get('refresh')
            access = serializer.context.get('access')
            return Response({'code': 100, 'msg': '登录成功', 'refresh': refresh, 'access': access})
        else:
            return Response({'code': 101, 'msg': serializer.errors})

(6)自定义用户表,手动签发和认证

手动签发

序列化类 最重要的是签发的部分 RefreshToken.for_user(user)

from rest_framework import serializers
from .models import User
from rest_framework.exceptions import ValidationError
from rest_framework_simplejwt.tokens import RefreshToken

class UserSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def get_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        user = User.objects.filter(username=username, password=password).first()
        if user:
            return user
        else:
            raise ValidationError('用户名或密码错误')

    def validate(self, attrs):
        user = self.get_user(attrs)
        token = RefreshToken.for_user(user)
        self.context['refresh'] = str(token)
        self.context['access_token'] = str(token.access_token)
        return attrs

views.py

class UserRZView(GenericViewSet):
    serializer_class = UserSerializer

    @action(methods=['POST'], detail=False)
    def login(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            refresh = serializer.context.get('refresh')
            access = serializer.context.get('access_token')
            return Response({'code': 200, 'msg': '登陆成功', 'refresh': refresh, 'access': access})
        else:
            return Response({'code': 201, 'msg': serializer.errors})

认证 最重要的是验证部分,当然要继承JWTAuthentication这个类之后调用类方法: self.get_validated_token(token) 返回的是可以信任的payload,可以直接用字典取值

认证类

from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import User

class JWTAuth(JWTAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_AUTHORIZATION')
        if token:
            # 校验token validated_token 返回的就是可以信任的payload
            validated_token = self.get_validated_token(token)
            user_id = validated_token['user_id']
            user = User.objects.filter(pk=user_id).first()
            return user, token
        else:
            raise AuthenticationFailed('请先登录!')

使用

from .permission import JWTAuth
from rest_framework.mixins import ListModelMixin

class PublishView(GenericViewSet,ListModelMixin):
    authentication_classes = [JWTAuth]
posted @ 2024-04-22 17:24  ssrheart  阅读(3)  评论(0编辑  收藏  举报