DRF视图家族操作版
drf 视图家族
使用DRF编写CBV类视图规范
- 当你需要对API的行为进行高度定制化的时候使用:"基础API类",可读性最高,代码量最多,灵活性最强。
- 当你需要对一个模型进行标准的CURD操作时使用:"通用generics.*类",可读性好,代码量适中,灵活性较强。
- 当你需要对一个模型进行标准的CURD操作并且不需要定制API行为的时候使用:"视图集view set",可读性最差,代码量最少,灵活性最弱。
前期准备
总路由 urls.py
from django.contib import admin
from django.views import serve
from django.conf import settings
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include('api.urls')),
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
]
基表:utils/models.py
from django.db import models
class BaseModel(models.Model):
is_delete = models.BooleanField(default=False)
create_time = models.DateTimeField(auto_now_add=True, null=True)
class Meta:
# 抽象表, 不会完成数据库迁移
abstract = True
模型层 api/models.py
默认值是 True,它会在数据库中创建外键约束,维护数据完整性。通常情况下,这符合大部分场景的需求。如果数据库中存在一些历史遗留的无效数据,则可以将其设置为 False,这时就需要自己去维护关联关系的正确性了。
from django.db import models
from utils.models import BaseModel
class Book(BaseModel):
name = models.CharField(max_length=64)
price = models.DecimalField(max_digits=6, decimal_places=2)
img = models.ImageField(upload_to='icon', default='icon/default.png')
publish = models.ForeignKey(to='Publish',
null=True,
related_name='books',
db_constraint=False,
on_delete=models.DO_NOTHING
)
authors = models.ManyToManyField(to='Author',
related_name='authors',
db_constraint=False
@property
def publish_info(self):
return {'name': self.publish.name, 'address': self.publish.address}
@property
def author_info(self):
author_list = []
for author in self.authors.all():
detail = AuthorDetail.objects.filter(author_id=self.author.id)
author_list.append({
'name': author.name,
'age': author.age,
'mobile': '未知' if not detail else author.detail.mobile
})
return author_list
class Meta:
db_table = "books"
verbose_name = '书籍'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Author(BaseModel):
name = models.CharField(max_length=64)
age = models.IntegerField()
@property
def mobile(self):
return self.detail.mobile
class Meta:
db_table = 'author'
verbose_name = '作者'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class AuthorDetail(BaseModel):
mobile = models.CharField(max_length=11)
author = models.OneToOneField(to='Author',
null=True,
related_name='detail',
db_constraint=False,
on_delete=models.CASCADE
)
class Meta:
db_table = 'detail'
verbose_name = '作者详情'
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.author.name}的详情"
class Publish(BaseModel):
name = models.CharField(max_length=64)
address = models.CharField(max_length=128)
class Meta:
db_table = 'publish'
verbose_name = '出版社'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
序列化器 api/serializers.py
from rest_framework import serializers
from . import models
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
for ind, obj in enumerate(instance):
for attr, value in validated_data[ind].items():
if hasattr(obj, attr):
set(obj, attr, value)
obj.save()
return instance
class BookV2ModelSerializer(serializers.ModelSerializer):
re_name = serializers.CharField(
min_length=3,
required=True,
write_only=True, # 只参与反序列化
error_messages={
'min_length': '太短了',
'required': '不能为空'
}
)
class Meta:
model = models.Book
fields = ('name', 're_name', 'price', 'img', 'publish', 'publish_info', 'authors', 'authors_info')
list_serializer_class = BookListSerializer
extra_kwargs = {
'name':{
'min_length': 3,
'error_messages': {
'min_length': '太短了',
'required': '不能为空'
}
},
# 有默认值的字段会默认required为False,在反序列化中如果不传值不会进行校验,但是如果传值就会进行校验
'publish':{
'required': True,
'write_only': True,
'error_messages':{
'required': '不能为空'
}
},
'authors':{
'required': True,
'write_only': True,
'error_messages':{
'required': '不能为空'
}
},
}
# 自定义校验规则
# 局部钩子
def validate_name(self, value):
if 'sb' in value:
raise serializers.ValidationError('书名包含敏感词汇')
return value
# 全局钩子
def validate(self, attr):
name = attr.get('name')
re_name = attr.get('re_name')
publish = attr.get('publish')
if name != re_name:
raise serializers.ValidationError(
{'re_name': '两次书名不一致'}
)
# 通过逻辑控制联合唯一
if models.Book.objects.filter(name=name, publish=publish):
raise serializers.ValidationError(
{'book': '书籍已存在'}
)
return attr
基本视图(views)
主要就是通过视图类APIView,里面的逻辑都需要自己去实现,需要自己写接口
子路由 api/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^books/$', views.BookAPIView.as_view()),
url(r'^books/(?P<pk>.*)/$', views.BookAPIView.as_view()),
]
视图层 api/views.py
from rest_framework import views
from . import models, serializers
from utils.response import APIResponse
class BookAPIView(views.APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
# 单取
if pk:
book_obj = models.Book.objects.filter(is_delete=False, pk=pk).first()
if not pk:
return APIResponse(1, 'pk有误')
book_ser = serializers.BookV2ModelSerializer(book_obj)
return APIResponse(0, 'ok', results=book_ser.data)
# 群取
book_obj_list = models.Book.objects.filter(is_delete=False).all()
if not book_obj_list:
return APIResponse(1, '没有数据')
book_ser = serializers.BookV2ModelSerializer(book_obj_list, many=True)
return APIResponse(0, 'ok', results=book_ser.data)
# 增加
def post(self, request, *args, **kwargs):
# 把单增也转换为群增
request_data = request.data
if isinstance(request_data, dict):
data = [request_data, ]
elif isinstance(request, list):
data = request_data
else:
return APIView(1, '数据格式有误')
book_ser = serializers.BookV2ModelSerializer(data=data, many=True)
if book_ser.is_valid():
book_obj_list = book_ser.save()
results = serializers.BookV2ModelSerializer(book_obj_list, many=True).data
return APIResponse(0, 'ok', results=results)
else:
return APIResponse(1, '添加失败', results=book_ser.errors)
5个视图工具类(mixins)
RetrieveModelMixin
:retrieve方法实现了获取一个对象ListModelMixin
:list方法实现了获取多个对象CreateModelMixin
:create方法实现了增加一个对象UpdateModelMixin
:update方法实现了单整体更新,partial_udate实现了单局部更新DestroyModelMixin
:destory方法实现了单独删除
一般结合generics工具视图使用
工具视图(generics)
GenericAPIView
GenericAPIView
,是generics家族的基类,主要帮我们把qureyset
和serializer_class
封装成了属性,提供了以下三种方法:
get_qureyset():
获取多个对象get_object():获取一个对象
get_serializer(*args, **kwargs):
获取序列化后的对象
GenericAPIView
的9个子类们
继承了mixins
中多个功能类和GenericAPIView
基类
类名 | 实现接口 |
---|---|
CreateAPIView |
单增(post) |
ListAPIView |
多取(get) |
RetrieveAPIView |
单取(get) |
DestroyAPIView |
单删(delete) |
UpdateAPIView |
单局部(patch)及整体(put)修改 |
ListCreateAPIView |
多取(get)和多增(post) |
RetrieveUpdateAPIView |
单取(get),单局部(patch)及整体(put)修改 |
RetrieveDestroyAPIView |
单取(get)和单删(delete) |
RetrieveUpdateDestroyAPIView |
单取(get),单局部(patch)及整体(put)修改和单删(delete) |
子路由 api/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
# generics
url(r'^v1/books/$', views.BookGenericAPIView.as_view()),
url(r'^v1/books/(?P<pk>.*)/$', views.BookGenericAPIView.as_view()),
# mixins + generics
url(r'^v2/books/$', views.BookMixinsGenericAPIView.as_view()),
url(r'^v2/books/(?P<pk>.*)/$', views.BookMixinsGenericAPIView.as_view()),
# 系统整合mixins、generics
url(r'^v3/books/$', views.BookRetrieveUpdateAPIView.as_view()),
url(r'^v3/books/(?P<pk>.*)/$', views.BookRetrieveUpdateAPIView.as_view()),
]
视图层 api/views.py
from rest_framework import generics, mixins
from . import models, serializers
from utils.response import APIResponse
# v1 generics - 视图基类
class BookGenericAPIView(generics.GenericAPIView):
queryset = models.Book.objects.filter(is_delete=False).order_by('-id')
serializer_class = serializers.BookModelSerializer
def get(self, request, *args, **kwargs):
if 'pk' in kwargs:
book_obj = self.get_object()
book_ser = self.get_serializer(book_obj)
return APIResponse(0, 'ok', results=book_ser.data)
book_query = self.get_queryset()
book_ser = self.get_serializer(book_query, many=True)
return APIResponse(0, 'ok', results=book_ser.data)
def post(self, request, *args, **kwargs):
book_ser = self.get_serializer(data=request.data)
book_ser.is_valid(raise_exception=True)
book_obj = book_ser.save()
return APIResponse(0, 'ok', results=self.get_serializer(book_obj).data)
# v2 mixins工具集 + generics视图基类
class BookMixinsGenericAPIView(mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
queryset = models.Book.objects.filter(is_delete=False).order_by('-id')
serializer_class = serializers.BookModelSerializer
def get(self, request, *args, **kwargs):
if 'pk' in kwargs:
response = self.retrieve(request, *args, **kwargs)
else:
response = self.list(request, *args, **kwargs)
return APIResponse(0, 'ok', results=response.data)
def post(self, request, *args, **kwargs):
response = self.create(request, *args, **kwargs)
return APIResponse(0, 'ok', results=response.data)
# v3 视图基类子类 - 工具视图类
class BookRetrieveUpdateAPIView(generics.RetrieveUpdateAPIView):
queryset = models.Book.objects.filter(is_delete=False).order_by('-id')
serializer_class = serializers.BookModelSerializer
视图集(viewsets)
ViewSetMixin
:视图集工具,重写了as_view方法, 完成了请求方式到视图类中的方法的映射
例如:.as_view({'get': 'retrieve', 'delete': 'remove_obj'})
, 表示当请求方式为get的时候,则会调用retrieve函数,当请求方式为delete的时候,则会调用remove_obj函数
GenericViewSet
:与模型类有关的接口视图集 - 可以从mixins那继承功能,也可以自定义功能
ViewSet
:与模型类无关或不是标准模型类接口 - 一般都是自定义功能
子路由 api/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^v4/books/$', views.BookGenericViewSet.as_view({
'get': 'list',
'post': 'create'
})),
url(r'^v4/books/(?P<pk>.*)/$', views.BookGenericViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'remove_book'
})),
]
视图层 api/views.py
from rest_framework import mixins, viewsets
from . import models, serializers
from utils.response import APIResponse
class BookGenericViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
queryset = models.Book.objects.filter(is_delete=False).order_by('-id')
serializer_class = serializers.BookModelSerializer
def remove_book(self, request, *args, **kwargs):
pk = kwargs.get('pk')
try:
book_obj = models.Book.objects.get(is_delete=False, pk=pk)
book_obj.is_delete = True
book_obj.save()
return APIResponse(0, '删除成功')
except:
return APIResponse(1, '删除失败')
分类: django