djangorestframework,简称为DRF,它是一种基于前后端分离设计架构的基于rest风格的一种API接口的实现,也就是说,它本身是一种基于rest的接口设计风格,并且是在Django中实现了这种设计。
常见的rest设计风格有9方面:
一、域名
尽量使用专有域名,如:api.zytk.xyz
二、版本
版本信息可放入url中,也可放入到请求的参数中,但建议最好放在url中。
三、路由
路由中只有名词,不能有动词,并且名词尽量使用复数形式。
四、请求
不同的请求使用不同的请求方法来表示,常见请求与请求方法对应如下表:
| 请求 |
请求方法 |
功能 |
| 获取信息 |
GET |
查 |
| 创建对象 |
POST |
增 |
| 更新对象 |
PUT |
改 |
| 删除对象 |
DELETE |
删 |
| 局部更新对象 |
PATCH |
局部改 |
| 获取资源的元数据 |
HEAD |
查资源头 |
五、过滤
过滤是指对信息的过滤或筛选,一般是将过滤的条件通过字符串查询参数的形式传给后端程序。然后后端程序对数进行过滤后,返回给前端。
六、状态码
不同的请求状态对应不同的状态码,每次请求的结果,服务器端都应该返回相应的状态码,常见的状态码如下表:
| 状态码 |
状态 |
| 201 |
创建对象成功 |
| 202 |
接受请求成功,正在排队等待处理... |
| 204 |
删除对象成功 |
| 200 |
查询信息成功 |
| 401 |
无权访问,认证失败 |
| 404 |
url有误,请检查请求的url |
七、错误处理
每一次请求如果出错,一定要返回错误处理信息。该信息的格式一般是json格式。
八、返回值格式
一般都要返回json格式,当然也可返回text、xml等其它格式,但是,最好是返回json这种格式的数据。
九、数据格式
一切数据格式,无论是请求时携带的参数,还是请求响应时返回的数据,包括错误信息以及状态码的返回,最好全部使用json数据格式。
第一个drf示例——手动实现一个杂志订阅的api接口
项目简介:实现杂志的增删改查操作
实现功能如下表所示:
| 功能 |
请求方式 |
请求路径 |
| 获取所有杂志对象 |
GET |
api/magazines |
| 创建单本杂志对象 |
POST |
api/magazines |
| 获取单本杂志对象 |
GET |
api/magazines/{pk} |
| 修改单本杂志对象 |
PUT |
api/magazines/{pk} |
| 删除单本杂志对象 |
DELETE |
api/magazines/{pk} |
从请求路径上来看分为两类请求:
一类是不需要带pk参数的请求
一类是需要带pk参数的请求
我们可以把不需要带pk参数的两个请求归类为列表视图,而把需要带pk参数的三个请求归类为详情视图。
项目数据库使用mongodb
步骤:
一、创建项目
1. 进入虚拟环境
2. 创建项目
django-admin startproject 【项目名】
二、创建一个新的Django应用
1. 进入项目根目录
2.创建新应用
python manage.py startapp 【应用名称】
3. 注册应用
在settings.py中的【INSTALLED_APPS】中添加一项“magazie”
三、配置mongodb库支持
在setting.py中【DATABASES】中添加一项配置,如下所示:
'mongodb': {
'ENGINE': None
}
然后再配置mongodb的连接方式
from mongoengine import *
conn = connect('magazines', host="127.0.0.1", port=27017, connect=False)
添加以上代码时,需要先安装mongoengine扩展,该扩展是用来操作mongodb数据库的python扩展之一。
四、配置路由
1.配置主路由
path('api/', include("magazine.urls")),
2.配置子路由
path('magazines/',views.MagazineListView.as_view()),
path('magazines/<pk>',views.MagazineDetailView.as_view())
五、创建数据模型
因为是基于mongodb的数据模型,所以要引入mongoengine
from mongoengine import *
创建模型类如下所示:
"""
杂志期刊类型模型
"""
class PublisherType(Document):
name = StringField()
publisherTime = DateTimeField()
"""
出版社
p1 = Publisher(name="人民出版社")
p1.save()
p2 = Publisher(name="清华出版社")
p2.save()
p3 = Publisher(name="河南出版社")
p3.save()
"""
class Publisher(Document):
name = StringField()
"""
杂志模型
m1 = Magazine(title="计算机应用基础",publisherType=pt1,price=35.6,publisher=p1)
m1.save()
m2 = Magazine(title="计算机组装与维修",publisherType=pt2,price=41,publisher=p2)
m2.save()
m3 = Magazine(title="电子商务基础",publisherType=pt3,price=60.412,publisher=p3)
m3.save()
"""
class Magazine(Document):
# 杂志名
title = StringField()
# 杂志期刊类型
publisherType = ReferenceField(PublisherType)
# 杂志价格
price = FloatField(default=0)
# 出版社
publisher = ReferenceField(Publisher)
六、编写视图
1. MagazineListView视图
在这个视图中实现两个操作:获取所有杂志对象和创建单个杂志对象。
获取所有杂志对象的请求方法是get方法,具体的实现步骤:
①从模型中获取所有杂志对象
queryset = models.Magazine.objects.all()
②将对象列表转换成字典列表(数据转换,序列化操作)
objlist=[]
objdict={}
for obj in queryset:
objdict = {
"id": str(obj.id),
"title":obj.title,
"publisherType": {
"id": str(obj.publisherType.id),
"name": obj.publisherType.name
},
"price": obj.price,
"publisher": {
"id": str(obj.publisher.id),
"name": obj.publisher.name
}
}
objlist.append(objdict)
③返回结果
return JsonResponse(objlist,safe=False)
创建单个杂志对象的请求方法是post方法,具体的实现步骤是:
①获得客户端传来的数据
objdict = json.loads(request.body.decode())
title = objdict["title"]
publisherType = objdict["publisherType"]
price = objdict["price"]
publisher = objdict["publisher"]
print(publisherType["id"])
②校验数据(省略)
因为字段越多,校验数据的复杂度越大,因此这里是实现的重点,但不是难点,就是代码量较大,所以在这里省略不写了,这是我们手写api时留下的一个问题。
③数据入库
A、查询期刊类型
publisherType = models.PublisherType.objects.get(pk=publisherType["id"])
publisher = models.Publisher.objects.get(pk=publisher["id"])
B、创建杂志并入库
magazine = models.Magazine.objects.create(title=title,publisherType=publisherType,price=price, publisher=publisher)
④数据转换
magazinedict = {
"id": str(magazine.id),
"title":magazine.title,
"publisherType": {
"id": str(magazine.publisherType.id),
"name": magazine.publisherType.name
},
"price": magazine.price,
"publisher": {
"id": str(magazine.publisher.id),
"name": magazine.publisher.name
}
}
⑤返回结果
return JsonResponse(magazinedict,safe=False)
2.MagazineDetailView视图
这个视图类实现了获取、修改和删除单本杂志对象的功能。
其源码如下:
class MagazineDetailViewset(View):
# 获取单个杂志对象
def get(self,request,pk):
# 1.根据参数pk获得单个杂志对象
magazine = models.Magazine.objects.get(pk=pk)
# 2.序列化该对象
magazinedict = {
"id": str(magazine.id),
"title":magazine.title,
"publisherType": {
"id": str(magazine.publisherType.id),
"name": magazine.publisherType.name
},
"price": magazine.price,
"publisher": {
"id": str(magazine.publisher.id),
"name": magazine.publisher.name
},
# "photo": magazine.photo
}
# 3.返回结果
return JsonResponse(magazinedict,safe=False)
def put(self,request,pk):
# 1.获取更新参数
objdict = json.loads(request.body.decode())
title = objdict["title"]
publisherType = objdict["publisherType"]
price = objdict["price"]
publisher = objdict["publisher"]
print(publisherType["id"])
print(objdict)
# 2.数据检验(省略)
# 3.获取要更新的对象
magazine = models.Magazine.objects.get(pk=pk)
# 4. 更新对象并入库
# 4.1. 查询期刊类型对象和出版社对象
publisherType = models.PublisherType.objects.get(pk=publisherType["id"])
publisher = models.Publisher.objects.get(pk=publisher["id"])
# 4.2. 更新对象
magazine.title = title
magazine.publisherType=publisherType
magazine.publisher = publisher
magazine.price = magazine.price
# 4.3.入库
magazine.save()
# 5.序列化数据
magazinedict = {
"id": str(magazine.id),
"title":magazine.title,
"publisherType": {
"id": str(magazine.publisherType.id),
"name": magazine.publisherType.name
},
"price": magazine.price,
"publisher": {
"id": str(magazine.publisher.id),
"name": magazine.publisher.name
},
# "photo": magazine.photo
}
# 6.返回结果
return JsonResponse(magazinedict,safe=False)
def delete(self,request,pk):
# 1.获取要删除的对象
magazine = models.Magazine.objects.get(pk=pk)
# 2. 删除对象
magazine.delete()
# 3.返回结果
return HttpResponse("-1")
到这里,我们就自己手写了一个基于rest风格的api接口。但就目前的设计而言,还存在着一些问题,这些问题如下:
1. 重复代码较多,不能很好的实现代码重用,因此,后面我们要对这些重复的代码进行封装,以实现代码的重用,使代码变的简洁。
2. 在接口的实现过程中,经常遇到将模型对象转换成json数据的过程,我们称之为序列化,这个序列化过程经常被用到,我们能否将这一过程抽离出来,形成一个仅对数据时行序列化的类,用这个类来完成这个专门的工作呢?
3. 在接口的实现过程中,还有对数据进行校验的操作,这个过程是否也可以抽取出来呢?
4. 同样,在增加对象接口的实现过程中,需要将外部传来的json数据转换成模型对象,这个过程我们称之为反序列化,那么,反序列化是否也能够封装呢?
下面,我们就来一步一步的进行封装和改进。
在改进前,我们先来了解几个概念。
概念一:序列化:就是将模型数据转换为json数据的过程。主要应用在get请求时,获取所有模型类对象或获取单个模型类对象。
概念二:反序列化:就是将json数据转换成模型数据的过程。主要用在post或put请求创建模型对象或修改模型对象的场景中。
概念三:序列化器:它是一个模型的序列化或反序列化的封装,它是一个类,该类的作用是用来序列化或反序列一个模型的对象。它起到的主要功能就是校验数据和数据转换。
序列化器的定义
目的:是参考数据模型,定义序列化器。
定义序列化器的4个原则:
1. 序列化器中的字段要和模型中的字段名相同。
2. 序列化器中的字段类型要和模型中的字段类型要一样。
3. 序列化器中的字段的选项也要和模型中的字段的选项相同。
4. 最重要的是序列化器要继承自DocumentSerializer类(针对于mongodb数据库模型的序列化器),即rest_framework_mongoengine.serializers.DocumentSerializer;而针对于mysql、sqite、sql等数据库模型类的序列化器要继承自Serializer类,即rest_framework.Serializer。