0. DRF之软件开发模式&CBV源码解析

1. Web应用模式

在开发Web应用中有两种应用模式:
1. 前后端不分离(混合开发), 需要写模板, 页面的渲染在服务器上完成.
2. 前后端分离, 只专注于写后端接口, 返回JSON/XML数据, 页面的渲染在浏览器完成. 

1.1 动/静态页面

动态页面: 显示的内容却是可以随着时间, 环境或者数据库操作的结果而发生改变的.
静态页面: , 无需经过服务器的编译, 直接加载到客户浏览器上显示出
静态页面因为不需要而外的操作, 所有请求速度快. 
大型的网站首页, 会将动态的页面静态化, 不需要每次都查询数据库, 当数据发生了变化之后再次生成一个静态页面.

1.2 前后端不分离

image-20220412224754481

用户发送请求后得到的是模板语法渲染后的HTML页面.

1. 3前后端分离

image-20220412225052251

用户第一次访问的时候, 被另一个静态文件服务接收, 并放回一个静态页面.
之后再基于静态页面的js代码向服务器发送请求, 服务返回页面渲染需要的数据, 静态页面拿到数据后渲染.

1.4 JSON/XML数据格式

json格式占用的资源比xml少.
1. json格式
* json使用的双引号
{
    
    "name":"kid"}
2. xml格式
<xml>
    <name>kid</name>
</xml>

1.5 服务器页面后缀

根据请求内容动态地生成HTML, XML或其他格式文档的Web网页, 返回给用户, 
部分编程语言使用的框架返回的页面会携带一个标识后缀.
java --> jsp
https://xxx.xxx.xxx/xxx.jsp
php  --> php
http://xxx.xxx.xxx/xxx.php
Python --> 没有
http://xxx.xxx.xxx/xxx

2. API接口

API接口: 规定前后端信息交互规则. 通过Web通信, 也被称为Web API.

2.1 由来

为了形成共识, 防止个人习惯引起的混乱, 需要一套统一的接口规范, 能让前后端写的接口用途一目了然.

2.2 特点

* 1. url长得像返回数据的url链接
	 eg: 百度的api: http://api.map.baidu.com/place/v2/search
* 2. 请求方式: get, post, put, path, delete
* 3. 请求参数: json或xml格式的key-value类型数据
* 4. 响应结果: json或xml的数据
在浏览器中输入: http://api.map.baidu.com/place/v2/search 返回百度地址的API接口
AK参数是自定义的token

image-20220413002141103

携带参数进行访问:
AK=6E823f587c95f0148c19993539b99295
region=上海
query=肯得基
output=json
http://api.map.baidu.com/place/v2/search?ak=1A43B68F2A55B3A07EB09E3B8303E5:FG=1&region=上海&query=肯得基&output=json

2022-04-13_00087

3. Portman

Postman是一个用于构建和使用API的API平台. 支持用户实时发送HTTP网页请求, 帮助用户更好地搭建应用程序,
提高程序开发效率, 同时软件内置了测试语言脚本, 支持用户自定义测试脚本使用.

3.1 下载

* 1. 下载地址: https://www.Postman.com/

2022-04-12_00080

* 2. 点击下载

2022-04-12_00079

3.2 安装

* 1. 双击安装, 没有配置可以选择.
     默认安装在 C:\Users\用户\AppData\Local\Postman

2022-04-12_00081

* 2. 进入软件界面

2022-04-12_00082

2022-04-12_00083

3.3 建立连接

* 1. 新建请求

2022-04-13_00088

* 2. 选择请求方式

2022-04-13_00089

* 3. 请求参数 请求头 请求体的设置

2022-04-13_00090

2022-04-13_00091

* 4. 新建一个收藏目录

2022-04-13_00092

* 5. 在目录下添加请求

image-20220413120022582

* 6. 收藏目录具备的功能
     没有可用的API就不测试运行了

2022-04-13_00094

3.4 导出与导入

在测试的时候可以将请求收藏目录导出成一个文件, 可以将文件发给自己合作伙伴, 对方将文件导入即可使用.
1. 导出
* 1. 导出请求文件

2022-04-13_00095

* 2. 选择位置保存

image-20220413120844548

