前置基础知识
web开发的两种模式
1. 前后端不分离
完整的html页面是在后端生成的,后端给前端返回完整的页面,前端只是进行展示,前端与后端的耦合度很高。
缺点:只适用于纯网页的应用。
优点:有利于网站的SEO优化
2. 前后端分离
完整的html页面是在前端生成的,后端只给前端返回所需的数据,前端将数据填充在页面上,前端与后端的耦合度很低。
优点:可以对接不同类型的客户端。
缺点:不利于SEO优化
3.API: 在前后端分离开发模式中,我们通常将后端开发的每个视图都称为一个接口或者API。
4.RESTful设计风格
统一的接口设计方式就是普遍采用的RESTful API设计风格
Restful风格设计-关键点
- URL地址尽量使用名词复数,不要使用动词。
- 访问同一个URL地址,采用不同的请求方式,代表要执行不同的操作。(GET)获取 POST(新增) PUT(修改)DELETE(删除) 不常用:PATCH(修改) HEAD(只返回请求头没有请求体) OPTIONS(获取信息)
- 过滤参数可以放在查询字符串中。常见的参数:
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&pagesize=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
- 针对不同操作,服务器向用户返回不同的响应数据。一般遵循以下规范:
1. 获取一组数据,返回一组数据
2. 获取指定数据,返回指定数据
3. 新增数据,返回新增的数据
4. 修改数据,返回修改的数据
5. 删除数据,返回空
- 服务器返回的响应数据格式,应该尽量使用JSON。
- 响应状态码,服务器向客户端返回的状态码和提示信息,常见的状态码如下:
200 OK - [GET/PUT]:服务器成功返回用户请求的数据
201 CREATED - [POST]:用户新建数据成功。
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
了解:
- 域名:使用专有域名
- 版本:将版本信息放在url地址
- 错误:将错误信息返回
- 在访问api接口时,将和接口相关的其他API接口的地址也在响应数据中返回
使用Django开发REST API
需求:
设计一套符合RestAPI风格的接口,提供以下5个接口:
1. 获取所有直播间数据:GET /lives/
2. 新增一本直播间数据:POST /lives/
3. 获取指定的直播间数据(根据id):GET /lives/(?P<pk>\d+)/
4. 修改指定的直播间数据(根据id):PUT /lives/(?P<pk>\d+)/
5. 删除指定的直播间数据(根据id):DELETE /lives/(?P<pk>\d+)/
模型类定义
# models.py
class LiveInfo(models.Model):
live_id = models.IntegerField(verbose_name='直播间号')
live_streamer = models.CharField(max_length=20, verbose_name='主播名字')
live_title = models.CharField(max_length=30, verbose_name='直播间标题')
live_pop = models.IntegerField(default=0, verbose_name='人气')
live_content = models.CharField(default='未设定', max_length=20, verbose_name='直播类型')
is_delete = models.BooleanField(default=False, verbose_name='删除标记')
class Meta:
db_table = 'tb_lives'
verbose_name = '直播间'
verbose_name_plural = verbose_name
def __str__(self):
return self.live_streamer
views.py文件中定义视图实现API接口:
class LiveListView(View):
def get(self, request):
"""获取所有直播间"""
lives = LiveInfo.objects.all()
lives_list = []
for live in lives:
lives_list.append({
'live_id': live.live_id,
'live_streamer': live.live_streamer,
'live_title': live.live_title,
'live_pop': live.live_pop,
'live_content': live.live_content
})
return JsonResponse(lives_list, safe=False)
def post(self, request):
"""新增直播间"""
pass
class LiveDetailView(View):
def get(self, request, pk):
"""查询一间直播间信息"""
pass
def put(self, request, pk):
"""修改直播间信息"""
pass
def delete(self, request, pk):
"""删除直播间"""
pass
RestAPI开发核心工作
分析上节直播间管理的5个API接口,可以发现,在开发REST API接口时,视图中做的最主要有三件事:
- 将请求的数据(如JSON格式)转换为模型类对象
- 操作数据库
- 将模型类对象转换为响应的数据(如JSON格式)
在以上操作中,涉及到两个概念:序列化和反序列化。
序列化Serialization
将程序中的一个数据结构类型转换为其他格式(字典、JSON、XML等),例如将Django中的模型类对象转换为字典或JSON字符串,这个转换过程我们称为序列化。
反序列化
将其他格式(字典、JSON、XML等)转换为程序中的数据,例如将JSON字符串或字典转换保存为Django中的模型类对象,这个过程我们称为反序列化。
在开发REST API接口时,我们在视图中在做的最核心的事是:
- 将数据库数据序列化为前端所需要的格式,并返回。
- 将前端发送的数据反序列化保存到模型类对象,并保存到数据库中。
Django REST framework 简介
Django REST framework 框架是一个用于构建Web API 的强大而又灵活的工具。通常简称为DRF框架 或 REST framework。
作用:帮助我们大大提高REST API的开发速度
核心功能:
- 序列化器:序列化和反序列化
- 类视图,MiXin扩展类:简化视图代码的编写
环境安装与使用:
pip install djangorestframework
# 注:DRF框架依赖于Django,需要先安装Django环境,再安装djangorestframework。
在Django项目中使用DRF框架进行开发时,需要将rest_framework在INSTALLED_APPS中进行注册:
INSTALLED_APPS = [
...
'rest_framework',
]
序列化器
序列化器类定义
from rest_framework import serializers
class LiveInfoSerializer(serializers.Serializer):
id = serializers.IntegerField(label='ID', read_only=True)
live_id = serializers.IntegerField(label='直播间号')
live_streamer = serializers.CharField(max_length=20, label='主播名字', required=False)
live_title = serializers.CharField(max_length=30, label='直播间标题')
live_pop = serializers.IntegerField(default=0, label='人气', required=False)
live_content = serializers.CharField(default='未设定', max_length=20, label='直播类型', required=False)
is_delete = serializers.BooleanField(default=False, label='删除标记', required=False)
选项参数:
- write_only:为True,字段只在反序列化时使用
- read_only:为True,字段只在序列化时使用
- required:为True,如果字段在反序列化时使用,该字段必传传入
- default:设置序列化和反序列化操作时的默认值
- max_length和min_length:设置字符串的最大长度和最小长度
- max_value和min_value:设置数字的最大值和最小值
序列化操作
- 序列化单个对象:序列化单个对象obj时,在创建序列化器对象时,将obj传递给instance即可
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveDetailView(View):
def get(self, request, pk):
"""查询一间直播间信息"""
try:
live = LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
return HttpResponse(status=404)
# 创建序列化器对象
serializer = LiveInfoSerializer(live)
# 返回序列化之后的数据
return JsonResponse(serializer.data)
- 序列化多个对象:如果要被序列化的是包含多个对象的查询集QuerySet或list,在创建序列化器对象时,需要添加many=True参数
class LiveListView(View):
def get(self, request):
"""获取所有直播间"""
lives = LiveInfo.objects.all()
# 创建序列化器对象
serialize# 获取序列化之后的数据r = LiveInfoSerializer(lives, many=True)
return JsonResponse(serializer.data, safe=False)
- 关联对象嵌套序列化
如果在序列化对象数据时,需要将其关联的对象一并序列化,则定义序列化器类的字段时,需要在定义对应的关联对象嵌套序列化字段,比如和直播分类关联的直播间。对于嵌套关联字段,可以采用以下3种方式进行定义:
1.PrimaryKeyRelatedField :将关联对象序列化为关联对象的主键。
# 在LiveInfoSerializer中添加此字段
Ltype = serializers.PrimaryKeyRelatedField(label='直播类型', read_only=True)
或
Ltype = serializers.PrimaryKeyRelatedField(label='直播类型', queryset=LiveInfo.objects.all())
2.使用关联对象的序列化器
Ltype = LiveInfoSerializer()
3.StringRelatedField:将关联对象序列化为关联对象模型类__str__方法的返回值。
Ltype = serializers.StringRelatedField(label='直播类型')
如果和一个对象关联的对象有多个,在序列化器类中定义嵌套序列化字段时,需要多添加一个many=True参数。
反序列化操作
# 1. 创建序列化器对象
serializer = 序列化器类(data=<待校验字典数据>)
# 2. 数据校验:成功返回True,失败返回False
serializer.is_valid()
# 3. 获取校验成功之后的数据
serializer.validated_data
# 4. 如果校验失败,获取失败的错误提示信息
serializer.errors
调用is_valid时,会根据对应序列化类字段是否需要传递、字段类型以及一些选项参数对data中的数据进行校验。
在调用is_valid进行数据校验时,除了一些基本的默认验证行为,可能还需要补充一些验证行为,可以使用以下三种方法:
数据校验通过之后,可以调用序列化对象的save方法进行数据保存,save方法内部会调用对应序列化器类中的create或update方法,可以在create中实现数据新增,在update中实现数据更新。
ModelSerializer使用
如果序列化器类对应的是Django的某个模型类,则定义序列化器类时,可以直接继承于ModelSerializer。
ModelSerializer是Serializer类的子类,相对于Serializer,提供了以下功能:
- 基于模型类字段自动生成序列化器类的字段
- 包含默认的create()和update()方法的实现
class LiveInfoSerializer(serializers.ModelSerializer):
class Meta:
model = LiveInfo
# 使用fields来指明依据模型类的哪些字段生成序列化器类的字段,__all__表明包含所有字段,也可以指明具体哪些字段
# fields = '__all__'
# fields = ('id', 'title', ...)
# 使用exclude可以指明排除哪些字段
exclude = ['is_delete']
extra_kwargs = {
# 'live_title': {'validators': [live_name]},
'live_streamer': {'required': False},
'live_pop': {'min_value': 0, 'required': False},
}
案例-序列化器改写后的视图代码:
import json
from django.http import JsonResponse, HttpResponse
from django.views import View
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveListView(View):
def get(self, request):
"""获取所有直播间"""
lives = LiveInfo.objects.all()
serializer = LiveInfoSerializer(lives, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request):
"""新增直播间"""
json_dict = json.dumps(request.body.decode())
serializer = LiveInfoSerializer(data=json_dict)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回新增直播间
return JsonResponse(serializer.data, status=201)
class LiveDetailView(View):
def get(self, request, pk):
"""查询一间直播间信息"""
try:
live = LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
return HttpResponse(status=404)
serializer = LiveInfoSerializer(live)
return JsonResponse(serializer.data)
def put(self, request, pk):
"""修改直播间信息"""
try:
live = LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
return HttpResponse(status=404)
json_dict = json.dumps(request.body.decode())
serializer = LiveInfoSerializer(live, data=json_dict)
serializer.is_valid(raise_exception=True)
serializer.save()
return JsonResponse(serializer.data)
def delete(self, request, pk):
"""删除直播间"""
try:
live = LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
return HttpResponse(status=404)
# 参数校验
live.delete()
return HttpResponse(status=204)
视图部分
APIView
APIView是REST framework提供的所有视图的基类,继承自Django的View类。
APIView和View的区别:
- 请求对象:Request类的对象request.data:解析之后的请求体数据 ,request.query_params:解析之后的查询字符串数据
- 响应对象:Response类对象统一返回Response对象,原始响应数据根据客户端请求头Accept转换为对应的格式进行返回
- 异常处理机制默认异常处理机制,会将视图中出现的异常处理成合适的响应返回给客户端
- 其他:认证&权限&限流
案例-使用APIView改写RestAPI:
GenericAPIView
继承自APIView,在APIView功能基础上,主要增加了操作序列化器和数据库查询的属性和方法。
GenericAPIView和APIView的区别:
- 继承自APIView
- 封装了操作序列化器的属性和方法属性:serializer_class方法:get_serializer_class和get_serializer
- 封装了数据库操作的属性和方法属性:queryset方法:get_queryset和get_object
- 其他:过滤和分页
案例-使用GenericAPIView改写RestAPI:
from rest_framework import status
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveListView(GenericAPIView):
serializer_class = LiveInfoSerializer
queryset = LiveInfo.objects.all()
def get(self, request):
"""获取所有直播间"""
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def post(self, request):
"""新增直播间"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回新增直播间
return Response(serializer.data, status=status.HTTP_201_CREATED)
class LiveDetailView(GenericAPIView):
serializer_class = LiveInfoSerializer
queryset = LiveInfo.objects.all()
def get(self, request, pk):
"""查询一间直播间信息"""
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def put(self, request, pk):
"""修改直播间信息"""
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def delete(self, request, pk):
"""删除直播间"""
instance = self.get_object()
# 参数校验
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Mixin扩展类
使用GenericAPIView改写之后的RestAPI接口中,图书管理的各个代码已经变成了通用的代码,这些代码和视图所使用的序列化器类和查询集已经没有直接的关系,DRF框架已经将这些代码做了封装,就是5个Mixin扩展类。
案例-Mixin改写RestAPI接口:
from rest_framework import mixins
from rest_framework.generics import GenericAPIView
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveListView(mixins.ListModelMixin, mixins.CreateModelMixin,GenericAPIView):
serializer_class = LiveInfoSerializer
queryset = LiveInfo.objects.all()
def get(self, request):
"""获取所有直播间"""
return self.list(request)
def post(self, request):
"""新增直播间"""
return self.create(request)
class LiveDetailView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView):
serializer_class = LiveInfoSerializer
queryset = LiveInfo.objects.all()
def get(self, request, pk):
"""查询一间直播间信息"""
return self.retrieve(request, pk)
def put(self, request, pk):
"""修改直播间信息"""
return self.update(request, pk)
def delete(self, request, pk):
"""删除直播间"""
return self.destroy(request, pk)
子类视图:Django框架为了方便视图的编写,还提供了9个子类视图类。
子类视图一定同时继承了GenericAPIView和对应的Mixin扩展类,同时类中提供了对应的请求处理方法,并且请求处理方法中调用的就是Mixin扩展类中封装的通用方法。
案例-子类视图改写RestAPI接口:
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveListView(ListCreateAPIView):
serializer_class = LiveInfoSerializer
queryset = LiveInfo.objects.all()
class LiveDetailView(RetrieveUpdateDestroyAPIView):
serializer_class = LiveInfoSerializer
queryset = LiveInfo.objects.all()
视图集ViewSet
将操作同一组资源相关的处理方法放在同一个类中,这个类叫做视图集。
基本使用:
常用视图集父类:
- ViewSet:继承自APIView与ViewSetMixin,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。
- GenericViewSet:继承自GenericAPIView与ViewSetMixin,可以直接搭配Mixin扩展类使用。
- ModelViewSet:继承自GenericViewSet和5个Mixin扩展类。
- ReadOnlyModelViewSet:继承自GenericViewSet,同时包括了ListModelMixin、RetrieveModelMixin。
案例-ViewSet改写:
from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveInfoViewSet(ViewSet):
def list(self, request):
"""获取所有直播间"""
lives = LiveInfo.objects.all()
serializer = LiveInfoSerializer(lives, many=True)
return Response(serializer.data)
def create(self, request):
"""新增直播间"""
serializer = LiveInfoSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回新增直播间
return Response(serializer.data, status=status.HTTP_201_CREATED)
def retrieve(self, request, pk):
"""查询一间直播间信息"""
try:
live = LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
raise Http404
serializer = LiveInfoSerializer(live)
return Response(serializer.data)
def update(self, request, pk):
"""修改直播间信息"""
try:
live = LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
raise Http404
serializer = LiveInfoSerializer(live, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def destroy(self, request, pk):
"""删除直播间"""
try:
live = LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
raise Http404
# 参数校验
live.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# urls.py
from django.conf.urls import url
from booktest import views
urlpatterns = [
url(r'^lives/#39;, views.LiveInfoViewSet.as_view({
'get': 'list',
'post': 'create'
})),
url(r'^lives/(?P<pk>\d+)/#39;, views.LiveInfoViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
}))
]
案例-GenericViewSet改写:
案例-ModelViewSet并添加了一些额外的方法改写:
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveInfoView(ModelViewSet):
serializer_class = LiveInfoSerializer
queryset = LiveInfo.objects.all()
lookup_value_regex = '\d+'
@action(methods='get', detail=False)
def latest(self, request):
"""查询最新的直播间"""
live = LiveInfo.objects.latest('pk')
serializer = self.get_serializer(live)
return Response(serializer.data)
@action(methods='put', detail=True)
def change_pop(self, request, pk):
"""修改指定直播间的人气"""
live = self.get_object()
live.bread = request.data.get('live_pop')
live.save()
serializer = self.get_serializer(live)
return Response(serializer.data)
# urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^lives/#39;, views.LiveInfoViewSet.as_view({
'get': 'list',
'post': 'create'
})),
url(r'^lives/(?P<pk>\d+)/#39;, views.LiveInfoViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
})),
url(r'^lives/latest/#39;, views.LiveInfoViewSet.as_view({
'get': 'latest'
})),
url(r'^lives/(?P<pk>\d+)/change_pop/#39;, views.LiveInfoViewSet.as_view({
'put': 'change_pop'
}))
]
路由Router:动态生成视图集中处理方法的url配置项。
对于视图集ViewSet,除了可以自己手动进行URL配置指明请求方式与action处理函数之间的对应关系外,还可以使用路由Router来自动生成路由信息。
REST framework提供了两个Router类:
- SimpleRouter
- DefaultRouter
案例-router改写:
# urls.py
from rest_framework.routers import DefaultRouter
from livetest import views
urlpatterns = []
# 创建Router对象
router = DefaultRouter()
# 注册router
router.register('lives', views.LiveInfoView, base_name='live')
# 添加配置项
urlpatterns += router.urls
DRF框架到此常用的功能都已经讲解完毕了,当然DRF框架还有其他功能:认证、权限、限流、过滤、排序、分页和异常处理机制。将在后面讲述,敬请关注。
作者简介:Python菜鸟工程师,将在接下来的一段时间内与大家分享一些与Python相关的知识点。如若文中出现问题,各位大佬多多指点,互相学习。喜欢的关注一个吧!谢谢!