DRF-1

1. Web应用模式

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

  1. 前后端不分离[客户端看到的内容和所有界面效果都是由服务端提供出来的。]

前后端不分离

  1. 前后端分离【把前端的界面效果(html,css,js分离到另一个服务端,python服务端只需要返回数据即可)】

    前端形成一个独立的网站,服务端构成一个独立的网站

前后端分离

2. api接口

应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址或者一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。

当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个问题,api接口往往都是一个函数、类方法、或者url或其他网络地址,不断是哪一种,当api接口编写过程中,我们都要考虑一个问题就是这个接口应该怎么编写?接口怎么写的更加容易维护和清晰,这就需要大家在调用或者编写api接口的时候要有一个明确的编写规范!!!

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

目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。

RPC( Remote Procedure Call ): 翻译成中文:远程过程调用[远程服务调用]. 从字面上理解就是访问/调用远程服务端提供的api接口。这种接口一般以服务或者过程式代码提供。

  • 服务端提供一个唯一的访问入口地址:http://api.xxx.com/ 或 http://www.xx.com/api 或者基于其他协议的地址

  • 客户端请求服务端的时候,所有的操作都理解为动作(action),一般web开发时,对应的就是HTTP请求的post请求

  • 通过请求体参数,指定要调用的接口名称和接口所需的参数

    action=get_all_student&class=301&sex=1

    m=get_all_student&sex=1&age=22

    command=100&sex=1&age=22

rpc接口多了,对应函数名和参数就多了,前端在请求api接口时难找.对于年代久远的rpc服务端的代码也容易出现重复的接口

restful: 翻译成中文: 资源状态转换.(表征性状态转移)

  • 把服务端提供的所有的数据/文件都看成资源, 那么通过api接口请求数据的操作,本质上来说就是对资源的操作了.

    因此,restful中要求,我们把当前接口对外提供哪种资源进行操作,就把资源的名称写在url地址

  • web开发中操作资源,最常见的最通用的无非就是增删查改,所以restful要求在地址栏中声明要操作的资源是什么。然后通过http请求动词来说明对该资源进行哪一种操作.

    POST http://www.xxx.com/api/students/ 添加学生数据

    GET http://www.xxx.com/api/students/ 获取所有学生

    DELETE http://www.xxx.com/api/students// 删除id=pk的一个学生

    PUT http://www.xxx.com/api/students// 修改一个学生的全部信息 [id,name,sex,age,]

    PATCH http://www.xxx.com/api/students// 修改一个学生的部分信息[age]

也就是说,我们仅需要通过url地址上的资源名称结合HTTP请求动作,就可以说明当前api接口的功能是什么了。

restful是以资源为主的api接口规范,体现在地址上就是资源就是以名词表达。

rpc则以动作为主的api接口规范,体现在接口名称上往往附带操作数据的动作。

3. RESTful API规范

restful

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

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

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

而对于数据资源分别使用POST、DELETE、GET、UPDATE等请求动作来表达对数据的增删查改。

GET/students获取所有学生
请求方法请求地址后端操作
POST/students增加学生
GET/students/获取编号为pk的学生
PUT/students/修改编号为pk的学生
DELETE/students/删除编号为pk的学生

restful规范是一种通用的规范,不限制语言和开发框架的使用。事实上,我们可以使用任何一门语言,任何一个框架都可以实现符合restful规范的API接口。

参考文档:http://www.runoob.com/w3cnote/restful-architecture.html

0. 幂等性

接口实现过程中,会存在幂等性。所谓幂等性是指代客户端发起多次同样请求时,是否对于服务端里面的资源产生不同结果。如果多次请求,服务端结果还是一样,则属于幂等接口,如果多次请求,服务端产生结果是不一样的,则属于非幂等接口

请求方式是否幂等是否安全
GET幂等安全
POST不幂等不安全
PUT/PATCH幂等不安全
DELETE幂等不安全\

1. 域名

应该尽量将API部署在专用域名之下。

https://api.example.com  

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

https://www.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

3. HTTP动词

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

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

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • DELETE(DELETE):从服务器删除资源。
CURD  Create、Update、Read、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应该提供参数,过滤返回结果。

下面是一些常见的参数。query_string 查询字符串,地址栏后面问号后面的数据,格式: name=xx&sss=xxx

完整的URL地址格式:
协议://域名(IP):端口号/students/?查询字符串#锚点

查询字符串: query_srting
格式与请求体类型:
    username=xiaoming&class=301
?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)

1xx 表示当前本次请求还是持续,没结束
2xx 表示当前本次请求成功/完成了
3xx 表示当前本次请求成功,但是服务器进行代理操作/重定向
4xx 表示当前本次请求失败,主要是客户端发生了错误
5xx 表示当前本次请求失败,主要是服务器发生了错误

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

  • 101 Switching Protocols - [*] 协议进行中,一般在http升级到websocket协议的时候就看到
  • 200 OK - [GET]:服务器成功返回用户请求的数据
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 301 Moved Permanently - [*]: 永久重定向
  • 302 Move Temporarily - [*]: 临时重定向
  • 304 Not Modified - [*]: 命中缓存
  • 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 - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
  • 507 Insufficient Storage [POST/PUT/PATCH] 数据存储出错,往往数据库操作错误出错,服务器就返回这个

状态码的完全列表参见这里这里

7. 错误处理(Error handling)

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

{
    error: "Invalid API key"
}

8. 返回结果

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

  • GET /collections:返回资源对象的列表(数组)
  • GET /collections/ID:返回单个资源字典(json)
  • POST /collections:返回新生成的资源字典(json)
  • PUT /collections/ID:返回修改后的资源字典(json)
  • DELETE /collections/ID:返回一个空文档(空字符串,空字典)

9. 超媒体(Hypermedia API)

RESTful API最好做到Hypermedia(即返回结果中提供链接,连向其他API方法),使得用户不查文档,也知道下一步应该做什么。

比如,Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

10. 其他

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

XML(eXtend Markup Language,可扩展标记语言)是W3C为了替换HTML研发出来的,但是现在很明显失败了。

语法:

1. xml常用场景:配置文件 微信开发  小程序  安卓

2. xml就是网页文档,文件以 .xml结尾

3. xml文档必须以文档声明开头,所有的xml文档内容都必须写在自定义的根标签内,xml文档有且只有1个根标签。
<xml version="1.0" charset="utf-8">
<根标签>
    ..... xml文档内容
</根标签>

4. xml里面也是由标签组成页面的。标签分单标签和双标签。其中,
   单标签写法: <标签名/>
   双标签写法: <标签名></标签名>

5. xml标签名除了文档声明,其他标签名和属性全部是开发人员自己自定义的。

6. 标签有0个或多个属性,属性必须有属性值。而且属性值必须使用引号圈起来。
<标签名 属性名="属性值" .../>
<标签名 属性名="属性值" ...>标签内容</标签名>

xml文档举例:

<?xml version="1.0" encoding="utf-8" ?>
<student-list>
<!-- 	<student>
	   <name>小红</name>
	   <age>17</age>
	   <sex>女</sex>
	   <class>301</class>
	</student>
	<student>
	   <name>小红</name>
	   <age>17</age>
	   <sex>女</sex>
	   <class>301</class>
	</student> -->
	<student age="17" sex="" class="301">小明</student>
	<student age="17" sex="" class="301">小明</student>
	<student age="17" sex="" class="301">小明</student>
	<student age="17" sex="" class="301">小明</student>
	<student age="17" sex="" class="301">小明</student>
</student-list>

json是目前市面上最流行的数据传输格式。JavaScript Object Notation js对象表示法

语法:

# 1. json文件以 .json结尾,一个json文件中只能保存一个json对象或者一个json数组
# 2. json中的属性类型:
  数组    []                       # python->列表
  对象    {}                       # python->字典
  数值    整型,浮点型,布尔值   
  字符串  双引号圈起来的文本内容
  null    空

# 3. 数组和对象的成员之间必须以英文逗号隔开,并且最后一个子成员后面不能出现逗号
# 4. json对象只能有属性不能出现方法,而且属性名必须以字符串格式来编写

举例1:

{
    "name": "张三",
    "sex": true,
    "age": 38,
    "money": 88.5,
    "child":{
        "name": "张思",
        "sex": false,
        "age": 4
    },
    "lve":["code","TV","swimming"]
}

举例2:

[
    {
        "name":"小明",
        "sex":true,
    },{
        "name":"小灰",
        "sex":true,
    }
]

使用xml和json表述一本书的内容信息,书的字段:

name varchar

price float

author varchar

pub_data date

word_total int

xml

<?xml version="1.0" encoding="utf-8" ?>
<book>
	<name>金瓶梅</name>
	<price>9999</price>
	<author>内容~~~适合多看</author>
	<pub_data>2020-9-8</pub_data>
	<word_total>999</word_total>
</book>

json

{
	"name":"三国演义",
	"price":1,
	"author":"诸葛亮草船借箭。。。",
	"pub_data":"2000-1-1",
	"word_total":50
}
http://www.xx.com:80/students?username=xiaoming&pwd=123

协议名://域名:端口/路径?查询字符串

课外补充:

1. python关于操作xml:  lxml
2. python关于操作json: json, ujson, orjson(rust)

4. 序列化

api接口开发,最核心最常见的一个代码编写过程就是序列化,所谓序列化就是把数据转换格式。常见的序列化方式:

json,pickle,base64,….

序列化可以分两个阶段:

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

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

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

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

image-20210826145519130

5. Django Rest_Framework

