drf入门规范

一 Web应用模式

在开发Web应用中,有两种应用模式:

1.1 前后端混合

后端人员,既要写后端,django,又要写前端。只能在网页中使用,不能在小程序、APP中使用,扩展性差。

重要的问题:
模板的渲染是在哪里完成的:前端的模板语法是经过后端渲染成功后,组成一个个字符串(html、css样式)后交给前端来渲染页面。

1.2 前后端分离

-后端人员,只写后端,写一个个的API接口
-前端人员,只写前端页面
-最后项目写完,前后端联调

前后端分离项目是:
前端人员只写页面,返回一个个没有数据的空页面。前端用ajax请求后端的数据,用js把一个个数据插入到对应的位置。
后端人员是只写后端,并且写一个个API接口(eg:BBS中的点赞点踩函数)

二 API接口

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

通过网络,规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介,它称之为API接口
-前端:向后端发送请求,获取数据 127.0.0.1:8080/index --->返回数据
-后端:请求某个地址,返回固定的数据

四大特点

Web API接口简单概括有下面四大特点:

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

  2. 请求方式:get、post、put、patch、delete

  3. 请求参数:

  • get请求参数放在问号后面:127.0.0.1:8080/books?name=红楼梦

  • post请求参数放在请求体中:json或xml格式的key-value类型数据

    • ak:6E823f587c95f0148c19993539b99295
    • region:上海
    • query:肯德基
    • output:json
  1. 响应结果:一般是json格式,也可能是xml

总结:向某个地址发送GET请求方式带一些参数,有返回结果,就是API接口。

三 接口测试工具:Postman

写好的接口,要测试,可以使用浏览器来做,但是浏览器只能发送get请求,接口有其他请求方式

专门的接口测试工具(使用习惯,大致一样)

  • postman,大部分公司用的,原来免费, 后来收费了,但是咱们只用免费部分就够了
  • postwomen
  • apifox

Postman可以直接从官网:https://www.getpostman.com/downloads/下载获得,然后进行安装。

Postman介绍

postman工具的使用:https://blog.csdn.net/zong596568821xp/article/details/123395046

  • 界面导航说明

  • 接口响应
    响应数据是发送请求后经过服务器处理后返回的结果,响应由三部分组成,分别是状态行、响应头、响应体。我们来看下postman的响应数据展示。

  • 注意:在postman中写地址一定要加上/

post请求的不同编码格式

post请求编码格式有多种,主流是3种。

-urlencoded 格式    ---> 默认格式  b'xx=xx&yy=yy'
-form-data 格式     ---> 传文件格式,也可以带数据
	----------------------------251971281264627087498320-  -  带数据,带文件
-json格式(用的最多)-->只能传数据   b'{"name":"lqz","age":18}'

写不同的接口,尝试用不同的编码格式。

路由:使用django1版本

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # get请求
    url(r'^index/', views.index),
    # post请求路由
    url(r'^post_func/', views.post_func),
    url(r'^post_form/', views.post_form),
    url(r'^post_json/', views.post_json),
]

get请求的视图函数

from django.shortcuts import render

# Create your views here.
from django.http import JsonResponse


def index(request):
    # get请求
    username = request.GET.get('username')
    age = request.GET.get('age')
    print(username)
    print(age)
    return JsonResponse({'book': '红楼梦', 'price': 999})

post请求,默认编码格式是urlencode

def post_func(request):
    # post请求,默认编码格式是urlencode
    name = request.POST.get('postname')
    age = request.POST.get('age')
    print(name)
    print(age)
    return JsonResponse({'request_method': 'post', 'code': 'urlencoded'})


"""
结果是:
xxx
20
[15/May/2023 16:17:59] "POST /post_func/ HTTP/1.1" 200 48
"""

post请求,编码格式是form-data,可以上传文件

