drf笔记

Django rest framework

开发模式

  • 普通开发模式(前后端不分离,前后端一起写)
  • 前后端分离

后端开发

  • 为前端提供URL(API/接口开发)
  • 永远返回HttpResponse

Django视图

django中的视图分为两类

  • CBV :class base view (基于类的视图)
  • FBV:function base view (基于函数的视图)
    • url 可以直接根据视图函数FBV 进入 找到对应的方法 GET、POST等 但是CBV是如何实现的呢?
    • 答案是反射
# FBV, function base view  基于函数的视图
	def user(request):
		if request.method == 'GET'
            user_list = ['ales', '123']
        	return HttpResponse(json.dumps(user_list))
# CBV, class base view    基于类的视图 
from django.views import View
class StudentsView(View):
	def get(self, request):
        return HttpResponse('GET')
    def post(self, request):
        return HttpResponse('post')
    def put(self, request):
        return HttpResponse('put')
    def delete(self, request):
        return HttpResponse('delete')

Django视图之CBV源码剖析

CBV:基于反射实现根据请求方式不同,执行不同的方法

CBV实现基本流程图(反射)

在这里插入图片描述

# 原理 : url -> view方法 -> dispatch(反射执行其他:GET/POST/PUT/DELETE)方法

# CBV 中 url调用StudentView.as_view()方法  而源码中as_view()方法返回的是view()方法 view方法最终返回的是dispatch方法
# 因此 访问url后 实际上直接执行的是视图的dispatch()方法
# dispatch方法内部则是基于反射,来执行GET、POST等方法 这就是url能找到CBV的原理 本质上就是基于反射实现的

class MyBaseView(object):
    # 根据dispatch方法进行反射  分发到对应方法
    def dispatch(self, request, *args, **kwargs):
        print('bofore')
        ret = super(MyBaseView, self).dispatch(request, *args, **kwargs)
        # 调用父类dispatch方法 父类中的dispatch其实就是反射到get等方法
         '''
       	def dispatch(self, request, *args, **kwargs):
        	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
        	return handler(request, *args, **kwargs)
        '''
        print('after')
        return ret

# url -> 进入视图后 会调用dispatch方法 如果当前类中没有 回去父类中调用 调用从左到右 按照mro顺序执行 
# MyBaseView 在左 所以会先去MyBaseView中寻找dispatch方法,进行反射,
class StudentView(MyBaseView, View):

    def get(self, request):
        return HttpResponse('get')
    def post(self, request):
        return HttpResponse('post')
    def put(self, request):
        return HttpResponse('put')
    def delete(self, request):
        return HttpResponse('delete')

列表生成式

class Foo(object):
    pass
class Bar(object):
    pass

v = [item() for item in [Foo, Bar]] # 对象列表[Foo(), Bar()]
# 等价于
v = []
for i in [Foo, Bar]:
    obj = i()
    v.append(obj)
# v 是一个对象列表

面向对象

封装
  • 对同一类方法封装到类中

    class File(object):
        # 对文件的curd方法
        pass
    class DB(object):
        # 对数据库的curd方法
        pass
    
  • 将数据封装到对象中

class File(object):
    def __init__(self,a1,a2):
        self.a1 = a1
        self.xxx = a2
    pass

obj1 = File(123,123)
# obj具有的属性是  a1 = 123 和 xxx = 123 每个对象都有各自的属性
obj2 = File(123,245)

面试题

0.什么是restful规范
restful规范 这种编程一般叫做面向资源编程, 它把网络上的一切视为资源, 而对这个资源可以CRUD
设置路由的时候最后一个单词一般是资源名词,对资源进行curd的时候根据method的不同进行操作
restful规范也建议数据传输使用json格式

首先介绍Restful是什么?是对接口的一个规范,用于程序和程序之间数据交换的默认的一套规则

Restful规范建议
1.建议用https代替http  以前可能数据传输过程中是明文,https数据传输是加密的,安全性更好

2.在url中要体现api,建议加上api标识
	https://www.baidu.com/api/gen/article/5/  正确 体现api标识
	https://api.baidu.com/api/gen/article/5/  这种涉及到了跨域

3.在URL中要体现版本  版本不一定非得写到url中 也可以写在请求头中 不过还是建议写在url中
	https://127.0.0.1:8000/api/v1/userinfo/
	https://127.0.0.1:8000/api/v2/userinfo/

4.一般情况下对于api接口,用名词不用动词  面向资源编程
	https://127.0.0.1:8000/api/v1/userinfo/  # 建议
	https://127.0.0.1:8000/api/v1/getinfo/  # 不建议使用动词
	

5.还是一种规范,不是必须遵循,只是一种建议,具体按照具体业务实现

6.如果有条件,在url后面传递
	https://127.0.0.1:8000/api/v1/userinfo/?page=1&category=2

7.根据method不同,做不同的操作,get/post/put/patch/delete
1.Restful10条规范(面试回答)?
第一步 :整体说Restful规范是什么?
Restful规范是对接口的一个规范,用于程序和程序之间数据交换的默认的一套规则

第二步:详细说Restful建议
	1. https代替http,保证数据传输更加安全
	2. 在url中一般要体现api标识,这样一看到url就知道他是一个api
		https://www.baidu.com/api/gen/article/5/  (建议使用这种,因为他不存在跨域问题)
		https://api.baidu.com/api/gen/article/5/  这种涉及到了跨域
	3. 在接口中要体现版本
		https://127.0.0.1:8000/api/v1/userinfo/ (建议使用这种,因为他不存在跨域问题)
		注意:版本还可以放在请求头中
        https://127.0.0.1:8000/api/userInfo/
        accept:...
    4. restful也称为面向资源编程,视网络上的一切都是资源,对资源可以进行操作,所以一般资源都用名词不用动词
    	https://127.0.0.1:8000/api/userInfo/
    5. 如果要加上一定的筛选条件,可以在url后面添加
        https://127.0.0.1:8000/api/userInfo/?page=1&category=2
    6. 根据method的不同做不同的操作
    7. 返回给用户状态码
   		- 200, 成功
   		- 300, 301永久,302临时
   		- 400, 403拒绝访问, 404 找不到页面
   		- 500, 服务端代码错误
   		
   		很多公司会带上自己定义的状态码
   			data_info = {
   				'code': 40003,
   				'data': random_string
   			}
   			
   	8. 返回值,针对不同的操作,服务器返回结果应遵循
   		get /userinfo/  返回资源对象的列表(数组)
   		get /userinfo/1/  返回单个资源对象
   		post /userinfo/	  返回新生成的资源对象
        put /userinfo/1/  返回完整的资源对象
        patch /userinfo/1/ 返回完整的资源对象
        delete /userinfo/1/ 返回一个空文档
        
   	9. 操作异常时候,要返回错误信息
   		{
   			"error": "用户认证失败"
   		}
   	10. 对于下一个请求,返回一些接口 Hypermedia API   基本不遵循
   		{
   			'id':1,
   			'name':'xiaoxia',
   			'age': 22,
   			'depart': "https://127.0.0.1:8000/api/depart/"
   		}
2.什么是drf
drf是基于django的一个框架,他可以帮助我们快速开发遵循Restful规范的接口,他本质上是django的一个app
他提供了一些方便的组件,
视图
	比如他给我们提供了一大堆的视图, APIView是最底部的,还有ListAPIView,CreateAPIView等等
	这些只能完成对表的最基本的增删改查,如果业务比较复杂,需要重写
解析器
	帮我重新封装了新的取值方法
	request.data 取出请求体中数据
	request.query_params url中的数据等
序列化
	帮助我们快速序列化以及数据校验
分页

生命周期
请求到来之后,先执行视图的dispatch方法
1.视图
2.版本处理
3.认证
4.权限
5.节流(频率限制)
6.解析器
7.序列化
8.筛选器
9.分页
10.渲染
3.django中间件方法
  • process_request
  • process_view
  • process_response
  • process_exception
  • process_render_template
4.使用中间件做过什么
  • 权限

  • 用户登录认证

  • django的csrf中间件是怎么实现的?

    process_view方法中
    1、检查视图是否被装饰器@csrf_exempt装饰(免除csrf认证)
    2、去请求体或者cookie中获取token
    

情况1: 全站都使用csrf认证 再不需要的视图函数使用装饰器@csrf_exempt免除csrf认证

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',  # 全站使用csrf认证
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt  # 免除当前视图函数csrf认证
def user(request):
    if request.method == 'GET':
        return HttpResponse('ok')

情况2:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    #'django.middleware.csrf.CsrfViewMiddleware',  # 全站都不使用csrf认证
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_protect  # 给当前视图函数添加csrf认证
def user1(request):
    if request.method == 'GET':
        return HttpResponse('ok')
5.CBV小知识

在使用method_decorator(csrf_exempt)方法给类视图中的某些方法如post/put 免除csrf认证需要装饰在dispatch方法之上

from django.shortcuts import render, HttpResponse
from django.views import View
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
# Create your views here.


class StudentView(View):
	
    # 装饰器需要加载dispatch方法上
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        ret = super(StudentView, self).dispatch(request, *args, **kwargs)
        return ret

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')
    def put(self, request):
        return HttpResponse('put')
    def delete(self, request):
        return HttpResponse('delete')
    
# 或者 装饰器加在类上
@method_decorator(csrf_exempt, name='dispatch')
class StudentView(View):
 
    def dispatch(self, request, *args, **kwargs):
        ret = super(StudentView, self).dispatch(request, *args, **kwargs)
        return ret

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')
    def put(self, request):
        return HttpResponse('put')
    def delete(self, request):
        return HttpResponse('delete')