核心思想: 大量缩减编写api接口的代码

Django REST framework是一个建立在Django基础之上的Web 应用开发框架,可以快速的开发REST API接口应用。在REST framework中,提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程,不仅如此,还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST framework还提供了认证、权限、限流、过滤、分页、接口文档等功能支持。REST framework提供了一个API 的Web可视化界面来方便查看测试接口。

drf_logo

中文文档:https://q1mi.github.io/Django-REST-framework-documentation/#django-rest-framework

github: https://github.com/encode/django-rest-framework/tree/master

特点

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

6. 环境安装与配置

DRF需要以下依赖:

  • Python (3.5 以上)
  • Django (2.2 以上)

DRF是以Django子应用的方式提供的,所以我们可以直接利用已有的Django环境而无需从新创建。(若没有Django环境,需要先创建环境安装Django)

6.1 安装DRF

前提是已经安装了django,建议安装在虚拟环境

# conda create -n drfdemo python=3.8
# conda activate drfdemo
# pip install django -i https://pypi.douban.com/simple
# conda install pymysql

pip install djangorestframework -i https://pypi.douban.com/simple

# 因为我们需要接下来,需要开发api接口肯定要操作数据,所以安装pymysql
pip install pymysql -i https://pypi.douban.com/simple

linux 复制 shift+insert

6.1.1 创建django项目

cd ~/Desktop
django-admin startproject drfdemo

1557022536078

使用pycharm打开项目,设置虚拟环境的解析器,并修改manage.py中的后缀参数。

6.2 添加rest_framework应用

settings.pyINSTALLED_APPS中添加’rest_framework’。

INSTALLED_APPS = [
    ...
    'rest_framework',
]

接下来就可以使用DRF提供的功能进行api接口开发了。在项目中如果使用rest_framework框架实现API接口,主要有以下三个步骤:

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

接下来,我们快速体验下四天后我们学习完成drf以后的开发代码。接下来代码不需要理解,看步骤。

配置数据库连接并初始化pymysql

settings.py,代码:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'school',     # 数据库名字
        'PORT': 3306,         # 数据库端口
        'HOST': '127.0.0.1',  # 数据库主机地址
        'USER': 'root',       # 数据库用户名
        'PASSWORD': '123',    # 数据库用户密码
    }
}

drfdemo.__init__.py,代码:

import pymysql
pymysql.install_as_MySQLdb()

6.3 体验drf完全简写代码的过程

创建students子应用,并在setings.py中进行注册rest_framework和students子应用

python manage.py startapp students

settings.py,代码:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'students',
]

6.3.1. 创建模型操作类

stuapi/models.py,代码:

from django.db import models


# Create your models here.
class Student(models.Model):
    """学生信息"""
    name = models.CharField(max_length=255, verbose_name="姓名")
    sex = models.BooleanField(default=1, verbose_name="性别")
    age = models.IntegerField(verbose_name="年龄")
    classmate = models.CharField(max_length=5, verbose_name="班级编号")
    description = models.TextField(max_length=1000, verbose_name="个性签名")

    class Meta:
        db_table = "tb_student"
        verbose_name = "学生"
        verbose_name_plural = verbose_name

为了方便测试,所以我们可以先创建一个数据库。

mysql -uroot -p123
create database students charset=utf8mb4;

1557023744365

6.3.1.1 原生Django操作流程
添加子应用

1557023819604

初始化数据库连接
安装pymysql
pip install pymysql
设置使用pymysql数据库驱动
import pymysql
pymysql.install_as_MySQLdb()
配置mysql
DATABASES = {
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    # },
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': "students",
        "HOST": "127.0.0.1",
        "PORT": 3306,
        "USER": "root",
        "PASSWORD":"123",
    },
}
数据迁移。
python manage.py makemigrations
python manage.py migrate
原生的Django基本API接口
import json

from django.views import View
from django.http.response import JsonResponse
from .models import Student

# Create your views here.
"""
POST /students/   添加一个学生信息
GET  /students/   获取所有学生信息

GET /students/<pk>/  获取一个学生信息
PUT /students/<pk>/  更新一个学生信息
DELETE /students/<pk>/  删除一个学生信息
 
一个路由对应一个视图类,所以我们可以把5个API分成2个类来完成
"""


class StudentView(View):
    """学生视图"""

    def post(self, request):
        """添加一个学生信息"""
        # 1. 接收客户单提交的数据,验证客户端的数据
        data = json.loads(request.body)
        name = data.get("name")
        sex = data.get("sex")
        age = data.get("age")
        classmate = data.get("classmate")
        description = data.get("description")

        # 2. 操作数据库,保存数据
        instance = Student.objects.create(
            name=name,
            sex=sex,
            age=age,
            classmate=classmate,
            description=description,
        )

        # 3. 返回结果
        return JsonResponse(data={
            "id": instance.pk,
            "name": instance.name,
            "sex": instance.sex,
            "age": instance.age,
            "classmate": instance.classmate,
            "description": instance.description,
        }, status=201)

    def get(self, request):
        """获取多个学生信息"""
        # 1. 读取数据库
        students_list = list(Student.objects.values())

        # 2. 返回数据
        return JsonResponse(data=students_list, status=200, safe=False)


class StudentInfoView(View):
    def get(self, request, pk):
        """获取一条数据"""
        try:
            instance = Student.objects.get(pk=pk)
            return JsonResponse(data={
                "id": instance.pk,
                "name": instance.name,
                "sex": instance.sex,
                "age": instance.age,
                "classmate": instance.classmate,
                "description": instance.description,
            }, status=200)

        except Student.DoesNotExist:
            return JsonResponse(data=None, status=404)  # 没有内容

    def put(self, request, pk):
        """更新一个学生信息"""
        # 1. 接收客户单提交的数据,验证客户端的数据
        data = json.loads(request.body)
        name = data.get("name")  # alt+j 选中多个一样的
        sex = data.get("sex")
        age = data.get("age")
        classmate = data.get("classmate")
        description = data.get("description")

        # 2. 操作数据库,保存数据
        try:
            instance = Student.objects.get(pk=pk)
            instance.name = name
            instance.sex = sex
            instance.age = age
            instance.classmate = classmate
            instance.description = description
            instance.save()

        except Student.DoesNotExist:
            return JsonResponse(data={}, status=404)  # 没有内容

        # 3. 返回结果
        return JsonResponse(data={
            "id": instance.pk,
            "name": instance.name,
            "sex": instance.sex,
            "age": instance.age,
            "classmate": instance.classmate,
            "description": instance.description,
        }, status=201)

    def delete(self, request, pk):
        """删除一个学生信息"""
        try:
            Student.objects.filter(pk=pk).delete()
        except:
            pass
        return JsonResponse(data={}, status=204)

子应用下创建urls.py
from django.urls import path,re_path
from . import views
urlpatterns = [
    path("students/", views.StudentView.as_view()),
    re_path("^students/(?P<pk>\d+)/$", views.StudentInfoView.as_view()),
]
配置子应用
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',
    'stuapi',
    'students',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
总路由,urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include("stuapi.urls")),
]

访问效果
GET  http://localhost:8000/api/students/

POST http://localhost:8000/api/students/
Content-Type: application/json

{
    "name": "xiaoming",
    "age": 18,
    "sex": true,
    "classmate": 301,
    "description": "hello,"
}


GET  http://localhost:8000/api/students/2/

PUT  http://localhost:8000/api/students/2/
Content-Type: application/json

{
    "name": "xiaoming",
    "age": 16,
    "sex": true,
    "classmate": 301,
    "description": "hello,"
}

DELETE http://localhost:8000/api/students/2/

接下来,我们可以采用django_restframework来完成上面的5个API接口。具体代码编写到另一个子应用。

6.3.2. 创建序列化器

例如,在django项目中创建学生子应用。

python manage.py startapp students

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

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

from rest_framework import serializers
# 序列化基类
# serializers.Serializers
# serializers.ModelSerializer
from .models import Student
class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = "__all__"
        # fileds = ["id","name"]
  • model 指明该序列化器处理的数据字段从模型类Student参考生成
  • fields 指明该序列化器包含模型类中的哪些字段,__all__指明包含所有字段

6.3.3. 编写视图

在students应用的views.py中创建视图StudentModelViewSet,这是一个视图集合。

from rest_framework.viewsets import ModelViewSet
from .models import Student
from .serializers import StudentModelSerializer
# Create your views here.

class StudentModelViewSet(ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
  • queryset 指明该视图集在查询数据时使用的查询集
  • serializer_class 指明该视图在进行序列化或反序列化时使用的序列化器

6.3.4. 定义路由

在students应用的urls.py中定义路由信息。

from django.urls import path
from . import views

urlpatterns =[

]

from rest_framework.routers import SimpleRouter,DefaultRouter

router = DefaultRouter()  # 可以处理视图的路由器
router.register("stu", viewset=views.StudentModelViewSet, basename="stu") # 向路由器中注册视图集
urlpatterns += router.urls # 将路由器中的所以路由信息追到到django的路由列表中

6.3.5. 运行测试

运行当前程序(与运行Django一样)

python manage.py runserver

在浏览器中输入网址127.0.0.1:8000,可以看到DRF提供的API Web浏览页面:

image-20210827152034265

1)点击链接127.0.0.1:8000/stu/students 可以访问获取所有数据的接口,呈现如下页面:

image-20210827152136089

2)在页面底下表单部分填写学生信息,可以访问添加新学生的接口,保存学生信息:

image-20210827152527799

点击POST后,返回如下页面信息:

