学无先后,达者为师

网站首页 编程语言 正文

引入DjangoRESTframework

作者:大托尔 更新时间: 2022-09-25 编程语言

引入DjangoRESTframework

  • web应用模式
    • 1 前后端不分离
    • 2 前后端分离
  • 认识RESTful
  • RESTful设计方法
    • 1. 域名
    • 2. 版本(Versioning)
    • 3. 路径(Endpoint)
    • 3. HTTP动词
    • 4. 过滤信息(Filtering)
    • 6. 状态码(Status Codes)
    • 7. 错误处理(Error handling)
    • 8. 返回结果
    • 9. 其他
  • 使用Django开发REST接口
    • 测试
  • 明确REST接口开发的核心任务
    • 序列化Serialization
    • 总结
  • DjangoRESTframework简介
    • 认识Django REST framework
    • 特点
  • 笔记

在本章中,我们要大家介绍为什么学习Django REST framework,它能帮助我们做哪些事情。
课程思路:
我们从分析现在流行的前后端分离Web应用模式说起,然后介绍如何设计REST API,通过使用Django来实现一个REST API为例,明确后端开发REST API要做的最核心工作,然后介绍Django REST framework能帮助我们简化开发REST API的工作。

web应用模式

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

  • 前后端不分离
  • 前后端分离

1 前后端不分离

在这里插入图片描述
在前后端不分离的应用模式中,前端页面看到的效果都是由后端控制,由后端渲染页面或重定向,也就是后端需要控制前端的展示,前端与后端的耦合度很高。

这种应用模式比较适合纯网页应用,但是当后端对接App时,App可能并不需要后端返回一个HTML网页,而仅仅是数据本身,所以后端原本返回网页的接口不再适用于前端App应用,为了对接App后端还需再开发一套接口。

2 前后端分离

在这里插入图片描述
前后端不分离,两个服务器
在前后端分离的应用模式中,后端仅返回前端所需的数据,不再渲染HTML页面,不再控制前端的效果。至于前端用户看到什么效果,从后端请求的数据如何加载到前端中,都由前端自己决定,网页有网页的处理方式,App有App的处理方式,但无论哪种前端,所需的数据基本相同,后端仅需开发一套逻辑对外提供数据即可。

在前后端分离的应用模式中 ,前端与后端的耦合度相对较低。

在前后端分离的应用模式中,我们通常将后端开发的每个视图都称为一个接口,或者API,前端通过访问接口来对数据进行增删改查。

前后端分类,js渲染,两个服务器,后端不用编写任何前端代码
前后端不分离,模板渲染,一个服务器,后端可能要编写前端代码

认识RESTful

在前后端分离的应用模式里,API接口如何定义?

例如对于后端数据库中保存了商品的信息,前端可能需要对商品数据进行增删改查,那相应的每个操作后端都需要提供一个API接口:

  1. POST /add-goods 增加商品
  2. POST /delete-goods 删除商品
  3. POST /update-goods 修改商品
  4. GET /get-goods 查询商品信息
    对于接口的请求方式与路径,每个后端开发人员可能都有自己的定义方式,风格迥异。

是否存在一种统一的定义方式,被广大开发人员接受认可的方式呢?

这就是被普遍采用的API的RESTful设计风格。

RESTful设计方法

RESTful 用于规范接口的设计,来使广大人员都可以接受

1. 域名

应该尽量将一类处理类似数据的API部署在专用域名之下。

比如说对手机操作的api 可以设置一个 shouji.jd.com 域名

https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

https://example.org/api/

2. 版本(Versioning)

应该将API的版本号放入URL。

http://www.example.com/app/1.0/foo

http://www.example.com/app/1.1/foo

http://www.example.com/app/2.0/foo

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URL。版本号可以在HTTP请求头信息的Accept字段中进行区分(参见Versioning REST Services):

Accept: vnd.example-com.foo+json; version=1.0

Accept: vnd.example-com.foo+json; version=1.1

Accept: vnd.example-com.foo+json; version=2.0

3. 路径(Endpoint)

路径又称"终点"(endpoint),表示API的具体网址,每个网址代表一种资源(resource)

(1) 资源作为网址,只能有名词,不能有动词,而且所用的名词往往与数据库的表名对应。

举例来说,以下是不好的例子:

/getProducts
/listOrders
/retreiveClientByOrder?orderId=1

对于一个简洁结构,你应该始终用名词。 此外,利用的HTTP方法可以分离网址中的资源名称的操作。