6.谈谈面向对象
  • 封装

    • 1 自定义分页器

      2 最近看了看rest framework源码,提现封装概念的有一个request 框架对request进行了封装

      他被重新封装成了新的包含了原生request和认证对象列表的request对象

  • 继承

    • APIView

      自定义类继承了APIView,APIView继承View 就是将公共的部分提取到一个基类中,然后使用继承快速完成或者加工功能

      Form表单继承了BaseForm,帮助实现了is_valid方法

  • 多态

    • 说出鸭子模型即可

      何为鸭子模型,只要一个东西会鸭子嘎嘎叫,那么就可以认为他是鸭子

      来了个鹦鹉会嘎嘎叫,那么也可以将他叫为鸭子

    • 例子

    • class WX:
          def send():
              pass
      class Email:
          def send():
              pass
      class Msg:
          def send():
              pass
      
      def func(arg):
          arg.send()
      
      obj = Email()
      func(obj)
      
7.Django生命周期
  1. wsgi
    • wsgi,是一个协议
    • wsgiref,实现了wsgi协议的一个模块。模块的本质:socket服务端 测试用 性能低(Django)
    • werkzeug,是实现了wsgi协议的一个模块。模块的本质:socket服务端 (Flask框架)
    • uwsgi(最后部署使用)
  2. 中间件
    • 穿过所有的中间件,做路由匹配找到视图函数,视图函数通过ORM找到数据,通过模板渲染返回给用户
8.中间件&装饰器
  • 例子:用户登录,最开始版本是在视图函数上+装饰器
  • 如果每个视图都需要登录校验,就得给每个函数上+装饰器,麻烦,这时候使用中间件非常合适
  • 中间件适合对所有视图函数统一进行操作
  • 对单个使用装饰器来做
  • 应用场景
    • 基于角色的权限控制
    • 用户认证
    • csrf(说原理)
    • session(说原理)
    • 黑名单
    • 日志记录
9.rest framework 框架原理
  • 认证

    • 请求进来 封装request ->initial-> perform_authentication -> request.user - > authenticate

  • 权限

    • 请求进来,initial -> check_permissions -> has_permission

  • 节流

    • 请求进来,initial -> check_throttles-> allow_request

10.什么jwt,优势是什么(面试题)
一般在前后端分离时,用于做用户登录认证所使用的一项技术
jwt的实现原理:
	- 用户登录成功后,会给前端返回一个token
	- token是由.分割的3段组成
        - 第一段:类型和算法信息  base64加密
        - 第二段:用户信息(非敏感数据),超时时间  base64加密
        - 第三段:hs256(前两段拼接后)加密 + base64url加密
	- 以后前端再发来信息时,
		- 超时验证
		- token合法性检验

jwt优势:
	- token只在前端保存 后端只负责校验
	- 内部集成超时时间,后端可以根据超时时间进行校验是否超时。
	- 内部存在hash256加密,所有用户不能够修改token,只要修改,认证失败

总结

  • 本质,基于反射实现的根据不同请求方式进行不同函数调用
  • 流程:路由 -> view -> dispatch(反射)
  • 取消csrf认证(需要使用method_decorator并且要在dispatch方法上进行装饰)

扩展:

  • csrf

    • 基于中间件的process_view方法
    • 装饰器给单独的函数进行设置(@csrf_exempt/@csrf_protect)
  • 中间件

  • csrf原理

  • Restful 10条规范

  • 面向对象

  • django请求声明周期

  • django请求声明周期(包含rest framework框架)PS:dispatch

  • rest framework 认证流程(封装request.user)

  • rest framework 权限流程

  • rest framework 节流流程

代码:

  • 认证demo

  • 权限demo(用户类型不同,权限不同)

  • 节流demo(匿名用户,登录用户,对于不同用户访问限制不同)

    三组件组合使用

Django rest framework 源码剖析

# 下载 django rest framework
pip install django rest framework

drf简单实用例子

原生django序列化操作

# urls.py
from django.urls import path, re_path
from . import views
urlpatterns = [
    path('category/', views.CategoryView.as_view()),
    re_path(r'^category/(?P<pk>\d+)/$', views.CategoryView.as_view())
]

# views.py
from django.shortcuts import render
from rest_framework.response import Response
from rest_framework.views import APIView
from . import models
# model_to_dict 可以将obj转换为dict类型
from django.forms.models import model_to_dict
# Create your views here.

class CategoryView(APIView):


    def get(self, request, *args, **kwargs):
        # get传参从kwarg中取
        pk = kwargs.get('pk')
        if not pk:
            queryset = models.Category.objects.all()
            # <QuerySet [<Category: Category object (2)>, <Category: Category object (3)>]>
            print(queryset)
            data_li = list(queryset)
            print(data_li)
            # <QuerySet [{'id': 2, 'name': 'sbn'}, {'id': 3, 'name': '其他'}]>
            queryset = models.Category.objects.all().values('id', 'name')
            print(queryset)
            data_list = list(queryset)
            print(data_list)
            # Response 会自动帮我们转换为json类型
            # 但是只能转换 字典 列表 类型的
            return Response(data_list)
        else:
            cat_obj = models.Category.objects.filter(id=pk).first()
            print(type(cat_obj))
            # model_to_dict 对象转换为字典
            data = model_to_dict(cat_obj)
            print(type(data))
            return Response(data)

    def post(self, request, *args, **kwargs):
        models.Category.objects.create(**request.data)
        return Response('成功')

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        models.Category.objects.filter(id=pk).delete()
        return Response('删除成功')

    def put(self, request, *args, **kwargs):
        '''更新'''
        pk = kwargs.get('pk')
        models.Category.objects.filter(id=pk).update(**request.data)
        return Response('更新成功')

drf序列化进行curd

class CategroySerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Category
        # fields = "__all__"
        fields = ['id', 'name']
class NewCategoryView(APIView):

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if not pk:
            queryset = models.Category.objects.all()
            ser = CategroySerializer(instance=queryset, many=True)
            return Response(ser.data)
        else:
            cat_obj = models.Category.objects.filter(id=pk).first()
            ser = CategroySerializer(instance=cat_obj, many=False)
            return Response(ser.data)

    def post(self, request, *args, **kwargs):
        print(request.data)
        ser = CategroySerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        # 一定.first()拿到对象 否则拿到的是queryset
        cat_obj = models.Category.objects.filter(id=pk).first()
        ser = CategroySerializer(instance=cat_obj, data=request.data)
        if not ser.is_valid():
            return Response(ser.errors)
        ser.save()
        return Response(ser.data)

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        models.Category.objects.filter(id=pk).delete()
        return Response('删除成功')

所有组件

  • 认证(重要)
  • 权限(重要)
  • 节流(重要)
  • 版本
  • 解析器
  • 序列化器(重要)
    • 对请求数据进行校验
    • QuerySet进行序列化
  • 分页
  • 路由
  • 视图
  • 渲染器
  • django组件:contenttype

1.认证

  • 认证引入

    • 问题1:有些API需要用户登录之后才能访问,有些无需登录才能访问
    • 基本认证组件使用
      • 解决
        • 创建两张表(UserInfo,UserToken)
        • 用户登录(返回token并且保存到数据库)
    • 认证原理流程图 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yJRiyo1e-1646892881897)(assets/image-20220207113146314-16442067250752.png)]
    • 在看一遍源码
      1. 局部视图使用 & 全局视图使用
      2. 匿名 request.user = None
    • 内置认证类
      1. 认证类,必须继承 from rest_framework.authentication import BaseAuthentication
      2. 其他认证类:BasicAuthentication
  • 认证流程

    • 仅使用

       # ----------------------------- rest framework --------------------------------
      import json
      from rest_framework.views import APIView
      from rest_framework.authentication import BaseAuthentication
      from rest_framework.exceptions import AuthenticationFailed
      from rest_framework.request import Request
      
      # 首先自定义认证类  也可以直接使用rest framework提供的认证类 
      # 所有认证类中必须有 这两个方法  authenticate中提供认证方法   authenticate_header
      class Myauthentication(object):
          def authenticate(self, request):
              token = request._request.GET.get('token')
              # 实际应该去数据库中取出数据 然后校验
              if not token:
                  raise AuthenticationFailed('用户认证失败')
              return ('xiaoxia', None)
      
          def authenticate_header(self, val):
              pass
      
      
      class DogView(APIView):
          # 使用的用户认证类必须写入authentication_classes列表中 ,rest framework会自动执行认证类校验
          authentication_classes = [Myauthentication, ]
      
          def get(self, request, *args, **kwargs):
              print(request.user) # request.user  就是认证通过后的对象 xiaoxia
              ret = {
                  'code': 1000,
                  'msg': 'xxx'
              }
              return HttpResponse(json.dumps(ret), status=201)
      
          def post(self, request, *args, **kwargs):
              return HttpResponse('创建dog')
          def put(self, request, *args, **kwargs):
              return HttpResponse('更新dog')
          def delete(self, request, *args, **kwargs):
              return HttpResponse('删除dog')
      
      
    • 认证源码执行流程

      url - > view -> dispatch()方法进行反射

      dispatch方法 首先对request进行加工 封装成了 Request() 包含了 原生request 以及 认证类对象

      request._request 取出原生request

      request.authenticators 取出认证类对象

       # 1、dispatch方法
          def dispatch(self, request, *args, **kwargs):
              """
              `.dispatch()` is pretty much the same as Django's regular dispatch,
              but with extra hooks for startup, finalize, and exception handling.
              """
              self.args = args
              self.kwargs = kwargs
              # 对原生的request进行加工(丰富了一些功能)
              # Request(request,parsers=self.get_parsers(),authenticators=self.get_authenticators(), # [Foo(), Bar() 对象]negotiator=self.get_content_negotiator(),parser_context=parser_context)
              
              #  1. 封装Request
              # request(原生request, [BaseAuthentication对象,])
              request = self.initialize_request(request, *args, **kwargs)
              # 获取原生request, request._request
              # 获取认证类的对象, request.authenticators
              
              self.request = request
              self.headers = self.default_response_headers  # deprecate?
      
              try:
                  # 2. 认证 - ↓
                  self.initial(request, *args, **kwargs)  # 进入initial方法
      
                  # Get the appropriate handler method
                  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)
      
              except Exception as exc:
                  response = self.handle_exception(exc)
      
              self.response = self.finalize_response(request, response, *args, **kwargs)
              return self.response
      
      # 2、initial方法
          def initial(self, request, *args, **kwargs):
              """
              Runs anything that needs to occur prior to calling the method handler.
              """
              self.format_kwarg = self.get_format_suffix(**kwargs)
      
              # Perform content negotiation and store the accepted info on the request
              neg = self.perform_content_negotiation(request)
              request.accepted_renderer, request.accepted_media_type = neg
      
              # Determine the API version, if versioning is in use.
              version, scheme = self.determine_version(request, *args, **kwargs)
              request.version, request.versioning_scheme = version, scheme
      
              # Ensure that the incoming request is permitted
              # 3. 实现认证
              # 用户认证
              self.perform_authentication(request)  # 进入perform_authentication方法
              # 权限控制
              self.check_permissions(request)
              # 节流控制(访问频率)
              self.check_throttles(request)
              
      # 3、perform_authentication方法
          def perform_authentication(self, request):
              request.user  # 返回request.user方法   进入user方法中
      
      # 4、user
      @property  # 魔法方法
          def user(self):
              if not hasattr(self, '_user'):
                  with wrap_attributeerrors():
                      # 4.获取用户对象 进行一步步认证
                      self._authenticate()  # 进入_authenticate方法
              return self._user
      
      # 5、_authenticate方法
              def _authenticate(self):
              """
              Attempt to authenticate the request using each authentication instance
              in turn.
              """
              # [BaseAuthentication, ]  认证类对象列表
              for authenticator in self.authenticators:
                  try:
                      # 执行认证类的authenticate方法
                      # 1 如果这个方法抛出异常 self._not_authenticated() 执行 并且在往上级抛出
                      # 2 有返回值  必须是元组 (request.user, request.auth)
                      # 3 返回None 当前认证不处理 交给下一个认证处理
                      user_auth_tuple = authenticator.authenticate(self)
                  except exceptions.APIException:
                      self._not_authenticated()
                      raise
      
                  if user_auth_tuple is not None:
                      self._authenticator = authenticator
                      self.user, self.auth = user_auth_tuple
                      return
      
              self._not_authenticated()
      