2. 导入
* 3. 选择导入

image-20220413121208022

* 2. 选择上传文件

image-20220413121334479

* 3. 找到文件, 点击确定

image-20220413121425825

* 4. 导入预览, 点击导入

image-20220413121502575

* 5. 导入成功

image-20220413121538956

4. Restful规范

4.1 简介

REST: 全称Rpsentaional State Transfer, 表特征状态转移.
Restful 是一种定义Web API接口的设置风格, 适用于前后端分离的应用模式.
可以在任何框架实现符合Restful规范的API接口.

4.2 规范

Restful拥有10条规范:
* 1. 数据安全保障: url链接一般采用https协议进行传输. https可以提高数据交互过程中的安全性.
     http  不加密的json格式被抓包工具(fiddler, charles...)捕获数据后很容易被解析出来.
* 2. 接口的特征表现: 一看就知道是API接口
     https://api.baidu.com
     https://www.baidu.com/api
     在路由或域名中携带api字眼
* 3. 多数据版本共存: 项目后续可能升级, 之前的API接口, 某些客户端可能在使用, 另外开设API接口.
     在url链接中标识数据的版本: 
     https://api.baidu.com/v1
     https://api.baidu.com/v2
     url链接v1, v2就是不同数据版本的体现, 只有在获取同一种资源下有多版本的情况.
动词不使用, 但利用请求方式去描述了需要操作的动词.
* 4. 数据及资源, 路径均使用名称(一般提倡资源使用复数形式)
     接口一般都是完成前后端数据的交互, 交互的数据称之为资源
     https://api.baidu.com/users
     https://api.baidu.com/books
     在url链接中, 尽量不要出来资源的动词, eg: get_books, del_books...
     特殊的接口可以出现动词, 这些接口没有明确的资源, 或这个动词就是接口的核心含义.
     https://api.baidu.com/place/search
     https://api.baidu.com/login
* 5. 资源操作由请求方式决定(method)
	 操作资源一般都会涉及增删改查, 通过不同的请求方式来标识这些动作.
	 https://api.baidu.com/books     get请求:    获取所有书籍
	 https://api.baidu.com/books/1   get请求:    获取主键为1的书籍
	 https://api.baidu.com/books     post请求:   新增书籍
	 https://api.baidu.com/books/    post请求:   修改主键为1的书籍
	 https://api.baidu.com/books/1   put请求:    整体修改主键为1的书籍
	 https://api.baidu.com/books/1   patch请求:  局部修改主键为1的书籍
	 https://api.baidu.com/books/1   delete请求: 删除主键为1的书籍
* 6. 过滤, 通过在url中传递参数的形式, 携带搜索条件
	 https://ap1.xxx.com/v1/books?limit=10   指定返回数据的数量
	 https://ap1.xxx.com/v1/books?offset=10  指定返回记录的开始位置, (获取了10条数据, 再向前偏移10)
	 https://ap1.xxx.com/v1/books?page=2&per_page=100 指定第几页, 以及每条的记录数
	 https://api.xxx.com/v1/books?sortby=name&order=asc 指定返回结果安装哪个属性排序, 及排序方式
	 https://ap1.xxx.com/v1/books?book_id=1  指定筛选条件
* 7. 响应状态码
     正常响应: 响应状态码2xx
     	200 常规请求
     	201 创建数据成功
     重定向响应: 响应状态码3xx
     	301 永久重定向
     	302 展示重定向
     客户端异常: 响应状态码4xx
     	403 请求无权限
     	404 请求路径不存在
     	405 请求路径存在,但是请求的方法不存在
     服务器异常:响应状态码5xx
     	500 服务器异常
* 8. 错误处理, 因该放回错误信息, error作为key
	 {
    
    
		error: '无权限操作!'
	 }
* 9. 返回结果, 针对不同的操作, 服务器想用户返回非结果符合以下规范:
	 GET  /books     返回资源对象的列表或者数组
	 GET  /books/1   返回单个资源对象
	 POST /books     返回新生成的对象资源
	 PUT  /books/1   返回完整的资源对象
     PATCH /books/1  返回完整的资源对象
     DELETE /books/1 返回一个空文档