def post_form(request):
    # post请求,编码格式是form-data,可以上传文件
    name = request.POST.get('postdata')
    age = request.POST.get('age')
    file_obj = request.FILES.get('myfile')

    print(name)
    print(age)
    print(file_obj, type(file_obj))  # token运行原理图片.png <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
    with open(file_obj.name, 'wb') as f:
        for line in file_obj:
            f.write(line)
    return JsonResponse({'request_method': 'post', 'code': 'form-data'})
"""
结果是:
data
22
token运行原理图片.png
[15/May/2023 16:24:23] "POST /post_form/ HTTP/1.1" 200 47
"""

post请求,编码格式是json,数据在request.body中

def post_json(request):
    # post请求,编码格式是json,数据在request.body中
    body = request.body  # b'{"json_name":"json111", "age": 20}'
    body_de = body.decode('utf-8')
    import json
    body_dic = json.loads(body_de)
    print(body_dic.get('json_name'))
    print(body_dic.get('age'))
    return JsonResponse({'request_method': 'post', 'code': 'json'})
"""
结果是:
[15/May/2023 16:46:13] "POST /post_json/ HTTP/1.1" 200 42
json111
20
"""

四 RESTful API规范

RESTful是一种定义API接口的设计风格,AIP接口的编写规范,,尤其适用于前后端分离的应用模式中

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

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

4.1 数据的安全保障

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

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

4.2 url地址中带接口标识:一般这样

用api关键字标识接口url:

4.3 多数据版本共存

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

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

4.6 url地址中带过滤条件 ?后带过滤条件

4.7 响应状态码

4.7.1 http的响应状态码

  • 请求正在处理:1xx

  • 正常响应:2xx

    • 200:常规请求
    • 201:创建成功
  • 重定向响应:3xx

    • 301:永久重定向
    • 302:暂时重定向
  • 客户端错误:4xx

    • 403:请求无权限
    • 404:请求路径不存在
    • 405:请求方法不存在
  • 服务器异常:5xx

    • 500:服务器异常

4.7.2 http的响应的数据中带状态码(公司自己规定的)

{code:100}

MySQLmysql自己规定了自己的状态码,以后发生错误,就可以直接使用状态码来搜索解决方式,这样比较准确。

4.8 返回的数据中带错误信息

{code:101,massige:用户名或密码错误}
{code:100,msg:成功}

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

  • 返回的数据是个字符串,只是有不同的规范。
* GET /collection:返回资源对象的列表(数组)
  - [{name:红楼梦,price:88},{name:西游记,price:88}]
* GET /collection/resource:返回单个资源对象
  - \{name:红楼梦,price:88\} 
* POST /collection:返回新生成的资源对象
  - {id:4,name:红楼梦,price:88}  
* PUT /collection/resource:返回完整的资源对象
  - {id:4,name:红楼梦,price:188}  
* DELETE /collection/resource:返回一个空文档

我们自己的规范是:

* GET  /books:
  - {code:100,msg:成功,data:[{name:红楼梦,price:88},{name:西游记,price:88}]}
* GET /books/1:
  - {code:100,msg:成功,data:{name:红楼梦,price:88}}
* POST /books:
  - {code:100,msg:成功}
* PUT /books/4:
  - {code:100,msg:修改成功}
* DELETE /books/4:      
  - {code:100,msg:删除成功}

4.10 需要url请求的资源需要访问资源的请求链接

# Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
{
  	"status": 0,
  	"msg": "ok",
  	"results":[
        {
            "name":"肯德基(罗餐厅)",
            "img": "https://image.baidu.com/kfc/001.png"
        }
      	...
		]
}

比较好的接口返回

# 响应数据要有状态码、状态信息以及数据本身
{
  	"status": 0,
  	"msg": "ok",
  	"results":[
        {
            "name":"肯德基(罗餐厅)",
            "location":{
                "lat":31.415354,
                "lng":121.357339
            },
            "address":"月罗路2380号",
            "province":"上海市",
            "city":"上海市",
            "area":"宝山区",
            "street_id":"339ed41ae1d6dc320a5cb37c",
            "telephone":"(021)56761006",
            "detail":1,
            "uid":"339ed41ae1d6dc320a5cb37c"
        }
      	...
		]
}