1.1 认证demo

模型类

# models.py

from django.db import models

# Create your models here.
from django.db import models

# Create your models here.

# 用户表
class UserInfo(models.Model):

    user_type_choices = (
        (1, '普通用户'),
        (2, 'VIP'),
        (3, 'SVIP')
    )

    user_type = models.IntegerField(choices=user_type_choices)
    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=256)

# 用户token表
class UserToken(models.Model):

    user = models.OneToOneField(to='UserInfo', on_delete=models.CASCADE)
    token = models.CharField(max_length=64)

视图函数

# views.py

from django.shortcuts import render
from django.http import JsonResponse, HttpResponse
from rest_framework.views import APIView
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
from api import models
# Create your views here.

def md5(user):
    import hashlib
    import time
    ctime = str(time.time())
    m = hashlib.md5(bytes(user, encoding='utf-8'))
    m.update(bytes(ctime, encoding='utf-8'))
    return m.hexdigest()

# 模拟数据库中的数据
ORDER_DICT = {
    1: {
        'name':'媳妇',
        'age': 12,
        'sex': '男',
        'content':'宪法关系'
    },
    2: {
        'name': '老狗',
        'age': 15,
        'sex': '女',
        'content': '主奴'
    }
}

# 路由 path(r'api/v1/auth/', views.AuthView.as_view())
class AuthView(APIView):
    '''用于用户登录认证'''
    def post(self, request, *args, **kwargs):

        ret = {'code':1000, 'msg': None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用户名或密码错误'
            # 为用户创建token
            token = md5(user)
            # 存在就创建 不存在就更新
            models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '请求异常'
        return JsonResponse(ret)


class Authtication(object):
	'''自定义认证类'''
    def authenticate(self, request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise AuthenticationFailed('用户认证失败')

        # 在drf内部 会将这两个字段赋值给request,以供后续使用
        # 分别通过 request.user  request.auth 调用拿到两个模型类对象
        return (token_obj.user, token_obj)

    def authenticate_header(self, request):
        pass

# 路由 path(r'api/v1/order/', views.OrderView.as_view())
class OrderView(APIView):
    '''订单相关业务,同样需要认证用户才能访问'''
	
    # 调用认证类  局部使用 也可以全局使用  在setting文件中配置全局使用
    authentication_classes = [Authtication, ]

    def get(self, request, *args, **kwargs):
        # 可以在每个视图中添加token判断 但是drf为我们做好了认证方法
        # token = request.GET.get('token')
        # if not token:
        #     return HttpResponse('用户未登录')
        print(request.user.username)
        print(request.auth.token)
        ret = {'code': 1000, 'msg': None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)
1.2 认证梳理
  1. 使用

    • 创建认证类,继承BaseAuthentication, 所有认证类中必须有 这两个方法 authenticate中提供认证方法 authenticate_header

      from rest_framework.exceptions import AuthenticationFailed
      from rest_framework.authentication import BaseAuthentication
      from api import models
      
      # 推荐使用继承BaseAuthentication类
      class Authtication(BaseAuthentication):
          '''自定义认证类'''
          def authenticate(self, request):
              token = request._request.GET.get('token')
              token_obj = models.UserToken.objects.filter(token=token).first()
              if not token_obj:
                  raise AuthenticationFailed('用户认证失败')
      
              # 在drf内部 会将这两个字段赋值给request,以供后续使用
              return (token_obj.user, token_obj)
      
          def authenticate_header(self, request):
              pass
      
    • 返回值有三种

      • None 该认证类不管了 交给下一个认证类执行。
      • raise AuthenticationFailed(‘用户认证失败’) # from rest_framework.exceptions import AuthenticationFailed
      • 元组(元素1, 元素2)# 元素1 赋值给 request.user 元素2 赋值给 request.auth
    • 局部使用

      class DogView(APIView):
          # 使用的用户认证类必须写入authentication_classes列表中 ,rest framework会自动执行认证类校验
          # 局部使用
          authentication_classes = [Myauthentication, ]
      
          def get(self, request, *args, **kwargs):
              print(request.user) # request.user  就是认证通过后的对象 xiaoxia
              ret = { 
                  'code': 1000,
                  'msg': 'xxx'
              }
              return HttpResponse(json.dumps(ret), status=201)
      
    • 全局使用(注意差别:一个引入类,一个引入路径)

      # rest framework 全局配置
      # rest framework中会自动加载 setting中的REST_FRAMEWORK配置项 这是一个字典
      # 在这里面可以写上认证,权限,节流等所有配置项
      REST_FRAMEWORK = {
          # 这里的认证类写的是路径  全局使用认证类
          'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.auth.Authtication'],
          # 这里设置 未通过认证用户的 user名称 默认是 AnonymousUser
          # 'UNAUTHENTICATED_USER': lambda :'匿名用户'   推荐使用None
          'UNAUTHENTICATED_USER': None,  # 匿名 未登录 request.user = None
          'UNAUTHENTICATED_TOKEN': None, # 匿名 request.auth = None
      }
      
    • 源码流程

      -dispatch
      	-封装request
          	-获取定义的认证类(全局/局部),通过列表生成式创建对象。
          - initial
          	-perform_authentication
              	request.user(内部循环认证)
      
1.3认证总结
当用户发来请求时, 找到所有的认证类并实例化成为对象列表,然后将对象列表封装到新的request对象中

以后在视图中调用request.user

在内部会循环认证的对象列表,并执行每个对象的authenticate方法,该方法用于认证
它会返回两个值分别赋值给request.user/request.auth
1.4 jwt原理以及案例
用于前后端分离,实现用户登录相关

一般用户认证有2种方式

  • token

    用户登录成功后,生成一个随机字符串,自己保留一份,返回给前端一份
    以后前端再来请求,需要携带token字符串
    后端对字符串校验。
    
  • jwt

    用户登录成功之后,生成一个随机字符串返回给前端
    	- 生成随机字符串
            {"typ":"jwt", "hs":"..."}	   {id:1, username:'xx', 'exp':10}	
            saoaohsduo123iho1asdhouasohd21.asohgoeji911h29821hoasfh0asdasssd.08qweu0qhfvwhasduha123sfaash
            - 类型信息通过base64加密
            - 数据通过base64加密
            - 两个密文拼接起来在通过h256加密+加盐
    	- 给前端返回
    		saoaohsduo123iho1asdhouasohd21.asohgoeji911h29821hoasfh0asdasssd.08qweu0qhfvwhasduha123sfaash
    	- 前端获取随机字符串之后,保留起来。
    	- 以后再发来请求时,携带字符串
    	- 后端接收到之后,
    		- 1.先做时间判断
    		- 2.字符串合法性校验
    
1.5 jwt使用
pip install djangorestframework-jwt

2.权限

  • 权限引入

    • 问题:不同的视图不同的权限才可以访问

    • 基本使用

      class MyPermission(object):
          '''自定义权限类'''
          def has_permission(self, request, view):
              if request.user.user_type != 3:
                  return False
              return True
         
      class OrderView(APIView):
          '''订单相关业务(只有SVIP可以访问)'''
          
          # 这里写入权限类
          permission_classes = [MyPermission, ]
      
          def get(self, request, *args, **kwargs):
              # 可以在每个视图中添加token判断 但是drf为我们做好了认证方法
              # token = request.GET.get('token')
              # if not token:
              #     return HttpResponse('用户未登录')
              print(request.user.username)
              print(request.auth.token)
              ret = {'code': 1000, 'msg': None}
              try:
                  ret['data'] = ORDER_DICT
              except Exception as e:
                  pass
              return JsonResponse(ret)
      
    • 权限源码流程(与认证特别相似)

      -url -> view -> dispatch(反射)
      	-> initial -> check_permissions
      	
          # 1 - dispatch
          def dispatch(self, request, *args, **kwargs):
              """
              `.dispatch()` is pretty much the same as Django's regular dispatch,
              but with extra hooks for startup, finalize, and exception handling.
              """
              self.args = args
              self.kwargs = kwargs
              # 对原生的request进行加工(丰富了一些功能)
              # Request(request,parsers=self.get_parsers(),authenticators=self.get_authenticators(), # [Foo(), Bar() 对象]negotiator=self.get_content_negotiator(),parser_context=parser_context)
              # request = Request
              # request(原生request, [BaseAuthentication对象,])
              request = self.initialize_request(request, *args, **kwargs)
              # 获取原生request, request._request
              # 获取认证类的对象, request.authenticators
              # 封装request
              self.request = request
              self.headers = self.default_response_headers  # deprecate?
      
              try:
                  # 认证
                  self.initial(request, *args, **kwargs)
      
                  # Get the appropriate handler method
                  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)
      
              except Exception as exc:
                  response = self.handle_exception(exc)
      
              self.response = self.finalize_response(request, response, *args, **kwargs)
              return self.response
          
          # 2 initial
              def initial(self, request, *args, **kwargs):
              """
              Runs anything that needs to occur prior to calling the method handler.
              """
              self.format_kwarg = self.get_format_suffix(**kwargs)
      
              # Perform content negotiation and store the accepted info on the request
              neg = self.perform_content_negotiation(request)
              request.accepted_renderer, request.accepted_media_type = neg
      
              # Determine the API version, if versioning is in use.
              version, scheme = self.determine_version(request, *args, **kwargs)
              request.version, request.versioning_scheme = version, scheme
      
              # Ensure that the incoming request is permitted
              # 认证
              self.perform_authentication(request)
              # 权限
              self.check_permissions(request)
              # 节流(访问频率)
              self.check_throttles(request)
              
              # 3 check_permissions
              def check_permissions(self, request):
                  """
              Check if the request should be permitted.
              Raises an appropriate exception if the request is not permitted.
              """
                  # 去取出每一个权限类 通过权限啥都不做,不通过就返回message
                  for permission in self.get_permissions():
                      if not permission.has_permission(request, self):
                          self.permission_denied(
                              request,
                              message=getattr(permission, 'message', None),
                              code=getattr(permission, 'code', None)
                          )
      