* 10. 需要url请求的资源, 需要返回资源的请求资源
	  返回结果在家哦你提供链接, 连向其他API方法, 使得用户不查文档, 也知道下一步需要做什么
	  {
    
    
	  	"code": 200,
	  	"msg": "ok",
	  	"results: [
	  		{
    
    
	  			"name": "开局签到荒古圣体",
	  			"img": "https://image.novels.com/books/cover.png"
	  		}
	  		...
	  	]
	  }

5. DRF安装与使用

全称: djangorestframework 是基于Django框架, 用于快速构建Web Restful API的工具.
新建一个Django项目, 模板目录路径报错问题.

image-20220413195932183

5.1 安装

使用3.10.3版本
pip3.6 install djangorestframework==3.10.3 

5.2 使用

* 1. 项目配置文件的INSTALLED_APPS属性中注册rest_framworh
    # 注册drf
    'rest_framework',
* 2. 在app01下的models.py文件中创建一个映射表关系的类
from django.db import models


# Create your models here.

# 0. 书籍表
class Book(models.Model):
    # 0.1 id自定生成
    # 0.2 书的名称
    title = models.CharField(max_length=32, verbose_name='书名')
    # 0.3 书的价格 共5位小数占两位
    price = models.DecimalField(max_digits=5, decimal_places=2)
    # 0.4 作者
    author = models.CharField(max_length=32, verbose_name=32)
* 3. 生成表
	1. 生成表记录:  python3.6 manage.py makemigrations
	2. 数据库迁移命令: python3.6 manage.py migrate
* 4. 在tests.py测试文件中 写入两条数据
	import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_drf_01.settings")
    import django

    django.setup()

    from app01.models import Book

    # 批量插入数据
    # 1. 定义空一个列表
    queryset_list = []
    #  2. 写入的数据
    data_list = [{
    
    'title': '开局签到荒古圣体', 'price': 123.12, 'author': 'kid'},
                 {
    
    'title': '开局签到五费卡', 'price': 321.21, 'author': 'qq'}]
    # 3. 生成对象并添加到列表中
    for dic in data_list:
        book_obj = Book(**dic)
        queryset_list.append(book_obj)

    # 4. 批量插入数据
    Book.objects.bulk_create(queryset_list)

image-20220413221317078

不想敲代码就手动输入
Navicat 快速打开sqlite3的文件

GIF 2022-4-13 22-35-24

* 4 . 在应用app01下创建一个ser.py 文件 序列化表
# 0. 导入模型序列化器
from rest_framework.serializers import ModelSerializer
# 1. 带入表
from app01.models import Book


# 定义一个模型序列化器
class BookModelSerializer(ModelSerializer):
    # 定义元类
    class Meta:
        # 需要转换的表
        model = Book
        # 需要转换的字段 所有
        fields = "__all__"
* 5. 写地址book的路由
from django.conf.urls import url
from django.contrib import admin
# 0. 导入视图层
from app01 import views
# 1. 导入 默认路由器
from rest_framework.routers import DefaultRouter

# 2. 生成一个路由对象 --> list
router = DefaultRouter()
print(router)
# 3. 注册一个路由
router.register('books', views.BooksViewSet)

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]
* 5. 写BooksViewSet的视图函数
# 0. 导入模型视图集
from rest_framework.viewsets import ModelViewSet
# 1. 导入模型层
from .models import Book
# 2. 导入模型序列化器
from .ser import BookModelSerializer


# 3. 定义视图函数
class BooksViewSet(ModelViewSet):
    # 查询表所有的数据
    queryset = Book.objects.all()
    # 查询表所有的数据并序列化
    serializer_class = BookModelSerializer
class BooksViewSet(ModelViewSet): 
ModelViewSet 继承 GenericViewSet 继承 views.APIView 继承 View
* 6. 启动Django
     在浏览器中输入 127.0.0.1:8000

image-20220413224108610

* 7. 在浏览器中输入 127.0.0.1:8000/books 或点击上图中的返回链接, 返回含义json格式数据的页面.
     127.0.0.1:8000/books/1 查询主键为1书籍的信息
     127.0.0.1:8000/books/2 查询主键为2书籍的信息

GIF 2022-4-13 22-46-50

5.3 Postman连接