image-20210827152549805

3)在浏览器中输入网址127.0.0.1:8000/stu/students/137/,可以访问获取单一学生信息的接口(id为5的学生),呈现如下页面:

image-20210827152616263

4)在页面底部表单中填写学生信息,可以访问修改学生的接口

image-20210827152644251

点击PUT,返回如下页面信息:

image-20210827152708611

5)点击DELETE按钮,可以访问删除学生的接口

image-20210827152726613

点击删除以后,返回如下页面:

image-20210827152749591

7. 序列化器-Serializer

作用:

1. 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串
2. 反序列化,把客户端发送过来的数据,经过request以后变成字典,序列化器可以把字典转成模型
3. 反序列化,完成数据校验功能

7.1 定义序列化器

Django REST framework中的Serializer使用类来定义,序列化器类必须直接或间接继承自rest_framework.serializers.Serializer。Serializer有个子类叫:ModelSerializer

接下来,为了方便演示序列化器的使用,我们先创建一个新的子应用sers

python manage.py startapp sers

注册子应用,并创建urls子路由文件,最后在总路由中注册子路由。

settings.py,代码:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'students',
    'sers',    # 序列化器
]

sers.urls,代码:

from django.urls import path
from . import views

urlpatterns =[

]

drfdemo.urls,代码:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('students/', include("students.urls")),
    path('sers/', include("sers.urls")),
]

我们已有了一个数据库模型类,所以我们就基于这个模型类来完成序列化器的学习。students.models,代码:

from django.db import models

# Create your models here.
class Student(models.Model):
    SEX_OPTION = (
        (0, "保密"),
        (1, "男"),
        (2, "女"),
    )
    name = models.CharField(max_length=20, verbose_name="姓名", help_text="请输入一个正常的人名!")
    age  = models.SmallIntegerField( verbose_name="年龄", help_text="请输入一个人的年龄,范围在1-100之间~")
    sex  = models.SmallIntegerField(choices=SEX_OPTION, verbose_name="性别")
    classmate = models.CharField(db_column="class", max_length=15, verbose_name="班级")
    description = models.TextField(null=True, blank=True, verbose_name="个性签名")

    class Meta:
        db_table = "db_student"
        verbose_name = "学生信息"
        verbose_name_plural = verbose_name

我们想为这个模型类提供一个序列化器,可以定义如下:

sers.serializers,代码:

from rest_framework import serializers
# 声明序列化器,所有的序列化器都要直接或者间接继承于 Serializer
# 其中,ModelSerializer是Serializer的子类,ModelSerializer在Serializer的基础上进行了代码简化
class StudentSerializer(serializers.Serializer):
    """学生信息序列化器"""
    # 1. 声明在请求和响应过程中,需要进行数据转换的字段
    id = serializers.IntegerField()
    name = serializers.CharField()
    age = serializers.IntegerField()
    sex = serializers.IntegerField()
    description = serializers.CharField()

    # 2. 如果序列化器集成的是ModelSerializer,则需要声明调用的模型信息

    # 3. 验证代码

    # 4. 编写模型的添加和更新代码
    

**注意:serializer不是只能为数据库模型类定义,也可以为非数据库模型类的数据定义。**serializer是独立于数据库之外的存在。

Serializers常用字段类型

字段字段构造方式
BooleanFieldBooleanField()
NullBooleanFieldNullBooleanField()
CharFieldCharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailFieldEmailField(max_length=None, min_length=None, allow_blank=False)
RegexFieldRegexField(regex, max_length=None, min_length=None, allow_blank=False)
SlugFieldSlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+
URLFieldURLField(max_length=200, min_length=None, allow_blank=False)
UUIDFieldUUIDField(format=‘hex_verbose’) format:
1) 'hex_verbose'"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
2) 'hex'"5ce0e9a55ffa654bcee01238041fb31a"
3)'int' - 如: "123456789012312313134124512351145145114"
4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressFieldIPAddressField(protocol=‘both’, unpack_ipv4=False, **options)
IntegerFieldIntegerField(max_value=None, min_value=None)
FloatFieldFloatField(max_value=None, min_value=None)
DecimalFieldDecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置
DateTimeFieldDateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateFieldDateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeFieldTimeField(format=api_settings.TIME_FORMAT, input_formats=None)
DurationFieldDurationField()
ChoiceFieldChoiceField(choices) choices与Django的用法相同
MultipleChoiceFieldMultipleChoiceField(choices)
FileFieldFileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ImageFieldImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ListFieldListField(child=, min_length=None, max_length=None)
DictFieldDictField(child=)

选项参数:

参数名称作用
max_length最大长度
min_lenght最小长度
allow_blank是否允许为空,是否为空字符串
trim_whitespace是否截断空白字符
max_value最小值
min_value最大值

通用参数:

参数名称说明
read_only表明该字段仅用于序列化输出,默认False
write_only表明该字段仅用于反序列化输入,默认False
required表明该字段在反序列化时必须输入,默认True
default反序列化时使用的默认值
allow_null表明该字段是否允许传入None,默认False
validators该字段使用的验证器,自定义验证函数
error_messages包含错误编号与错误信息的字典
label用于HTML展示API页面时,显示的字段名称,
如果没有设置,则会出现英文的字段名或者模型中verbose_name
help_text用于HTML展示API页面时,显示的字段帮助提示信息
如果没有设置,则会出现英文的字段名或者模型中help_text

7.2 创建Serializer对象

定义好Serializer类后,就可以创建Serializer对象了。

Serializer的构造方法为:

Serializer(instance=None, data=empty, many=True/False, **kwarg)

说明:

1)用于序列化时,将模型类对象传入instance参数

2)用于反序列化时,将要被反序列化的数据传入data参数

3)转换数据可以是一条也可以是多条,如果多条数据,则需要通过many=True来设置。

4)除了instance和data参数外,在构造Serializer对象时,还可通过context参数额外添加数据,如

serializer = StudentSerializer(模型, context={'request': request})

通过context参数附加的数据,可以通过Serializer对象的context属性获取。

  1. 使用序列化器的时候一定要注意,序列化器声明了以后,不会自动执行,需要我们在视图中进行调用才可以。
  2. 序列化器无法直接接收数据,需要我们在视图中创建序列化器对象时把使用的数据传递过来。
  3. 序列化器的字段声明类似于我们前面使用过的表单系统。
  4. 开发restful api时,序列化器会帮我们把模型数据转换成字典.
  5. drf提供的视图会帮我们把字典转换成json,或者把客户端发送过来的数据转换字典.

7.3 序列化器的使用

序列化器的使用分两个阶段:

  1. 在客户端请求时,使用序列化器可以完成对数据的反序列化。
  2. 在服务器响应时,使用序列化器可以完成对数据的序列化。

7.3.1 序列化

7.3.1.1 基本使用

1) 先查询出一个学生对象

from students.models import Student

# student = Student.objects.get(id=3)
student = Student.objects.first()

2) 构造序列化器对象

from .serializers import StudentSerializer
# serializer = StudentSerializer(instance=student)
serializer = StudentSerializer(instance=student)

3)获取序列化数据

通过data属性可以获取序列化后的数据

serializer.data
# {'id': 4, 'name': '小张', 'age': 18, 'sex': True, 'description': '猴赛雷'}

视图代码:

"""因为我们现在刚开始学习drf的序列化器,还没有学到drf的视图类,所以暂时先使用django原生提供的django.views.View"""
from django.views import View
from students.models import Student
from .serializers import StudentSerializer
from django.http.response import JsonResponse
class Student1View(View):
    def get1(self,request):
        """
        使用序列化器对数据进行序列化格式转换,提供给客户端
        序列化器的序列化阶段
        """
        """序列化一条数据"""
        # 对数据进行查询
        student = Student.objects.first()
        """实例化序列化器得到序列化器对象"""
        # StudentSerializer(instance=student)  # instance是第一个参数,可以不填
        serializer = StudentSerializer(student)
        # print(f"serializer={serializer}")
        # 获取到格式转换后的数据,data内转换
        # print(f"serializer.data={serializer.data}")
        return JsonResponse(serializer.data)

4)如果要被序列化的是包含多条数据的查询集QuerySet,可以通过添加many=True参数补充说明

    def get(self,request):
        """使用序列化器对数据进行序列化格式转换,提供给客户端
        序列化器的序列化阶段
        """
        """序列化多条数据"""
        # 1. 对数据进行查询
        student_list = Student.objects.all()
        # 2. 实例化序列化器对象,并传入要转换的数据
        serializer = StudentSerializer(student_list,many=True)
        print(serializer.data)
        """
        [
            OrderedDict([('id', 1), ('name', '赵华'), ('age', 22), ('sex', 1), ('description', '对于勤奋的人来说,成功不是偶然;对于懒惰的人来说,失败却是必然。')]), 
            OrderedDict([('id', 2), ('name', '程星云'), ('age', 20), ('sex', 1), ('description', '人生应该如蜡烛一样,从顶燃到底,一直都是光明的。')]),
            ...
        ]
        OrderedDict 是python内置的高级数据类型,叫有序字典。主要为了解决基本数据类型的dict在存储数据时的随机性问题。
        OrderedDict 的使用和基本类型的dict是一模一样的。可以保证,写入的数据格式顺序在读取时保持一致。
        from collections import OrderedDict
        """
        
        return JsonResponse(serializer.data, safe=False)

注意:序列化器的序列化器阶段,不仅可以转换模型对象的数据格式,只要是python中的对象,属性对应序列化器中的字段,都可以完成转换成字典或者列表。