2.1 权限梳理
  1. 使用

    • 创建权限类,必须继承BasePermission, 必须实现has_permission方法 可以在类中定义message属性作为权限不通过时的返回内容

      from rest_framework.permissions import BasePermission
      
      
      class SSVIPPermission(BasePermission):
          '''自定义权限类'''
      	# 没有权限时,返回的信息
          message = '只有SSVIP才能访问'
      
          def has_permission(self, request, view):
              if request.user.user_type != 3:
                  return False
              return True
      
    • 返回值有两种

      • True 有权访问
      • FALSE 无权访问
    • 局部使用

      class OrderView(APIView):
          '''订单相关业务(只有SVIP可以访问)'''
      
          # 这里写入权限类  局部使用
          permission_classes = [SSVIPPermission]
          def get(self, request, *args, **kwargs):
              # 可以在每个视图中添加token判断 但是drf为我们做好了认证方法
              # token = request.GET.get('token')
              # if not token:
              #     return HttpResponse('用户未登录')
              print(request.user.username)
              print(request.auth.token)
              ret = {'code': 1000, 'msg': None}
              try:
                  ret['data'] = ORDER_DICT
              except Exception as e:
                  pass
              return JsonResponse(ret)
      
    • 全局使用(注意差别:一个引入类,一个引入路径)

      REST_FRAMEWORK = {
          # 认证类全局配置
          # 这里的认证类写的是路径  全局使用认证类
          'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.auth.Authtication'],
          # 这里设置 未通过认证用户的 user名称 默认是 AnonymousUser
          # 'UNAUTHENTICATED_USER': lambda :'匿名用户'   推荐使用None
          'UNAUTHENTICATED_USER': None,  # 匿名 未登录 request.user = None
          'UNAUTHENTICATED_TOKEN': None,  # 匿名 request.auth = None
      
          # 权限类全局配置
          'DEFAULT_PERMISSION_CLASSES': ['api.utils.permission.SSVIPPermission']
      
      }
      
    • 源码流程

      -dispatch
      	-封装request
          	-获取自定义权限类(全局/局部), 通过列表生成式创建对象
          - initial
          	- check_permissions(找到权限类)
              	- has_permission(调用权限方法)
                  	- 当访问单个数据 RetrieveAPIView时,会调用权限验证类的第二个方法has_object_permission
      

3.节流(访问频率控制)

  • 节流引入

    • 问题:控制访问频率

    • 基本使用

      # 自定义节流类实现原理
      class VisitThrottle(object):
      
          def __init__(self):
              self.history = None
      
          def allow_request(self, request, view):
              # return True 表示可以继续访问 return False 表示访问频率太高,被限制
              # 1. 获取用户IP
              remote_addr = request.META.get('REMOTE_ADDR')
              import time
              ctime = time.time()
              if remote_addr not in VISIT_RECORD:
                  VISIT_RECORD[remote_addr] = [ctime]
                  return True
              history = VISIT_RECORD.get(remote_addr)
              self.history = history
              while history and history[-1] < ctime - 60:
                  history.pop()
      
              if len(history) < 3:
                  history.insert(0, ctime)
                  return True
      
          def wait(self):
              '''还需要等多少秒才可以继续访问'''
              ctime = time.time()
              return 60 - (ctime - self.history[-1])
          
      # 应用
      class AuthView(APIView):
          '''用于用户登录认证'''
      
          # 全局配置了认证类  这里定义空列表就能解除认证类
          authentication_classes = []
          # 局部配置权限类
          permission_classes = []
          # 局部配置节流类
          throttle_classes = [VisitThrottle]
      
          def post(self, request, *args, **kwargs):
      
              ret = {'code':1000, 'msg': None}
              try:
                  user = request._request.POST.get('username')
                  pwd = request._request.POST.get('password')
                  obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
                  if not obj:
                      ret['code'] = 1001
                      ret['msg'] = '用户名或密码错误'
                  # 为用户创建token
                  token = md5(user)
                  # 存在就创建 不存在就更新
                  models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
                  ret['token'] = token
              except Exception as e:
                  ret['code'] = 1002
                  ret['msg'] = '请求异常'
              return JsonResponse(ret)
      
    • 节流源码流程(其实认证、权限、节流流程机会一致,看懂一个全都会了)

      -url -> view -> dispatch(反射)
      	-> initial -> check_throttles
      
          def dispatch(self, request, *args, **kwargs):
              """
              `.dispatch()` is pretty much the same as Django's regular dispatch,
              but with extra hooks for startup, finalize, and exception handling.
              """
              self.args = args
              self.kwargs = kwargs
              # 对原生的request进行加工(丰富了一些功能)
              # Request(request,parsers=self.get_parsers(),authenticators=self.get_authenticators(), # [Foo(), Bar() 对象]negotiator=self.get_content_negotiator(),parser_context=parser_context)
              # request = Request
              # request(原生request, [BaseAuthentication对象,])
              request = self.initialize_request(request, *args, **kwargs)
              # 获取原生request, request._request
              # 获取认证类的对象, request.authenticators
              # 封装request
              self.request = request
              self.headers = self.default_response_headers  # deprecate?
      
              try:
                  # 认证
                  self.initial(request, *args, **kwargs)
      
                  # Get the appropriate handler method
                  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)
      
              except Exception as exc:
                  response = self.handle_exception(exc)
      
              self.response = self.finalize_response(request, response, *args, **kwargs)
              return self.response
          
          def initial(self, request, *args, **kwargs):
              """
              Runs anything that needs to occur prior to calling the method handler.
              """
              self.format_kwarg = self.get_format_suffix(**kwargs)
      
              # Perform content negotiation and store the accepted info on the request
              neg = self.perform_content_negotiation(request)
              request.accepted_renderer, request.accepted_media_type = neg
      
              # Determine the API version, if versioning is in use.
              version, scheme = self.determine_version(request, *args, **kwargs)
              request.version, request.versioning_scheme = version, scheme
      
              # Ensure that the incoming request is permitted
              # 认证
              self.perform_authentication(request)
              # 权限
              self.check_permissions(request)
              # 节流(访问频率)
              self.check_throttles(request)
              
          def check_throttles(self, request):
              """
              Check if request should be throttled.
              Raises an appropriate exception if the request is throttled.
              """
              throttle_durations = []
              for throttle in self.get_throttles():
                  if not throttle.allow_request(request, self):
                      throttle_durations.append(throttle.wait())
      
              if throttle_durations:
                  # Filter out `None` values which may happen in case of config / rate
                  # changes, see #1438
                  durations = [
                      duration for duration in throttle_durations
                      if duration is not None
                  ]
      
                  duration = max(durations, default=None)
                  self.throttled(request, duration)
      