四 序列化和反序列化

api接口开发,最核心最常见的一个过程就是序列化

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

# 反序列化:把别人提供的数据转换/还原成我们需要的格式。
  - 例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中
  • 前端
序列化:drf称为 read      序列化,拿数据的过程,后端从数据库中查询,查询之后给我前端,后端把数据序列化之后给到我前端,我来读取(read)数据,称之为序列化
反序列化:drf称为 write   反序列化,我从前端提交了一些数据,要保存到,写到(write)数据库中,称之为反序列化。

五 Django Rest_Framework

5.1 使用原生的django写图书的增删改查

# book表为例,写5个接口(后面你写的所有接口,都是这5个,及其变形)
    -查询所有图书
    -新增一本图书
    -修改一本图书
    -查询一本图书
    -删除一本图书

5.1.1 创建模型操作类,models.py

from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()

数据库迁移命令:

python38 manage.py makemigrations
python38 manage.py migrate

5.1.2 路由:

from django.urls import path
from app01 import views

urlpatterns = [
    path('books/', views.BookView.as_view()),
    path('books/<int:pk>/', views.BookDetailView.as_view()),
]

5.1.3 视图views.py

from django.http import JsonResponse
from django.views import View
from . import models  # 一个点代表当前目录(即当前app01目录)
import json


# 视图类
class BookView(View):
    back_dic = {'code': 200, 'msg': ''}

    def get(self, request):
        # 查询所有
        book_obj = models.Book.objects.all()
        # 操作数据表
        # 定义一个空列表,循环每个queryset对象,加到新的空列表中
        book_list = []
        for obj in book_obj:
            book_list.append({'name': obj.name, 'price': obj.price})

        # 修改返回信息
        self.back_dic['msg'] = '查询所有成功'
        self.back_dic['data'] = book_list
        return JsonResponse(self.back_dic)
	
    # 路径一致,就可以写在同一个视图类中
    def post(self, request):
        # 新增一条记录
        name = request.POST.get('name')
        price = request.POST.get('price')
        # print(name, price)
        # 操作数据表
        models.Book.objects.create(name=name, price=price)
        # 修改返回信息
        self.back_dic['msg'] = '新增一条成功'
        self.back_dic['data'] = {'name': name, 'price': price}
        return JsonResponse(self.back_dic)


class BookDetailView(View):
    back_dic = {'code': 200, 'msg': ''}

    def get(self, request, pk):
        # 查询单条数据
        book = models.Book.objects.filter(pk=pk).first()
        print(book)

        # 修改返回信息
        self.back_dic['msg'] = '查询一条成功'
        self.back_dic['data'] = {'name': book.name, 'price': book.price}
        return JsonResponse(self.back_dic)

    def put(self, request, pk):
        # 修改一条数据,使用put请求方式,使用json格式提交数据
        # put 请求urlencode提交的数据,在request.POST取不到数据
        print(request.POST)  # <QueryDict: {}>
        # 反序列化数据
        dic = json.loads(request.body.decode('utf-8'))  # {'name': '红楼梦新版', 'price': 999}
        # 操作数据表
        book_obj = models.Book.objects.filter(pk=pk).update(name=dic.get('name'), price=dic.get('price'))
        print(book_obj)  # 1  # ????????????????????????????

        # 修改返回信息
        self.back_dic['msg'] = '修改一条成功'
        self.back_dic['data'] = dic
        return JsonResponse(self.back_dic)

    def delete(self, request, pk):
        # 操作数据表
        models.Book.objects.filter(pk=pk).delete()
        # 修改返回信息
        self.back_dic['msg'] = '删除记录成功'
        return JsonResponse(self.back_dic)

5.2 drf介绍

django 中自带的app,djangorestframework:drf,帮助我们,快速实现符合resful规范的接口(只能在django框架中使用)

下载