部分起你去Postman可能不支持重定向操作, 就手动在后面加上/
Django让请求端加/后再次请求, Django能操作浏览的url, 操作不动Postman...
1. 查
提交post请求返回json格式的数据
127.0.0.1:8000/books/
127.0.0.1:8000/books/1/
127.0.0.1:8000/books/2/

GIF 2022-4-13 22-55-18

2. 删
提交delete请求, 删除数据返回空
127.0.0.1:8000/books/1/    

2022-04-13_00108

3. 改
提交put请求, 返回数据对象
提交数据选择body, 点击raw, 选择JSON格式数据
127.0.0.1:8000/books/2/    
{
    
    
    "id": 2,
    "title": "开局签到金克斯",
    "price": "12.30",
    "author": "qq"
}

2022-04-13_00109

返回的信息

image-20220413231356605

4. 增
提交post请求, 返回新增的对象
提交数据选择body, 点击raw, 选择JSON格式数据
127.0.0.1:8000/books/
{
    
    
    "title": "Python入门到放弃",
    "price": "66.6",
    "author": "kid"
}

image-20220413231927274

6. Dajngo View源码解析

CBV视图类必须继承View
Python下载的模块Python的lib/site-packages下
在看源码的时候可以打开展示组成部分, 代开之后, py文件可以展示函数, ..
在源码中会看见一些函数名点属性的情况, 那么在这说明一下, Python中一切皆对象,
只要对象有.__setattr__方法就可以拥有属性, 即可以存储值, 那么对象有.__getattr__便可以取值.
def func2():
    pass


func2.x = 1
print(type(func2), func2.x)  # <class 'function'> 1

"""
func即是函数内存地址也是一个function类,
func.属性 = 值
.属性会执行类中__setattr__方法, 函数是由关键字def构建的, 这个__setattr__方法无法看见
只需要知道可以这样操作即可
"""
查看源码先将展示组成部分打开, 方便查找.

2022-04-13_00115

image-20220413235654119

* 1. 写一个路由
    # 写一个路由
    url(r'^books2/', views.Books2.as_view()),
* 2. 视图类
# 4. 导入Vies类
from django.views import View
# 5. 导入HttpResponse
from django.shortcuts import HttpResponse


# 6. 定义视图类
class Books2(View):
    # 6.1定义get方法
    def get(self, request):
        # 6.2 返回响应
        return HttpResponse('OK')
url(r'^books2/', views.Books2.as_view())  
url的第二个参数是一个函数的内存地址, 
那么Books2.as_view执行之后肯定得到一个函数的内存地址.
在Books2中没有写.as_view方法, 那么就去父类中查找, 父类View中有该方法.
View文件所在文件:
C:\Users\用户名\AppData\Roaming\Python\Python36\site-packages\django\views\generic\base.py
class classonlymethod(classmethod):
    ...
# classonlymethod继承了classmethod, 在它的基础上添加了方法
    
@classonlymethod 
def as_view(cls, **initkwargs):
	...
# as_view就是一个类方法, 给类使用, 将类名作为一个参数自动传递.

2022-04-14_00116

url(r'^books2/', view(request))  
路由匹配成功的时候, 函数内存地址会加括号执行, 并把request作为第一个参数进行传递
def view(request, *args, **kwargs):
    # request 是当前请求的数据
    # cls是Books2, 得到一个Books2的对象
    self = cls(**initkwargs)
    # hasattr反射, 如果对象中用get方法 and 没有head方法
    if hasattr(self, 'get') and not hasattr(self, 'head'):
        # 对象的head属性 = self的get
        self.head = self.get
        # 对象的request属性 = request请求数据
        self.request = request
        # 对象的args属性接收所有位置参数
        self.args = args
        # 对象的**kwargs属性接收所有的关键字参数
        self.kwargs = kwargs
        # 对象的dispatch加括号执行, 自己写的Books2类中没有改方法就去父类中查找
        return self.dispatch(request, *args, **kwargs)
父类中有dispath方法

2022-04-14_00120