3.1 节流梳理
  1. 使用

    • 创建节流类,继承BaseThrottle类, 实现allow_request 和 wait方法

    • 节流类,继承SimpleRateThrottle, 继承后只需要实现 get_cache_key 和 scope(配置文件中的key)

      import time
      from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
      
      VISIT_RECORD = {}
      
      # 两种方法  1 继承BaseThrottle
      class VisitThrottle1(BaseThrottle):
      
          def __init__(self):
              self.history = None
      
          def allow_request(self, request, view):
              # return True 表示可以继续访问 return False 表示访问频率太高,被限制
              # 1. 获取用户IP
              remote_addr = request.META.get('REMOTE_ADDR')
              ctime = time.time()
              if remote_addr not in VISIT_RECORD:
                  VISIT_RECORD[remote_addr] = [ctime]
                  return True
              history = VISIT_RECORD.get(remote_addr)
              self.history = history
              while history and history[-1] < ctime - 60:
                  history.pop()
      
              if len(history) < 3:
                  history.insert(0, ctime)
                  return True
      
          def wait(self):
              '''还需要等多少秒才可以继续访问'''
              ctime = time.time()
              return 60 - (ctime - self.history[-1])
      
      # 继承SimpleRateThrottle
      class VisitThrottle(SimpleRateThrottle):
          '''节流类'''
          scope = 'xiaoxia'
      
          # 按照IP进行节流
          def get_cache_key(self, request, view):
              return self.get_ident(request)
      
    • 局部使用

      class VisitThrottle(SimpleRateThrottle):
          '''节流类'''
          scope = 'xiaoxia'
      
          # 按照IP进行节流
          def get_cache_key(self, request, view):
              return self.get_ident(request)
          
      class AuthView(APIView):
          '''用于用户登录认证'''
      
          # 全局配置了认证类  这里定义空列表就能解除认证类
          authentication_classes = []
          permission_classes = []
          # 局部使用 节流类
          throttle_classes = [VisitThrottle]
          def post(self, request, *args, **kwargs):
      
              ret = {'code':1000, 'msg': None}
              try:
                  user = request._request.POST.get('username')
                  pwd = request._request.POST.get('password')
                  obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
                  if not obj:
                      ret['code'] = 1001
                      ret['msg'] = '用户名或密码错误'
                  # 为用户创建token
                  token = md5(user)
                  # 存在就创建 不存在就更新
                  models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
                  ret['token'] = token
              except Exception as e:
                  ret['code'] = 1002
                  ret['msg'] = '请求异常'
              return JsonResponse(ret)
      
    • 全局使用

      # rest framework 全局配置
      # rest framework中会自动加载 setting中的REST_FRAMEWORK配置项 这是一个字典
      # 在这里面可以写上认证,权限,节流等所有配置项
      REST_FRAMEWORK = {
          # 认证类全局配置
          # 这里的认证类写的是路径  全局使用认证类
          'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.auth.Authtication'],
          # 这里设置 未通过认证用户的 user名称 默认是 AnonymousUser
          # 'UNAUTHENTICATED_USER': lambda :'匿名用户'   推荐使用None
          'UNAUTHENTICATED_USER': None,  # 匿名 未登录 request.user = None
          'UNAUTHENTICATED_TOKEN': None,  # 匿名 request.auth = None
      
          # 权限类全局配置
          'DEFAULT_PERMISSION_CLASSES': ['api.utils.permission.SSVIPPermission'],
      
          # 节流类全局配置
          'DEFAULT_THROTTLE_CLASSES': ['api.utils.throttle.VisitThrottle'],
          'DEFAULT_THROTTLE_RATES': {
              'xiaoxia': '3/m'
          }
      
      }
      
    • 源码流程

      • -dispatch
        	-封装request
            	-获取自定义权限类(全局/局部), 通过列表生成式创建对象
            - initial
            	- check_throttles(找到节流类)
                	- allow_request(调用节流方法)
        
4.3节流面试回答
  1. 如何实现访问频率限制

    - 匿名用户,用IP作为用户唯一标识,但是如果用户换了代理IP, 无法做到真正的限制
    - 登录用户,用用户名或者ID做标识
    具体实现:
    	在django的缓存中维护了有一个字典 = {
    		throttle_anon_1.1.1.1: [1010101,]
    		1.1.1.2: [123131232, 213213213]   # 列表中存的是访问ip的时间戳
    	} 
    
    限制:60s访问3次
    	来访问时:
    		1.获取当前时间 100121280
    		2.100121280 - 60 = 1001121220, 小于1001121220的所有记录删除
    		3.判断1min中内已经访问了多少次? 4
    		4.无法访问
    	停一会
    		来访问时:
    		1.获取当前时间 100121340
    		2.100121340 - 60 = 1001121220, 小于1001121220的所有记录删除
    		3.判断1min中内已经访问了多少次? 0
    		4.可以访问
    

4.版本

4.1基本使用
  • URL通过GET传参

    • 自定义版本类

      # http://127.0.0.1:8000/api/users/?version=v1
      # url path('users/', views.UserView.as_view()),
      class ParamVersion(object):
          '''版本类'''
          def determine_version(self, request, *args, **kwargs):
              version = request.query_params.get('version')
              return version
          
      # 局部使用
      class UserView(APIView):
      	# 这里配置局部版本类 
          versioning_class = ParamVersion
          def get(self, request, *args, **kwargs):
              print(request.version)
              return HttpResponse('用户列表')
      
  • 在URL中传参(推荐使用)

    • 使用 URLPathVersioning

    • 全局配置版本类

      # urls.py
      # http://127.0.0.1:8000/api/v1/users/
      urlpatterns = [
          re_path(r'^(?P<version>[v1|v2]+)/users/$', views.UserView.as_view()),
      ]
      # 全局配置
      # REST_FRAMEWORK
      REST_FRAMEWORK = {
          # 全局版本配置
          # 版本类路径
          'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
          # 默认版本
          'DEFAULT_VERSION': 'v1',
          # 允许的版本
          'ALLOWED_VERSIONS': ['v1', 'v2'],
          # url参数
          'VERSION_PARAM': 'version'
      }
      
    • 源码流程

      url -> views -> dispatch()
      		-> initial -> determine_version -> return request.version, request.versioning.scheme
      
      class UserView(APIView):
      
          def get(self, request, *args, **kwargs):
              self.dispatch()
              # 获取版本
              print(request.version)
              # 获取版本对象  URLPathVersioning()
              print(request.versioning_scheme)
              return HttpResponse('用户列表')
      
4.2 版本梳理

总结

  • 使用

    • 配置文件

      # REST_FRAMEWORK
      REST_FRAMEWORK = {
          # 全局版本配置
          'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
          'DEFAULT_VERSION': 'v1',
          'ALLOWED_VERSIONS': ['v1', 'v2'],
          'VERSION_PARAM': 'version'
      }
      
    • 路由系统

      # 路由分发 urls.py
      from django.urls import path, include
      
      urlpatterns = [
          path('api/', include('api.urls'))
      ]
      # api.urls.py
      urlpatterns = [
          # 路由得这么写
          re_path(r'^(?P<version>[v1|v2]+)/users/$', views.UserView.as_view(), name='user'),
      ]
      
      
    • 视图

      class UserView(APIView):
      
          def get(self, request, *args, **kwargs):
              # 获取版本
              print(request.version)
              # 获取版本对象  URLPathVersioning()
              print(request.versioning_scheme)
              # 反向生成url(rest framework)
              url = request.versioning_scheme.reverse(viewname='user', request=request)
              print(url)
              # 反向生成url(django)
              url = reverse(viewname='user', kwargs={'version':2})
              print(url)
              return HttpResponse('用户列表')
      

5.解析器

作用

对用户请求体中的数据进行解析,依靠请求头中的Content-Type对请求体中的数据进行解析,解析到request.data中去

前戏(DJango解析器)

django:request.POST / request.body

  • 发送POST请求过来后,request.body中一定有值,但是request.POST中不一定有值

  • 其实request.POST中的数据就是从request.body转换过来的,只要符合一定的请求条件,request.POST中就会有值

    • 请求头要求

      • 如果请求头中的 Content-Type:application/x-www-form-urlencoded, request.POST才会有值(去request.body中解析数据)
    • 数据格式要求:

      • name = ‘xiaoxia’ & age = 18 & gender = ‘男’
    • 只要两个要求都满足的时候,request.POST中才会有值

    • 例子

      • form表单提交,默认转换为这种格式 name = ‘xiaoxia’ & age = 18 & gender = ‘男’ POST中有值

      • ajax提交

        • 情况1

          # body有值  POS有值
          $.ajax({
              url;...,
              type:POST,
              data:[name:xxx,age:12] # 内部自动转换为 name='xiaoxia'&age=18&gender = '男'
          })
          
        • 情况2

          # body有值  POST无值
          $.ajax({
              url;...,
              type:POST,
              headers:['Content-Type':'application/json']
              data:[name:xxx,age:12] # 内部自动转换为 name='xiaoxia'&age=18&gender = '男'
          })
          
        • 情况3

          # body有值  POST无值
          $.ajax({
              url;...,
              type:POST,
              data:JSON.stringfy([name:xxx,age:12]) # {"name":"xiaoxia", "age": "16"}
          })
          
