drf视图层
介绍
本章主要讲解了视图层常见的一些视图类
- 两个视图基类
- APIView和GenericAPIView
- 5个视图扩展类
- ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestroyModelMixin
- 9个视图子类
- ListAPIView、CreateAPIView、ListCreateAPIView、RetrieveAPIView、DestroyAPIView、UpdateAPIView、RetrieveUpdateAPIView、RetrieveDestroyAPIView、RetrieveUpdateDestroyAPIView
- 视图集(viewsets)
- ModelViewSet、ReadOnlyModelViewSet、ViewSetMixin、ViewSet、GenericViewSet
环境准备
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('book/', views.BookView.as_view()),
path('book/<int:pk>/', views.BookDetailView.as_view()),
]
models.py
from django.db import models
# Create your models here.
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
# 图书与出版社外键字段,一对多
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
# 图书与作者外键字段,多对多
authors = models.ManyToManyField(to='Author')
@property
def publish_detail(self):
return {'name': self.publish.name, 'addr': self.publish.addr}
@property
def author_detail(self):
author_list = []
for author in self.authors.all():
author_list.append({'name': author.name, 'phone': author.phone})
return author_list
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
class Author(models.Model):
name = models.CharField(max_length=32)
phone = models.CharField(max_length=11)
serializer.py
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = ['name', 'price', 'publish', 'authors', 'publish_detail', 'author_detail']
read_only_fields = ['publish_detail', 'author_detail']
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True}
}
name = serializers.CharField(max_length=8, error_messages={'max_length': '不能超过8个字符'})
price = serializers.CharField(max_length=8)
基于APIView的增删改查五个接口
from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
class BookView(APIView):
# 查询所有
def get(self, request):
book_obj = models.Book.objects.all()
book_ser = BookModelSerializer(instance=book_obj, many=True)
return Response(book_ser.data)
# 新增一条数据
def post(self, request):
book_ser = BookModelSerializer(data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response({'code': 200, 'msg': '新增成功', 'result': book_ser.data})
else:
return Response({'code': 201, 'msg': book_ser.errors})
class BookDetailView(APIView):
# 查询一条数据
def get(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
book_ser = BookModelSerializer(instance=book_obj)
return Response(book_ser.data)
# 修改一条数据
def put(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
book_ser = BookModelSerializer(data=request.data, instance=book_obj)
if book_ser.is_valid():
book_ser.save()
return Response({'code': '200', 'msg': '修改成功', 'result': book_ser.data})
else:
return Response({'code': '201', 'msg': book_ser.errors})
# 删除一条数据
def delete(self, request, pk):
models.Book.objects.filter(pk=pk).delete()
return Response({''})
如果需要写多个表的接口,可以看到需要写非常多重复的代码,那么为了节省代码量,drf封装了一些方法
第一步封装-GenericAPIView
GenericAPIView中使用的一些方法
属性 | 说明 |
---|---|
queryset | 指定要序列化或反序列化的表模型数据 |
serializer_class | 指定使用的序列化类 |
lookup_field | 查询单条时指定传入的主键值的Key是什么,因为默认是PK,但路由中如果传入的键为ID时,则需要配置此变量为ID |
filter_backends | 过虑类的配置 |
pagination_class | 分页类的配置 |
方法 | 说明 |
---|---|
get_queryset | 获取要序列化的对象 |
get_object | 获取单个对象使用 |
get_serializer | 获取序列化类,而get_serializer_class方法,是在有需要的时候进行重新def定义使用的 |
filter_queryset | 过滤相关 |
使用GenericAPIView写的代码,将序列化类与表的关系写在的类的最前面,供下面的增删改查来使用,这时候,如果写多个表的接口,则只需要修改queryset和serializer_class就可以了,但代码并未节省太多。
from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
def get(self, request):
obj = self.get_queryset()
ser_obj = self.get_serializer(instance=obj, many=True)
return Response({'code': 200, 'msg': '查询成功', 'result': ser_obj.data})
def post(self, request):
obj = self.get_serializer(data=request.data)
if obj.is_valid():
obj.save()
return Response({'code': 200, 'msg': '新增成功', 'result': obj.data})
class BookDetailView(GenericAPIView):
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
def get(self, request, pk):
# get_object()实际上是通过get_queryset()方法获得一个QuerySet对象,然后通过该QuerySet对象调用filter()方法来获取符合条件的数据,最终获得一条数据并返回。
obj = self.get_object()
ser_obj = self.get_serializer(instance=obj)
return Response({'code': 200, 'msg': '查询成功', 'result': ser_obj.data})
def put(self, request, pk):
obj = self.get_object()
ser_obj = self.get_serializer(data=request.data, instance=obj)
if ser_obj.is_valid():
ser_obj.save()
return Response({'code': 200, 'msg': '修改', 'result': ser_obj.data})
else:
return Response({'code': '201', 'msg': ser_obj.errors})
def delete(self, request, pk):
self.get_object().delete()
return Response({''})
现在的问题是,每个都包含增删改查四个方法,那这四个方法是不是可以封装一下?
第二步封装-五个视图扩展类
使用了GenericAPIView后,发现每个视图类都有重复的get(所有数据)/get(单条数据)/post/put/delete五个方法,那么这五个方法是否可以使用class封装起来,这样在每个类使用的时候继承这五个封装后的类就可以了,这样就节省了写这五个方法的代码量了!
所以drf提供了五个视图扩展类,搭配GenericAPIView视图类使用,因为是搭配使用,所以在继承时需要继承两个类,不能单独使用扩展类。(相像一下,存储和存储扩展柜的关系)
名称 | 说明 | 方法 |
---|---|---|
ListModelMixin | 查询所有数据 | list |
RetrieveModelMixin | 查询单条数据 | retrieve |
CreateModelMixin | 新增一条数据 | create |
UpdateModelMixin | 修改一条数据 | update |
DestroyModelMixin | 删除一条数据 | destroy |
- 下面基于五个视图扩展类写五个接口
from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin, \
ListModelMixin
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class BookDetailView(GenericAPIView, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin):
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
# 这里因为前端要传入pk,所以这里直接使用*args接收,这前前端不管传来的键是PK还是ID还是别的都可以接收
def 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)
可以看到,代码的简洁程度更高了,但是,还是有很多重复代码,如:
获取所有与新增一条中的return内容是相同的。
获取单条、修改、删除中的return内容是相同的
是不是可以继续封装呢?
第三步封装-九个视图子类
九个视图子类封装了get/put/delete/create这些方法,所以在使用的时候代码会进一步减少
视图子类 | 说明 |
---|---|
ListAPIView | 查询所有数据 |
CreateAPIView | 新增一条数据 |
ListCreateAPIView | 查询所有数据+新增一条数据 |
RetrieveAPIView | 查询一条数据 |
DestroyAPIView | 删除数据 |
UpdateAPIView | 修改数据 |
RetrieveUpdateAPIView | 查询一条数据+修改数据 |
RetrieveDestroyAPIView | 查询一条数据+删除数据 |
RetrieveUpdateDestroyAPIView | 查询一条数据+修改数据+删除数据 |
- 使用九个视图子类写5个接口
from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.generics import ListAPIView, CreateAPIView, ListCreateAPIView, RetrieveAPIView, DestroyAPIView, UpdateAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView
class BookView(ListCreateAPIView):
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
现在已经简化成了两个视图类,但是现在还有一个方法可以做成一个视图类,那就是使用ModelViewSet
第四步封装-viewsets
viewsets下包含的几个方法
名称 | 关系 | 说明 | |
---|---|---|---|
ModelViewSet | 继承5个视图扩展类加ViewSetMixin加GenericAPIView | 与GenericViewSet不同的是,它提供了一些通用的视图方法,不可以自定义get/put等映射关系,而GenericViewSet则可以。可以参照下面的ModelViewSet中的对应put/get等关系对应表 | |
ReadOnlyModelViewSet | 继承2个视图扩展类加ViewSetMixin加GenericAPIView | 与ModelViewSet相同,但只提供查询接口 | |
ViewSetMixin | 重写了as_view方法,只要继承它,就需要改变路由的写法,对应映射关系 | ||
ViewSet | 继承了ViewSetMixin和APIView | 它继承的两个类都未定义与数据库交互的内容,如果未来的接口不需要与数据库交互,就继承这个类 | |
GenericViewSet | 继承了ViewSetMixin和GenericAPIView | 由于GenericAPIView定义了与数据库交互的方法,所以未来如果要跟数据库作交互就继承这个类 |
ModelViewSet
使用ModelViewSet必须在urls.py中定义方法的对应关系,否则会报错。这是因为ModelViewSet改写了五个方法。
- 语法如下:
path('book/', views.BookView.as_view({'get': 'list', 'post': 'create'})),
path('book/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
- 对应关系如下
名称 | 对应 | 说明 |
---|---|---|
get | list | 查询所有数据 |
post | create | 新增一条数据 |
get | retrieve | 查询一条数据 |
put | update | 修改一条数据 |
delete | destroy | 删除一条数据 |
- 使用ModelViewSet编写五个接口
urlpatterns = [
path('admin/', admin.site.urls),
path('book/', views.BookView.as_view({'get': 'list', 'post': 'create'})),
path('book/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
可以看到,一个类就解决了
ReadOnlyModelViewSet
ReadOnlyModelViewSet只包含了读取一条和读取所有数据两个功能,所以在路由中只能指定这两个功能,如下
urlpatterns = [
path('admin/', admin.site.urls),
# 因为ReadOnly只对应两个读方法,所以在路由里面只有两个只读的路由对应关系
path('book/', views.BookView.as_view({'get': 'list'})),
path('book/<int:pk>/', views.BookView.as_view({'get': 'retrieve'})),
]
as_view解析
ModelViewSet下的GenericViewSet下的ViewSetMixin中,找到as_view的源码,在as_view中找到了view方法,在此方法中改写了as_view。
所以,只要继承了ViewSetMixin类,路由写法就与传统写法不一样了,需要传入对应函数的字典
自定义增删改查名称
因为as_view被改写了,所以我们也可以自定义as_view中的字典的映射关系,只要继承了ViewSetMixin后,在urls.py中定义好字典即可。
urlpatterns = [
# 定义映射关系,比如将get定义为login
path('test/', views.Test.as_view({'get': 'login'}))
]
from rest_framework.viewsets import ModelViewSet,ViewSetMixin
from rest_framework.views import APIView
from rest_framework.response import Response
class Test(ViewSetMixin, APIView):
def login(self, request):
# 我是登录接口的逻辑
return Response('我是登录接口')
部分关系图
其它关系见下表
名称 | 关系 | 说明 | |
---|---|---|---|
ModelViewSet | 继承5个视图扩展类加ViewSetMixin加GenericAPIView | 与GenericViewSet不同的是,它提供了一些通用的视图方法,不可以自定义get/put等映射关系,而GenericViewSet则可以。可以参照下面的ModelViewSet中的对应put/get等关系对应表 | |
ReadOnlyModelViewSet | 继承2个视图扩展类加ViewSetMixin加GenericAPIView | 与ModelViewSet相同,但只提供查询接口 | |
ViewSetMixin | 重写了as_view方法,只要继承它,就需要改变路由的写法,对应映射关系 | ||
ViewSet | 继承了ViewSetMixin和APIView | 它继承的两个类都未定义与数据库交互的内容,如果未来的接口不需要与数据库交互,就继承这个类 | |
GenericViewSet | 继承了ViewSetMixin和GenericAPIView | 由于GenericAPIView定义了与数据库交互的方法,所以未来如果要跟数据库作交互就继承这个类 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类