# 对象.方法将对象作为第一参数进行传递
def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
    # 判断当前的请求方式, 转小写 是否在对象的http_method_names属性中
	if request.method.lower() in self.http_method_names:
        # 如果存在就通过反射拿到这个方法的内置地址
        # 自己没有在类中定义就无法获取数据, 返回默认值, 抛出一个错误信息,请求方法不允许.
		handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
	else:
        # 请求不方式不在对象的http_method_names属性中, 抛出一个错误信息,请求方法不允许.
		handler = self.http_method_not_allowed
    # 获取到到当前请求方法对应的方法加括号执行
	return handler(request, *args, **kwargs)
父类中有http_method_names属性

2022-04-14_00121

http_method_names 在自己写的类中定义的话可以对请求进行限制.
执行了方法之后, 开始返回数据.

2022-04-14_00122

7. DRF APIView源码解析

APIView是rest_framework的
导入 from rest_framework.views import APIView
* 1. 定义一个路由
    # 写一个路由 查看APIvies源码
    url(r'^books3/', views.Books3.as_view()),
* 2. 定义视图类
# 7. 导入 APIView
from rest_framework.views import APIView


# 8. 定义视图类Books3
class Books3(APIView):
    # 8.1 定义get方法
    def get(self, request):
        # 8.2 返回响应
        return HttpResponse('OK')
url(r'^books3/', views.Books3.as_view())  
那么Books3.as_view执行之后肯定得到一个函数的内存地址.
在Books2中没有写.as_view方法, 那么就去父类中查找, 父类APIView中有该方法.
APIView的中有通过super().as_view(**initkwargs)去拿到View的as_view方法, 得到一个view函数的内存地址.
class APIView(View):
    ...

    @classmethod
    def as_view(cls, **initkwargs):
        # 这句不看
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            ...
        # 调用父类View的as_view方法, 该方法返回一个 view的内置地址 
        view = super().as_view(**initkwargs)
        # 记录参数
        view.cls = cls
        view.initkwargs = initkwargs
		# 添加禁用csrf检验的装饰器, 看下下
        return csrf_exempt(view)
局部禁用csrf检验在函数上加@csrf_exempt
@csrf_exempt  # ==> csrf_exempt(func)
def func(request):
	...
还有一种写法, 在路由中局部添加禁用csrf装饰器
# views.test是一个函数的内存地址, csrf_exempt(函数内存地址) 为函数加上装饰器
from django.views.decorators.csrf import csrf_exempt
url('^test/', csrf_exempt(views.test)),
APIView文件所在位置:
D:\Python\Python3.6\Lib\site-packages\rest_framework\views.py

2022-04-14_00125

只要继承了APIView, 执行as_views(), 在父类的基础上添加了添加scrf_exempt装饰器去除csrf检验
拿到vies函数名后加括号调用
vies() --> View的执行as_views的vies函数
def view(request, *args, **kwargs):
    # 类名加括号, 生成一个books3对象
    self = cls(**initkwargs)
    # 判断对象是都有get方法 and 没有head方法
    if hasattr(self, 'get') and not hasattr(self, 'head'):
		...这里没什么不看了...
    # 对象执行dispatch方法
    return self.dispatch(request, *args, **kwargs)
Books3中没有dispatch方法去父类APIView中查找
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 ha	ndling.
    """
    self.args = args
    self.kwargs = kwargs
    #  self.initialize_request(request, *args, **kwargs), 中request是当前请求的
    #  request 是执行对象的initialize_request重新包装的request, 
    # 对象中没有initialize_request方法去父类中APIView中查找
    request = self.initialize_request(request, *args, **kwargs)
    # 将数据保存
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        # 三大认证模块
        self.initial(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
		# 获取到请求方法之后加括号调用, 将重新包装的request传递进去
        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

8. DRF Request类

看下initialize_request是怎么样包装request的
def initialize_request(self, request, *args, **kwargs):
	# 解析器上下文
    parser_context = self.get_parser_context(request)
	# 通过执行Request包装的
    return Request(
        request,
        parsers=self.get_parsers(),
        authenticators=self.get_authenticators(),
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context
    )
class Request:
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.

    Kwargs:
        - request(HttpRequest). The original request instance.
        - parsers_classes(list/tuple). The parsers to use for parsing the
          request content.
        - authentication_classes(list/tuple). The authentications used to try
          authenticating the request's user.
    """

    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )

        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {
    
    }
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)
APIView文件所在位置:
D:\Python\Python3.6\Lib\site-packages\rest_framework\request.py