7.3.2 反序列化

7.3.2.1 数据验证

使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或保存成模型类对象。

在获取反序列化的数据前,必须调用序列化器对象提供的**is_valid()**方法进行调用序列化内部预先写好的验证代码,验证成功返回True,否则返回False。

验证失败,可以通过序列化器对象的errors属性获取错误信息(字典格式),包含了字段和字段的错误提示。如果是非字段错误,可以通过修改REST framework提供的配置选项NON_FIELD_ERRORS_KEY来控制错误字典中的键名。

验证成功,可以通过序列化器对象的validated_data属性获取验证后的数据。

在定义序列化器时,指明每个字段的序列化类型和选项参数,当然这个设置本身就是一种验证行为。

如我们前面定义过的StudentSerializer

sers.serializers,代码:

from rest_framework import serializers
# 声明序列化器,所有的序列化器都要直接或者间接继承于 Serializer
# 其中,ModelSerializer是Serializer的子类,ModelSerializer在Serializer的基础上进行了代码简化
class StudentSerializer(serializers.Serializer):
    """学生信息序列化器"""
    # 1. 声明在请求和响应过程中,需要进行数据转换的字段
    # read_only=True 表示用户提交数据进行反序列化时,序列化器不会启用这个字段,而是在序列化器进行序列化转换数据给客户端时才启用这个字段
    id = serializers.IntegerField(read_only=True)
    # required=True 表示用户移交数据时,这个字段时必填的
    # max_length=20 表示当前字符串字段最大长度为20个字符
    name = serializers.CharField(required=True, max_length=20)
    classmate = serializers.CharField(required=True, max_length=3)
    age = serializers.IntegerField()
    sex = serializers.IntegerField()
    # required=False 表示当前字段,用户提交数据时,可以不提交这个字段的数据
    # default        表示当前字段,用户提交数据时,没有值的情况下,采用这个值作为默认值传递验证结果的返回值中。
    description = serializers.CharField(required=False, default="这家伙很懒,什么也没写!")
    
    # 2. 如果序列化器集成的是ModelSerializer,则需要声明调用的模型信息

    # 3. 验证代码

    # 4. 编写模型的添加和更新代码

通过构造序列化器对象,并将要反序列化的数据传递给data构造参数,进而进行验证

sers.views,代码:

    def get(self,request):
        """反序列化一般就是在用户添加数据或更新数据时使用"""
        """添加数据时的反序列化"""
        # 模拟用户提交过来的数据
        params = {
            "name": "王小明",
            "age": 17,
            "sex": 1,
        }

        # 1. 实例化序列化器
        serializer = StudentSerializer(data=params)
        # raise_exception=True 表示在反序列化验证数据失败以后,由drf直接抛出异常,返回错误提示给客户端
        # drf将来提供的视图类会自动把错误信息转换成json数据提供给客户端
        # serializer.is_valid(raise_exception=True)
        ret = serializer.is_valid()
        print(f"验证结果={ret}") # 验证数据通过则True,否则为False
        if ret:
            # 获取验证后的结果,注意此时我们还没有学习到自动保存数据,所以这里仅仅是验证而已,没有到数据库中呢
            print(f"serializer.validated_data={serializer.validated_data}")
        else:
            # 获取验证后的错误
            print(f"serializer.errors={serializer.errors}")
            for item in serializer.errors.items():
                print(f"字段名={item[0]}")
                for error in item[1]:
                    print(error.code)
                    print(error)


        return JsonResponse({"msg":"ok"})

is_valid()方法还可以在验证失败时抛出异常serializers.ValidationError,可以通过传递raise_exception=True参数开启,REST framework接收到此异常,会向前端返回HTTP 400 Bad Request响应。

sers.views,代码:

    def get(self,request):
        """反序列化一般就是在用户添加数据或更新数据时使用"""
        """添加数据时的反序列化"""
        # 模拟用户提交过来的数据
        params = {
            "name": "王小明",
            "age": 17,
            "sex": 1,
        }

        # 1. 实例化序列化器
        serializer = StudentSerializer(data=params)
        # raise_exception=True 表示在反序列化验证数据失败以后,由drf直接抛出异常,返回错误提示给客户端
        # drf将来提供的视图类会自动把错误信息转换成json数据提供给客户端
        serializer.is_valid(raise_exception=True)
        # ret = serializer.is_valid()
        # print(f"验证结果={ret}") # 验证数据通过则True,否则为False
        # if ret:
        #     # 获取验证后的结果,注意此时我们还没有学习到自动保存数据,所以这里仅仅是验证而已,没有到数据库中呢
        #     print(f"serializer.validated_data={serializer.validated_data}")
        # else:
        #     # 获取验证后的错误
        #     print(f"serializer.errors={serializer.errors}")
        #     for item in serializer.errors.items():
        #         print(f"字段名={item[0]}")
        #         for error in item[1]:
        #             print(error.code)
        #             print(error)


        return JsonResponse({"msg":"ok"})

is_valid会自动调用序列化器中所有的验证代码,上面is_valid就调用了我们在序列化器字段声明的选项对数据进行验证。如果觉得这些还不够,还可以继续补充其他相加详尽的验证代码让is_valid进行调用,你可以使用以下3种方法编写验证代码:

1) 单字段验证方法,validate_字段名

<field_name>字段进行验证,如

from rest_framework import serializers
# 声明序列化器,所有的序列化器都要直接或者间接继承于 Serializer
# 其中,ModelSerializer是Serializer的子类,ModelSerializer在Serializer的基础上进行了代码简化
class StudentSerializer(serializers.Serializer):
    """学生信息序列化器"""
    # 1. 声明在请求和响应过程中,需要进行数据转换的字段
    # read_only=True 表示用户提交数据进行反序列化时,序列化器不会启用这个字段,而是在序列化器进行序列化转换数据给客户端时才启用这个字段
    id = serializers.IntegerField(read_only=True)
    # required=True 表示用户移交数据时,这个字段时必填的
    # max_length=20 表示当前字符串字段最大长度为20个字符
    name = serializers.CharField(required=True, max_length=20)
    classmate = serializers.CharField(required=True, max_length=3, error_messages={"required":"当前字段必须填写!","max_length": "对不起,长度不能超过3个字符"})
    age = serializers.IntegerField()
    sex = serializers.IntegerField()
    # required=False 表示当前字段,用户提交数据时,可以不提交这个字段的数据
    # default        表示当前字段,用户提交数据时,没有值的情况下,采用这个值作为默认值传递验证结果的返回值中。
    description = serializers.CharField(required=False, default="这家伙很懒,什么也没写!")

    # 2. 如果序列化器集成的是ModelSerializer,则需要声明调用的模型信息

    # 3. 验证代码
    # 3.1 对单个字段数据进行验证,
    # 格式:
    #     一个序列化器中可以有0个或多个验证字段方法,方法名唯一。
    #     必须以 "validate_<字段名>"作为验证方法名,否则is_valid找不到的
    #     验证方法执行时,is_valid会把当前客户端提交的字段数据传递到验证方法,我们需要接收该参数进行验证
    #     验证方法必须把当前字段的数据作为方法的返回值,回传给序列化器,否则该数据丢失。
    def validate_name(self, data):
        print(f"验证方法[validate_nmae]={data}")
        if data == "python":
            # 序列化器中要抛出异常,可以通过serializers.ValidationError()把异常抛出给is_valid进行处理
            raise serializers.ValidationError(code="name", detail="当前数据属于敏感字符!")

        # 必须有返回值,而且返回值必须是当前字段的数据值,可以改动数据,但是必须返回
        return data

    # 4. 编写模型的添加和更新代码

测试,视图代码:

    def get(self, request):
        """添加数据时的反序列化[指定更多的验证规则]"""
        # 模拟用户提交过来的数据
        params = {
            "name": "python",
            "age": 17,
            "sex": 1,
            "classmate": 501,
        }

        # 1. 实例化序列化器
        serializer = StudentSerializer(data=params)

        # 2. 调用is_valid进行验证数据
        serializer.is_valid(raise_exception=True)
        # 如果上一行没有抛出异常,则表示验证通过,我们就可以下面代码中获取验证后的数据结果
        print(serializer.validated_data)
        return JsonResponse({"msg":"ok"})
2) 多字段验证方法,validate

在序列化器中需要同时对多个字段进行比较验证时,可以定义validate方法来验证,如