restframework解析器
解析器梳理
  • 局部使用

    # urls.py
    urlpatterns = [
        re_path(r'^(?P<version>[v1|v2]+)/parser/$', views.ParserView.as_view()),
    ]
    
    # views.py
    from rest_framework.parsers import JSONParser, FormParser
    
    class ParserView(APIView):
        # 局部配置解析器列表
        parser_classes = [JSONParser, FormParser]
        '''
        JSONParser:表示只能解析Content-Type:application-json头
        FormParser:表示只能解析Content-Type:x-www-form-urlencoded头
        '''
    
        def post(self, request, *args, **kwargs):
            # 获取解析之后的结果 根据解析器不同 结果不同
            '''
            其实是request.data来触发解析器的
            光配置解析器,不调用request.data是不会触发解析器的
            1.获取用户请求
            2.获取用户请求体
            3.根据用户请求头和 parser_classes = [JSONParser, FormParser]中支持的请求头进行比较
            4.JSONParser对象去解析
            5.request.data 获取解析后的数据
            '''
            print(request.data)
            return HttpResponse('ParserView')
    
  • 全局使用(推荐使用,配置好即可)

    # REST_FRAMEWORK
    REST_FRAMEWORK = {
        # 全局版本配置
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
        'DEFAULT_VERSION': 'v1',
        'ALLOWED_VERSIONS': ['v1', 'v2'],
        'VERSION_PARAM': 'version',
    
        # 全局解析器配置
        'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser']
    }
    
  • 源码流程 & 本质 可能会问到的面试题

    • 本质:根据请求头Content-Type的不同,调用不同的解析器进行处理,解析就是将请求体中的数据转化为不同格式的数据

      • 请求头

        Accept:浏览器能够处理的内容类型
        Accept-Charset:浏览器能够显示的字符集
        Accept-Encoding:浏览器能够处理的压缩编码
        Accept-Language:浏览器当前设置的语言
        Connection:浏览器与服务器之间连接的类型
        Cookie:当前页面设置的任何Cookie
        Host:发出请求的页面所在的域
        Referer:发出请求的页面的URL
        User-Agent:浏览器的用户代理字符串
        Content-Type: 发送请求的数据格式
        
      • 状态码

        200 - 请求成功
        301 - 资源(网页等)被永久转移到其它URL
        404 - 请求的资源(网页等)不存在
        500 - 内部服务器错误
        
      • 请求方法:GET/POST/PUT/PATCH不常用/DELETE/等

    • 源码流程

      • dispatch:request封装 原生request、认证对象、解析器对象
      • 然后通过一些认证、权限、节流方法之后,通过反射找到对应请求的GET、POST方法
      • request.data 执行后会重新找到对应解析器进行解析处理

6.序列化

序列化器功能:

  • 数据校验 (表单校验)
  • 序列化,对对象(obj)或者对象列表(queryset)进行序列化操作

####基本使用

import json
from rest_framework import serializers

# Role表
class Role(models.Model):
    title = models.CharField(max_length=32)

# 自定义序列化器
class RolesSerializers(serializers.Serializer):
    # 字段对应数据库中的字段
    id = serializers.IntegerField()
    title = serializers.CharField()

# 视图函数
class RolesView(APIView):
    def get(self, request, *args, **kwargs):
        # 方式1
        # roles = models.Role.objects.all().values('id', 'title')
        # roles = list(roles)
        # ret = json.dumps(roles, ensure_ascii=False)

        # 方式2:序列化器 对于 [obj, obj, obj,]
        # roles = models.Role.objects.all()
        # ser = RolesSerializers(instance=roles, many=True)
        # print(ser.data)
        # ret = json.dumps(ser.data, ensure_ascii=False)

        # 对于单个对象
        roles = models.Role.objects.all().first()
        # 单个对象 Many=False
        ser = RolesSerializers(instance=roles, many=False)
        ret = json.dumps(ser.data, ensure_ascii=False)

        return HttpResponse(ret)

序列化自定义字段

# models.py
from django.db import models

# Create your models here.
from django.db import models

# Create your models here.



class UserGroup(models.Model):
    title = models.CharField(max_length=32)


class UserInfo(models.Model):

    user_type_choices = (
        (1, '普通用户'),
        (2, 'VIP'),
        (3, 'SVIP')
    )
	# 默认取出的是 1,2,3  如果想要取出具体用户, 需要调用 get_user_type_display()方法
    user_type = models.IntegerField(choices=user_type_choices)
    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=256)
    group = models.ForeignKey('UserGroup', on_delete=models.CASCADE)
    roles = models.ManyToManyField('Role')

class UserToken(models.Model):

    user = models.OneToOneField(to='UserInfo', on_delete=models.CASCADE)
    token = models.CharField(max_length=64)


class Role(models.Model):
    title = models.CharField(max_length=32)
    
# 自定义序列化器(理解source和自定义字段方法)
class UserInfoSerializer(serializers.Serializer):
    # xxx = serializers.CharField(source='get_user_type_display')
    # source就是源,指向user_type 就是去指定数据库取user_type字段
    # 它内部会判断是否是可执行的字段
    # 它会判断是否是可执行对象,如果是可执行字段,那么自动执行 user_type()
    # 如果不是可执行的,那么不加 user_type  这是source原理
    # 只有choices 和 ForeignKey类型可以加  ManytoMany类型不能使用source
    user_type = serializers.CharField(source='get_user_type_display')
    username = serializers.CharField()
    password = serializers.CharField()
    gp = serializers.CharField(source='group.title')
    # rls = serializers.CharField(source='roles.all')
    # roles是manytomany字段 不能够直接使用source
    # 因此使用这种方法
    rls = serializers.SerializerMethodField()  # 自定义显示

    # 自定义get_字段名()方法 return什么 那么字段返回什么

    def get_rls(self, row):
        roles_obj_list = row.roles.all()
        ret = []
        for item in roles_obj_list:
            ret.append({'id':item.id, 'title':item.title})
        return ret

# views.py
class UserInfoView(APIView):

    def get(self, request, *args, **kwargs):
        users = models.UserInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

序列化ModelSerializer

# 继承ModelSerializer
class UserInfoSerializer(serializers.ModelSerializer):
    user_type = serializers.CharField(source='get_user_type_display')
    rls = serializers.SerializerMethodField()  # 自定义显示
	
    # 元类
    class Meta:
        model = models.UserInfo
        # fields = '__all__'
        fields = ['id', 'username', 'password', 'user_type', 'group', 'rls']

    # 自定义序列化方法
    def get_rls(self, row):
        roles_obj_list = row.roles.all()
        ret = []
        for item in roles_obj_list:
            ret.append({'id':item.id, 'title':item.title})
        return ret

# views.py
class UserInfoView(APIView):

    def get(self, request, *args, **kwargs):
        users = models.UserInfo.objects.all()
        # 实例化序列化器类 将要序列化的数据传入 instance中 多个数据使用many=True
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

序列化案例:

# models.py
from django.db import models

# Create your models here.

class Category(models.Model):
    '''文章分类'''
    name = models.CharField(verbose_name='分类', max_length=32)


class Article(models.Model):
    '''文章表'''
    status_choices = (
        (1, '发布'),
        (2, '删除')
    )
    status = models.IntegerField(verbose_name='状态', choices=status_choices, default=1)
    title = models.CharField(verbose_name='标题', max_length=32)
    summary = models.CharField(verbose_name='简介', max_length=255)
    content = models.TextField(verbose_name='文章内容')
    category = models.ForeignKey(verbose_name='分类', to='Category', on_delete=models.CASCADE)
    tag = models.ManyToManyField(verbose_name='标签', to='Tag', null=True, blank=True)

class Tag(models.Model):
    '''标签'''
    title = models.CharField(verbose_name='标签', max_length=32)

# 序列化器
class AritcleSerizlizer(serializers.ModelSerializer):
    status_txt = serializers.CharField(source='get_status_display', required=False)
    category_txt = serializers.CharField(source='category.name', required=False)
    # 钩子字段 manytomany 只能使用depth 或者 SerializerMethodField来跨表查询
    tag_txt = serializers.SerializerMethodField()

    # 钩子函数 
    def get_tag_txt(self, obj):
        # return [row.title for row in obj.tag.all()]
        return [row for row in obj.tag.all().values('id', 'title')]
    class Meta:
        model = models.Article
        # fields = "__all__"
        # depth = 1
        fields = "__all__"

        