2022-04-14_00126

# 7. 导入 APIView
from rest_framework.views import APIView


# 8. 定义视图类Books3
class Books3(APIView):
    # 8.1 定义get方法
    def get(self, request):
        # 查询request对象
        print(request, request._request)
        # <rest_framework.request.Request object at 0x000001BE69EBB5C0>  Request重新封装的
        # <WSGIRequest: GET '/books3/'>  WSGI封装的原始Request
        # 8.2 返回响应
        return HttpResponse('OK')
那么使用request的方法会是怎么样的
没有不要这样去写 request._request.method
# 8. 定义视图类Books3
class Books3(APIView):
    # 8.1 定义get方法
    def get(self, request):  
        print(request.method)  # GET
        # 8.2 返回响应
        return HttpResponse('OK')
被包装request有.属性那么可能写了__getattr__方法
def __getattr__(self, attr):

    try:
        return getattr(self._request, attr)
    # 如果取不出来在通过另一种反法再取
    except AttributeError:
        return self.__getattribute__(attr)

2022-04-14_00127

Request类将所有的post请求数据都封装到data属性中.
# 7. 导入 APIView
from rest_framework.views import APIView

# 8. 定义视图类Books3
class Books3(APIView):
    # 8.1 定义get方法
    def get(self, request):
        print(request.method)  # GET
        # 8.2 返回响应
        return HttpResponse('OK')

    # 8.3 定义post请求方法
    def post(self, request):
        print(request.data)
        # 8.4 返回响应
        return HttpResponse('OK')

image-20220414132837902

image-20220414132940176

image-20220414133125865

data是一个静态方法

2022-04-14_00133

get请求,地址中的参数 request.query_params
print(request.GET)
# 7. 导入 APIView
from rest_framework.views import APIView


# 8. 定义视图类Books3
class Books3(APIView):
    # 8.1 定义get方法
    def get(self, request):
        print(request.query_params)  # GET的请求数据
        # 8.2 返回响应
        return HttpResponse('OK')

image-20220414172505238

APIView的initial方法
def initial(self, request, *args, **kwargs):
    # 认证组件:校验用户 - 游客、合法用户、非法用户
    # 游客:代表校验通过,直接进入下一步校验(权限校验)
    # 合法用户:代表校验通过,将用户存储在request.user中,再进入下一步校验(权限校验)
    # 非法用户:代表校验失败,抛出异常,返回403权限异常结果
    self.perform_authentication(request)
    # 权限组件:校验用户权限 - 必须登录、所有用户、登录读写游客只读、自定义用户角色
    # 认证通过:可以进入下一步校验(频率认证)
    # 认证失败:抛出异常,返回403权限异常结果
    self.check_permissions(request)
    # 频率组件:限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s)
    # 没有达到限次:正常访问接口
    # 达到限次:限制时间内不能访问,限制时间达到后,可以重新访问
    self.check_throttles(request)

9. 读源码总结

CBV源码分析:
	1. 视图类, 必须继承Django的View类
	2. 类中支持多种请求方式, get, post, put, delete, ...请求来执行对应的方法
	3. Django程序执行到 views..as_views(), 立刻执行as_views()方法, 自己的类中没有去父类View中找
	4. 最后一个view闭包函数的内存地址
	5. 请求的路由匹配成功 view加括号调用, 并把第request作为第一个参数传入
	6. view(request) 执行 --> 通过闭包函数的特性拿到类名, 类名加括号生成一个对象, 对象.dispatch()
	7. 自己写的类没有.dispatch()方法去父类Django的View类中查找
	8. dispatch方法将请求转为小写, 在去判断请求是否在属性http_method_names列表中
	9. 自己写的类中没有http_method_names属性去父类Django的View类中查找
	10. 如果请求满足条件, 通过反射获取请求的方法 , 再加括号执行, 并且将request传入.