GET /products :将返回所有产品清单
POST /products :将产品新建到集合
GET /products/4 :将获取产品 4
PATCH(或)PUT /products/4 :将更新产品 4

(2) API中的名词应该使用复数。无论子资源或者所有资源。

举例来说,获取产品的API可以这样定义

获取单个产品:http://127.0.0.1:8080/AppName/rest/products/1
获取所有产品: http://127.0.0.1:8080/AppName/rest/products

最后的数字表示操作数据的id

3. HTTP动词

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面四个(括号里是对应的SQL命令)。

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • DELETE(DELETE):从服务器删除资源。

还有三个不常用的HTTP动词。

  • PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)。
  • HEAD:获取资源的元数据。
  • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

下面是一些例子。

GET /zoos:列出所有动物园
POST /zoos:新建一个动物园(上传文件)
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

动物园的指定动物

4. 过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoos/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

6. 状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

  • 200 OK - [GET]:服务器成功返回用户请求的数据
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 更多情况直接返回200
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作
  • 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
    状态码的完全列表参见这里或这里。

记住 200 401 403 404 500

7. 错误处理(Error handling)

如果状态码是4xx,服务器就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

{
    error: "Invalid API key"
}

8. 返回结果

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

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

9. 其他

服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

使用Django开发REST接口

在这里插入图片描述
前端人员可以通过这个写ajax请求

我们以在Django框架中使用的图书英雄案例来写一套支持图书数据增删改查的REST API接口,来理解REST API的开发。
在这里插入图片描述

在这里插入图片描述
在这可以快速进行数据导入

在此案例中,前后端均发送JSON格式数据。
在这里插入图片描述
新增数据的时候,传递的参数根据模型类设计,没有默认值,没有允许为空一般就是必传的字段
在这里插入图片描述
在这里插入图片描述

最外层是列表的,想返回json数据的,要添加:JsonResponse(book_list, safe=False)
在这里插入图片描述
status为返回的状态码
在这里插入图片描述
返回创建后的结果
在这里插入图片描述
发送json数据一定要使用双引号
在这里插入图片描述
发送的时候会出现csrf认证失败,先注销掉csrf机制

# views.py

from datetime import datetime

class BooksAPIVIew(View):
    """
    查询所有图书、增加图书
    """
    def get(self, request):
        """
        查询所有图书
        路由:GET /books/
        """
        queryset = BookInfo.objects.all()
        book_list = []
        for book in queryset:
            book_list.append({
                'id': book.id,
                'btitle': book.btitle,
                'bpub_date': book.bpub_date,
                'bread': book.bread,
                'bcomment': book.bcomment,
                'image': book.image.url if book.image else ''
            })
        return JsonResponse(book_list, safe=False)

    def post(self, request):
        """
        新增图书
        路由:POST /books/ 
        """
        json_bytes = request.body
        json_str = json_bytes.decode()
        book_dict = json.loads(json_str)

        # 此处详细的校验参数省略

        book = BookInfo.objects.create(
            btitle=book_dict.get('btitle'),
            bpub_date=datetime.strptime(book_dict.get('bpub_date'), '%Y-%m-%d').date()
        )

        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image.url if book.image else ''
        }, status=201)


class BookAPIView(View):
    def get(self, request, pk):
        """
        获取单个图书信息
        路由: GET  /books/<pk>/
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        return JsonResponse({
        # 此段代码直接进行复制
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image.url if book.image else ''
        })

    def put(self, request, pk):
        """
        修改图书信息
        路由: PUT  /books/<pk>
        """
        # 更新数据的代码
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        json_bytes = request.body
        json_str = json_bytes.decode()
        book_dict = json.loads(json_str)

        # 此处详细的校验参数省略

        book.btitle = book_dict.get('btitle')
        book.bpub_date = datetime.strptime(book_dict.get('bpub_date'), '%Y-%m-%d').date()
        book.save()

        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image.url if book.image else ''
        })

    def delete(self, request, pk):
        """
        删除图书
        路由: DELETE /books/<pk>/
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        book.delete()

        return HttpResponse(status=204)
# urls.py

urlpatterns = [
    url(r'^books/$', views.BooksAPIVIew.as_view()),
    url(r'^books/(?P<pk>\d+)/$', views.BookAPIView.as_view())
]

测试

使用Postman测试上述接口

1) 获取所有图书数据

GET 方式访问 http://127.0.0.1:8000/books/, 返回状态码200,数据如下