from rest_framework import serializers
# 声明序列化器,所有的序列化器都要直接或者间接继承于 Serializer
# 其中,ModelSerializer是Serializer的子类,ModelSerializer在Serializer的基础上进行了代码简化
class StudentSerializer(serializers.Serializer):
    """学生信息序列化器"""
    # 1. 声明在请求和响应过程中,需要进行数据转换的字段
    # read_only=True 表示用户提交数据进行反序列化时,序列化器不会启用这个字段,而是在序列化器进行序列化转换数据给客户端时才启用这个字段
    id = serializers.IntegerField(read_only=True)
    # required=True 表示用户移交数据时,这个字段时必填的
    # max_length=20 表示当前字符串字段最大长度为20个字符
    name = serializers.CharField(required=True, max_length=20)
    classmate = serializers.CharField(required=True, max_length=3, error_messages={"required":"当前字段必须填写!","max_length": "对不起,长度不能超过3个字符"})
    age = serializers.IntegerField()
    sex = serializers.IntegerField()
    # required=False 表示当前字段,用户提交数据时,可以不提交这个字段的数据
    # default        表示当前字段,用户提交数据时,没有值的情况下,采用这个值作为默认值传递验证结果的返回值中。
    description = serializers.CharField(required=False, default="这家伙很懒,什么也没写!")

    # 2. 如果序列化器集成的是ModelSerializer,则需要声明调用的模型信息

    # 3. 验证代码
    # 3.1 对单个字段数据进行验证,
    # 格式:
    #     一个序列化器中可以有0个或多个验证字段方法,方法名唯一。
    #     必须以 "validate_<字段名>"作为验证方法名,否则is_valid找不到的
    #     验证方法执行时,is_valid会把当前客户端提交的字段数据传递到验证方法,我们需要接收该参数进行验证
    #     验证方法必须把当前字段的数据作为方法的返回值,回传给序列化器,否则该数据丢失。
    def validate_name(self, data):
        print(f"验证方法[validate_nmae]={data}")
        if data == "python":
            # 序列化器中要抛出异常,可以通过serializers.ValidationError()把异常抛出给is_valid进行处理
            raise serializers.ValidationError(code="name", detail="当前数据属于敏感字符!")

        # 必须有返回值,而且返回值必须是当前字段的数据值,可以改动数据,但是必须返回
        return data

    # 3.2 对多个字段的数据进行验证
    # 格式:
    #    一个序列化器中只能有0个或1个多字段验证方法,方法名唯一
    #    方法名必须叫 validated(self, data),否则is_valid找不到的
    #    验证方法执行时,is_valid会把当前客户端提交的所有字段数据传递到验证方法,我们需要接收该参数进行验证
    #    验证方法必须把当前字段的数据作为方法的返回值,回传给序列化器,否则该数据丢失。
    def validate(self, attrs):
        # 多字段的验证一般用于,用户注册时密码和确认密码,2个字段的密码必须一样。这种情况
        # 判断年龄在20以下不能添加到5字头的班级
        age = attrs.get("age")
        classmate = attrs.get("classmate")
        if (age < 20) and str(classmate).startswith("5"):
            raise serializers.ValidationError("对不起!年龄在20以下不能添加到5字头的班级")
        return attrs
    # 4. 编写模型的添加和更新代码

测试

    def get(self, request):
        """添加数据时的反序列化[指定更多的验证规则]"""
        # 模拟用户提交过来的数据
        params = {
            "name": "李小明",
            "age": 17,
            "sex": 1,
            "classmate": 501,
        }

        # 1. 实例化序列化器
        serializer = StudentSerializer(data=params)

        # 2. 调用is_valid进行验证数据
        serializer.is_valid(raise_exception=True)
        # 如果上一行没有抛出异常,则表示验证通过,我们就可以下面代码中获取验证后的数据结果
        print(serializer.validated_data)
        return JsonResponse({"msg":"ok"})
3) 单字段验证函数,validators

在序列化器的字段选项中添加validators选项参数,也可以补充验证代码。

选项validators的值是一个列表,列表的成员必须一个函数名,这个函数名不能是字符串。

例如:

from rest_framework import serializers
# 声明序列化器,所有的序列化器都要直接或者间接继承于 Serializer
# 其中,ModelSerializer是Serializer的子类,ModelSerializer在Serializer的基础上进行了代码简化

# validators选项的验证函数可以有0到多个,这些验证函数和写在序列化器内部的验证方法使用方法一致,几乎没区别
# 函数名叫什么无所谓!函数必须有一个参数接收is_valid传递进行的字段数据
# validators是通用的选项,任何字段都可以指定自己多个外部验证函数
# 一般就是验证函数比较通用的时候,没必要把验证数据额代码写在每一个序列化器中时就可以使用外部验证函数了。
def check_sex(data):
    # 必须把数据返回,否则数据丢失了
    if data > 2:
        raise serializers.ValidationError(code="sex", detail="对不起,地球上没有这个性别选项!")

    # 必须返回验证后的数据
    return data

class StudentSerializer(serializers.Serializer):
    """学生信息序列化器"""
    # 1. 声明在请求和响应过程中,需要进行数据转换的字段
    # read_only=True 表示用户提交数据进行反序列化时,序列化器不会启用这个字段,而是在序列化器进行序列化转换数据给客户端时才启用这个字段
    id = serializers.IntegerField(read_only=True)
    # required=True 表示用户移交数据时,这个字段时必填的
    # max_length=20 表示当前字符串字段最大长度为20个字符
    name = serializers.CharField(required=True, max_length=20)
    classmate = serializers.CharField(required=True, max_length=3, error_messages={"required":"当前字段必须填写!","max_length": "对不起,长度不能超过3个字符"})
    age = serializers.IntegerField()
    sex = serializers.IntegerField(validators=[check_sex,])
    # required=False 表示当前字段,用户提交数据时,可以不提交这个字段的数据
    # default        表示当前字段,用户提交数据时,没有值的情况下,采用这个值作为默认值传递验证结果的返回值中。
    description = serializers.CharField(required=False, default="这家伙很懒,什么也没写!")

    # 2. 如果序列化器集成的是ModelSerializer,则需要声明调用的模型信息

    # 3. 验证代码
    # 3.1 对单个字段数据进行验证,
    # 格式:
    #     一个序列化器中可以有0个或多个验证字段方法,方法名唯一。
    #     必须以 "validate_<字段名>"作为验证方法名,否则is_valid找不到的
    #     验证方法执行时,is_valid会把当前客户端提交的字段数据传递到验证方法,我们需要接收该参数进行验证
    #     验证方法必须把当前字段的数据作为方法的返回值,回传给序列化器,否则该数据丢失。
    def validate_name(self, data):
        print(f"验证方法[validate_nmae]={data}")
        if data == "python":
            # 序列化器中要抛出异常,可以通过serializers.ValidationError()把异常抛出给is_valid进行处理
            raise serializers.ValidationError(code="name", detail="当前数据属于敏感字符!")

        # 必须有返回值,而且返回值必须是当前字段的数据值,可以改动数据,但是必须返回
        return data

    # 3.2 对多个字段的数据进行验证
    # 格式:
    #    一个序列化器中只能有0个或1个多字段验证方法,方法名唯一
    #    方法名必须叫 validated(self, data),否则is_valid找不到的
    #    验证方法执行时,is_valid会把当前客户端提交的所有字段数据传递到验证方法,我们需要接收该参数进行验证
    #    验证方法必须把当前字段的数据作为方法的返回值,回传给序列化器,否则该数据丢失。
    def validate(self, attrs):
        # 多字段的验证一般用于,用户注册时密码和确认密码,2个字段的密码必须一样。这种情况
        # 判断年龄在20以下不能添加到5字头的班级
        age = attrs.get("age")
        classmate = attrs.get("classmate")
        if (age < 20) and str(classmate).startswith("5"):
            raise serializers.ValidationError("对不起!年龄在20以下不能添加到5字头的班级")
        return attrs
    # 4. 编写模型的添加和更新代码

测试,视图代码:

    def get(self, request):
        """添加数据时的反序列化[指定更多的验证规则]"""
        # 模拟用户提交过来的数据
        params = {
            "name": "李小明",
            "age": 27,
            "sex": 3,
            "classmate": 501,
        }

        # 1. 实例化序列化器
        serializer = StudentSerializer(data=params)

        # 2. 调用is_valid进行验证数据
        serializer.is_valid(raise_exception=True)
        # 如果上一行没有抛出异常,则表示验证通过,我们就可以下面代码中获取验证后的数据结果
        print(serializer.validated_data)
        return JsonResponse({"msg":"ok"})
7.3.2.2 反序列化-保存数据

前面的验证数据成功后,我们可以使用序列化器来完成数据反序列化的过程.这个过程可以把数据转成模型类对象.

可以通过实现create()和update()两个方法来实现。

from rest_framework import serializers
from students.models import Student

# 声明序列化器,所有的序列化器都要直接或者间接继承于 Serializer
# 其中,ModelSerializer是Serializer的子类,ModelSerializer在Serializer的基础上进行了代码简化

# validators选项的验证函数可以有0到多个,这些验证函数和写在序列化器内部的验证方法使用方法一致,几乎没区别
# 函数名叫什么无所谓!函数必须有一个参数接收is_valid传递进行的字段数据
# validators是通用的选项,任何字段都可以指定自己多个外部验证函数
# 一般就是验证函数比较通用的时候,没必要把验证数据额代码写在每一个序列化器中时就可以使用外部验证函数了。
def check_sex(data):
    # 必须把数据返回,否则数据丢失了
    if data > 2:
        raise serializers.ValidationError(code="sex", detail="对不起,地球上没有这个性别选项!")

    # 必须返回验证后的数据
    return data

class StudentSerializer(serializers.Serializer):
    """学生信息序列化器"""
    # 。。。。
    
    
    
    # 4. 编写模型的添加和更新代码
    # 添加操作,在视图调用序列化器的save时,必须在序列化器中实现create方法,否则报错
    def create(self, validated_data):
        """添加数据,到模型中的实现过程"""

        # student = Student.objects.create(**validated_data)
        # 如果validated_data有多余的字段,不属于模型对象,则自定义字典
        student = Student.objects.create(
            name=validated_data.get("name"),
            age=validated_data.get("age"),
            sex=validated_data.get("sex"),
            classmate=validated_data.get("classmate"),
            description=validated_data.get("description"),
        )
        # 强烈建议返回添加后的模型对象
        return student

    def update(self, instance, validated_data):
        """
        参数1:instance 本次更新数据的模型对象
        参数2:validated_data 本次客户端提交并经过验证后的数据
        """
        instance.name = validated_data.get("name")
        instance.age = validated_data.get("age")
        instance.sex = validated_data.get("sex")
        instance.classmate = validated_data.get("classmate")
        # 针对可选字段,进行条件判断
        if validated_data.get("description"):
            instance.description = validated_data.get("description")

        # 模型调用ojbects提供的save方法保存数据到数据库中。
        instance.save()
        return instance