APIView源码解析:
	1. 视图类, 继承DRF的APIView, APIView继承Django的View
	2. 类中支持多种请求方式, get, post, put, delete, ...请求来执行对应的方法
	3. Django程序执行到views..as_views(), 立刻执行as_views()方法, 自己的类中没有去父类APIView中找
	4. APIView中as_views()继承Django的View类的as_views()方法, 得到一个view闭包函数
       在为这个闭包函数加上一个装饰器器去除csrf校验
	5. 请求的路由匹配成功 view加括号调用, 并把第request作为第一个参数传入
    6. view(request) 执行 --> 通过闭包函数的特性拿到类名, 类名加括号生成一个对象, 对象.dispatch()
    7. 自己写的类没有.dispatch()方法去父类DRF的APIView类中查找
    8. dispatch方法
       8.1 将request重新封装, 得到一个新的request
       8.2 异常捕获下面代码
       8.3 加上三大认证模块
       8.4 再将请求转为小写, 在去判断请求是否在属性http_method_names列表中自己写的类中没有     		       http_method_names属性去父类DRF的APIView类中查找, 还没有则去Django的View中找
       8.5 如果请求满足条件, 通过反射获取请求的方法, 再加括号执行, 并且将重新封装的request传入.
       8.6 将对应请求方法执行的结果赋值给response
       8.7 将异常的结果赋值给response
       8.8 渲染模块, 浏览访问访问什么样的数据格式, Pastman访问返回什么样的数据

    * 注意点视图类的request是DRF的APIView类中的方法重新封装的, 使用方法与原来一模一样, 
      所有的post请求数据被封装到data属性中, 以字典的形式表示.

10 练习

新建一个图书表, 5个符合restful规范的接口, 用CBV的APIView实现.

10.1 创建环境

* 1. 写路由
from django.conf.urls import url
from django.contrib import admin
# 导入视图层
from app01 import views
# 导入禁用csrf装饰器
from django.views.decorators.csrf import csrf_exempt
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 1.路由带api字眼, 2.路由的名字是名词最好是复数, 3.查询数据带版本号
    url(r'^api/books/v1/', scrf_exempt(views.Book.as_view()),
]

* 2. 写CBV视图类
from django.shortcuts import render
# 导入JsonResponse
from django.http import JsonResponse

# Create your views here.
# 导入 View
from django.views import View

# 导入 json模块
import json

# 导入模板
from app01 import models



# 写数图类, 继承View
class Book(View):
    # 定义五个方法 get, post, put, patch, delete
    # 查
    def get(self, request):
        pass

    # 增
    def post(self, request):
        pass

    # 局部修改
    def put(self, request):
        pass

    # 全局修改
    def patch(self, request):
        pass

    # 删除
    def delete(self, request):
        pass

* 3. 模型中写映射表关系的类
from django.db import models


# Create your models here.

# 书籍表 继承Model
class Book(models.Model):
    # 主键自动创建 非空且唯一 自增
    # 书名
    title = models.CharField(max_length=32, verbose_name='书名')
    # 数价格 共5位小数占2为
    price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='价格')
    # 作者
    author = models.CharField(max_length=32, verbose_name='作者')
    
    
    # 获取所有数据组织成一个字段
    def get_data(self):
        book_dict = {
    
    
            'id': self.id,
            'title': self.title,
            'price': float(self.price),  # 将Decimal转为float
            'author': self.author
        }
        return book_dict
* 4. 生成表
	 生成操作记录 python manage.py makemigrations
	 数据库迁移 python manage.py migrate

image-20220414175621287

10.2 增

提交post请求, 创建成功之后返回单个数据对象
提交地址: 127.0.0.1:8000/api/books/v1
提交数据格式json: (id不需要提交, json格式数据是双引号)
{
    
    
    "title": "book1",
    "price": 123.1,
    "author": "aa"
}
django中的request.POST只能取到Content-Type(请求头)为application/x-www-form-urlencoded(form表单默认格式)的数据,如果请求头为application/json(json格式),multipart/form-data(文件)等格式无法取到,只有在request.body里面能取到原生的数据。当发送过来的是JSON数据是,request.POST取到的数据是空的,这时只有用request.body取,再反序列化才能使用。
    # 增
    def post(self, request):
        # 获取提交json格式的数据
        post_data = json.loads(request.body)
        # 将数据写入到表中, 写入成功放回创建的数据对象
        # book_obj = models.Book.objects.create(**post_data)  # Book object 数据对象
        book_obj = models.Book.objects.filter(pk=1).first()
        # 组织返回的数据格式:
        back_dic = {
    
    
            'code': 201,
            'msg': '操作成功',
        }
        back_dic['book_obj'] = book_obj.get_data()
        print(back_dic)
        return JsonResponse(back_dic)