[
    {
        "id": 1,
        "btitle": "射雕英雄传",
        "bpub_date": "1980-05-01",
        "bread": 12,
        "bcomment": 34,
        "image": ""
    },
    {
        "id": 2,
        "btitle": "天龙八部",
        "bpub_date": "1986-07-24",
        "bread": 36,
        "bcomment": 40,
        "image": ""
    },
    {
        "id": 3,
        "btitle": "笑傲江湖",
        "bpub_date": "1995-12-24",
        "bread": 20,
        "bcomment": 80,
        "image": ""
    },
    {
        "id": 4,
        "btitle": "雪山飞狐",
        "bpub_date": "1987-11-11",
        "bread": 58,
        "bcomment": 24,
        "image": ""
    },
    {
        "id": 5,
        "btitle": "西游记",
        "bpub_date": "1988-01-01",
        "bread": 10,
        "bcomment": 10,
        "image": "booktest/xiyouji.png"
    },
    {
        "id": 6,
        "btitle": "水浒传",
        "bpub_date": "1992-01-01",
        "bread": 10,
        "bcomment": 11,
        "image": ""
    },
    {
        "id": 7,
        "btitle": "红楼梦",
        "bpub_date": "1990-01-01",
        "bread": 0,
        "bcomment": 0,
        "image": ""
    }
]

2)获取单一图书数据

GET 访问 http://127.0.0.1:8000/books/5/ ,返回状态码200, 数据如下

{
    "id": 5,
    "btitle": "西游记",
    "bpub_date": "1988-01-01",
    "bread": 10,
    "bcomment": 10,
    "image": "booktest/xiyouji.png"
}

GET 访问http://127.0.0.1:8000/books/100/,返回状态码404

3)新增图书数据

POST 访问http://127.0.0.1:8000/books/,发送JSON数据:

{
    "btitle": "三国演义",
    "bpub_date": "1990-02-03"
}

返回状态码201,数据如下

{
    "id": 8,
    "btitle": "三国演义",
    "bpub_date": "1990-02-03",
    "bread": 0,
    "bcomment": 0,
    "image": ""
}

4)修改图书数据

PUT 访问http://127.0.0.1:8000/books/8/,发送JSON数据:

{
    "btitle": "三国演义(第二版)",
    "bpub_date": "1990-02-03"
}

返回状态码200,数据如下

{
    "id": 8,
    "btitle": "三国演义(第二版)",
    "bpub_date": "1990-02-03",
    "bread": 0,
    "bcomment": 0,
    "image": ""
}

5)删除图书数据

DELETE 访问http://127.0.0.1:8000/books/8/,返回204状态码

明确REST接口开发的核心任务

分析一下上节的案例,可以发现,在开发REST API接口时,视图中做的最主要有三件事:

  • 将请求的数据(如JSON格式)转换为模型类对象
  • 操作数据库
  • 将模型类对象转换为响应的数据(如JSON格式)

序列化Serialization

这里是将字典列表等,转换为json字符串,这里反序列化是将前端传递的数据转换为数据对象的过程
维基百科中对于序列化的定义:

序列化(serialization)在计算机科学的资料处理中,是指将数据结构或物件状态转换成可取用格式(例如存成档案,存于缓冲,或经由网络中传送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始物件相同语义的副本。对于许多物件,像是使用大量参照的复杂物件,这种序列化重建的过程并不容易。面向对象中的物件序列化,并不概括之前原始物件所关联的函式。这种过程也称为物件编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组, deserialization, unmarshalling)。
序列化在计算机科学中通常有以下定义:

​ 在数据储存与传送的部分是指将一个对象。这程序被应用在不同应用程序之间传送对象),以及服务器将对象)储存到档案或数据库。相反的过程又称为反序列化。

简而言之,我们可以将序列化理解为:

将程序中的一个数据结构类型转换为其他格式(字典、JSON、XML等),例如将Django中的模型类对象装换为JSON字符串,这个转换过程我们称为序列化。

如:

queryset = BookInfo.objects.all()
book_list = []
# 序列化
for book in queryset:
    book_list.append({
        'id': book.id,
        'btitle': book.btitle,
        'bpub_date': book.bpub_date,
        'bread': book.bread,
        'bcomment': book.bcomment,
        'image': book.image.url if book.image else ''
    })
return JsonResponse(book_list, safe=False)