# views.py
class AritcleView(APIView):
    '''crud'''

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if not pk:
            queryset = models.Article.objects.all()
            ser = AritcleSerizlizer(instance=queryset, many=True)
            return Response(ser.data)
        else:
            art_obj = models.Article.objects.filter(id=pk).first()
            ser = AritcleSerizlizer(instance=art_obj)
            return Response(ser.data)

    def post(self, request, *args, **kwargs):
        ser = AritcleSerizlizer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        obj = models.Article.objects.filter(id=pk).first()
        ser = AritcleSerizlizer(instance=obj, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def patch(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        obj = models.Article.objects.filter(id=pk).first()
        ser = AritcleSerizlizer(instance=obj, data=request.data, partial=True)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        models.Article.objects.filter(id=pk).update(status=2)
        return Response('删除成功')

序列化器部分总结
1.写类
  • 继承 serializers.Serializer

    class UserInfoSerializer(serializers.Serializer):
        username = serializers.CharField()
        password = serializers.CharField()
    
  • 继承 serializers.ModelSerializer

    # 根据model自动生成序列化字段
    class UserInfoSerializer(serializers.ModelSerializer):
        user_type = serializers.CharField(source='get_user_type_display')
        rls = serializers.SerializerMethodField()  # 自定义显示
        class Meta:
            # 对应的model表
            model = models.UserInfo
            # 对应的字段
            # fields = '__all__'
            fields = ['id', 'username', 'password', 'user_type', 'group', 'rls']
    
2.自定义字段
# source会判断是否是可执行对象,如果是执行对象,直接执行get_user_type_display()
# 如果不是可执行对象,直接get_user_type_display
user_type = serializers.CharField(source='get_user_type_display')
rls = serializers.SerializerMethodField()  # 自定义显示
 # 自定义序列化方法
    def get_rls(self, row):
        roles_obj_list = row.roles.all()
        ret = []
        for item in roles_obj_list:
            ret.append({'id':item.id, 'title':item.title})
        return ret
3.自动序列化连表操作(牛逼)深度控制
# models.py
from django.db import models

# Create your models here.
from django.db import models

# Create your models here.



class UserGroup(models.Model):
    title = models.CharField(max_length=32)


class UserInfo(models.Model):

    user_type_choices = (
        (1, '普通用户'),
        (2, 'VIP'),
        (3, 'SVIP')
    )

    user_type = models.IntegerField(choices=user_type_choices)
    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=256)
    group = models.ForeignKey('UserGroup', on_delete=models.CASCADE)
    roles = models.ManyToManyField('Role')

class UserToken(models.Model):

    user = models.OneToOneField(to='UserInfo', on_delete=models.CASCADE)
    token = models.CharField(max_length=64)

# 自定义序列化器 
class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = '__all__'
# views.py
class UserInfoView(APIView):

    def get(self, request, *args, **kwargs):
        users = models.UserInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

默认访问后数据为

[
    {
        "id": 1,
        "user_type": 1,
        "username": "小夏",
        "password": "123",
        "group": 1,
        "roles": [
            1,
            2,
            3
        ]
    },
    {
        "id": 2,
        "user_type": 2,
        "username": "小盛",
        "password": "123",
        "group": 1,
        "roles": [
            1,
            3
        ]
    },
    {
        "id": 3,
        "user_type": 1,
        "username": "小宋",
        "password": "123",
        "group": 1,
        "roles": [
            1
        ]
    }
]

roles和user_type这些显示的都是基层,没有往深入去取,但我们往往想要的是具体数据,就得使用想上面一样的自定义序列化方法或者别的方法。

  • 这里提供更简单的方法 深度控制

  • 使用depth可以控制查询连表的深度

    class UserInfoSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = models.UserInfo
            fields = '__all__'
            # 深度控制 尽量不要超过3  建议 1-10
            depth = 1
    
    class UserInfoView(APIView):
    
        def get(self, request, *args, **kwargs):
            users = models.UserInfo.objects.all()
            ser = UserInfoSerializer(instance=users, many=True)
            ret = json.dumps(ser.data, ensure_ascii=False)
            return HttpResponse(ret)
    

    加上depth=1 再次访问url获取数据为

    [
        {
            "id": 1,
            "user_type": 1,
            "username": "小夏",
            "password": "123",
            "group": {
                "id": 1,
                "title": "IT组"
            },
            "roles": [
                {
                    "id": 1,
                    "title": "学生"
                },
                {
                    "id": 2,
                    "title": "老师"
                },
                {
                    "id": 3,
                    "title": "护士"
                }
            ]
        },
        {
            "id": 2,
            "user_type": 2,
            "username": "小盛",
            "password": "123",
            "group": {
                "id": 1,
                "title": "IT组"
            },
            "roles": [
                {
                    "id": 1,
                    "title": "学生"
                },
                {
                    "id": 3,
                    "title": "护士"
                }
            ]
        },
        {
            "id": 3,
            "user_type": 1,
            "username": "小宋",
            "password": "123",
            "group": {
                "id": 1,
                "title": "IT组"
            },
            "roles": [
                {
                    "id": 1,
                    "title": "学生"
                }
            ]
        }
    ]
    
跨表访问(深层访问)
# 文章表序列化器
class ArticleSerializer(serializers.ModelSerializer):
    # 跨表访问  
    # 1 使用source  
    # 2 使用 SerializerMethodField() 定义钩子函数 
    # 3 depth 不推荐
    category = serializers.CharField(source='category.name')
    # 深入访问
    status = serializers.CharField(source='get_status_display')
    status_txt = serializers.SerializerMethodField()
    # MethodField 钩子方法
    x1 = serializers.SerializerMethodField()
    class Meta:
        model = models.Article
        fields = ['id', 'title', 'summary', 'content', 'category', 'x1', 'status', 'status_txt']
        # depth = 1  可以用 但是不推荐

    # 定义钩子函数 get_字段名 obj是返回的每一行对象
    def get_x1(self, obj):
        return obj.category.name

    def get_status_txt(self, obj):
        return obj.get_status_display()

# 文章表curd
class Article(APIView):
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        print(pk)
        if not pk:
            queryset = models.Article.objects.all()
            # 序列化操作
            ser = ArticleSerializer(instance=queryset, many=True)
            return Response(ser.data)
        else:
            art_obj = models.Article.objects.filter(id=pk).first()
            print(art_obj)
            ser = ArticleSerializer(instance=art_obj, many=False)
            return Response(ser.data)

    def post(self, request, *args, **kwargs):
        # 数据校验 表单验证
        ser = ArticleSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        else:
            return Response(ser.errors)

    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        art_obj = models.Article.objects.filter(id=pk).first()
        ser = ArticleSerializer(instance=art_obj, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        else:
            return Response(ser.errors)

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        models.Article.objects.filter(id=pk).delete()
        return Response('删除成功')
4.给字段反向生成链接
class UserInfoSerializer(serializers.ModelSerializer):
    # 给group反向生成链接 固定用法 serializers.HyperlinkedIdentityField
    group = serializers.HyperlinkedIdentityField(view_name='gp', lookup_field='group_id', lookup_url_kwarg='pk')
    class Meta:
        model = models.UserInfo
        fields = ['id', 'username', 'password', 'group']

class UserInfoView(APIView):

    def get(self, request, *args, **kwargs):
        users = models.UserInfo.objects.all()
        # 如果使用HyperlinkedIdentityField 实例化序列化器时候需要多加 context={'request': request}参数
        ser = UserInfoSerializer(instance=users, many=True, context={'request': request})
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

返回数据格式效果

[
    {
        "id": 1,
        "username": "小夏",
        "password": "123",
        "group": "http://127.0.0.1:8000/api/v1/group/1"
    },
    {
        "id": 2,
        "username": "小盛",
        "password": "123",
        "group": "http://127.0.0.1:8000/api/v1/group/1"
    },
    {
        "id": 3,
        "username": "小宋",
        "password": "123",
        "group": "http://127.0.0.1:8000/api/v1/group/1"
    }
]
5.请求数据校验

使用序列化器进行数据校验

基本使用

# 序列化器
class UserGroupSerializer(serializers.Serializer):
    # 这里可以写自定义字段校验类
    title = serializers.CharField(error_messages={'required': '标题不能为空'}, validators=[])
	# 同样可以写钩子函数进行数据校验
    # 序列化校验方法 validate_字段名
    def validate_title(self, val):
        print(val, 1111)
        return val
    
# views.py
class UserGroupView(APIView):

    def post(self, request, *args, **kwargs):
		# print(request.data)  request.data就是解析之后的对象
        # 实例化serializer对象
        ser = UserGroupSerializer(data=request.data)
        # 数据通过验证
        if ser.is_valid():
            print(ser.validated_data['title'])
        else:
            print(ser.errors)
        return HttpResponse('提交数据')

7.分页

rest framework提供的三种分页方法(实质上是继承不同的类即可实现不同的方法,基本使用一致)

  • 分页,看第n页,每页显示多少n条数据 继承 PageNumberPagination
  • 分页,在n个位置,向后查n条数据 继承 LimitOffsetPagination
  • 加密分页,上一页和下一页 继承 CursorPagination
分页器基本使用

1.使用rest framework提供好的

from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response # 使用这个Response可以显示rest framework页面 看着比较舒服

# 分页序列化器
class Page1Serializer(serializers.ModelSerializer):

    class Meta:
        model = models.Role
        fields = '__all__'

# 分页
class Page1View(APIView):

    def get(self, request, *args, **kwargs):
        # 获取所有数据
        roles = models.Role.objects.all()
        # 创建分页对象
        pg = PageNumberPagination()
        # 在数据库中获取分页的数据 三个参数  queryset request self
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
        # 对数据进行序列化
        ser = Page1Serializer(instance=pager_roles, many=True)
        return Response(ser.data)
    
# REST_FRAMEWORK
REST_FRAMEWORK = {
    # 全局版本配置
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    'DEFAULT_VERSION': 'v1',
    'ALLOWED_VERSIONS': ['v1', 'v2'],
    'VERSION_PARAM': 'version',

    # 全局解析器配置
    'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser'],
    # 分页配置
    'PAGE_SIZE': 3,
}

2.自定义分页器

from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response # 使用这个Response可以显示rest framework页面 看着比较舒服

# 自定义分页器
class MyPageNumberPagination(PageNumberPagination):
    # 自定义pagesize  默认去配置文件中读 可以自己设置
    page_size = 2
    # 可以在url中控制每页显示多少条数据的参数 size=3
    # http://127.0.0.1:8000/api/v1/page1/?page=1&size=3 修改每页显示数据个数
    page_size_query_param = 'size'
    # 每页最多显示多少条
    max_page_size = 5
    # 控制分页的参数  page=1
    page_query_param = 'page'

# 分页序列化器
class Page1Serializer(serializers.ModelSerializer):

    class Meta:
        model = models.Role
        fields = '__all__'

# 分页
class Page1View(APIView):

    def get(self, request, *args, **kwargs):
        # 获取所有数据
        roles = models.Role.objects.all()
        # 创建分页对象
        pg = PageNumberPagination()
        # 在数据库中获取分页的数据
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
        # 对数据进行序列化
        ser = Page1Serializer(instance=pager_roles, many=True)
        # return Response(ser.data)
        # # 使用 get_paginated_response后会多显示 总数据个数以及上一页下一页的路由地址
        return pg.get_paginated_response(ser.data)

使用 get_paginated_response后会多显示 总数据个数以及上一页下一页的路由地址

{
    "count": 5,
    "next": "http://127.0.0.1:8000/api/v1/page1/?page=2&size=2",
    "previous": null,
    "results": [
        {
            "id": 1,
            "title": "学生"
        },
        {
            "id": 2,
            "title": "老师"
        }
    ]
}
分页总结(有面试题)
  1. 数据量特别大,如何分页?

    数据量特别大,每次请求mysql,都需要很长时间,如果一直不断的请求,mysql很可能会挂掉
    最近再看rest frame源码,觉得搞的分页不错
    是记录每页的第一条数据和最后一条数据来分页
    比如每页显示10条数据,现在是第一页
    那么就记录第1和第10id,根据这两个id进行下一页和上一页的切换,不需要每次将每页数据全都读出来,大大解决了时间
    并且如果有用户在url上自己输入特别大的页码,也会给mysql造成很大压力
    rest framework中就有一种加密分页方法,每次分页返回的数据会携带上上一页和下一页的url,这段url是加密的字符串
    因此用户不能够自己在url上随意控制分页,只能通过返回的url进行翻页
    

8.视图

视图基本使用
  • 过去VIew

    class PagerView(View):
        pass
    
  • 现在APIView

    # APIView 继承 View
    class Pager1View(APIView):
        pass
    
  • 无用 GenericAPIView(和APIView差不多,几乎没有优化)

    # 视图
    from rest_framework.generics import GenericAPIView
    
    # GenericAPIView 继承 APIView
    class View1View(GenericAPIView):
    
        queryset = models.Role.objects.all()
        serializer_class = Page1Serializer
        pagination_class = MyPageNumberPagination
    
        def get(self, request, *args, **kwargs):
            # 获取数据
            roles = self.get_queryset()  # models.Role.objects.all()
            # 获取分页后的queryset对象
            pager_roles = self.paginate_queryset(roles)
            # 序列化
            ser = self.get_serializer(instance=pager_roles, many=True)
            return Response(ser.data)
    
  • GenericViewSet(ViewSetMixin, generics.GenericAPIView) 此时路由系统已经发生改变 ViewSetMixin重写了as_view方法

    # as_view传入的参数 是Method方法对应的函数  get请求过来去执行list方法
    re_path(r'^(?P<version>[v1|v2]+)/view1/$', views.View1View.as_view({'get': 'list'})),
    
    # views.oy
    from rest_framework.viewsets import GenericViewSet
    class View1View(GenericViewSet):
        queryset = models.Role.objects.all()
        serializer_class = Page1Serializer
        pagination_class = MyPageNumberPagination
    
        def list(self, request, *args, **kwargs):
            # 获取数据
            roles = self.get_queryset()  # models.Role.objects.all()
            # 获取分页后的queryset对象
            pager_roles = self.paginate_queryset(roles)
            # 序列化
            ser = self.get_serializer(instance=pager_roles, many=True)
            return Response(ser.data)
    
  • 最牛逼 ModelViewSet 非常好用

    # 继承了很多类  分别完成了增删改查
    ModelViewSet(mixins.CreateModelMixin,
                       mixins.RetrieveModelMixin,
                       mixins.UpdateModelMixin,
                       mixins.DestroyModelMixin,
                       mixins.ListModelMixin,
                       GenericViewSet):
    
    # urls.py 
    # 路由 每个请求方法 对应一个函数
    # 以后一个url写两个路由即可完成 所有的增删改查操作
    urlpatterns = [
        re_path(r'^(?P<version>[v1|v2]+)/view1/$', views.View1View.as_view({'get': 'list'})),
        re_path(r'^(?P<version>[v1|v2]+)/view1/(?P<pk>\d+)$', views.View1View.as_view({'get': 'retrieve', 'post': 'create', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
    
    ]
        
    # views.py 视图只需要写这样即可
    from rest_framework.viewsets import ModelViewSet
    
    class View1View(ModelViewSet):
        queryset = models.Role.objects.all()
        serializer_class = Page1Serializer
        pagination_class = MyPageNumberPagination
    
视图总结
  • 增删改查 ModelViewSet

  • 增删 CreateModelMixin DestroyModelMixin GenericViewSet

  • 复杂逻辑 GenericViewSet 或者 APIView

  • PS:还债

    GenericViewSet.get_object()
    	check_object_permissions
        	has_object_permission
    

8.路由系统

urlpatterns = [
    # http://127.0.0.1:8000/api/v1/view1/?format=json
    re_path(r'^(?P<version>[v1|v2]+)/view1/$', views.View1View.as_view({'get': 'list'})),
    
    # http://127.0.0.1:8000/api/v1/view1.json 可以直接路由访问.json格式
    re_path(r'^(?P<version>[v1|v2]+)/view1\.(?P<format>\w+)$', views.View1View.as_view({'get': 'list'})),
	
    # http://127.0.0.1:8000/api/v1/view1/1/?format=json 访问json格式
    re_path(r'^(?P<version>[v1|v2]+)/view1/(?P<pk>\d+)$', views.View1View.as_view({'get': 'retrieve', 'post': 'create', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
    
    # http://127.0.0.1:8000/api/v1/view1/1.json 可以直接路由访问.json格式
    re_path(r'^(?P<version>[v1|v2]+)/view1/(?P<pk>\d+)\.(?P<format>\w+)$$', views.View1View.as_view({'get': 'retrieve', 'post': 'create', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
]

自动生成路由

from django.urls import path, re_path, include
from api import views
from rest_framework import routers

# 创建路由对象
routers = routers.DefaultRouter()
# 注册路由对应视图
routers.register(r'rt', views.View1View)

urlpatterns = [
    # 自动生成路由
    re_path(r'^(?P<version>[v1|v2]+)/', include(routers.urls)),
]

# 自动生成路由会直接生成增删改查路由
api/ ^(?P<version>[v1|v2]+)/ ^rt/$ [name='role-list']
api/ ^(?P<version>[v1|v2]+)/ ^rt\.(?P<format>[a-z0-9]+)/?$ [name='role-list']
api/ ^(?P<version>[v1|v2]+)/ ^rt/(?P<pk>[^/.]+)/$ [name='role-detail']
api/ ^(?P<version>[v1|v2]+)/ ^rt/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='role-detail']
api/ ^(?P<version>[v1|v2]+)/ ^$ [name='api-root']
api/ ^(?P<version>[v1|v2]+)/ ^\.(?P<format>[a-z0-9]+)/?$ [name='api-root']

9.渲染器

from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer

class TestView(APIView):
    # 渲染器局部配置 其实就是渲染成什么格式的数据在页面上 
    # 通常都是JSON格式
    # BrowsableAPIRenderer 则是让页面更好看一些而已
    renderer_classes = [JSONRenderer, BrowsableAPIRenderer]

    def get(self, request, *args, **kwargs):
        # 获取所有数据
        roles = models.Role.objects.all()
        # 创建分页对象
        pg = MyPageNumberPagination()
        # 在数据库中获取分页的数据
        pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self)
        # 对数据进行序列化
        ser = Page1Serializer(instance=pager_roles, many=True)
        # 使用 get_paginated_response后会多显示 总数据个数以及上一页下一页的路由地址
        return pg.get_paginated_response(ser.data)

# 全局配置
# REST_FRAMEWORK
REST_FRAMEWORK = {
    # 全局版本配置
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    'DEFAULT_VERSION': 'v1',
    'ALLOWED_VERSIONS': ['v1', 'v2'],
    'VERSION_PARAM': 'version',

    # 全局解析器配置
    'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser'],
    # 分页配置
    'PAGE_SIZE': 3,
    # 渲染器配置
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer'
    ]
}
# http://127.0.0.1:8000/api/v1/view1.json 可以直接路由访问.json格式
re_path(r'^(?P<version>[v1|v2]+)/view1\.(?P<format>\w+)$', views.View1View.as_view({'get': 'list'})),

# http://127.0.0.1:8000/api/v1/view1/1/?format=json 访问json格式
re_path(r'^(?P<version>[v1|v2]+)/view1/(?P<pk>\d+)$', views.View1View.as_view({'get': 'retrieve', 'post': 'create', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),

# http://127.0.0.1:8000/api/v1/view1/1.json 可以直接路由访问.json格式
re_path(r'^(?P<version>[v1|v2]+)/view1/(?P<pk>\d+)\.(?P<format>\w+)$$', views.View1View.as_view({'get': 'retrieve', 'post': 'create', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),

]


自动生成路由

```python
from django.urls import path, re_path, include
from api import views
from rest_framework import routers

# 创建路由对象
routers = routers.DefaultRouter()
# 注册路由对应视图
routers.register(r'rt', views.View1View)

urlpatterns = [
    # 自动生成路由
    re_path(r'^(?P<version>[v1|v2]+)/', include(routers.urls)),
]

# 自动生成路由会直接生成增删改查路由
api/ ^(?P<version>[v1|v2]+)/ ^rt/$ [name='role-list']
api/ ^(?P<version>[v1|v2]+)/ ^rt\.(?P<format>[a-z0-9]+)/?$ [name='role-list']
api/ ^(?P<version>[v1|v2]+)/ ^rt/(?P<pk>[^/.]+)/$ [name='role-detail']
api/ ^(?P<version>[v1|v2]+)/ ^rt/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='role-detail']
api/ ^(?P<version>[v1|v2]+)/ ^$ [name='api-root']
api/ ^(?P<version>[v1|v2]+)/ ^\.(?P<format>[a-z0-9]+)/?$ [name='api-root']

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值