实现了上述两个方法后,在反序列化数据的时候,就可以通过save()方法返回一个数据对象实例了

视图,代码:

    def get(self, request):
        """更新数据时的反序列化[验证数据后调用模型保存数据]"""
        # 模拟url地址中的pk值[restful中规定,修改,删除,查询1条数据,需要把id写在地址栏中]
        pk = 140
        # 模拟用户提交过来的数据
        params = {
            "name": "李大明",
            "age": 25,
            "sex": 1,
            "classmate": 401,
        }

        # 1. 先从数据库中获取本次要更新数据的模型对象
        student = Student.objects.get(pk=pk)

        # 2. 实例化序列化器
        serializer = StudentSerializer(instance=student, data=params)
        # 3. 调用is_valid进行验证数据
        serializer.is_valid(raise_exception=True)
        # 4. 调用save方法调用模型存储数据
        # 注意,此处的save是序列化器内部的save方法,不是我们之前模型使用的save方法
        serializer.save()
        # 再次序列化器,把更新后的数据返回给客户端。
        print(serializer.data)
        return JsonResponse(serializer.data)

如果实例化序列化器对象时,没有传递instance实例,则序列化器调用save()方法的时候,create()被调用。

相反,实例化序列化器对象时传递了instance实例,则序列化器调用save()方法的时候,update()被调用。

7.3.2.3 附加说明

1) 在对序列化器进行save()保存时,save方法可以追加默认的额外数据到序列化器中的,这些数据可以在create()和update()中的validated_data参数获取到。

    def save(self, **kwargs):
        assert hasattr(self, '_errors'), (
            'You must call `.is_valid()` before calling `.save()`.'
        )

        assert not self.errors, (
            'You cannot call `.save()` on a serializer with invalid data.'
        )

        # Guard against incorrect use of `serializer.save(commit=False)`
        assert 'commit' not in kwargs, (
            "'commit' is not a valid keyword argument to the 'save()' method. "
            "If you need to access data before committing to the database then "
            "inspect 'serializer.validated_data' instead. "
            "You can also pass additional keyword arguments to 'save()' if you "
            "need to set extra attributes on the saved model instance. "
            "For example: 'serializer.save(owner=request.user)'.'"
        )

        assert not hasattr(self, '_data'), (
            "You cannot call `.save()` after accessing `serializer.data`."
            "If you need to access data before committing to the database then "
            "inspect 'serializer.validated_data' instead. "
        )
        # 把save的参数注入到validated_data中,后面在序列化器的create和upfate方法中通过validated_data参数来获取到
        validated_data = {**self.validated_data, **kwargs}

        # self.instance 就是在实例化序列化器时传递进来的模型对象
        if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)
            assert self.instance is not None, (
                '`update()` did not return an object instance.'
            )
        else:
            self.instance = self.create(validated_data)
            assert self.instance is not None, (
                '`create()` did not return an object instance.'
            )

        return self.instance

2)默认序列化器必须传递所有required的字段,否则会抛出验证异常。

针对一些特殊使用场景,例如用户注册时对数据进行必填的验证,如手机号码,用户名。在修改密码时如果使用了同一个序列化器,我们修改密码的时候是不需要用户在此填写其他注册信息的。这种情况我们就要允许用户在这种情况下,只提交部分数据。序列化器针对这种情况,允许我们使用partial参数来允许反序列时,针对用户提交的部分字段进行校验。也就是说,没有提交的字段,在设置了partial=True时,不会去校验。

视图代码:

    def get(self, request):
        """在特殊业务下,可以让客户端绕过部分字段的验证,仅针对客户端提交的数据进行验证"""

        pk = 140
        params = {'age': 17}

        # 1. 先从数据库中获取本次要更新数据的模型对象
        student = Student.objects.get(pk=pk)
        # 2. 实例化序列化器
        # partial=True 设置只验证有值的字段
        serializer = StudentSerializer(instance=student, data=params, partial=True)
        # 3. 调用is_valid进行验证数据
        serializer.is_valid(raise_exception=True)
        # 4. 调用save方法调用模型存储数据
        # 注意,此处的save是序列化器内部的save方法,不是我们之前模型使用的save方法
        serializer.save()
        return JsonResponse(serializer.data)

调整序列化器,代码:

from rest_framework import serializers
from students.models import Student

# 声明序列化器,所有的序列化器都要直接或者间接继承于 Serializer
# 其中,ModelSerializer是Serializer的子类,ModelSerializer在Serializer的基础上进行了代码简化

# validators选项的验证函数可以有0到多个,这些验证函数和写在序列化器内部的验证方法使用方法一致,几乎没区别
# 函数名叫什么无所谓!函数必须有一个参数接收is_valid传递进行的字段数据
# validators是通用的选项,任何字段都可以指定自己多个外部验证函数
# 一般就是验证函数比较通用的时候,没必要把验证数据额代码写在每一个序列化器中时就可以使用外部验证函数了。
def check_sex(data):
    # 必须把数据返回,否则数据丢失了
    if data > 2:
        raise serializers.ValidationError(code="sex", detail="对不起,地球上没有这个性别选项!")

    # 必须返回验证后的数据
    return data

class StudentSerializer(serializers.Serializer):
    """学生信息序列化器"""
    # 1. 声明在请求和响应过程中,需要进行数据转换的字段
    # read_only=True 表示用户提交数据进行反序列化时,序列化器不会启用这个字段,而是在序列化器进行序列化转换数据给客户端时才启用这个字段
    id = serializers.IntegerField(read_only=True)
    # required=True 表示用户移交数据时,这个字段时必填的
    # max_length=20 表示当前字符串字段最大长度为20个字符
    name = serializers.CharField(required=True, max_length=20)
    classmate = serializers.CharField(required=True, max_length=3, error_messages={"required":"当前字段必须填写!","max_length": "对不起,长度不能超过3个字符"})
    age = serializers.IntegerField()
    sex = serializers.IntegerField(validators=[check_sex,])
    # required=False 表示当前字段,用户提交数据时,可以不提交这个字段的数据
    # default        表示当前字段,用户提交数据时,没有值的情况下,采用这个值作为默认值传递验证结果的返回值中。
    description = serializers.CharField(required=False, default="这家伙很懒,什么也没写!")

    # 2. 如果序列化器集成的是ModelSerializer,则需要声明调用的模型信息

    # 3. 验证代码
    # 3.1 对单个字段数据进行验证,
    # 格式:
    #     一个序列化器中可以有0个或多个验证字段方法,方法名唯一。
    #     必须以 "validate_<字段名>"作为验证方法名,否则is_valid找不到的
    #     验证方法执行时,is_valid会把当前客户端提交的字段数据传递到验证方法,我们需要接收该参数进行验证
    #     验证方法必须把当前字段的数据作为方法的返回值,回传给序列化器,否则该数据丢失。
    def validate_name(self, data):
        print(f"验证方法[validate_nmae]={data}")
        if data == "python":
            # 序列化器中要抛出异常,可以通过serializers.ValidationError()把异常抛出给is_valid进行处理
            raise serializers.ValidationError(code="name", detail="当前数据属于敏感字符!")

        # 必须有返回值,而且返回值必须是当前字段的数据值,可以改动数据,但是必须返回
        return data

    # 3.2 对多个字段的数据进行验证
    # 格式:
    #    一个序列化器中只能有0个或1个多字段验证方法,方法名唯一
    #    方法名必须叫 validated(self, data),否则is_valid找不到的
    #    验证方法执行时,is_valid会把当前客户端提交的所有字段数据传递到验证方法,我们需要接收该参数进行验证
    #    验证方法必须把当前字段的数据作为方法的返回值,回传给序列化器,否则该数据丢失。
    def validate(self, attrs):
        # 多字段的验证一般用于,用户注册时密码和确认密码,2个字段的密码必须一样。这种情况
        # 判断年龄在20以下不能添加到5字头的班级
        age = attrs.get("age")
        classmate = attrs.get("classmate")
        if (age < 20) and str(classmate).startswith("5"):
            raise serializers.ValidationError("对不起!年龄在20以下不能添加到5字头的班级")
        return attrs

    # 4. 编写模型的添加和更新代码
    # 添加操作,在视图调用序列化器的save时,必须在序列化器中实现create方法,否则报错
    def create(self, validated_data):
        """添加数据,到模型中的实现过程"""

        # student = Student.objects.create(**validated_data)
        # 如果validated_data有多余的字段,不属于模型对象,则自定义字典
        student = Student.objects.create(
            name=validated_data.get("name"),
            age=validated_data.get("age"),
            sex=validated_data.get("sex"),
            classmate=validated_data.get("classmate"),
            description=validated_data.get("description"),
        )
        # 强烈建议返回添加后的模型对象
        return student

    def update(self, instance, validated_data):
        """
        参数1:instance 本次更新数据的模型对象
        参数2:validated_data 本次客户端提交并经过验证后的数据
        """
        if validated_data.get("name"):
            instance.name = validated_data.get("name")
        if validated_data.get("age"):
            instance.age = validated_data.get("age")
        if validated_data.get("sex"):
            instance.sex = validated_data.get("sex")
        if validated_data.get("classmate"):
            instance.classmate = validated_data.get("classmate")
        # 针对可选字段,进行条件判断
        if validated_data.get("description"):
            instance.description = validated_data.get("description")

        # 模型调用ojbects提供的save方法保存数据到数据库中。
        instance.save()
        return instance

