文章目录
- 1.简介及安装
- 2.案例模型
- 2.1.创建模型
- 2.2.安装mysql必要组件
- 2.3.管理后台转中文
- 2.4.启动后台
- 3.数据序列化
- 4.RESTful规范
- 4.1.协议、域名和版本
- 4.2.uri(统一资源标识符)
- 4.3.查增删改
- 4.4.过滤信息(Filtering)
- 4.5.状态码(Status Codes)
- 5.序列化和反序列数据
- 6.数据视图
- 6.1.基于函数的视图
- 6.2.基于类的视图
- 6.2.1.APIView类
- 6.2.2.使用Mixin类和GenericAPI类混配
- 6.2.3.使用通用视图generics.*类
- 6.2.4.使用视图集(viewset)
- 6.2.5.小节
- 7.序列化器(Serializer)
- 7.1.指定source来源
- 7.2.使用SerializerMethodField自定义方法
- 7.3.使用嵌套序列化器
- 7.4.数据验证 (Validation)
- 7.5.引发无效数据的异常 (Raising an exception on invalid data)
- 7.6.字段级别验证 (Field-level validation)
- 7.7.对象级别验证 (Object-level validation)
- 7.8.验证器 (Validators)
- 7.9.重写序列化器的create和update方法
- 7.10.小节
- 8.总结
1.简介及安装
Django REST framework (DRF)是基于Django实现的一个RESTful风格API框架,能够帮助我们快速开发RESTful风格的API,文档地址如下所示:
切换到对应python环境下,输入如下命令安装:
pip install django==3.2 -i https://mirrors.aliyun.com/pypi/simple/pip install djangorestframework -i https://mirrors.aliyun.com/pypi/simple/#创建项目python django-admin.py startproject apiproject # 创建项目cd apiproject # 进入项目目录python manage.py startapp zlblog
现在可以编辑apiproject/settings.py文件, 如下所示:
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','rest_framework', #新添加'zlblog',#新添加]
2.案例模型
2.1.创建模型
编辑zlblog/models.py文件, 创建Article模型,用于存储我们博客的文章数据。用户(User)与文章(Article)是单对多的关系(ForeinKey),因为一个用户可以发表多篇文章。为了方便,用户模型我们使用了Django自带的用户模型。
# zlblog/models.py# Create your models here.from django.db import modelsfrom django.utils.translation import ugettext_lazy as _from django.contrib.auth import get_user_model#get_user_model()函数是Django特有的一个函数,它的作用是返回当前项目中使用的用户模型类(User Model)。User = get_user_model()class Article(models.Model):"""Article Model"""STATUS_CHOICES = (('p', _('Published')),('d', _('Draft')),)title = models.CharField(verbose_name=_('Title (*)'), max_length=90, db_index=True)body = models.TextField(verbose_name=_('Body'), blank=True)author = models.ForeignKey(User, verbose_name=_('Author'), on_delete=models.CASCADE, related_name='articles')status = models.CharField(_('Status (*)'), max_length=1, choices=STATUS_CHOICES, default='s', null=True, blank=True)create_date = models.DateTimeField(verbose_name=_('Create Date'), auto_now_add=True)def __str__(self):return self.titleclass Meta:ordering = ['-create_date']verbose_name = "Article"verbose_name_plural = "Articles"
2.2.安装mysql必要组件
查看文章:https://plugin.blog.csdn.net/article/details/131202070?ydreferer=aHR0cHM6Ly9tcC5jc2RuLm5ldC9tcF9ibG9nL21hbmFnZS9hcnRpY2xlP3NwbT0zMDAxLjUyOTg%3D
将django配置好数据库连接。
模型创建好后,执行如下命令同步数据库并创建超级用户, Django会自动根据模型字段生成数据表。
python manage.py makemigrationspython manage.py migratepython manage.py createsuperuser
新建数据表目录如下所示:
之所以我们要创建超级用户是因为我们要通过Django自带的后台admin添加文章和用户信息, 以便测试我们的API接口能否正常工作。
编辑zlblog/admin.py文件, 添加如下代码:
# Register your models here.from django.contrib import adminfrom .models import Article# Register your models here.class ArticleAdmin(admin.ModelAdmin):list_display = ('title', 'status', 'create_date')'''filter options'''list_filter = ('status', )'''10 items per page'''list_per_page = 10admin.site.register(Article, ArticleAdmin)
2.3.管理后台转中文
修改settings文件
LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC'
修改为:
LANGUAGE_CODE = 'zh-Hans' TIME_ZONE = 'Asia/Shanghai'
2.4.启动后台
采用命令行工具启动后台系统。
python manage.py runserver
3.数据序列化
每种编程语言都有各自的数据类型, 将属于自己语言的数据类型或对象转换为可通过网络传输或可以存储到本地磁盘的数据格式(如:XML、JSON或特定格式的字节串)的过程称为序列化(seralization);反之则称为反序列化。API开发的本质就是各种后端语言的自己的数据类型序列化为通用的可读可传输的数据格式,比如常见的JSON类型数据。
Django编程就是是python编程,python的序列化方法对django也是适用的。不同的是Django还有自己专属的数据类型比如查询集QuerySet和ValueQuerySet类型数据,还提供了更便捷的serializers类。使用Django自带的serializers类也可以轻易将QuerySet格式的数据转化为json格式。
# Django Queryset数据 to Jsonfrom django.core import serializersdata = serializers.serialize("json", SomeModel.objects.all())data1 = serializers.serialize("json", SomeModel.objects.all(), fields=('name','id'))data2 = serializers.serialize("json", SomeModel.objects.filter(field = some_value))
尽管Django自带的serializer类也能将Django的查询集QuerySet序列化成json格式数据,Django REST Framework才是你真正需要的序列化工具。与django自带的serializers类相比,rest framework支持token认证、过滤和限流等多种强大功能,我们后面会陆续详细介绍。
4.RESTful规范
REST是REpresentational State Transfer三个单词的缩写,由Roy Fielding于2000年论文中提出。简单来说,就是用URI表示资源,用HTTP方法(GET, POST, PUT, DELETE)表征对这些资源进行操作。而如果想你的api被称为restful api,只要遵循其规定的约束。网上有很多文章对RESTful API规范做了详细介绍。
4.1.协议、域名和版本
尽量使用https协议,使用专属域名来提供API服务,并在URL里标注api版本,如下所示:
https://api.example.com/v1https://www.example.com/api/v1
4.2.uri(统一资源标识符)
在RESTful架构中,每个网址代表一种资源(resource),这个网络地址就是uri (uniform resource identifier), 有时也被称为URL(uniform resource locator)。 因为URI对应一种资源,所以里面不能有动词,只能有名词。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。
https://api.example.com/v1/users # 用户列表资源地址https://api.example.com/v1/users/{id}# 用户id=5资源。注意:这里是users/5,而不是user/5https://api.example.com/v1/users/{id}/articles# 用户id=5发表的文章列表
如果需要对一个用户信息进行编辑或删除,一个传统Django开发者可能将URL写成如下所示
https://api.example.com/v1/users/{id}/edit/ # 编辑用户https://api.example.com/v1/users/{id}/delete/# 删除用户
上面URL设计其实是不符合RESTful规范的。一个 URI就应该是一个资源,本身不能包含任何动作。REST的规范是资源的URI地址是固定不变的,对同一资源应使用不同的HTTP请求方法进行不同的操作,比如常见的增删查改。
[POST]https://api.example.com/v1/users // 新增[GET] https://api.example.com/v1/users/1 // 查询[PATCH] https://api.example.com/v1/users/1 // 更新[PUT] https://api.example.com/v1/users // 覆盖,全部更新[DELETE]https://api.example.com/v1/users // 删除
有时候URL比较长,可能由多个单词组成,建议使用中划线”-“分割,而不是下划线”_“作为分隔符,另外每个url的结尾不能加斜线”/”。
https://api.example.com/v1/user-management/users/{id} # 好https://api.example.com/v1/user_management/users/{id}# 下划线-不好https://api.example.com/v1/user-management/users/{id}/# 尾部添加反斜杠-不好
4.3.查增删改
常用的HTTP请求方法有下面五个(括号里是对应的SQL命令),每个方法对应一个操作。客户端在消费服务器提供的API服务时不仅要指定请求地址,还需指定请求方法。
GET(SELECT):从服务器取出资源(一项或多项)。POST(CREATE):在服务器新建一个资源。PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。DELETE(DELETE):从服务器删除资源。
另外还有两个不常用方法HEAD和OPTIONS,HEAD请求的是资源的元数据,比如一张照片,的元数据则可能包含了,照片拍摄的设备,地点,时间等。服务器一般将对应资源的元数据置于响应的报头集合返回给客户端,这样的响应一般不具有主体部分。OPTIONS则是发送一种“探测”请求以确定针对某个目标地址的请求必须具有怎样的约束(比如应该采用怎样的HTTP方法以及自定义的请求报头),然后根据其约束发送真正的请求。
注:不像Django,DRF支持所有以上请求方法。
4.4.过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。符合RESTful规范的API应该支持过滤。下面是一些常见的过滤参数。
" />4.5.状态码(Status Codes)
服务器在处理客户端请求后还应向用户返回响应的状态码和提示信息,常见的有以下一些状态码。
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)204 NO CONTENT - [DELETE]:用户删除数据成功。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 - [*]:服务器发生错误
注:DRF给出Respone时可以指定各种各样状态码,很容易使用。
5.序列化和反序列数据
利用DRF开发Web API的第一步总是自定义序列化器(serializers)。序列化器的作用是将模型实例(比如用户、文章)序列化和反序列化为诸如json之类的表示形式。一个模型实例可能有许多字段属性,但一般情况下你不需要把所有字段信息以JSON格式数据返回给用户。序列化器定义了需要对一个模型实例的哪些字段进行序列化/反序列化, 并可对客户端发送过来的数据进行验证和存储。
就像Django提供了Form类和ModelForm类两种方式自定义表单一样,REST framework提供了Serializer类和ModelSerializer类两种方式供你自定义序列化器。Serializer类需手动指定需要序列化和反序列化的字段,ModelSerializer类根据模型(model)生成需要序列化和反序列化的字段,可以使代码更简洁。
下面我们将分别展示如何使用Serializer类和ModelSerializer类自定义序列化器。
使用Serializers类#在blog的目录下创建一个名为serializers.py文件,并添加以下内容。from rest_framework import serializersfrom .models import Articlefrom django.contrib.auth import get_user_modelUser = get_user_model()class ArticleSerializer(serializers.Serializer):id = serializers.IntegerField(read_only=True)title = serializers.CharField(required=True, allow_blank=True, max_length=90)body = serializers.CharField(required=False, allow_blank=True)author = serializers.ReadOnlyField(source="author.id")status = serializers.ChoiceField(choices=Article.STATUS_CHOICES, default='p')create_date = serializers.DateTimeField(read_only=True)def create(self, validated_data):""Create a new "article" instance"""return Article.objects.create(**validated_data)def update(self, instance, validated_data): """Use validated data to return an existing `Article`instance。 """instance.title = validated_data.get('title', instance.title)instance.body = validated_data.get('body', instance.body)instance.status = validated_data.get('status', instance.status)instance.save()return instance
序列化器类的第一部分定义了序列化/反序列化的字段。create()和update()方法定义了在调用serializer.save()时如何创建和修改完整的实例。序列化器类与Django Form类非常相似,并在各种字段中设置各种验证,例如required,max_length和default。
注意:定义序列化器时一定要注明哪些是仅可读字段(read-only fields),哪些是普通字段。对于read-only fields,客户端是不需要也不能够通过POST或PUT请求提交相关数据进行反序列化的。
本例中ID和create_date都是由模型自动生成,每个article的author我们也希望在视图中与request.user绑定,而不是由用户通过POST或PUT自行修改,所以这些字段都是read-only。相反title,body和status是用户可以添加或修改的字段,所以不能设成read-only。使用ModelSerializers类
我们的ArticleSerializer类中重复了很多包含在Article模型(model)中的字段信息。使用ModelSerializer类可以重构我们的序列化器类,使整体代码更简洁。#再次打开blog/serializers.py文件,并将ArticleSerializer类替换为以下内容。两者作用是一样的。class ArticleSerializer(serializers.ModelSerializer):class Meta:model = Articlefields = '__all__'read_only_fields = ('id', 'author', 'create_date')
6.数据视图
接下来我们要使用自定义的序列化器来编写API视图,处理客户端的请求,并给出响应。与Django一样,DRF也支持使用基于函数的视图(Functional Based View, FBV)和基于类的视图(Class Based View, CBV)来编写视图(views)。
6.1.基于函数的视图
先编写两个基于函数的视图:article_list和article_detail。
# Create your views here.from rest_framework import statusfrom rest_framework.decorators import api_viewfrom rest_framework.response import Responsefrom .models import Articlefrom .serializers import ArticleSerializer@api_view(['GET', 'POST'])def article_list(request):"""List all articles, or create a new article."""if request.method == 'GET':articles = Article.objects.all()serializer = ArticleSerializer(articles, many=True)return Response(serializer.data)elif request.method == 'POST':serializer = ArticleSerializer(data=request.data)if serializer.is_valid():# Very important. Associate request.user with authorserializer.save(author=request.user)return Response(serializer.data, status=status.HTTP_201_CREATED)return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
注意:由于序列化器中author是read-only字段,用户是无法通过POST提交来修改的,我们在创建Article实例时需手动将author和request.user绑定,如下所示:
serializer.save(author=request.user)
以下是views.py模块中单个article的视图。
# coding=utf-8@api_view(['GET', 'PUT', 'DELETE'])def article_detail(request, pk):"""Retrieve,update or delete an article instance。"""try:article = Article.objects.get(pk=pk)except Article.DoesNotExist:return Response(status=status.HTTP_404_NOT_FOUND)if request.method == 'GET':serializer = ArticleSerializer(article)return Response(serializer.data)elif request.method == 'PUT':serializer = ArticleSerializer(article, data=request.data)if serializer.is_valid():serializer.save()return Response(serializer.data)return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)elif request.method == 'DELETE':article.delete()return Response(status=status.HTTP_204_NO_CONTENT)
这两个函数视图看似和Django普通函数视图非常类似,但其作用大不相同。这里我们使用了DRF提供的@api_view这个非常重要的装饰器,实现了以下几大功能:
- 与Django传统函数视图相区分,强调这是API视图,并限定了可以接受的请求方法。
- 拓展了django原来的request对象。新的request对象不仅仅支持request.POST提交的数据,还支持其它请求方式如PUT或PATCH等方式提交的数据,所有的数据都在request.data字典里。这对开发Web API非常有用。
注意: 我们不再显式地将请求或响应绑定到特定的内容类型比如HttpResponse和JSONResponse,我们统一使用Response方法返回响应,该方法支持内容协商,可根据客户端请求的内容类型返回不同的响应数据。
添加格式后缀
为了充分利用我们的响应不再与单一内容类型连接,我们可以为API路径添加对格式后缀的支持。使用格式后缀给我们明确指定了给定格式的URL,这意味着我们的API将能够处理诸如http://example.com/api/items/4.json之类的URL。
像下面这样在这两个视图中添加一个format关键字参数。def article_list(request, format=None):......def article_detail(request, pk, format=None):
现在更新blog/urls.py文件,给现有的URL后面添加一组format_suffix_patterns。
# coding=utf-8from django.urls import re_pathfrom rest_framework.urlpatterns import format_suffix_patternsfrom zlblog import views urlpatterns = [re_path(r'^articles/$', views.article_list),re_path(r'^articles/(?P[0-9]+)$', views.article_detail),]urlpatterns = format_suffix_patterns(urlpatterns)
还需要把app的urls加入到项目URL配置application/urls.py文件中,如下所示:
# coding=utf-8from django.contrib import adminfrom django.urls import path, includeurlpatterns = [path('admin/', admin.site.urls),path('v1/', include('zlblog.urls')), #包含其它页面的路由地址]
include()路径调度
通常我们在根路由中使用它:# root/urls.pyfrom django.urls import path, includepath('post/', include('post.urls', namespace='post')),
后端在匹配到 post/ 后,继续在 post 模块的 urls.py 中处理剩下的部分:
# post/urls.py...path('john/', some_view, name='user')
它两配合起来就可以匹配类似这样的地址:
'/post/john/'
定位到指定位置如下图所示:
在调试模式下,可以非常方便的查看接口。
在Get按钮下获取json,然后通过Put方法,可以非常方便的修改数据。6.2.基于类的视图
DRF推荐使用基于类的视图(CBV)来开发API, 并提供了4种开发CBV开发模式。
使用基础APIView类
使用Mixins类和GenericAPI类混配
使用通用视图generics.*类, 比如 generics.ListCreateAPIView
使用视图集ViewSet和ModelViewSet6.2.1.APIView类
DRF的APIView类继承了Django自带的View类, 一样可以按请求方法调用不同的处理函数,比如get方法处理GET请求,post方法处理POST请求。不过DRF的APIView要强大得多。它不仅支持更多请求方法,而且对Django的request对象进行了封装,可以使用request.data获取用户通过POST, PUT和PATCH方法发过来的数据,而且支持插拔式地配置认证、权限和限流类。
现在我们可以使用APIView类重写我们之前的函数视图了。# blog/views.py# 使用基础APIView类# coding=utf-8from django.shortcuts import render# Create your views here.from rest_framework import statusfrom rest_framework.decorators import api_viewfrom rest_framework.response import Responsefrom .models import Articlefrom .serializers import ArticleSerializer# 使用基础APIView类from rest_framework.views import APIViewfrom django.http import Http404class ArticleList(APIView):"""List all articles, or create a new article."""def get(self, request, format=None):articles = Article.objects.all()serializer = ArticleSerializer(articles, many=True)return Response(serializer.data)def post(self, request, format=None):serializer = ArticleSerializer(data=request.data)if serializer.is_valid():# 注意:手动将request.user与author绑定serializer.save(author=request.user)return Response(serializer.data, status=status.HTTP_201_CREATED)return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)class ArticleDetail(APIView):"""Retrieve, update or delete an article instance."""def get_object(self, pk):try:return Article.objects.get(pk=pk)except Article.DoesNotExist:raise Http404def get(self, request, pk, format=None):article = self.get_object(pk)serializer = ArticleSerializer(article)return Response(serializer.data)def put(self, request, pk, format=None):article = self.get_object(pk)serializer = ArticleSerializer(instance=article, data=request.data)if serializer.is_valid():serializer.save()return Response(serializer.data)return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)def delete(self, request, pk, format=None):article = self.get_object(pk)article.delete()return Response(status=status.HTTP_204_NO_CONTENT)
或许你已经注意到,这段代码跟之前基于函数的视图差别并不大。最大不同的是我们不需要在对用户的请求方法进行判断。该视图可以自动将不同请求转发到相应处理方法,逻辑上也更清晰。
现在我们还需要修改我们的url配置, 让其指向新的基于类的视图。# blog/urls.pyfrom django.urls import re_pathfrom rest_framework.urlpatterns import format_suffix_patternsfrom . import viewsurlpatterns = [# re_path(r'^articles/$', views.article_list),# re_path(r'^articles/(" />(r'^articles/$', views.ArticleList.as_view()),re_path(r'^articles/(?P[0-9]+)$', views.ArticleDetail.as_view()),]urlpatterns = format_suffix_patterns(urlpatterns)
6.2.2.使用Mixin类和GenericAPI类混配
使用基础的APIView类并没有大量简化我们的代码。如果你仔细地观察,你还会发现与增删改查操作相关的代码包括返回内容对所有模型几乎都是一样的。比如你现在需要对文章类别Category模型也进行序列化和反序列化,你只需要复制Article视图代码,将Article模型改成Category模型, 序列化类由ArticleSeralizer类改成CategorySerializer类就行了。
对于这些通用的增删改查行为,DRF已经提供了相应的Mixin类。Mixin类可与generics.GenericAPI类联用,灵活组合成你所需要的视图。
现在来使用Mixin类和generics.GenericAPI类重写类视图。# coding=utf-8from .models import Articlefrom .serializers import ArticleSerializer# 使用GENERIC APIView & Mixinsfrom rest_framework import mixinsfrom rest_framework import genericsclass ArticleList(mixins.ListModelMixin,mixins.CreateModelMixin,generics.GenericAPIView):queryset = Article.objects.all()serializer_class = ArticleSerializerdef get(self, request, *args, **kwargs):return self.list(request, *args, **kwargs)def post(self, request, *args, **kwargs):return self.create(request, *args, **kwargs)
我们现在需要花些时间研究下这段代码,看看到底发生了什么。GenericAPIView 类继承了APIView类,提供了基础的API视图。它对用户请求进行了转发,并对Django自带的request对象进行了封装。不过它比APIView类更强大,因为它还可以通过queryset和serializer_class属性指定需要序列化与反序列化的模型或queryset及所用到的序列化器类。
这里的 ListModelMixin 和 CreateModelMixin类则分别引入了.list() 和 .create() 方法,当用户发送get请求时调用Mixin提供的list()方法,将指定queryset序列化后输出,发送post请求时调用Mixin提供的create()方法,创建新的实例对象。
DRF还提供RetrieveModelMixin, UpdateModelMixin和DestroyModelMixin类,实现了对单个对象实例的查、改和删操作,如下所示:
class ArticleDetail(mixins.RetrieveModelMixin,mixins.UpdateModelMixin,mixins.DestroyModelMixin,generics.GenericAPIView):queryset = Article.objects.all()serializer_class = ArticleSerializerdef get(self, request, *args, **kwargs):return self.retrieve(request, *args, **kwargs)def put(self, request, *args, **kwargs):return self.update(request, *args, **kwargs)def delete(self, request, *args, **kwargs):return self.destroy(request, *args, **kwargs)
或许你现在要问已经有get, post, delete等方法了,为什么mixin类引入的方法要以list, create, retrieve, destroy方法命名呢? 这是因为请求方法不如操作名字清晰,比如get方法同时对应了获取对象列表和单个对象两种操作,使用list和retrieve方法后则很容易区分。另外post方法接受用户发过来的请求数据后,有时只需转发不需要创建模式对象实例,所以post方法不能简单等于create方法。
新的ArticleList视图类看似正确,但其实还有一个问题。 我们定义的序列化器ArticleSeralizer类并不包含author这个字段的,这是因为我们希望在创建article实例时我们将author与request.user进行手动绑定。在前面的例子中我们使用serializer.save(author=request.user)这一方法进行手动绑定。
现在使用mixin类后,我们该如何操作呢? 答案是重写perform_create方法,如下所示:class ArticleList(mixins.ListModelMixin,mixins.CreateModelMixin,generics.GenericAPIView):queryset = Article.objects.all()serializer_class = ArticleSerializerdef get(self, request, *args, **kwargs):return self.list(request, *args, **kwargs)def post(self, request, *args, **kwargs):return self.create(request, *args, **kwargs)# 将request.user与author绑定def perform_create(self, serializer):serializer.save(author=self.request.user)
.perform_create这个钩子函数是CreateModelMixin类自带的,用于执行创建对象时先需要执行的其它方法,比如发送邮件等功能,有点类似于Django的信号。类似的钩子函数还有UpdateModelMixin提供的.perform_update方法和DestroyModelMixin提供的.perform_destroy方法。
6.2.3.使用通用视图generics.*类
将Mixin类和GenericAPI类混配,已经帮助我们减少了一些代码,但我们还可以做得更好,比如将get请求与mixin提供的list方法进行绑定感觉有些多余。幸好DRF还提供了一套常用的将 Mixin 类与 GenericAPI类已经组合好了的视图,开箱即用,可以进一步简化我们的代码,如下所示:
# coding=utf-8from .models import Articlefrom .serializers import ArticleSerializer# generic class-based viewsfrom rest_framework import genericsclass ArticleList(generics.ListCreateAPIView):queryset = Article.objects.all()serializer_class = ArticleSerializer# 将request.user与author绑定def perform_create(self, serializer):serializer.save(author=self.request.user)class ArticleDetail(generics.RetrieveUpdateDestroyAPIView):queryset = Article.objects.all()serializer_class =ArticleSerializer
顾名思义,generics.ListCreateAPIView类支持List、Create两种视图功能,分别对应GET和POST请求。generics.RetrieveUpdateDestroyAPIView支持Retrieve、Update、Destroy操作,其对应方法分别是GET、PUT和DELETE。
寥寥几行,实现了我们所有想要的功能,神不神奇?
其它常用generics.*类视图还包括ListAPIView, RetrieveAPIView, RetrieveUpdateAPIView等等。6.2.4.使用视图集(viewset)
使用通用视图generics.*类后视图代码已经大大简化,但是ArticleList和ArticleDetail两个类中queryset和serializer_class属性依然存在代码重复。使用视图集可以将两个类视图进一步合并,一次性提供List、Create、Retrieve、Update、Destroy这5种常见操作,这样queryset和seralizer_class属性也只需定义一次就好, 如下所示:
blog/views.py# coding=utf-8# blog/views.pyfrom .models import Articlefrom .serializers import ArticleSerializerfrom rest_framework import viewsetsclass ArticleViewSet(viewsets.ModelViewSet):# 用一个视图集替代ArticleList和ArticleDetail两个视图queryset = Article.objects.all()serializer_class = ArticleSerializer# 自行添加,将request.user与author绑定def perform_create(self, serializer):serializer.save(author=self.request.user)
使用视图集后,我们需要使用DRF提供的路由router来分发urls,因为一个视图集现在对应多个urls的组合,而不像之前的一个url对应一个视图函数或一个视图类。
# blog/urls.py# coding=utf-8from django.urls import re_pathfrom rest_framework.urlpatterns import format_suffix_patternsfrom . import viewsfrom rest_framework.routers import DefaultRouterrouter = DefaultRouter()router.register(r'articles', viewset=views.ArticleViewSet)urlpatterns = [# re_path(r'^articles/$', views.ArticleList.as_view()),# re_path(r'^articles/(?P[0-9]+)$', views.ArticleDetail.as_view()),]# urlpatterns = format_suffix_patterns(urlpatterns)urlpatterns += router.urls
你或许又要问了,一个视图集对应List、Create、Retrieve、Update、Destroy这5种操作。有时候我只需要其中的几种操作,该如何实现呢?答案是在urls.py中指定方法映射即可,如下所示:
# coding=utf-8from django.urls import re_pathfrom rest_framework.urlpatterns import format_suffix_patternsfrom . import viewsarticle_list = views.ArticleViewSet.as_view({'get': 'list',#'post': 'create'})article_detail = views.ArticleViewSet.as_view({'get': 'retrieve', # 只处理get请求,获取单个记录})urlpatterns = [re_path(r'^articles/$', article_list),re_path(r'^articles/(?P[0-9]+)$', article_detail),]urlpatterns = format_suffix_patterns(urlpatterns)
另外DRF还提供了ReadOnlyModelViewSet这个类,仅支持list和retrive这个操作。
Django视图集viewset代码最少,但这是以牺牲了代码的可读性为代价的,因为它对代码进行了高度地抽象化。另外urls由router生成,不如自己手动配置的清楚。6.2.5.小节
DRF提供了4种编写CBV类API的方式,哪种更好呢? 答案是各有利弊。小编个人认为大家只需掌握以下三种方式即可:
- 基础的API类:可读性最高,代码最多,灵活性最高。当你需要对的API行为进行个性化定制时,建议使用这种方式。
- 通用generics.*类:可读性好,代码适中,灵活性较高。当你需要对一个模型进行标准的增删查改全部或部分操作时建议使用这种方式。
- 使用视图集viewset: 可读性较低,代码最少,灵活性最低。当你需要对一个模型进行标准的增删查改的全部操作且不需定制API行为时建议使用这种方式。
7.序列化器(Serializer)
在本节中,我们将玩转DRF的序列化器,教你如何修改序列化器控制序列化后响应数据的输出格式, 如何在反序列化时对客户端提供过来的数据进行验证(validation)以及如何重写序列化器类自带的的create和update方法。
[GET] http://127.0.0.1:8000/v1/articles
如下图所示:
在这里你可以看到序列化后输出的json格式数据里author字段输出的是用户id,而不是用户名,status输出的是p或者d,而不是输出Published或Draft这样的完整状态,这显然对用户不是很友好的。这时我们就要修改序列化器,控制序列化后的数据输出格式,这里我们将介绍几种常用的方式。7.1.指定source来源
打开blog/serializers.py,新建两个可读字段author和status字段,用以覆盖原来Article模型默认的字段,其中指定author字段的来源(source)为原单个author对象的username,status字段为get_status_display方法返回的完整状态。
# coding=utf-8from rest_framework import serializersfrom .models import Articlefrom django.contrib.auth import get_user_modelUser = get_user_model()class ArticleSerializer(serializers.ModelSerializer):author = serializers.ReadOnlyField(source="author.username")status = serializers.ReadOnlyField(source="get_status_display")class Meta:model = Articlefields = '__all__'read_only_fields = ('id', 'author', 'create_date')
这个看似完美,但里面其实有个错误。我们定义了一个仅可读的status字段把原来的status字段覆盖了,这样反序列化时用户将不能再对文章发表状态进行修改(原来的status字段是可读可修改的)。一个更好的方式在ArticleSerializer新增一个为full_status的可读字段,而不是简单覆盖原本可读可写的字段。
对于每个具有choices 的字段,每个对象将具有一个get_xx_display() 方法,其中xx 为该字段的名称。 这个方法返回该字段对“人类可读”的值。
class Person(models.Model):SHIRT_SIZES = (('S', 'Small'),('M', 'Medium'),('L', 'Large'),)name = models.CharField(max_length=60)shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)
7.2.使用SerializerMethodField自定义方法
上面例子中文章状态status都是以Published或Draft英文字符串表示的,但是如果你想在输出的json格式数据中新增cn_status字段,显示中文发表状态。但cn_status本身并不是Article模型中存在的字段,这时你应该怎么做呢?答案是使用SerializerMethodField,它可用于将任何类型的数据添加到对象的序列化表示中, 非常有用。
# coding=utf-8from rest_framework import serializersfrom .models import Articlefrom django.contrib.auth import get_user_modelUser = get_user_model()class ArticleSerializer(serializers.ModelSerializer):author = serializers.ReadOnlyField(source="author.username")full_status= serializers.ReadOnlyField(source="get_status_display")cn_status = serializers.SerializerMethodField()class Meta:model = Articlefields = '__all__'read_only_fields = ('id', 'author', 'create_date')def get_cn_status(self, obj):if obj.status == 'p':return "已发表"elif obj.status == 'd':return "草稿"else:return ''
不过需要注意的是SerializerMethodField通常用于显示模型中原本不存在的字段,类似可读字段,你不能通过反序列化对其直接进行修改。
7.3.使用嵌套序列化器
我们文章中的author字段实际上对应的是一个User模型实例化后的对象,既不是一个整数id,也不是用户名这样一个简单字符串,我们怎样显示更多用户对象信息呢" /># coding=utf-8from rest_framework import serializersfrom .models import Articlefrom django.contrib.auth import get_user_modelUser = get_user_model()class UserSerializer(serializers.ModelSerializer):class Meta:model = Userfields = ('id', 'username', 'email')class ArticleSerializer(serializers.ModelSerializer):author = UserSerializer() # 设置required=False表示可以接受匿名用户full_status = serializers.ReadOnlyField(source="get_status_display")cn_status = serializers.SerializerMethodField()class Meta:model = Articlefields = '__all__'read_only_fields = ('id', 'author', 'create_date')def get_cn_status(self, obj):if obj.status == 'p':return "已发表"elif obj.status == 'd':return "草稿"else:return ''
此时发送GET请求展示文章列表资源是没问题的,但如果你希望发送POST请求到v1/articles/提交新文章你将会收到author字段是required的这样一个错误。为了使我们代码正确工作,我们还需要手动指定read_only=True这个选项。尽管我们在Meta选项已经指定了author为read_only_fields, 但使用嵌套序列化器时还需要重新指定一遍。
author = UserSerializer(read_only=True)
另一个解决方式是不使用嵌套序列化器,通过设置关联模型的深度depth(通常1-4)实现, 如下所示:
展示效果如下。这种方法虽然简便,但使用时要非常小心,因为它会展示关联模型中的所有字段。比如下例中连密码password都展示出来了,显然不是我们想要的。
前面我们介绍的都是如何通过修改序列化器来控制输出数据的展现形式,下面我们将着重看下如何在反序列化时对客户端提供过来的数据进行验证(validation)以及如何重写序列化类自带的的save和update方法。由于官方文档中有更好的例子,我们将会使用这些案例。7.4.数据验证 (Validation)
在反序列化数据时,在尝试访问经过验证的数据或保存对象实例之前,总是需要调用 is_valid()方法。如果发生任何验证错误,.errors 属性将包含表示结果错误消息的字典,如下所示:
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})serializer.is_valid()# Falseserializer.errors# {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}
字典中的每个键都是字段名称,值是与该字段对应的任何错误消息的字符串列表。non_field_errors 键也可能存在,并将列出任何常规验证错误。可以使用 REST framework 设置中的 NON_FIELD_ERRORS_KEY 来自定义 non_field_errors 键的名称。
当反序列化项目列表时,错误将作为表示每个反序列化项目的字典列表返回。
7.5.引发无效数据的异常 (Raising an exception on invalid data)
.is_valid() 方法使用可选的 raise_exception 标志,如果存在验证错误,将会抛出 serializers.ValidationError 异常。这些异常由 REST framework 提供的默认异常处理程序自动处理,默认情况下将返回 HTTP 400 Bad Request 响应。
# Return a 400 response if the data was invalid.serializer.is_valid(raise_exception=True)
7.6.字段级别验证 (Field-level validation)
您可以通过向您的 Serializer 子类中添加 .validate_ 方法来指定自定义字段级的验证。这些类似于 Django 表单中的 .clean_ 方法。这些方法采用单个参数,即需要验证的字段值。
您的 validate_ 方法应该返回已验证的值或抛出 serializers.ValidationError 异常。例如:# coding=utf-8from rest_framework import serializersfrom .models import Articlefrom django.contrib.auth import get_user_modelUser = get_user_model()class UserSerializer(serializers.ModelSerializer):class Meta:model = Userfields = ('id', 'username', 'email')class ArticleSerializer(serializers.ModelSerializer):title = serializers.CharField(max_length=100)# author = UserSerializer(read_only=True) # 设置required=False表示可以接受匿名用户full_status = serializers.ReadOnlyField(source="get_status_display")cn_status = serializers.SerializerMethodField()def validate_title(self, value):"""Check that the article is about Django."""if 'django' not in value.lower():raise serializers.ValidationError("Article is not about Django")return valueclass Meta:model = Articlefields = '__all__'read_only_fields = ('id', 'author', 'create_date')depth = 1def get_cn_status(self, obj):if obj.status == 'p':return "已发表"elif obj.status == 'd':return "草稿"else:return ''
注意:如果在您的序列化器上声明了 的参数为 required=False,那么如果不包含该字段,则此验证步骤不会发生。
7.7.对象级别验证 (Object-level validation)
要执行需要访问多个字段的任何其他验证,请添加名为 .validate() 的方法到您的 Serializer 子类中。此方法采用单个参数,该参数是字段值的字典。如果需要,它应该抛出 ValidationError 异常,或者只返回经过验证的值。例如:
from rest_framework import serializersclass EventSerializer(serializers.Serializer):description = serializers.CharField(max_length=100)start = serializers.DateTimeField()finish = serializers.DateTimeField()def validate(self, data):"""Check that the start is before the stop."""if data['start'] > data['finish']:raise serializers.ValidationError("finish must occur after start")return data
7.8.验证器 (Validators)
序列化器上的各个字段都可以包含验证器,通过在字段实例上声明,例如:
def multiple_of_ten(value):if value % 10 != 0:raise serializers.ValidationError('Not a multiple of ten')class GameRecord(serializers.Serializer):score = IntegerField(validators=[multiple_of_ten])...
DRF还提供了很多可重用的验证器,比如UniqueValidator,UniqueTogetherValidator等等。通过在内部 Meta 类上声明来包含这些验证器,如下所示。下例中会议房间号和日期的组合必须要是独一无二的。
class EventSerializer(serializers.Serializer):name = serializers.CharField()room_number = serializers.IntegerField(choices=[101, 102, 103, 201])date = serializers.DateField()class Meta:# Each room only has one event per day.validators = UniqueTogetherValidator(queryset=Event.objects.all(),fields=['room_number', 'date'])
7.9.重写序列化器的create和update方法
假设我们有个Profile模型与User模型是一对一的关系,当用户注册时我们希望把用户提交的数据分别存入User和Profile模型,这时我们就不得不重写序列化器自带的create方法了。下例演示了如何通过一个序列化器创建两个模型对象。
class UserSerializer(serializers.ModelSerializer):profile = ProfileSerializer()class Meta:model = Userfields = ('username', 'email', 'profile')def create(self, validated_data):profile_data = validated_data.pop('profile')user = User.objects.create(**validated_data)Profile.objects.create(user=user, **profile_data)return user
同时更新两个关联模型实例时也同样需要重写update方法。
def update(self, instance, validated_data):profile_data = validated_data.pop('profile')# 除非应用程序正确地强制始终设置该字段,否则就应该抛出一个需要处理的`DoesNotExist`。profile = instance.profileinstance.username = validated_data.get('username', instance.username)instance.email = validated_data.get('email', instance.email)instance.save()profile.is_premium_member = profile_data.get('is_premium_member',profile.is_premium_member)profile.has_support_contract = profile_data.get('has_support_contract',profile.has_support_contract )profile.save()return instance
因为序列化器使用嵌套后,创建和更新的行为可能不明确,并且可能需要相关模型之间的复杂依赖关系,REST framework 3 要求你始终显式的编写这些方法。默认的 ModelSerializer .create() 和 .update() 方法不包括对可写嵌套表示的支持,所以我们总是需要对create和update方法进行重写。
7.10.小节
- 改变序列化输出数据的格式可以通过指定字段的source来源,使用SerializerMethodField自定义方法以及使用嵌套序列化器。
- 反序列化时需要对客户端发送的数据进行验证。你可以通过自定义validate方法进行字段或对象级别的验证,你还可以使用自定义的validators或DRF自带的验证器。
- 当你使用嵌套序列化器后,多个关联模型同时的创建和更新的行为并不明确,你需要显示地重写create和update方法。
8.总结
本文详细总结了Django REST Framework的基础知识和使用方法,包含RESTful规范,数据的序列化和反序列化以,数据视图和与常规Django数据视图路由的比较,可以用更少的代码,更快速的写出更强大的功能。后续将继续介绍权限验证,分页,限流和缓存等内容。