pip3.8 install djangorestframework==稍微降版本(使用django2)
# 如果你是django2, 直接这样装,装最新drf,他们不匹配---> pip会自动把django卸载,安装最新django,安装最新drf

# django3 ,这样没有任何问题,就可以安装最新版本


pip3.8 install djangorestframework --upgrade  # 强制更新


# 注意:
  -python和pip是成对的
  -如果装了多版本python一定要注意这个pip是跟谁一对


# 补充一点点东西:
    -如果写了一个包,或app,想给别人用---> 把你写的包,放到pypi上别人pip install 安装--> 使用

5.3 drf快速使用(不需要会)

这个是终极写法,现在只需了解,最后学完drf会写即可。

5.3.1 定义路由

from app01.views import BookView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('books', BookView, 'books')
urlpatterns = [
]
urlpatterns += router.urls

5.3.2 视图函数

from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet


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

# 5个接口只写一个函数类就行了

5.3.3 序列化类

在app01应用目录中新建serializers.py用于保存该应用的序列化器。

创建一个BookSerializer用于序列化与反序列化。

# 创建序列化器类,回头会在试图中被调用
from rest_framework import serializers
from .models import Book


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

5.4 基于APIView的5个接口(drf提供的)

序列化类serializer.py

from rest_framework import serializers
from .models import Book


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

路由

from django.urls import path
from app01 import views

urlpatterns = [
    path('books/', views.BookView.as_view()),
    path('books/<int:pk>/', views.BookDetailView.as_view()),
]

视图类:

from .models import Book  # 一个点代表当前目录(即当前app01目录)
from rest_framework.views import APIView
from .serializer import BookSerializer  # 序列化类
from rest_framework.response import Response


# drf中的views.py文件中有个APIView类
# APIView+序列化类+Response写5个接口
class BookView(APIView):
    def get(self, request):
        # 查询所有
        book_list = Book.objects.all()
        # drf提供了序列化组件(先别关注)
        ser = BookSerializer(instance=book_list, many=True)  # 序列化
        return Response({'code': 100, 'msg': '查询成功', 'result': ser.data})

    def post(self, request):
        # 新增数据
        ser = BookSerializer(data=request.data)  # 反序列化
        if ser.is_valid():  # 数据校验---> 有些不合法的禁止
            ser.save()  # 保存到数据库中

        return Response({'code': 100, 'msg': '新增数据成功'})


class BookDetailView(APIView):
    def get(self, request, pk):
        # 查询单条
        book = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=book, many=False)  # 序列化
        return Response({'code': 100, 'msg': '单条查询成功', 'result': ser.data})

    def put(self, request, pk):
        # 修改单条
        book = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=book, data=request.data)  # 反序列化
        if ser.is_valid():  # 数据校验---> 有些不合法的禁止
            ser.save()  # 保存到数据库中
        return Response({'code': 100, 'msg': '修改数据成功', 'data': ser.data})

    def delete(self, request, pk):
        # 删除单条
        Book.objects.filter(pk=pk).delete()
        return Response({'code': 100, 'msg': '删除成功'})

六 源码分析

导入View的不同方式

from django.views import View
from django.views.generic import View
from django.views.generic.base import View


# View类的具体位置就在:django包下的views包下的generic包下的base.py文件中

# 其他方式为什么能够导入?
    1. views的__init__.py文件中直接导入了View类
        from django.views.generic.base import View

        __all__ = ['View']

    2. generic的__init__.py文件也导入了View类
        from django.views.generic.base import View

        __all__ = ['View']

6.1 CBV源码分析

# cbv写法:
	1 视图中写视图类,继承View,写跟请求方式同名的方法
    	class BookView(View):
            def get(self,request):
                return 四件套(render,reidrect,HttpResponse, JsonResponse)
     2 在路由中写
    	path('books/', BookView.as_view())  # 一定要加括号
        
        
        
# 如上写法,为什么能够执行

