为什么要学习Django REST Framework (DRF)
DRF是django的RESTful风格API框架。能够帮助我们快速开发RESTful风格的API
python
pip install djangorestframework
什么是数据的序列化(Serialization)以及什么是RESTful规范的API
每种编程语言都有各自的数据类型, 将属于自己语言的数据类型或对象转换为可通过网络传输或可以存储到本地磁盘的数据格式(如:XML、JSON或特定格式的字节串)的过程称为序列化(seralization);反之则称为反序列化。
API开发的本质就是各种后端语言的自己的数据类型序列化为通用的可读可传输的数据格式,比如常见的JSON类型数据。
比如将python的字典转为json数据
python
>>> json.dumps({"name":"John", "score": 112})
'{"name": "John", "score": 112}'
django的模型有很多种类型,但他自己也提供了序列化的方法,如
python
from django.core import serializers
# values转为字典, list() 转为List
user = list(SysUser.objects.all().values())
return JsonResponse(ResultResponse.success(data=user))
data = serializers.serialize('json', SysUser.objects.all())
print(data, type(data)) # str
return JsonResponse(ResultResponse.success(data=json.loads(data)))
第一种就是正常将模型数据通过vlaues转为字典,再通过list转为数组去返回。
第二种就是通过serializers.serialize转为json字符串,通过json.lodas解析返回。
此外,values还支持指定字段返回,也是需要通过list转换。
python
import json
from django.core.serializers.json import DjangoJSONEncoder
queryset = myModel.objects.filter(foo_icontains=bar).values('f1', 'f2', 'f3')
data4 = json.dumps(list(queryset), cls=DjangoJSONEncoder)
虽然提供了。但还是繁琐,使用DRF能更方便。
RESTfule规范API: url一样,但是方法不同,通过不同方法对资源进行不同操作。
如果不用DRF,我们需要
python
# 原生 Django 视图写法(不推荐)
from django.http import JsonResponse
from .models import Article
def get_articles(request):
articles = Article.objects.all() # 从数据库查出所有的文章对象
# 手动把对象转换为字典,再拼接成 JSON...(极其痛苦)
data_list = []
for article in articles:
data_list.append({
"id": article.id,
"title": article.title,
"content": article.content,
# 如果这里碰到了日期时间字段,或者外键,原生转换直接报错!
# "created_at": article.created_at.strftime('%Y-%m-%d %H:%M:%S'),
})
return JsonResponse({"code": 200, "data": data_list})
第一步自定义序列化器(serializers)
序列化器定义了需要对一个模型实例的哪些字段进行序列化/反序列化, 并可对客户端发送过来的数据进行验证和存储。
核心概念:序列化与反序列化
序列化 (Serialization):也就是出参(读数据)。把后端的"模型对象 (Model Object)"翻译成前端需要的"JSON 字典"。
反序列化 (Deserialization):也就是入参(写数据)。把前端传来的"JSON 字典"翻译成"模型对象"存进数据库。
定义一个model
python
# models.py (类似数据库表的结构图)
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100) # 商品名称
price = models.DecimalField(max_digits=10, decimal_places=2) # 价格
stock = models.IntegerField(default=0) # 库存
边写一个序列化类
ModelSerializer直接对比模型
python
rom rest_framework import serializers
from .models import Product
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product # 绑定对应的模型类
fields = '__all__' # 魔法指令:把模型里所有的字段自动暴露为 JSON 字段
# fields = ['id', 'name', 'price'] # 也可以这样按需暴露字段
也可以通过Serializer来自定义
自己定义哪些字段的序列,然后需要重写creae/update方法。
python
# serializers.py
from rest_framework import serializers
from .models import Product
class ProductSerializer(serializers.Serializer):
# 1. 逐一显式声明前端可见的字段,且需要指定类型和参数
id = serializers.IntegerField(read_only=True) # id 通常由数据库自动生成,设为只读
name = serializers.CharField(max_length=100)
price = serializers.DecimalField(max_digits=10, decimal_places=2)
stock = serializers.IntegerField(default=0)
# 2. 手动定义反序列化(前端传 JSON 过来保存为新数据)的逻辑
def create(self, validated_data):
"""
当调用 serializer.save() 且没有传入 instance 时,会触发此方法
"""
return Product.objects.create(**validated_data)
# 3. 手动定义反序列化(前端传 JSON 过来更新旧数据)的逻辑
def update(self, instance, validated_data):
"""
当调用 serializer.save() 且传入了原有的 instance 时,会触发此方法
"""
instance.name = validated_data.get('name', instance.name)
instance.price = validated_data.get('price', instance.price)
instance.stock = validated_data.get('stock', instance.stock)
instance.save()
return instance
使用,创建类视图
python
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Article
from .serializers import ArticleSerializer
class ArticleListView(APIView):
# 处理 GET 请求:获取列表
def get(self, request):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True) # many=True 表示这是一个数组
return Response(serializer.data)
# 处理 POST 请求:创建数据
def post(self, request):
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)
DRF 提供了APIView基类,我们只需要将拿到的模型数据,通过序列化类转化,就变成了json数据。
处理post请求创建数据,也只需要通过序列化类,将数据传入,就会获得一个实例,调用save,就会调用create方法,创建一个模型数据。
那如果每个模型都比较简单的话,就可以使用封装类:Mixins(混入类) 和 GenericAPIView(通用视图)。
python
from rest_framework.generics import ListCreateAPIView
from .models import Article
from .serializers import ArticleSerializer
class ArticleListView(ListCreateAPIView):
# 你只需要告诉 DRF 两件事:
queryset = Article.objects.all() # 1. 去哪里查数据?
serializer_class = ArticleSerializer # 2. 用哪个序列化器转换数据?
# get() 和 post() 方法完全不用写了!底层全帮你干了!
上述是针对模型的全部查+创建,如果我们要单个模型的详情呢
ModelViewSet,将增删改查列表都封装了
python
from rest_framework.viewsets import ModelViewSet
from .models import Article
from .serializers import ArticleSerializer
class ArticleViewSet(ModelViewSet):
"""
就这几行代码,自动拥有:
1. GET /articles/ (列表)
2. POST /articles/ (创建)
3. GET /articles/{id}/ (详情)
4. PUT /articles/{id}/ (全量更新)
5. PATCH /articles/{id}/ (局部更新)
6. DELETE /articles/{id}/ (删除)
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
视图写好了。url怎么配呢?
DRF提供了DefaultRouter,只要注册就行了,并将ViewSet传入。
python
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet
# 1. 实例化路由器
router = DefaultRouter()
# 2. 注册你的 ViewSet (起个名字叫 articles)
router.register('articles', ArticleViewSet)
# 3. 将生成的路由无缝接入 Django 路由系统
urlpatterns = [
path('api/', include(router.urls)),
]
APIView
DRF的APIView类继承了Django自带的View类, 一样可以按请求方法调用不同的处理函数,比如get方法处理GET请求,post方法处理POST请求。对Django的request对象进行了封装,可以使用request.data获取用户通过POST, PUT和PATCH方法发过来的数据,而且支持插拔式地配置认证、权限和限流类
python
# blog/views.py
# 使用基础APIView类
from rest_framework.views import APIView
from django.http import Http404
from .models import Article
from .serializers import ArticleSerializer
class 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)
列表页和创建
python
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 Http404
def 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配置
类视图需要调用as_view()的方法才能在视图中实现查找指定方法, 比如GET请求执行get方法。
python
# re_path(r'^articles/$', views.article_list),
# re_path(r'^articles/(?P<pk>[0-9]+)$', views.article_detail),
re_path(r'^articles/$', views.ArticleList.as_view()),
re_path(r'^articles/(?P<pk>[0-9]+)$', views.ArticleDetail.as_view()),
]
视图集 ModelViewSet
封装一个模型的列表,单个的增删改查方法。
python
# blog/views.py
from rest_framework import viewsets
class 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)
只需要上述两行代码即可,ModelViewSet底层已经实现。
小结
- 基础的API类:函数,可读性最高、代码最多、灵活性最高。当你需要对的API行为进行个性化定制时,建议使用这种方式。
- 通用generics类:可读性好、代码适中、灵活性较高。当你需要对一个模型进行标准的增删查改全部或部分操作时建议使用这种方式。
- 使用视图集viewset: 可读性较低、代码最少、灵活性最低。当你需要对一个模型进行标准的增删查改的全部操作且不需定制API行为时建议使用这种方式。
玩转序列化器
使用普通序列化器的时候,不能指定返回的内容,比如关联用户只能返回id,状态只能返回英文枚举,那如果想返回对应的关联用户全部信息,新增一个中文状态返回呢?
python
# blog/models.py
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(verbose_name='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.title
author作为外建关联User表
普通的序列化器写法
python
# blog/serializers.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
read_only_fields = ('id', 'author', 'create_date')
这样只返回原始数据,如果想指定返回内容,可以指定source来源。
python
class ArticleSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source="author.username")
status = serializers.ReadOnlyField(source="get_status_display")
class Meta:
model = Article
fields = '__all__'
read_only_fields = ('id', 'author', 'create_date')
如上,authro将会显示用户名称,staus将会显示状态值。
python
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email')
class ArticleSerializer(serializers.ModelSerializer):
# 嵌套序列化器
author = UserSerializer(read_only=True) # required=False表示可接受匿名用户,many=True表示有多个用户。
full_status = serializers.ReadOnlyField(source="get_status_display") # 不会覆盖status值
# SerializerMethodField新增字段返回
cn_status = serializers.SerializerMethodField()
class Meta:
model = Article
fields = '__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 ''
如上,使用嵌套序列化器,指定author数据,新增full_status字段防止原始status字段被覆盖,使用SerializerMethodField新增cn_status字段,需要定义get_xx函数去指定返回数据。

这样就达到我们要的效果了。
关系序列化方法
上述我们是通过article的外建绑定得到authro的信息,那如果查询user,怎么知道当前用户写了多少篇文章呢?
使用Django REST Framework提供的关系序列化方法。
python
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')
def __str__(self):
return self.title
class UserSerializer(serializers.ModelSerializer):
# 返回id
articles = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
# 返回articles模型定义的__str__方法的内容,上述返回title
articles = serializers.StringRelatedField(many=True, read_only=True)
# 返回文章详情的link方法
articles = serializers.HyperlinkedRelatedField(
many=True,
read_only=True,
view_name='article-detail'
)
class Meta:
model = User
fields = ('id', 'username', 'articles',)
read_only_fields = ('id', 'username',)
数据验证 (Validation)
通过序列化器修改,或者保存实例的时候,需要调用is_valid方法进行验证。
字段级别验证
python
from rest_framework import serializers
class ArticleSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
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 value
对象级别验证
python
from rest_framework import serializers
class 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
验证器
python
def title_gt_90(value):
if len(value) < 90:
raise serializers.ValidationError('标题字符长度不低于90。')
class Article(serializers.Serializer):
title = seralizers.CharField(validators=[title_gt_90])
...
# UniqueTogetherValidator判断是否唯一
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']
)
动态加载序列化器类
可以通过动态制定不同序列化器,决定返回内容。
python
class UserViewSet(CreateModelMixin,
RetrieveModelMixin,UpdateModelMixin,viewsets.GenericViewSet):
# 这个就不需要了
#serializer_class = XXXSerializer
def get_serializer_class(self):
if self.action == 'create':
return CustomSerializer1
elif self.action == 'list':
return XXXSerializer
return CustomSerializer1
小结
- 改变序列化输出数据的格式可以通过指定字段的source来源,使用SerializerMethodField和to_representation方法以及使用嵌套序列化器。
- 反序列化时需要对客户端发送的数据进行验证。你可以通过自定义validate方法进行字段或对象级别的验证,你还可以使用自定义的validators或DRF自带的验证器。
- 当你使用嵌套序列化器后,多个关联模型的创建和更新的行为并不明确,你需要显示地重写create和update方法
认证和权限
认证与权限的区别
认证(Authentication)与权限(Permission)不是一回事。认证是通过用户提供的用户ID/密码组合或者Token来验证用户的身份。权限(Permission)的校验发生验证用户身份以后,是由系统根据分配权限确定用户可以访问何种资源以及对这种资源进行何种操作,这个过程也被称为授权(Authorization)。
之前
python
from rest_framework import generics
class ArticleList(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# 由于ArticleSerializer中author字段仅为可读,需手动关联
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class ArticleDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class =ArticleSerializer
没有做权限控制。
python
from rest_framework import generics
from rest_framework import permissions
from .permissions import IsOwnerOrReadOnly
class ArticleList(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
# 手动绑定
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class ArticleDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class =ArticleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
使用permissions的IsAuthenticatedOrReadOnly类。
自定义权限
自定义的权限类需要继承BasePermission类并根据需求重写has_permission(self,request,view)和has_object_permission(self,request, view, obj)方法
python
# blog/permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
自定义权限只允许对象的创建者才能编辑它。"""
def has_object_permission(self, request, view, obj):
# 读取权限被允许用于任何请求,
# 所以我们始终允许 GET,HEAD 或 OPTIONS 请求。
if request.method in permissions.SAFE_METHODS:
return True
# 写入权限只允许给 article 的作者。
return obj.author == request.user
使用
python
#blog/views.py
from rest_framework import generics
from rest_framework import permissions
from .permissions import IsOwnerOrReadOnly
class ArticleList(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
# important
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class ArticleDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class =ArticleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
在permission_classes加入IsAuthenticatedOrReadOnly,这样只有创建人才可以编辑/删除 该详情。
全局默认权限在settings.py中文件编写
python
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
分页
Django REST Framework提供了一些分页类,接下来我们会分别进行演示。
-
PageNumberPagination:普通分页器。支持用户按?page=3&size=10这种更灵活的方式进行查询,这样用户不仅可以选择页码,还可以选择每页展示数据的数量。通常还需要设置max_page_size这个参数限制每页展示数据的最大数量,以防止用户进行恶意查询(比如size=10000), 这样一页展示1万条数据将使分页变得没有意义。
-
imitOffsetPagination:偏移分页器。支持用户按?limit=20&offset=100这种方式进行查询。offset是查询数据的起始点,limit是每页展示数据的最大条数,类似于page_size。当你使用这个类时,你通常还需要设置max_limit这个参数来限制展示给用户数据的最大数量。
python
#blog/pagination.py
from rest_framework.pagination import PageNumberPagination
class MyPageNumberPagination(PageNumberPagination):
page_size = 2 # default page size
page_size_query_param = 'size' # ?page=xx&size=??
max_page_size = 10 # max page size
from rest_framework.pagination import LimitOffsetPagination
class MyLimitOffsetPagination(LimitOffsetPagination):
default_limit = 5 # default limit per age
limit_query_param = 'limit' # default is limit
offset_query_param = 'offset' # default param is offset
max_limit = 10 # max limit per age
定义一个分页类,继承PageNumberPagination,使用:
python
from rest_framework import viewsets
from .pagination import MyPageNumberPagination
class ArticleViewSet(viewsets.ModelViewSet):
# 用一个视图集替代ArticleList和ArticleDetail两个视图
queryset = Article.objects.all()
serializer_class = ArticleSerializer
pagination_class = MyPageNumberPagination
pagination_class = MyLimitOffsetPagination
# 自行添加,将request.user与author绑定
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# 自行添加,将request.user与author绑定
def perform_update(self, serializer):
serializer.save(author=self.request.user)
在类中定义属性pagination_class即可。
过滤
- 1 通过model.object.filter(id=id)这种去过滤。
- 2 通过django-filter库提供的DjangoFilterBackend类,
自定义需要过滤的字段非常方便, 还可以对每个字段指定过滤方法(比如模糊查询和精确查询),
在settings.py的app增加"django_filters",将其设置为过滤后台
REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] }
使用:
python
from django_filters import rest_framework
class ArticleList(generics.ListCreateAPIView):
# new: filter backends and classes
filter_backends = (rest_framework.DjangoFilterBackend,)
filterset_fields = ['title', 'status']
直接在类中指定属性filter_backends,然后可以定义fileds判断过滤。
也可以指定自定义类
python
import django_filters
from .models import Article
class ArticleFilter(django_filters.FilterSet):
q = django_filters.CharFilter(field_name='title', lookup_expr='icontains')
class Meta:
model = Article
fields = ('title', 'status')
使用
python
# New for django-filter
from django_filters import rest_framework
from .filters import ArticleFilter
class ArticleList(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
pagination_class = MyPageNumberPagination
# new: filter backends and classes
filter_backends = (rest_framework.DjangoFilterBackend,)
filter_class = ArticleFilter
# associate request.user with author.
def perform_create(self, serializer):
serializer.save(author=self.request.user)
不再指定filterset_fields,而是指定filter_class
排序类
跟过滤类用法类似,在视图类中
python
class ArticleList(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
pagination_class = MyPageNumberPagination
# new: filter backends and classes
filter_backends = (rest_framework.DjangoFilterBackend, filters.OrderingFilter)
filter_class = ArticleFilter
# 指定排序字段
ordering_fields = ('create_date')
# associate request.user with author.
def perform_create(self, serializer):
serializer.save(author=self.request.user)
在参数上加上?ordering=create_date或者?ordering=-create_date即可实现对结果按文章创建时间正序和逆序进行排序
限流
防止接口频繁调用卡死服务
DRF提供:
- AnonRateThrottle 用于限制未认证用户的请求频率,主要根据用户的 IP地址来确定用户身份。
- UserRateThrottle 用于限定认证用户的请求频率,可对不同类型的用户实施不同的限流政策。
- ScopeRateThrottle可用于限制对 API 特定部分的访问。只有当正在访问的视图包含 throttle_scope 属性时才会应用此限制。这个与UserRateThrottle类的区别在于一个针对用户限流,一个针对API接口限流。