Django rest_framework是第三方框架,为在Django项目中实现Restful风格的数据接口提供非常友好支持。
1.安装
pip install djangorestframework
2.创建项目
创建子项目:
# 创建项目tutorial
django-admin startproject tutorial
# 创建knowledge子项目
cd tutorial/
django-admin startapp knowledge
添加路由,编辑主项目路由模块tutorial/tutorial/urls.py,如下:
urlpatterns = [
path('admin/', admin.site.urls),
path('knowledge/', include('knowledge.urls'))
]
在配置中INSTALLED_APPS添加如下:
INSTALLED_APPS = [
...
'rest_framework',
'quickstart'
]
编辑子项目的models.py,代码如下:
from django.db import models
from uuid import uuid4
class KnowledgeItem(models.Model):
"""知识项数据模型"""
id = models.UUIDField(verbose_name="编号", primary_key=True, default=uuid4) # 知识编号
title = models.CharField(verbose_name="标题", max_length=100) # 标题
content = models.TextField(verbose_name="内容") # 知识内容
publish_time = models.DateTimeField(verbose_name="发布时间",auto_now_add=True) # 知识发布时间
last_update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True)# 知识修改时间
remark = models.TextField(verbose_name="备注", null=True, blank=True) # 知识备注条目
因此项目用于开发接口,不需要处理网页视图,所以不再创建网页文件结构。
后面的案例以此项目为例说明。
3.分页配置
Django rest_framework还提供了自己的分页配置方式,编辑配置文件,增加配置如下:
# 分页配置
REST_FRAMEWORK = {
# 默认的数据分页处理模块
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 每页展示数据量
'PAGE_SIZE': 10
}
参考:https://blog.csdn.net/weixin_30369087/article/details/96688821
数据序列化是数据接口开发的核心,定义序列化模型一般有两种方式,一是使用serializers.Serializer,二是使用serializers.ModelSerializer。
这种序列化的操作方式是Django rest_framework框架提供的底层序列化方式,可以对指定的数据模型对象进行数据序列化转换。
在子项目中,创建序列化模块serializers.py,定义序列化组件如下:
from rest_framework import serializers
from .models import KnowledgeItem
class KnowledgeSerializer(serializers.Serializer):
"""知识项序列化类型"""
id = serializers.UUIDField(read_only=True) # 编号
title = serializers.CharField(required=True, max_length=100) # 标题
content = serializers.CharField(required=True) # 内容
remark = serializers.CharField(required=False, allow_blank=True, allow_null=True)# 备注
def create(self, validated_data):
"""使用封装后的验证方法返回知识项对象"""
return KnowledgeItem.objects.create(**validated_data)
def update(self, instance, validated_data):
"""使用数据更新已有对象属性的方法"""
instance.title = validated_data.get("title", instance.title)
instance.content = validated_data.get("content", instance.content)
instance.remark = validated_data.get("remark", instance.remark)
这个序列化类中有两部分:
1.定义了要进行序列化的字段;这些字段负责将Python类型转换成JSON类型,即序列化的过程;
2.定义了当serializer.save()时用于生成Model对象的create()和update()方法,这两方法负责将JSON类型的数据转换成Python类型的数据,即反序列化的过程。
缺点:用Serializer还需要使用serializers.fields模块里的类型去自定义序列化字段,与model里代码重复。具体Serializer中的Field有哪些字段,可以参考源码,与Django中模型字段类似。
使用ModelSerializer继承了Serializer,在Serializer基础上封装了很多操作。最大好处是可根据指定的model自动检测并生成序列化的字段,不需要再定义,默认情况下,将所有Model类中的字段映射到ModelSerializer类中相应的字段。
修改上面的代码如下:
class KnowledgeSerializer(ModelSerializer):
"""知识项序列化类型"""
class Meta:
# 关联数据模型
model = KnowledgeItem
# 关联操作字段
fields = ('id', 'title', 'content', 'remark')
Serializer序列化对象有以下属性和方法:
*1.save()*
在调用serializer.save()
时,会创建或者更新一个Model实例(调用create()
或update()
创建),具体根据序列化类的实现而定,如:
# .save() will create a new instance.
serializer = StudentSerializer(data=data)
# .save() will update the existing `comment` instance.
serializer = StudentSerializer(comment, data=data)
*2.create()、update()*
Serializer中的create()和update()方法用于创建生成一个Model实例,在使用Serializer时,如果要保存反序列化后的实例到数据库,则必须要实现这两方法之一,生成的实例则作为save()返回值返回。方法属性validated_data表示校验的传入数据。
*3. is_valid()*
当反序列化时,在调用Serializer.save()之前必须要使用is_valid()方法进行校验,如果校验成功返回True,失败则返回False,同时会将错误信息保存到serializer.errors属性中。
*4.data*
serializer.data中保存了序列化后的数据。
*5.errors*
当serializer.is_valid()进行校验后,如果校验失败,则将错误信息保存到serializer.errors属性中。
Django rest_framework对请求对象(Request)和响应对象(Response)进行了再次封装,扩展了功能。
Django rest_framework的Request对象提供以下两个方法获取参数:
这个对象可以根据客户端发送的请求头中规范的请求格式,对响应数据进行格式化转换和渲染
return Response(data)
REST framework为每个状态码提供了更加明确的标识,全部内容封装在rest_framework.status模块中。这样我们可以直接使用返回相应的状态码。代码如下:
"""
Descriptive HTTP status codes, for code readability.
See RFC 2616 - https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
And RFC 6585 - https://tools.ietf.org/html/rfc6585
And RFC 4918 - https://tools.ietf.org/html/rfc4918
"""
def is_informational(code):
return 100 <= code <= 199
def is_success(code):
return 200 <= code <= 299
def is_redirect(code):
return 300 <= code <= 399
def is_client_error(code):
return 400 <= code <= 499
def is_server_error(code):
return 500 <= code <= 599
HTTP_100_CONTINUE = 100
HTTP_101_SWITCHING_PROTOCOLS = 101
HTTP_102_PROCESSING = 102
HTTP_103_EARLY_HINTS = 103
HTTP_200_OK = 200
HTTP_201_CREATED = 201
HTTP_202_ACCEPTED = 202
HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
HTTP_204_NO_CONTENT = 204
HTTP_205_RESET_CONTENT = 205
HTTP_206_PARTIAL_CONTENT = 206
HTTP_207_MULTI_STATUS = 207
HTTP_208_ALREADY_REPORTED = 208
HTTP_226_IM_USED = 226
HTTP_300_MULTIPLE_CHOICES = 300
HTTP_301_MOVED_PERMANENTLY = 301
HTTP_302_FOUND = 302
HTTP_303_SEE_OTHER = 303
HTTP_304_NOT_MODIFIED = 304
HTTP_305_USE_PROXY = 305
HTTP_306_RESERVED = 306
HTTP_307_TEMPORARY_REDIRECT = 307
HTTP_308_PERMANENT_REDIRECT = 308
HTTP_400_BAD_REQUEST = 400
HTTP_401_UNAUTHORIZED = 401
HTTP_402_PAYMENT_REQUIRED = 402
HTTP_403_FORBIDDEN = 403
HTTP_404_NOT_FOUND = 404
HTTP_405_METHOD_NOT_ALLOWED = 405
HTTP_406_NOT_ACCEPTABLE = 406
HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
HTTP_408_REQUEST_TIMEOUT = 408
HTTP_409_CONFLICT = 409
HTTP_410_GONE = 410
HTTP_411_LENGTH_REQUIRED = 411
HTTP_412_PRECONDITION_FAILED = 412
HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
HTTP_414_REQUEST_URI_TOO_LONG = 414
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
HTTP_417_EXPECTATION_FAILED = 417
HTTP_418_IM_A_TEAPOT = 418
HTTP_421_MISDIRECTED_REQUEST = 421
HTTP_422_UNPROCESSABLE_ENTITY = 422
HTTP_423_LOCKED = 423
HTTP_424_FAILED_DEPENDENCY = 424
HTTP_425_TOO_EARLY = 425
HTTP_426_UPGRADE_REQUIRED = 426
HTTP_428_PRECONDITION_REQUIRED = 428
HTTP_429_TOO_MANY_REQUESTS = 429
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451
HTTP_500_INTERNAL_SERVER_ERROR = 500
HTTP_501_NOT_IMPLEMENTED = 501
HTTP_502_BAD_GATEWAY = 502
HTTP_503_SERVICE_UNAVAILABLE = 503
HTTP_504_GATEWAY_TIMEOUT = 504
HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
HTTP_506_VARIANT_ALSO_NEGOTIATES = 506
HTTP_507_INSUFFICIENT_STORAGE = 507
HTTP_508_LOOP_DETECTED = 508
HTTP_509_BANDWIDTH_LIMIT_EXCEEDED = 509
HTTP_510_NOT_EXTENDED = 510
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
@api_view装饰器可以用于装饰视图函数,在Django框架提供的用户请求类型的基础,对客户端请求进行进一步封装得到Request对象,在处理完成后,可以通过Response对象对响应数据进行转换和渲染。
在项目中不再使用JsonResponse这样的响应类型进行请求的响应操作,而是使用@api_view装饰器,把视图处理函数升级为Django rest_framework中封装的视图处理函数。
可参考3.6的示例。
format_suffix_patterns用于添加请求格式标识符。代码如下:
urlpatterns = [
# 增删改查知识项对象数据
path("/" , views.knowledge_detail),
# 查询知识项列表数据
path("", views.knowledge_list),
]
urlpatterns = format_suffix_patterns(urlpatterns)
添加之前,数据访问方式是http://127.0.0.1:8000/knowledge(访问指定的url地址),Accept: application/josn(限制接受JSON格式的数据)。
添加之后,可以通过在访问路由后添加后缀名称,直接访问数据的格式,并从接口获取数据,比如http://127.0.0.1:8000/knowledge/.json,表示直接从接口提取JSON格式数据。
#######################################################################
# 基于函数的视图处理组件
#######################################################################
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import KnowledgeItem
from .serializers import KnowledgeSerializer
@csrf_exempt
@api_view(['GET', 'POST'])
def knowledge_list(request, format=None):
"""知识项列表视图处理函数"""
if request.method == "GET":
# 查询所有的知识项数据
knowledges = KnowledgeItem.objects.all()
# 序列化对象数据
serializers = KnowledgeSerializer(knowledges, many=True)
# 返回查询到的知识项序列数据列表
return JsonResponse(serializers.data, safe=False)
elif request.method == "POST":
# 获取知识项新数据
# data = JSONParser().parse(request)
# 反序列化数据
serializers = KnowledgeSerializer(data=request.data)
# 验证
if serializers.is_valid():
# 存储数据
serializers.save()
# 返回操作的数据
return Response(serializers.data, status=status.HTTP_201_CREATED)
return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST)
@csrf_exempt
@api_view(['GET', 'PUT', 'DELETE'])
def knowledge_detail(request, pk, format=None):
"""知识项操作视图处理函数"""
# 查询获取对应的知识项对象
knowledge = get_object_or_404(KnowledgeItem, pk=pk)
# 判断不同请求方式下的数据处理
if request.method == "GET":
"""查询获取知识项对象"""
serializer = KnowledgeSerializer(knowledge)
return Response(serializer.data)
elif request.method == "PUT":
"""更新知识项对象"""
# 根据请求提交的数据反序列化数据
serializer = KnowledgeSerializer(knowledge, 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":
# 删除对象数据
knowledge.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
和Django框架一样, Django rest_framework也提供了视图处理函数和基于类型的视图组件,也就是CBV(Class Base View)。
Django rest_framework通过APIView类实现了基于类型的视图组件构建,只要继承此类即可使用,定义方法时,通过方法名来区分请求方式。
还是以上面的子项目knowledge为例,重构view.py模块如下:
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import KnowledgeItem
from .serializers import KnowledgeSerializer
class KnowledgeView(APIView):
"""知识项视图组件"""
def get(self, request, format=None):
"""GET请求方式处理函数"""
# 查询获取所有知识项对象
knowledges = KnowledgeItem.objects.all()
# 序列化数据
serializers = KnowledgeSerializer(knowledges, many=True)
# 返回响应数据
return Response(serializers.data)
def post(self, request, format=None):
"""新增知识项数据"""
# 接受请求中包含的数据,并序列化数据
serializer = KnowledgeSerializer(data=request.data)
# 验证有效性
if serializer.is_valid():
# 存储数据
serializer.save()
# 返回新增数据
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class KnowledgeDetailView(APIView):
"""知识项详情视图组件"""
def get_object(self, pk):
"""获取知识项对象的函数"""
return get_object_or_404(KnowledgeItem, pk=pk)
def get(self, request, pk, format=None):
"""GET请求方式视图处理方法"""
serializer = KnowledgeSerializer(self.get_object(pk))
return Response(serializer.data)
def put(self, request, pk, format=None):
"""PUT请求方式视图处理方法"""
serializer = KnowledgeSerializer(self.get_object(pk), request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
"""DELETE请求方式视图处理方法"""
knowledge = self.get_object(pk)
knowledge.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
构建完视图后,添加路由的方式也相应改变:
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from . import views
app_name = 'knowledge'
urlpatterns = [
# 增删改查知识项对象数据
path("/" , views.KnowledgeDetailView.as_view()),
# 查询知识项列表数据
path("", views.KnowledgeView.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
路由中的映射关系,通过APIView中的as_view方法进行绑定。
为了提高项目开发效率,Django rest_framework框架对项目中常见的操作功能进行了封装,并提供了mixin组件。常用组件如下:
CreateModelMixin
:提供了一个快速创建一个model实例的方法create();ListModelMixin
:提供了一个快速获取列表数据的方法list();RetrieveModelMixin
:提供了一个快速获取单个实例数据的方法retrieve();UpdateModelMixin
:提供了一个快速更新单个实例数据的方法update();DestroyModelMixin
:提供了一个快速删除单个实例数据的方法destroy();源码如下:
"""
Basic building blocks for generic class based views.
We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
class UpdateModelMixin:
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
class DestroyModelMixin:
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
示例:
重构knowledge子项目的视图views.py,代码如下:
from rest_framework import mixins, status, generics
from rest_framework.response import Response
from .models import KnowledgeItem
from .serializers import KnowledgeSerializer
class KnowledgeView(generics.GenericAPIView, # 视图组件
mixins.CreateModelMixin, # 对象创建组件
mixins.ListModelMixin,): # 对象类表查看组件
"""知识项视图处理组件"""
# 关联数据查询结果集
queryset = KnowledgeItem.objects.all()
# 关联数据序列化组件
serializer_class = KnowledgeSerializer
def get(self, request, *args, **kwargs):
"""GET请求处理方法"""
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""POST请求处理方法"""
return self.create(request, *args, **kwargs)
class KnowledgeDetailView(generics.GenericAPIView, # 视图组件
mixins.RetrieveModelMixin, # 数据查询组件
mixins.UpdateModelMixin, # 数据更新组件
mixins.DestroyModelMixin,): # 数据删除组件
# 关联数据查询结果集
queryset = KnowledgeItem.objects.all()
# 关联数据序列化组件
serializer_class = KnowledgeSerializer
def get(self, request, *args, **kwargs):
"""GET请求处理方法"""
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
"""PUT请求处理方法"""
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""DELETE请求处理方法"""
return self.destroy(request, *args, **kwargs)
注意使用时,还要继承GenericAPIView,它为其他组件提供了基础功能。其中queryset和serializer_class属性就是供他使用的,用来指定数据查询结果集和使用的序列化组件,方便后续其他方法和组件后续使用。
通过mixin组件对项目中常见操作功能进行封装后,视图组件已经得到优化,但是对于常规的操作,不同数据模型对对象的增删改查大同小异,所以Django rest_framework对CRUD再次封装,提供了以下常规视图组件类型:
示例:
重构knowledge子项目的视图views.py,代码如下:
from rest_framework import generics
from .serializers import KnowledgeSerializer
from .models import KnowledgeItem
class KnowledgeView(generics.ListCreateAPIView):
"""知识项视图处理组件"""
# 指定查询结果集
queryset = KnowledgeItem.objects.all()
# 指定序列化组件
serializer_class = KnowledgeSerializer
class KnowledgeDetailView(generics.RetrieveUpdateDestroyAPIView):
"""知识项删改查处理组件"""
# 指定查询结果集
queryset = KnowledgeItem.objects.all()
# 指定序列化组件
serializer_class = KnowledgeSerializer
新建一个子项目,用于演示本节内容:
# 创建knowledge子项目
cd tutorial/
django-admin startapp articles
创建数据模型:
from django.db import models
from django.contrib.auth.models import User
from uuid import uuid4
class Article(models.Model):
"""文章数据类型"""
id = models.UUIDField(verbose_name="文章编号", primary_key=True, default=uuid4)
title = models.CharField(verbose_name="文章标题", max_length=100)
content = models.TextField(verbose_name="文章内容")
publish_time = models.DateTimeField(verbose_name="发表时间", auto_now_add=True)
update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True)
user = models.ForeignKey(verbose_name="作者", to=User, related_name='articles', on_delete=models.CASCADE)
创建序列化模块serializer.py,添加系统用户数据序列化类和文章数据序列化类:
from rest_framework.serializers import (ModelSerializer,
HyperlinkedModelSerializer,
PrimaryKeyRelatedField,
ReadOnlyField)
from django.contrib.auth.models import User
from .models import Article
class UserSerializer(HyperlinkedModelSerializer):
"""用户数据序列化组件"""
# 关联文章序列化数据
articles = PrimaryKeyRelatedField(many=True, queryset=Article.objects.all())
class Meta:
# 关联数据模型
model = User
# 关联属性字段
fields = ('id', 'username', 'email', 'articles')
class ArticleSerializer(HyperlinkedModelSerializer):
"""文章数据序列化组件"""
user = ReadOnlyField(source='user.username')
class Meta:
# 关联数据模型
model = Article
# 关联数据属性字段
fields = ('id', 'title', 'content', 'user')
视图处理模块,articles/views.py,代码如下:
from rest_framework import status, generics, permissions
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework.reverse import reverse
from django.contrib.auth.models import User
from .models import Article
from .serializers import UserSerializer, ArticleSerializer
from .permissions import IsOwnerPermission
##################################################################
# 基于APIView视图类 构建视图组件
##################################################################
class UserListView(generics.ListCreateAPIView):
"""用户列表视图组件"""
# 视图结果集
queryset = User.objects.all()
# 指定序列化类型
serializer_class = UserSerializer
class UserDetailView(generics.RetrieveUpdateDestroyAPIView):
"""用户详情操作视图组件"""
# 视图结果集
queryset = User.objects.all()
# 指定序列化类型
serializer_class = UserSerializer
class ArticleView(generics.ListCreateAPIView):
"""文章列表查看"""
# 指定数据查询集
queryset = Article.objects.all()
# 指定序列化类型
serializer_class = ArticleSerializer
class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
"""文章数据详情操作"""
# 指定数据查询集
queryset = Article.objects.all()
# 指定序列化类型
serializer_class = ArticleSerializer
然后自行添加一些数据,下面进行权限讲解。
Django rest_framework提供了内建的权限认证模块 rest_framework.permissions。
常用的认证类如下:
修改articles/views.py,代码如下:
from rest_framework import status, generics, permissions
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework.reverse import reverse
from django.contrib.auth.models import User
from .models import Article
from .serializers import UserSerializer, ArticleSerializer
from .permissions import IsOwnerPermission
##################################################################
# 基于APIView视图类 构建视图组件
##################################################################
class UserListView(generics.ListCreateAPIView):
"""用户列表视图组件"""
# 视图结果集
queryset = User.objects.all()
# 指定序列化类型
serializer_class = UserSerializer
class UserDetailView(generics.RetrieveUpdateDestroyAPIView):
"""用户详情操作视图组件"""
# 视图结果集
queryset = User.objects.all()
# 指定序列化类型
serializer_class = UserSerializer
# 权限设置
permission_classes = (permissions.IsAuthenticated,)
class ArticleView(generics.ListCreateAPIView):
"""文章列表查看"""
# 指定数据查询集
queryset = Article.objects.all()
# 指定序列化类型
serializer_class = ArticleSerializer
# 权限设置
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
"""文章数据详情操作"""
# 指定数据查询集
queryset = Article.objects.all()
# 指定序列化类型
serializer_class = ArticleSerializer
# 权限设置
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
上面添加身份认证后,需要身份认证通过才能访问接口数据。
但是我们也可以添加用于访问所有路由数据的子路由,方便用户看到所有的子路由。
在主项目urls.py文件添加路由代码如下:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('quickstart/', include('quickstart.urls')),
path('knowledge/', include('knowledge.urls')),
path('articles/', include('articles.urls')),
# 添加身份认证管理子路由
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
]
在Django rest_framework框架的子路由中,包含了如下两个路由:
启动项目,访问http://127.0.0.1:8000/api-auth/login/,可以看到如下的登录页面:
登录后,直接访问http://127.0.0.1:8000/articles/,可以查看文章子项目下所有路由,如下:
无论是否添加了权限认证的视图组件,都可以访问到数据。
有些接口需求,比如项目中指允许发表文章的用户修改删除自己的文章,其他用户只能查看文章,这时候框架默认权限无法操作,就需要自定义权限。
Django rest_framework框架提供了permissions.BasePermission来让开发自定义权限策略。该模块主要提供了两个权限认证方法:
示例:
在前面articles子项目中,创建权限认证模块permissions.py,添加代码如下:
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsOwnerPermission(BasePermission):
"""自定义权限"""
def has_object_permission(self, request, view, obj):
"""判断对于指定对象Obj的操作权限"""
if request.method == SAFE_METHODS:
# 判断请求方式是GET/HEAD/OPTIONS安全请求,直接返回True
return True
# 验证操作文章对象的用户是否发表文章的用户
return obj.user == request.user
然后修改视图组件的文章视图,添加我们自定义的权限,代码如下:
class ArticleView(generics.ListCreateAPIView):
"""文章列表查看"""
# 指定数据查询集
queryset = Article.objects.all()
# 指定序列化类型
serializer_class = ArticleSerializer
# 权限设置
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
"""文章数据详情操作"""
# 指定数据查询集
queryset = Article.objects.all()
# 指定序列化类型
serializer_class = ArticleSerializer
# 权限设置
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerPermission)
在写完后端接口后,为针对不同客户端进行数据接口的统一管理,后期进行项目交接时提供完整的接口文档即可。
为此,可以专门定义一个视图函数,用于管理项目中指定数据的接口信息。
编辑articles/views.py,如下:
# 添加此路由函数
@api_view(["GET"])
def api_root(request, format=None):
return Response({
'users':reverse('articles:user_list', request=request, format=format),
'articles':reverse('articles:article_list', request=request, format=format),
})
然后重构路由模块articles/urls.py:
from django.urls import path, include
from . import views
app_name = "articles"
urlpatterns = [
path("", views.api_root),
path("users/", views.UserListView.as_view(), name='user_list'),
path("users//" , views.UserDetailView.as_view(), name='user_detail'),
path("articles/", views.ArticleView.as_view(), name='article_list'),
path("articles//" , views.ArticleDetailView.as_view(), name='article_detail'),
]
这样启动项目,访问文章数据接口,可以看到具体的接口信息。
除了上面说到的Django rest_framework内建的generic模块提供的简洁的视图操作方式,还提供了更加高度抽象的视图集ViewSets,它与组件 rest_framework.urls.routers配合,实现了数据接口的自动封装和数据请求的自动处理。
ViewSets组件中,不再提供如get、post等类似的请求处理方法,而是在操作过程中由路由模块动态地将ViewSets组件绑定到请求方式。
下面将我们的articles子项目重构使用视图集的方式。
1.视图处理组件
首先使用ViewSets重构视图集组件,编辑articles/views.py,如下:
from rest_framework import viewsets, permissions
from django.contrib.auth.models import User
from .serializers import UserSerializer, ArticleSerializer
from .models import Article
from .permissions import IsOwnerPermission
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""用户视图集"""
# 指定查询结果集
queryset = User.objects.all()
# 指定序列化组件
serializer_class = UserSerializer
class ArticleViewSet(viewsets.ModelViewSet):
"""文章视图集"""
# 指定查询结果集
queryset = Article.objects.all()
# 指定序列化组件
serializer_class = ArticleSerializer
# 指定权限操作
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerPermission)
如上,使用viewsets.ReadOnlyModelViewSet
替换原来的UserListView和UserDetailView两个视图组件。
同样的,使用viewsets.ModelViewSet
构建的数据访问视图集,涵盖了以前GET、POST、PUT、DELETE的所有增删改查操作。
2.路由
接着完成路由模块定义,编辑articles/urls.py,如下:
from django.urls import path, include
from rest_framework import renderers
from . import views
app_name = "articles"
# 用户视图集底层请求构建
user_list = views.UserViewSet.as_view({
'get': 'list'
})
user_detail = views.UserViewSet.as_view({
'get': 'retrieve'
})
# 文章视图集底层请求构建
article_list = views.ArticleViewSet.as_view({
'get': 'list',
'post': 'create'
})
article_detail = views.ArticleViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})
# 将视图集构建对象,映射到路由关系中
urlpatterns = [
path("", views.api_root),
path("users/", user_list, name="user_list"),
path("users//" , user_detail, name="user_detail"),
path("articles/", article_list, name="article_list"),
path("articles//" , article_detail, name="article_detail"),
]
上面的代码中,显示的指定了对于不同的请求方式绑定的操作,也就是框架自动完成了客户端的请求地址和视图集组件之间的路由。
但是上述是为了我们方便理解视图集是如何区分不同请求方式而这么写的,实际中,一般会通过框架内建的路由模块直接绑定视图集。重新写代码如下:
from django.urls import path, include
from rest_framework import renderers
from . import views
app_name = "articles"
##################################################################
# 基于ViewSets视图集 构建路由关系【常规】
##################################################################
from rest_framework import routers
# 构建路由对象
router = routers.DefaultRouter()
# 挂载注册视图集
router.register(r'users', views.UserViewSet)
router.register(r'articles', views.ArticleViewSet)
# 添加路由映射
urlpatterns = [
path("", include(router.urls))
]
这样针对不同的请求方式的数据操作,由框架按照内建的通用约定方式进行了自动处理。