# 前置条件:前端请求,一旦路径匹配成功,就会执行  BookView.as_view()(request传入,)
# 1.入口在  BookView.as_view()--->执行结果---> View中有个as_view类的绑定方法
    @classmethod
    def as_view(cls, **initkwargs):
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            res=self.dispatch(request, *args, **kwargs)
            return res
        return view
    
# 2.执行结果是view 的内存地址: 请求来了,执行view(request)
	path('books/', view)
    
# 3.执行 View类中的as_view方法中的内层的view函数,路由匹配成功,本质是在执行
	self.dispatch(request, *args, **kwargs)
    # self是谁的对象?BookView的对象
    # 去BookView中dispatch,找不到,去父类,View中找到了
    
    
# 4.View这个类的dispatch
    def dispatch(self, request, *args, **kwargs):
        # request.method.lower() 如果是get请求,  'get' 在这个列表里面
        if request.method.lower() in self.http_method_names:
            # handler=getattr(BookView的对象,'get')   
            # handler就是BookView类中的get方法
            handler = getattr(self, request.method.lower())
        else:
            handler = self.http_method_not_allowed
        # 执行 BookView类中的get方法 (request)
        return handler(request, *args, **kwargs)
    
# 最终本质跟写fbv的执行流程一样
# 最终结论:什么请求方式,就会执行视图类中的什么方法


# 补充:
1.path()是个函数,一旦路径匹配成功,就会调用第二个参数加括号执行,并把第二个参数传入进去。
2.反射,getattr(对象, '字符串'),根据字符串动态的去对象中找属性或者方法,如果能找到直接返回属性值或函数内存地址。不存在,有第三个参数返回第三个参数,没有就报错。

6.2 APIView执行流程分析

# 有了drf,后期都写CBV,都是继承APIView及其子类
# 执行流程:
	-入口:path('books/', BookView.as_view())---> 请求来了,执行BookView.as_view()(request)
    -as_view 是谁的? APIView的as_view
        @classmethod
        def as_view(cls, **initkwargs):
            # super()代指的是:父类对象  View类的对象
            # super()在哪里就从那个类开始找
            # View的as_view(**initkwargs)----> 执行结果是view,是View类的as_view方法中的view
            view = super().as_view(**initkwargs)
            view=csrf_exempt(view)  # 局部禁用csrf,跟加上csrf_exempt装饰器的作用是一样的
            return view
        
  	-path('books/', View类的as_view中的view,只是去掉了csrf的认证)
	-请求来了,执行 【View类的as_view中的view,只是去掉了csrf的认证(request)】
    -执行:self.dispatch(request, *args, **kwargs), self要从根上找
	-self.dispatch 是APIView的dispatch,源码如下
        def dispatch(self, request, *args, **kwargs):
            # 等号右边括号内的request是老的request,(django.core.handlers.WSGIRequest)
            # 等号左边的request 是新的request,(rest_framework.request.Request)   
            # 把老的request封装到了新的request中了
            request = self.initialize_request(request, *args, **kwargs)
            self.request = request
            try:
                # 执行了认证,权限和频率。
                self.initial(request, *args, **kwargs)
                # 在执行视图类方法之前,去掉了csrf认证,包装了新的request,执行了认证频率和权限
                #### 执行请求方式字符串对应的方法
                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
                # 真正的执行自己类中的get()方法
                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
    
        
# 补充(装饰器语法糖):
  1.fbv,局部禁用csrf,如何写?
    @csrf_exempt
    def index(request):
        pass
    
    本质原理是(装饰器本质):index=csrf_exempt(index)

  2.闭包函数的定义(闭包函数跟语言无关)
    - 定义在函数内部
    - 对外部作用域有引用

总结:

# 总结:
    1  以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
    2  以后只要继承APIView的所有视图类的方法 中的request是新的request了
    3  在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
    4  期间除了各种错误,都会被异常捕获,统一处理

源码跳转比较快,找不见原来的位置,就需要显示工具栏

posted @ 2023-05-17 21:51  星空看海  阅读(28)  评论(0编辑  收藏  举报