反之,将其他格式(字典、JSON、XML等)转换为程序中的数据,例如将JSON字符串转换为Django中的模型类对象,这个过程我们称为反序列化。

如:

json_bytes = request.body
json_str = json_bytes.decode()

# 反序列化
book_dict = json.loads(json_str)
book = BookInfo.objects.create(
    btitle=book_dict.get('btitle'),
    bpub_date=datetime.strptime(book_dict.get('bpub_date'), '%Y-%m-%d').date()

我们可以看到,在开发REST API时,视图中要频繁的进行序列化与反序列化的编写。

总结

在开发REST API接口时,我们在视图中需要做的最核心的事是:

  • 将数据库数据序列化为前端所需要的格式,并返回;

  • 将前端发送的数据反序列化为模型类对象,并保存到数据库中。

DjangoRESTframework简介

  1. 在序列化与反序列化时,虽然操作的数据不尽相同,但是执行的过程却是相似的,也就是说这部分代码是可以复用简化编写的。
  2. 在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增、删、改、查的实现流程基本套路化,所以这部分代码也是可以复用简化编写的:
  • :校验请求数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
  • :判断要删除的数据是否存在 -> 执行数据库删除
  • :判断要修改的数据是否存在 -> 校验请求的数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
  • :查询数据库 -> 将数据序列化并返回
    Django REST framework可以帮助我们简化上述两部分的代码编写,大大提高REST API的开发速度。

认识Django REST framework

在这里插入图片描述

Django REST framework 框架是一个用于构建Web API 的强大而又灵活的工具。

通常简称为DRF框架 或 REST framework。

DRF框架是建立在Django框架基础之上,由Tom Christie大牛二次开发的开源项目。

特点

  • 提供了定义序列化器Serializer的方法,可以快速根据 Django ORM 或者其它库自动序列化/反序列化;
  • 提供了丰富的类视图、Mixin扩展类,简化视图的编写;
  • 丰富的定制层级:函数视图、类视图、视图集合到自动生成 API,满足各种需要;
  • 多种身份认证和权限认证方式的支持;
  • 内置了限流系统;
  • 直观的 API web 界面;
  • 可扩展性,插件丰富

资料:

  • 官方文档
  • Github源码

笔记

  1. 后台主要是给运营人员用,主要用来增加商品信息,运营好像不会编程,要提供图形化界面,后台的前端页面已经写完了,需要我们补充后端逻辑

  2. 前后端不分离需要后端模板渲染,前后端分离在进行数据加载的时候统一返回json数据,由前端进行渲染

  3. 美多商城是一个BtoC(企业对用户)的模式,淘宝是CtoC的模式,还有BtoB模式,

  4. 电商类网站是在所有网站中包含功能最多的,会开发电商,开发其他网站就比较容易了,做新的网站时,可以借鉴美多商城的实现方案

  5. 在这里插入图片描述

  6. 在这里插入图片描述

  7. 在这里插入图片描述
    这样使用 127.0.0.1:8000 都可以进行访问

  8. 这里修改前端html页面时进行了手动服务器刷新

  9. mounted 指向的函数是页面加载完成之后执行的

  10. 在这里插入图片描述
    有了接口,前端和后端就能进行代码的编写,接口是由后端人员进行定义的

  11. 在这里插入图片描述
    前后端分离使用前端进行跳转
    这是跳转代码

  12. 前后端分离的结果由测试人员进行查看

  13. 可以使用postman自己进行测试,测试小组发现bug会发邮件给开发小组

  14. 在这里插入图片描述

  15. 后端的工作主要就是设计接口,写后端逻辑

  16. python manage.py 输入后会显示这个模块的功能选项信息

  17. 创建完应用要进行注册,否则可能没法生成迁移文件或是使用模板

  18. 在这里插入图片描述
    连接成功点击apply

  19. 函数参数内可放字典,数据之间允许有空白符,字典 k 冒号 v 之间允许有空白符 v 可以写一个表达式,字典存放的是表达式计算的结果

  20. 在这里插入图片描述

  21. postman 测试完成后,就可以给测试小组

  22. 字典通过 [],获取参数,不存在的话会抛出异常,如果通过get方式,没有取到数据的话会返回None

  23. 在这里插入图片描述
    判断数据是否为空

  24. json.loads 返回的是字典

  25. 在这里插入图片描述
    get不到东西会抛出异常

  26. 在这里插入图片描述
    这是物理删除

原文链接:https://blog.csdn.net/weixin_43297727/article/details/114891761

栏目分类
最近更新