7.3.3 模型类序列化器

如果我们想要使用序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。

ModelSerializer与常规的Serializer相同,但提供了:

  • 基于模型类自动生成一系列字段
  • 基于模型类自动为Serializer生成validators,比如unique_together
  • 包含默认的create()和update()的实现
7.3.3.1 定义

比如我们创建一个StudentSerializer

"""模型类序列化器"""
from students.models import Student
class StudentModelSerializer(serializers.ModelSerializer):
    # 1. 声明要转换的字段[当部分字段不是来自于模型类时,就要自己手动声明]
    # 2. 模型类信息
    class Meta:
        model = Student
        fields = "__all__"
  • model 指明参照哪个模型类来转换字段
  • fields 指明为模型类的哪些字段生成,exclude互斥

视图代码:

from .serializers import StudentModelSerializer
class Student2View(View):
    """模型类序列化器"""
    def get1(self,request):
        """序列化一条数据"""
        student = Student.objects.first()
        serializer = StudentModelSerializer(instance=student)
        print(serializer.data)
        return JsonResponse(serializer.data)
    def get(self,request):
        """序列化多条数据"""
        student_list = Student.objects.all()
        serializer = StudentModelSerializer(instance=student_list, many=True)
        print(serializer.data)
        return JsonResponse(serializer.data, safe=False)

sers.urls,代码:

from django.urls import path
from . import views

urlpatterns =[
    path("s1/", views.Student1View.as_view()),
    path("s2/", views.Student2View.as_view()),
]
7.3.3.2 指定字段
  1. 使用fields来明确字段,__all__表名包含所有字段,也可以写明具体哪些字段,如
"""模型类序列化器"""
from students.models import Student
class StudentModelSerializer(serializers.ModelSerializer):
    # 1. 声明要转换的字段[当部分字段不是来自于模型类时,就要自己手动声明]
    user = serializers.CharField(write_only=True,error_messages={"required":"管理员身份必须填写!"})
    # 2. 模型类信息
    class Meta:
        model = Student
        fields = ["id", "name","sex","classmate","user"]  # 指定要经过序列化器调用的字段填写进来
        # fields = "__all__"
        # exclude = ["description"]  # 把不需要序列化器转换的字段进行排除,保留剩余

使用exclude可以明确排除掉哪些字段

显式指明字段,一般用于在修改数据库本身已有的外键字段或者数据库模型中没有声明的字段中。

指明只读字段

可以通过read_only_fields指明只读字段,即仅用于序列化输出的字段

from students.models import Student
class StudentModelSerializer(serializers.ModelSerializer):
    # 1. 声明要转换的字段[当部分字段不是来自于模型类时,就要自己手动声明]
    user = serializers.CharField(write_only=True,error_messages={"required":"管理员身份必须填写!"})
    # 2. 模型类信息
    class Meta:
        model = Student
        fields = ["id", "name","sex","classmate","user"]  # 指定要经过序列化器调用的字段填写进来
        # fields = "__all__"
        # exclude = ["description"]  # 把不需要序列化器转换的字段进行排除,保留剩余
        read_only_fields = ["id"]    # 指定只读字段列表,写在这里的字段不需要用户上传,服务端直接返回
        # 当然,此处的id即便我们不声明,drf也会把当前设置为字段字段。
7.3.3.3 添加额外字段声明

我们可以使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数

"""模型类序列化器"""
from students.models import Student
class StudentModelSerializer(serializers.ModelSerializer):
    # 1. 声明要转换的字段[当部分字段不是来自于模型类时,就要自己手动声明]
    user = serializers.CharField(write_only=True,error_messages={"required":"管理员身份必须填写!"})
    # 2. 模型类信息
    class Meta:
        model = Student
        fields = ["id", "name","sex","classmate","user"]  # 指定要经过序列化器调用的字段填写进来
        # fields = "__all__"
        # exclude = ["description"]  # 把不需要序列化器转换的字段进行排除,保留剩余
        read_only_fields = ["id"]    # 指定只读字段列表,写在这里的字段不需要用户上传,服务端直接返回

        # 字段补充声明
        extra_kwargs = {
            "age": {
                "min_value": 0,
                "max_value": 50,
                "error_messages": {
                    "min_value": "年龄不能负数!",
                    "max_value": "年龄不能超过50岁",
                }
            },
            "name": {
                "error_messages":{
                    "max_length": "学生姓名不能超过20个字符!",
                }
            },
            "classmate": {
                "error_messages": {
                    "required": "班级编号必须填写!"
                }
            }
        }

视图,代码:

from .serializers import StudentModelSerializer
class Student2View(View):
    """模型类序列化器"""
    def get1(self,request):
        """序列化一条数据"""
        student = Student.objects.first()
        serializer = StudentModelSerializer(instance=student)
        print(serializer.data)
        return JsonResponse(serializer.data)
    def get2(self,request):
        """序列化多条数据"""
        student_list = Student.objects.all()
        serializer = StudentModelSerializer(instance=student_list, many=True)
        print(serializer.data)
        return JsonResponse(serializer.data, safe=False)
    def get3(self, request):
        """添加数据时的反序列化"""
        # 模拟用户提交过来的数据
        params = {
            "name": "王小明",
            "age": 17,
            "sex": 1,
            "classmate": "401"
        }

        serializer = StudentModelSerializer(data=params)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return JsonResponse(serializer.data, safe=False)
    def get(self, request):
        """更新数据时的反序列化"""
        # 模拟url地址中的pk值[restful中规定,修改,删除,查询1条数据,需要把id写在地址栏中]
        pk = 142
        # 模拟用户提交过来的数据
        params = {
            "name": "李大黑",
            "age": 25,
            "sex": 1,
            "classmate": 401,
            "user": "root",
        }

        # 1. 先从数据库中获取本次要更新数据的模型对象
        student = Student.objects.get(pk=pk)
        serializer = StudentModelSerializer(instance=student, data=params)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return JsonResponse(serializer.data, safe=False)

7.4 序列化器嵌套使用

在序列化器使用过程中,一般一个序列化器对应一个模型数据。往往因为模型之间会存在外键关联,所以一般在输出数据时不仅要获取当前模型的数据,甚至其他模型的数据也需要同时返回,这种情况下,我们可以通过序列化器嵌套调用的方式,帮我们把当前模型数据进行转换以外还额可以同时转换外键对应的模型数据。

为了方便演示,此处我们再次创建一个新的子应用来完成这块内容的学习。

python manage.py startapp school

注册子应用,settings.py,代码:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',
    'stuapi',   # django原生实现的API接口
    'students', # drf实现的API接口
    'sers',     # 序列化器的学习
    "school",  # 序列化器嵌套
    'req',      # drf提供的请求与响应
    'demo',    # 视图
]

子应用的子路由文件urls.py,代码:

from django.urls import path
from . import views

urlpatterns = [
]

总路由,urls.py,代码:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('students/', include("students.urls")),
    path('sers/', include("sers.urls")),
    path('school/', include("school.urls")),
]

为了方便演示嵌套这个过程。所以我们在这里简单创建几个关联模型关系。模型models.py,代码:

from django.db import models
from django.utils import timezone as datetime 
# Create your models here.
# 学生     sch_student        1                           id=1   name=xiaoming      
# 成绩     sch_achievement    n    n                      id=17  student_id=1   score=97
# 课程     sch_course              1    n
# 老师     sch_teacher                  1

class Student(models.Model):
    name = models.CharField(max_length=50, verbose_name="姓名")
    age  = models.SmallIntegerField(verbose_name="年龄")
    sex  = models.BooleanField(default=False)
    class Meta:
        db_table = "sch_student"

    def __str__(self):
        return self.name

class Course(models.Model):
    name = models.CharField(max_length=50, verbose_name="课程名称")
    teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, related_name="course",db_constraint=False)
    class Meta:
        db_table = "sch_course"

    def __str__(self):
        return self.name

class Teacher(models.Model):
    name = models.CharField(max_length=50, verbose_name="姓名")
    sex  = models.BooleanField(default=False)
    class Meta:
        db_table = "sch_teacher"

    def __str__(self):
        return self.name

class Achievement(models.Model):
    score = models.DecimalField(default=0, max_digits=4,decimal_places=1, verbose_name="成绩")
    student = models.ForeignKey(Student,on_delete=models.DO_NOTHING, related_name="s_achievment",db_constraint=False)
    course = models.ForeignKey(Course,on_delete=models.DO_NOTHING,related_name="c_achievement",db_constraint=False)
    create_dtime = models.DateTimeField(auto_created=datetime.now)
    class Meta:
        db_table = "sch_achievement"

    def __str__(self):
        return self.score

数据迁移,终端执行:

python manage.py makemigrations
python manage.py migrate

测试数据,代码:

INSERT INTO students.sch_teacher (id, name, sex) VALUES (1, '李老师', 0);
INSERT INTO students.sch_teacher (id, name, sex) VALUES (2, '曹老师', 1);
INSERT INTO students.sch_teacher (id, name, sex) VALUES (3, '许老师', 1);

