对于一个资源,我们的API几乎都是围绕资源的增、删、查、改四个操作开发的,为了数据安全,对于用户数据模型应只开放创建用户、查询用户信息和更新用户API,对于删除用户,只能通过管理后端由管理员来处理,django建议不直接删除用户,而是将用户置为不活跃状态。下面实现我们的资源API,看看如何在具体设计中应用以上原则。
资源API视图以实现产品(KaopuShopProduct)的增删查改为例进行开发。在kaopu_shop应用文件夹下新建一个kaopu_shop_views包,在下面新建一个product_views.py,然后编写视图响应。
product_views.py
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from KaopuBackendDoNotDelete.restful_api_settings.api_exception_handle import KaopuParseError, KaopuNotFoundError, \
KaopuValueError, KaopuSuccessResponse
from ..kaopu_shop_models.kaopu_shop_product import KaopuShopProduct, KaopuShopProductSerializer
from ..kaopu_shop_responses.kaopu_shop_responses import KaopuShopProductResponse
class KaopuProductViewSet(viewsets.ModelViewSet):
"""
产品资源API
"""
# 默认数据集
queryset = KaopuShopProduct.objects.all()
# 序列化器
serializer_class = KaopuShopProductSerializer
# 访问权限
permission_classes = (IsAuthenticated, TokenHasReadWriteScope)
def list(self, request, *args, **kwargs):
"""
GET: 获取产品列表,序列化器加入context={"request": request}参数将返回绝对URL
:param request:
:param args:
:param kwargs:
:return: 产品列表
"""
try:
serializer = KaopuShopProductSerializer(self.queryset, many=True, context={"request": request})
return KaopuShopProductResponse(data=serializer.data, state=request.query_params['state'])
except KaopuValueError as e:
return Response(e.get_full_details(), status=e.status_code)
except KeyError:
e = KaopuValueError()
return Response(e.get_full_details(), status=e.status_code)
def create(self, request, *args, **kwargs):
"""
POST:创建新产品
:param request:
:param args:
:param kwargs:
:return: 新创建的产品信息
"""
try:
serializer = KaopuShopProductSerializer(data=request.data, context={"request": request})
if serializer.is_valid(raise_exception=True):
new_product = serializer.create(serializer.validated_data)
return Response(KaopuShopProductSerializer(new_product).data)
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
except ValidationError as e:
error = KaopuParseError(detail=e.messages)
return Response(error.get_full_details(), status=error.status_code)
except KaopuValueError as e:
# rest_framework 验证错误
return Response(e.get_full_details(), status=e.status_code)
except KeyError:
e = KaopuValueError()
return Response(e.get_full_details(), status=e.status_code)
def retrieve(self, request, pk=None, *args, **kwargs):
"""
GET:获取新产品
:param request:
:param pk: 主键(product_id)
:param args:
:param kwargs:
:return:
"""
try:
product = KaopuShopProduct.objects.get(product_id=pk)
serializer = KaopuShopProductSerializer(product, context={"request": request})
return KaopuShopProductResponse(data=serializer.data, state=request.query_params['state'])
except ValidationError as e:
error = KaopuParseError(detail=e.messages)
return Response(error.get_full_details(), status=error.status_code)
except ObjectDoesNotExist:
not_found_error = KaopuNotFoundError()
return Response(not_found_error.get_full_details(), status=not_found_error.status_code)
except KaopuValueError as e:
# rest_framework 验证错误
return Response(e.get_full_details(), status=e.status_code)
except KeyError:
e = KaopuValueError()
return Response(e.get_full_details(), status=e.status_code)
def update(self, request, pk=None, *args, **kwargs):
"""
PUT: 更改产品,客户端提供全部参数
:param request:
:param pk: 主键(product_id)
:param args:
:param kwargs:
:return:
"""
try:
product = KaopuShopProduct.objects.get(product_id=pk)
serializer = KaopuShopProductSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
modified_product = serializer.update(product, serializer.validated_data)
return KaopuShopProductResponse(
data=KaopuShopProductSerializer(modified_product, context={"request": request}).data,
state=request.query_params['state'])
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except ValidationError as e:
error = KaopuParseError(detail=e.messages)
return Response(error.get_full_details(), status=error.status_code)
except ObjectDoesNotExist:
not_found_error = KaopuNotFoundError()
return Response(not_found_error.get_full_details(), status=not_found_error.status_code)
except KaopuValueError as e:
# rest_framework 验证错误
return Response(e.get_full_details(), status=e.status_code)
except KeyError:
e = KaopuValueError()
return Response(e.get_full_details(), status=e.status_code)
def partial_update(self, request, pk=None, *args, **kwargs):
"""
PATCH: 更新产品部分信息,客户端只需提供部分参数
:param request:
:param pk: 主键(product_id)
:param args:
:param kwargs:
:return:
"""
try:
product = KaopuShopProduct.objects.get(product_id=pk)
serializer = KaopuShopProductSerializer(data=request.data, partial=True)
if serializer.is_valid(raise_exception=True):
modified_product = serializer.update(product, serializer.validated_data)
return KaopuShopProductResponse(
data=KaopuShopProductSerializer(modified_product, context={"request": request}).data,
state=request.query_params['state'])
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except ValidationError as e:
error = KaopuParseError(detail=e.messages)
return Response(error.get_full_details(), status=error.status_code)
except ObjectDoesNotExist:
not_found_error = KaopuNotFoundError()
return Response(not_found_error.get_full_details(), status=not_found_error.status_code)
except KaopuValueError as e:
# rest_framework 验证错误
return Response(e.get_full_details(), status=e.status_code)
except KeyError:
e = KaopuValueError()
return Response(e.get_full_details(), status=e.status_code)
def destroy(self, request, pk=None, *args, **kwargs):
"""
DELETE: 删除单个产品
:param request:
:param pk: 主键(product_id)
:param args:
:param kwargs:
:return:
"""
try:
product = KaopuShopProduct.objects.get(product_id=pk)
product.delete()
success_response = KaopuSuccessResponse()
return Response(success_response.get_full_details(), status=success_response.status_code)
except ValidationError as e:
error = KaopuParseError(detail=e.messages)
return Response(error.get_full_details(), status=error.status_code)
except ObjectDoesNotExist:
not_found_error = KaopuNotFoundError()
return Response(not_found_error.get_full_details(), status=not_found_error.status_code)
except KeyError:
e = KaopuValueError()
return Response(e.get_full_details(), status=e.status_code)
@action(detail=False, methods=['post'], url_name='kaopu_shop_product_batches_destroy',
url_path='batches_destroy')
def batches_destroy(self, request):
"""
POST: 批量删除,传入主键列表字符串,用英文逗号分隔,如:{"pk_list": "1,2,3"}
:param request:
:return:
"""
try:
# 要删除的产品主键列表
pk_list = str(request.data['pk_list']).split(',')
products = KaopuShopProduct.objects.filter(pk__in=pk_list)
products.delete()
success_response = KaopuSuccessResponse()
return Response(success_response.get_full_details(), status=success_response.status_code)
except KeyError:
error = KaopuParseError(detail='Invalid params,please check your request.')
return Response(error.get_full_details(), status=error.status_code)
except ValidationError as e:
error = KaopuParseError(detail=e.messages)
return Response(error.get_full_details(), status=error.status_code)
在kaopu_shop文件夹下新建一个urls.py文件,注册资源API路径:
urls.py
from django.urls import path, include
from rest_framework import routers
from kaopu_shop.kaopu_shop_views.product_views import KaopuProductViewSet
router = routers.SimpleRouter()
router.register(r'products', KaopuProductViewSet)
urlpatterns = router.urls
urlpatterns = [
path('', include(router.urls)),
]
这样将自动生成我们的资源API URL。
权限控制主要指配置Viewset的permission_class属性,通常是一个列表或者是元组,可选值见https://www.django-rest-framework.org/api-guide/permissions/。该属性可以控制允许什么样的用户访问资源API。
过滤器参见:https://django-filter.readthedocs.io/en/latest/ref/filters.html#field-name
添加django_filters到Django的INSTALLED_APPS:
INSTALLED_APPS = [
...
'django_filters',
...
]
将过滤器后端添加到设置中:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
然后创建过滤器类和过滤规则,这里仅实现常见的几种情况(参见2.4(6))。
首先定义过滤器类
from django_filters import rest_framework as filters, ChoiceFilter
from rest_framework import serializers
Product_KINDS = (
('A', "类型A"), ('A', "类型A"), ('C', "类型C"), )
class KaopuShopProduct(models.Model):
......
class ProductFilter(filters.FilterSet):
# 产品类型过滤
product_type = filters.ChoiceFilter(choices=Product_KINDS, field_name="product_type")
# 价格下限
price_gte = filters.NumberFilter(field_name="price", lookup_expr='gte')
# 价格上限
price_lte = filters.NumberFilter(field_name="price", lookup_expr='lte')
class Meta:
model = KaopuShopProduct
fields = ['price', 'product_type', 'price_gte', 'price_lte', 'product_name']
因为过滤器类和数据模型密切相关,因此建议将其和数据模型放在一个py文件下,方便对照和管理。定义完过滤器类,还需要在ViewSet 中启用它们,在KaopuProductViewSet中添加以下代码
class KaopuProductViewSet(viewsets.ModelViewSet):
.....
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = ProductFilter
def filter_queryset(self, queryset):
# Other condition for different filter backend goes here
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, view=self)
return queryset
......
更多过滤选项参看django-filter文档进行配置。
数据分页参见:https://www.django-rest-framework.org/api-guide/pagination/
在restful_api_settings.py中添加设置:
REST_FRAMEWORK = {
......
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 10,
......
}
这里使用的是偏移分页样式,即使用通过当前数据页+数据页偏移确定要加载的数据页。
def filter_queryset(self, queryset):
# Other condition for different filter backend goes here
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, view=self)
# 筛选结束后数据分页
return self.pagination_class().paginate_queryset(queryset, self.request, view=self)
继承内置的数据分页类:
class LargeResultsSetPagination(PageNumberPagination):
page_size = 1000
page_size_query_param = 'page_size'
max_page_size = 10000
然后在要使用分页的Viewset中设置pagination_class 属性,同样要重写filter_queryset方法。
客户端指定API版本通常有三种方式:Accept
标头中将版本号指定为媒体类型的一部分、将版本号指定为URL路径的一部分、将版本号作为URL中的查询参数。通常推荐将版本号指定为URL路径的一部分,因为这样更清晰明了。
REST_FRAMEWORK = {
......
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
'DEFAULT_VERSION': 'v1', # 默认的版本
'ALLOWED_VERSIONS': ['v1','v2'], # 有效的版本
'VERSION_PARAM': 'version', # 版本的参数名与URL_conf中一致
......
}
router = routers.SimpleRouter()
router.register(r'^(?P[v1丨v2]+)/products', KaopuProductViewSet)
urlpatterns = router.urls
urlpatterns = [
path('', include(router.urls)),
]
http://localhost:8000/kaopushop/v1/products/
以后在视图中就可以通过访问 request.version 来获取当前请求的具体版本,然后根据不同的版本来返回不同的内容,例如之前的Viewset可以改成:
class KaopuProductViewSet(viewsets.ModelViewSet):
def list(self, request, *args, **kwargs):
"""
GET: 获取产品列表,序列化器加入context={"request": request}参数将返回绝对URL
:param request:
:param args:
:param kwargs:
:return: 产品列表
"""
try:
serializer = KaopuShopProductSerializer(self.filter_queryset(self.queryset),
many=True, context={"request": request})
if request.version=='v1':
return KaopuShopProductResponseV1(data=serializer.data, state=request.query_params['state'])
if request.version=='v2':
return KaopuShopProductResponseV2(data=serializer.data, state=request.query_params['state'])
except KaopuValueError as e:
return Response(e.get_full_details(), status=e.status_code)
except KeyError:
e = KaopuValueError()
return Response(e.get_full_details(), status=e.status_code)
节流参见:https://www.django-rest-framework.org/api-guide/throttling/
API节流的目的是,对于某些特别耗费资源的API,需要限制其访问频率,避免服务器过载而宕机;对于一些短期内发起大量请求的恶意用户,要限制其访问,保护服务器安全;或者限制非VIP用户的带宽。在restful_api_settings.py中添加节流策略:
REST_FRAMEWORK = {
......,
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.ScopedRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'contacts': '1000/day',
'uploads': '20/day'
},
......
}
频率描述可以使用second
,minute
,hour
或day。也可以使用Viewset的
throttle_classes来单独为某个Viewset设置节流策略,实现更细粒度的节流控制。
下一节:Django 架设 Restful API(六)项目部署:静态文件部署和CDN使用
https://blog.csdn.net/anbuqi/article/details/115280213