image-20220414185936951

10.3 查

请求方式get
127.0.0.1:8000/api/books/v1/ 获取所有的数据 数据为一个列表套字典, js的元组套对象.
定义一个函数去处理路由的数据, 获取路由中的主键
# 定义一个函数获取路由中的主键值
def get_id(request):
    path = request.path  # /api/books/v1/

    path_list = path.split('/')
    # ['', 'api', 'books', 'v1', ''] 去除空 ['api', 'books', 'v1']
    for i in path_list:
        if not i:
            path_list.remove(i)

    # 解压赋值拿到最后一个值
    *_path, book_id = path_list
    return book_id

# 写数图类, 继承View
class Book(View):
    # 定义五个方法 get, post, put, patch, delete
    # 查
    def get(self, request):
        # 获取get请求数据
        book_id = get_id(request)
        # 返回值
        back_dic = {
    
    
            'code': 200,
            'msg': '查询成功',
        }

        # 如果是book_id是纯数数字则作为主键取值, 不是则取所有的数据
        if book_id.isdigit():
            book_obj = models.Book.objects.filter(pk=book_id).first()
            back_dic['book_obj'] = book_obj.get_data()

        # 获取所有数据
        else:
            book_obj = models.Book.objects.all()
            book_obj_list = []
            for obj in book_obj:
                print(obj)
                book_obj_list.append(obj.get_data())

            back_dic['book_obj'] = book_obj_list

        print(back_dic)
        return JsonResponse(back_dic)

image-20220414195832180

image-20220414195852744

10.3 改

提交put请求, 创建成功之后返回单个数据对象 修改数据以路由中的之间为主
提交地址: 127.0.0.1:8000/api/books/v1/1/	
提交数据格式json: (id不需要提交, json格式数据是双引号)
    # 增
    def post(self, request):
        # 获取提交json格式的数据
        post_data = json.loads(request.body)
        # 将数据写入到表中, 写入成功放回创建的数据对象
        # book_obj = models.Book.objects.create(**post_data)  # Book object 数据对象
        book_obj = models.Book.objects.filter(pk=1).first()
        # 组织返回的数据格式:
        back_dic = {
    
    
            'code': 201,
            'msg': '操作成功',
        }
        back_dic['book_obj'] = book_obj.get_data()
        print(back_dic)
        return JsonResponse(back_dic)

    # 局部修改
    def put(self, request):
        # 主键需要以地址为准
        book_id = get_id(request)

        if book_id.isdigit():

            # 反序列化
            put_data = json.loads(request.body)
            # 修改数据
            models.Book.objects.filter(pk=book_id).update(**put_data)
            # 查
            book_obj = models.Book.objects.filter(pk=book_id).first()
            # 返回值
            back_dic = {
    
    
                'code': 200,
                'msg': '查询成功',
            }
            back_dic['book_obj'] = book_obj.get_data()

        else:
            back_dic = {
    
    
                'code': 404,
                'msg': '路径不存在',
            }
        return JsonResponse(back_dic)

2022-04-14_00140

10.5 删

提交delete请求, 删除成功之后返回空, 删除数据以路由中的之间为主
提交地址: 127.0.0.1:8000/api/books/v1/1/	
提交数据格式json: (id不需要提交, json格式数据是双引号)
    # 删除
    def delete(self, request):
        book_id = get_id(request)
        if book_id.isdigit():
            # 删除数据
            num = models.Book.objects.filter(pk=book_id).delete()
            # 返回数据
            print(num)  # (0, {'app01.Book': 0})
            if num[0]:
                back_dic = ''
                return HttpResponse(back_dic)

        back_dic = {
    
    
            'code': 404,
            'msg': '路径不存在!'
        }

        return JsonResponse(back_dic)

image-20220414204244277

image-20220414204303492

猜你喜欢

转载自blog.csdn.net/qq_46137324/article/details/124170222