INSERT INTO students.sch_student (id, name, age, sex) VALUES (1, '小明', 18, 1);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (2, '小黑', 22, 1);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (3, '小白', 18, 1);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (4, '小红', 17, 0);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (5, '小程', 18, 0);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (6, '小辉', 16, 0);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (7, '小明2', 18, 1);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (8, '小黑2', 22, 1);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (9, '小白2', 18, 1);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (10, '小红2', 17, 0);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (11, '小程2', 18, 0);
INSERT INTO students.sch_student (id, name, age, sex) VALUES (12, '小辉2', 16, 0);

INSERT INTO students.sch_course (id, name, teacher_id) VALUES (1, 'python入门', 1);
INSERT INTO students.sch_course (id, name, teacher_id) VALUES (2, 'python进阶', 2);
INSERT INTO students.sch_course (id, name, teacher_id) VALUES (3, 'python高级', 3);
INSERT INTO students.sch_course (id, name, teacher_id) VALUES (4, 'django入门', 2);
INSERT INTO students.sch_course (id, name, teacher_id) VALUES (5, 'django进阶', 3);
INSERT INTO students.sch_course (id, name, teacher_id) VALUES (6, 'django高级', 3);

INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 1, 100.0, 1, 1);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 2, 100.0, 2, 2);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 3, 100.0, 3, 3);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 4, 78.0, 4, 4);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 5, 78.0, 5, 5);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 6, 78.0, 6, 6);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 7, 78.0, 1, 7);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 8, 80.0, 2, 8);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 9, 80.0, 3, 9);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 10, 80.0, 4, 10);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 11, 80.0, 5, 11);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 12, 60.0, 6, 12);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 13, 100.0, 1, 2);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 14, 100.0, 2, 3);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 15, 100.0, 3, 4);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 16, 78.0, 4, 5);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 17, 78.0, 5, 6);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 18, 78.0, 6, 7);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 19, 78.0, 1, 8);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 20, 80.0, 2, 9);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 21, 80.0, 3, 10);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 22, 80.0, 4, 11);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 23, 80.0, 5, 12);
INSERT INTO students.sch_achievement (create_dtime, id, score, course_id, student_id) VALUES ('2021-06-16 01:18:25.496000', 24, 60.0, 6, 1);

新建序列化器,重新指定关联属性的值

一对多或者多对多的序列化器嵌套返回多个数据情况

默认情况下,模型经过序列化器的数据转换,对于外键的信息,仅仅把数据库里面的外键ID返回。

序列化器,serializers.py,代码:

from rest_framework import serializers
from .models import Teacher,Course

class CourseModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = ["id","name"]

class TeacherModelSerializer(serializers.ModelSerializer):
    # 1. 重写模型对应的外键,指定返回的数据是什么
    course = CourseModelSerializer(many=True)
    class Meta:
        model = Teacher
        fields = ["id","name","course"]

视图,views.py,代码:

# Create your views here.
from .models import Teacher
from .serializers import TeacherModelSerializer
from django.views import View
from django.http.response import JsonResponse

class TeacherView(View):
    def get1(self,request):
        """获取一个老师信息"""
        teacher = Teacher.objects.last()
        serializer = TeacherModelSerializer(instance=teacher)
        return JsonResponse(serializer.data, json_dumps_params={"ensure_ascii":False})

    def get(self,request):
        """获取多个老师信息"""
        teacher = Teacher.objects.all()
        serializer = TeacherModelSerializer(instance=teacher,many=True)
        return JsonResponse(serializer.data, safe=False, json_dumps_params={"ensure_ascii":False})

路由,urls.py,代码:

from django.urls import path
from . import views

urlpatterns = [
    path("s1/", views.TeacherView.as_view())
]

访问效果:

image-20210830163402975

多对一或者一对一的序列化器嵌套返回一个数据情况

序列化器,serializers.py,代码:

class Teacher2ModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = ["id","name"]

class Course2ModelSerializer(serializers.ModelSerializer):
    teacher = Teacher2ModelSerializer()
    class Meta:
        model = Course
        fields = ["id","name","teacher"]

视图,views.py,代码:

from .serializers import Course2ModelSerializer
class CourseView(View):
    def get1(self,request):
        """获取一个课程信息"""
        course = Course.objects.first()
        serializer = Course2ModelSerializer(instance=course)
        return JsonResponse(serializer.data, safe=False, json_dumps_params={"ensure_ascii":False})

    def get(self,request):
        """获取多个课程信息"""
        course = Course.objects.all()
        serializer = Course2ModelSerializer(instance=course,many=True)
        return JsonResponse(serializer.data, safe=False, json_dumps_params={"ensure_ascii":False})

路由,urls.py,代码:

from django.urls import path
from . import views

urlpatterns = [
    path("s1/", views.TeacherView.as_view()),
    path("s2/", views.CourseView.as_view())
]

访问效果:

image-20210830163345993

source选项取代主键数值

在多对一或者一对一的序列化器嵌套中,通过source选项,直接通过外键指定返回的某个字段数据。

序列化器,serializers.py,代码:

class Course2ModelSerializer(serializers.ModelSerializer):
    # teacher = Teacher2ModelSerializer()
    teacher = serializers.CharField(source="teacher.name")
    teacher_id = serializers.CharField(source="teacher.id")
    class Meta:
        model = Course
        fields = ["id","name","teacher","teacher_id"]

访问效果:

image-20210830163427611

直接指定关联深度属性

序列化器,代码:

from rest_framework import serializers
from .models import Teacher,Course,Student

class Course2ModelSerializer(serializers.ModelSerializer):
    # teacher = Teacher2ModelSerializer()
    # teacher = serializers.CharField(source="teacher.name")
    class Meta:
        model = Course
        fields = ["id","name","teacher"]
        # 指定关联深度
        # 从老师模型->课程 = 1
        # 从老师模型->课程->成绩 = 2
        # 从老师模型->课程->成绩->学生 = 3
        # ....
        depth = 1

class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = ["id","name","s_achievment"]
        depth = 3

视图代码:

from .serializers import Course2ModelSerializer
class CourseView(View):
    def get1(self,request):
        """获取一个课程信息"""
        course = Course.objects.first()
        serializer = Course2ModelSerializer(instance=course)
        return JsonResponse(serializer.data, safe=False, json_dumps_params={"ensure_ascii":False})

    def get(self,request):
        """获取多个课程信息"""
        course = Course.objects.all()
        serializer = Course2ModelSerializer(instance=course,many=True)
        return JsonResponse(serializer.data, safe=False, json_dumps_params={"ensure_ascii":False})

from .serializers import StudentModelSerializer
class StudentView(View):
    def get(self,request):
        """获取一个学生信息"""
        student = Student.objects.first()
        serializer = StudentModelSerializer(instance=student)
        return JsonResponse(serializer.data, safe=False, json_dumps_params={"ensure_ascii":False})

路由代码:

from django.urls import path
from . import views

urlpatterns = [
    path("s1/", views.TeacherView.as_view()),
    path("s2/", views.CourseView.as_view()),
    path("s3/", views.StudentView.as_view()),
]

自定义模型属性方法

模型,models.py,代码:

from django.db import models
from django.utils import timezone as datetime
# Create your models here.
# 学生     sch_student        1
# 成绩     sch_achievement    n    n
# 课程     sch_course              1    n
# 老师     sch_teacher                  1

class Student(models.Model):
    name = models.CharField(max_length=50, verbose_name="姓名")
    age  = models.SmallIntegerField(verbose_name="年龄")
    sex  = models.BooleanField(default=False)
    class Meta:
        db_table = "sch_student"

    def __str__(self):
        return self.name
    
    # 自定义模型方法
    @property
    def achievement(self):
        return self.s_achievment.values()
        # return self.s_achievment.values("course__teacher__name", "course__name", "score")

class Course(models.Model):
    name = models.CharField(max_length=50, verbose_name="课程名称")
    teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, related_name="course",db_constraint=False)
    class Meta:
        db_table = "sch_course"

    def __str__(self):
        return self.name

class Teacher(models.Model):
    name = models.CharField(max_length=50, verbose_name="姓名")
    sex  = models.BooleanField(default=False)
    class Meta:
        db_table = "sch_teacher"

    def __str__(self):
        return self.name

class Achievement(models.Model):
    score = models.DecimalField(default=0, max_digits=4,decimal_places=1, verbose_name="成绩")
    student = models.ForeignKey(Student,on_delete=models.DO_NOTHING, related_name="s_achievment",db_constraint=False)
    course = models.ForeignKey(Course,on_delete=models.DO_NOTHING,related_name="c_achievement",db_constraint=False)
    create_dtime = models.DateTimeField(auto_created=datetime.now)
    class Meta:
        db_table = "sch_achievement"

    def __str__(self):
        return str( float(self.score) )

序列化器,代码:

class Student2ModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = ["id","name","achievement"]

视图代码:

from .serializers import StudentModelSerializer,Student2ModelSerializer
class StudentView(View):
    def get1(self,request):
        """获取一个学生信息"""
        student = Student.objects.first()
        serializer = StudentModelSerializer(instance=student)
        return JsonResponse(serializer.data, safe=False, json_dumps_params={"ensure_ascii":False})

    def get(self,request):
        """获取一个学生信息"""
        student = Student.objects.first()
        serializer = Student2ModelSerializer(instance=student)
        return JsonResponse(serializer.data, safe=False, json_dumps_params={"ensure_ascii": False})

路由,代码:

from django.urls import path
from . import views

urlpatterns = [
    path("s1/", views.TeacherView.as_view()),
    path("s2/", views.CourseView.as_view()),
    path("s3/", views.StudentView.as_view()),
]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值