1.9个视图扩展类
1. 两个视图基类:APIView、GenricAPIView
2.5 个视图扩展类:CreateModelMixin,UpdateModelMixin,RetrieveModelMixin,ListModelMixin,DestroyModelMixin
3.9 个视图子类:CreateAPIView,DestroyAPIView,ListAPIView,RetrieveAPIView,UpdateAPIView,ListCreateAPIView,RetrieveDestroyAPIView,RetrieveUpdateAPIView,RetrieveUpdateDestroyAPIView
(没有RetrieveUpdateAPIView)
4. 利用9 个视图自类编写5 个接口(继承视图子类则不需要继承GenericAPIView):
views.py:
from rest_framework.generics import CreateAPIView,DestroyAPIView,ListAPIView,RetrieveAPIView,UpdateAPIView,ListCreateAPIView,RetrieveDestroyAPIView,RetrieveUpdateAPIView,RetrieveUpdateDestroyAPIView
class BookView (ListCreateAPIView ):
queryset = Book.objects.all ()
serializer_class = BookSerializer
class BookDetailView (RetrieveUpdateDestroyAPIView ):
queryset = Book.objects.all ()
serializer_class = BookSerializer
models.py:(和之前未变)
from django.db import models
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_list (self ):
l = []
for author_obj in self.authors.all ():
l.append({'name' :author_obj.name,'phone' :author_obj.phone})
return l
class Publish (models.Model):
name = models.CharField(max_length=32 )
addr = models.CharField(max_length=32 )
class Author (models.Model):
name = models.CharField(max_length=32 )
phone = models.CharField(max_length=11 )
serializer.py:
from rest_framework import serializers
from .models import Book
from rest_framework.exceptions import ValidationError
class BookSerializer (serializers.ModelSerializer):
class Meta :
model = Book
fields = ['name' ,'price' ,'publish' ,'authors' ,'publish_detail' ,'author_list' ]
extra_kwargs = {
'name' :{'max_length' :8 },
'price' :{'max_length' :8 },
'publish' :{'write_only' :True },
'authors' :{'write_only' :True },
'publish_detail' :{'read_only' :True },
'author_list' :{'read_only' :True }
}
def validate_name (self,name ):
book_obj = Book.objects.filter (name=name)
if book_obj:
raise ValidationError(f'书籍{name} 已存在' )
return name
"""
新需求:如果不需要全部接口功能,只需要查询所有以及删除一个,如何实现?
"""
我们只需要修改继承的类即可,查询所有是ListAPIView,删除一个是DestroyAPIView。serializer.py和models.py中代码未修改。
class BookView (ListAPIView ):
queryset = Book.objects.all ()
serializer_class = BookSerializer
class BookDetailView (DestroyAPIView ):
queryset = Book.objects.all ()
serializer_class = BookSerializer
2.视图集
2.1 通过ModelViewSet编写5个接口
使用ModelViewSet模块写接口,可以只写一个视图类,不用再写类BookDetailView,但是在路由层需要将两种类型分开,但是关联的视图类是同一个视图类,因此视图类中可以简写。
urls.py:
urlpatterns = [
path('admin/' , admin.site.urls),
path('books/' ,views.BookView.as_view({'get' :'list' ,'post' :'create' })),
path('books/<int:pk>/' ,views.BookView.as_view({'get' :'retrieve' ,'put' :'update' ,'delete' :'destroy' }))
]
views.py:
from rest_framework.viewsets import ModelViewSet
class BookView (ModelViewSet ):
queryset = Book.objects.all ()
serializer_class = BookSerializer
通过查看源码得知,ModelViewSet继承了类:CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin,.ListModelMixin。所以也可以找得到这些类中的方法list 、pos、get、update、delete。
"""
报错分析:该错误提示create()` must be implemented,原因是在进行创建图书操作时在序列化类中没有写create()方法,而继承的Serializer中也没有该方法所以会报错。解决方法是要么在序列化类中重写create()方法要么继承ModelSerializer,该类中有方法create()和update()。
"""
2.2 通过ReadOnlyModelViewSet编写2个只读接口
只读接口包括:读所有和只读一个。如果我们只想编写只读接口,我们可以用模块ReadOnlyModelViewSet。继承该模块之后只能编写2 个查询接口,编写其他接口会直接报错。
urls.py:
urlpatterns = [
path('admin/' , admin.site.urls),
path('books/' ,views.BookView.as_view({'get' :'list' })),
path('books/<int:pk>/' ,views.BookView.as_view({'get' :'retrieve' }))
]
views.py:
from rest_framework.viewsets import ReadOnlyModelViewSet
class BookView (ReadOnlyModelViewSet ):
queryset = Book.objects.all ()
serializer_class = BookSerializer
2.3 ViewSetMixin源码分析
1. 请求来时,路由匹配成功,会执行views.BookView.as_view({'get' :'list' ,'post' :'create' })(),我们需要找到as_view()方法。视图类BookView中没有,继续往上找:BookView>>>ModelViewSet>>>GenericViewSet>>>ViewSetMixin。
发现在ViewSetMixin中有as_view()方法。
2. as_view()源码如下:
@classonlymethod
def as_view (cls, actions=None , **initkwargs ):
...
if not actions:
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`" )
...
return csrf_exempt(view)
3. 查找view:
view查找顺序:BookView>>>ModelViewSet>>>GenericViewSet>>>ViewSetMixin>>>as_view()>>>view。
源码:
def view (request, *args, **kwargs ):
self = cls(**initkwargs)
...
for method, action in actions.items():
handler = getattr (self, action)
setattr (self, method, handler)
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
结论:
1. 只要继承了ViewSetMixin的视图类,路由写法就变了,需要上传actions参数(上传一个字典)。
2. 以{'get' : 'list' }为例,以后访问get就是访问list 。以后视图类中的方法名,可以任意命名,只需要在路由中做好映射即可。
2.4 from rest_framework.viewsets包下的类
ModelViewSet:5 个视图扩展类+GenericViewSet
ReadOnlyModelViewSet:2 个视图扩展类(mixins.RetrieveModelMixin, mixins.ListModelMixin)+GenericViewSet
ViewSetMixin:魔法,重新定义了as_view,以后继承它,路由写法就变成了映射的写法(继承ViewSetMixin类一定要将它放在第一个)
ViewSet:ViewSetMixin+APIView
GenericViewSet:ViewSetMixin+GenericAPIView
以后想继承APIView,但是想改变路由写法,就继承ViewSet。想继承GenericViewSet,但是想改变路由写法,就继承GenericViewSet。
3.视图类大总结
1. 两个视图基类:APIView、GenricAPIView
2.5 个视图扩展类:CreateModelMixin,UpdateModelMixin,RetrieveModelMixin,ListModelMixin,DestroyModelMixin
3.9 个视图子类:CreateAPIView,DestroyAPIView,ListAPIView,RetrieveAPIView,UpdateAPIView,ListCreateAPIView,RetrieveDestroyAPIView,RetrieveUpdateAPIView,RetrieveUpdateDestroyAPIView
(没有RetrieveUpdateAPIView)
4. 视图类:
ModelViewSet:路由写法变了,只需要写两行,5 个接口都有了
ReadOnlyModelViewSet:路由写法变了,只需要写两行,2 个只读接口都有了
ViewSetMixin:不是视图类,是魔法,路由写法变了,变成映射了
ViewSet:ViewSetMixin+APIView
GenericViewSet:ViewSetMixin+GenericAPIView
4.路由系统
4.1自动生成路由
1. 用SimpleRouter来写(较为常见):
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('books' ,views.BookView,'books' )
方式一:
urlpatterns += router.urls
方式二:
urlpatterns = [
path('admin/' , admin.site.urls),
path('api/v1/' ,include(router.urls))
]
"""
用方式二可以在路由中增加api表示和版本信息,此后访问的路有需要变成
http://127.0.0.1:8000/api/v1/books/
或:http://127.0.0.1:8000/api/v1/books/5/
"""
2. 用DefaultRouter来写:
from rest_framework.route rs import DefaultRouter
router = DefaultRouter()
router.register('books' ,views.BookView,'books' )
urlpatterns += router.urls
DefaultRouter和SimpleRouter的区别是:DefaultRouter会多附带一个默认的API根视图、返回一个包含所有列表视图超链接响应数据。
可以发现DefaultRouter的路由要比SimpleRouter多几个:
自动生成路由本质就是做映射,能够自动生成的前提是:视图类中要有5 个方法:
get>>>list
get>>>retrieve
put>>>update
post>>>create
delele>>>destroy
ModelViewSet,ReadOnlyModelViewSet可以自动生成
9 个视图子类+配合ViewSetMixin 才可以自动生成
5 个视图扩展类+GenericAPIView+ViewSetMixin(ViewSetMixin+GenericAPIView=GenericViewSet)
5.action装饰器(配合自动生成路由使用)
"""
当我们自动生成路由时,我们想在路由中通过get方法定义到自定义名字的一个方法名字。这时需要用到action装饰器。action可以控制提交方式映射的对象。
"""
使用步骤:
views.py:
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
class SendView (ViewSet ):
@action(methods=['GET' ],detail=False ,url_path='send_msg' ,url_name='send_msg' )
"""
methods:指定请求方式,结果是个列表,列表内要传提交方式的大写,可以传一个,也可以传多个。
detail:上传的结果是True或者False,False:不带id的路径:
http://127.0.0.1:8000/api/v1/send/send_msg/?phone=111
True:带id的路径:
http://127.0.0.1:8000/api/v1/send/1/send_msg/?phone=111(并且在send_msg中还要加一个参数pk,此时pk是1)
url_path:拼接的路径,可以改,当前路径为:http://127.0.0.1:8000/api/v1/send/send_msg/?phone=111
url_name:别名,反向解析用,一般和函数名保持一致
"""
def send_msg (self,request ):
phone = request.query_params.get('phone' )
print (f'成功向{phone} 发送信息' )
return Response({'code' :100 ,'msg' :'发送成功' })
urls.py:
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('send' ,views.SendView,'send' )
urlpatterns = [
path('admin/' , admin.site.urls),
path('api/v1/' ,include(router.urls))
]
补充思路:
不同的action使用不同的序列化类:
class SendView (ViewSet ):
queryset = None
serializer_class = '序列化类'
def get_serializer (self,*args,**kwargs ):
if self.action == 'send_msg' :
return '某个序列化类'
else :
return '某个序列化类'
@action(methods=['GET' ],detail=False ,url_path='send_www' ,url_name='send_msg' )
def send_msg (self,request ):
pass
@action(methods=['POST' ],detail=False ,url_path='phone_msg' ,url_name='phone_msg' )
def phone_msg (self,request ):
pass
6.登陆接口
编辑一个登陆接口,如果用户未登陆过第一次登陆就在UserToken中插一条随机字符串,登陆过再次登陆时就修改此条字符串:
urls.py:
router.register('user' ,views.UserView,'user' )
urlpatterns = [
path('admin/' , admin.site.urls),
path('api/v1/' ,include(router.urls))
]
models.py:
class User (models.Model):
username = models.CharField(max_length=32 )
password = models.CharFiel2023-02-13 10 :00 :02 星期一d(max_length=32 )
class UserToken (models.Model):
token = models.CharField(max_length=64 )
user = models.OneToOneField(to='User' ,on_delete=models.CASCADE)
views.py:
from .models import User,UserToken
import uuid
class UserView (ViewSet ):
@action(methods=['POST' ],detail=False )
def login (self,request ):
username = request.data.get('username' )
password = request.data.get('password' )
user_obj = User.objects.filter (username=username,password=password).first()
if user_obj:
token = str (uuid.uuid4())
UserToken.objects.update_or_create(user=user_obj,defaults={'token' :token})
return Response({'code' :100 ,'msg' :'登陆成功' ,'token' :token})
else :
return Response({'code' :100 ,'msg' :'用户名或密码错误' })
"""
update_or_create()里面有两个参数,第一个参数上市表中的某个字段,第二个参数是defaults,指定一个字典,第一个参数找到对象之后,将对象的指定参数修改为字典中的值;如果第一个参数找不到对象,那么就按照defaults中的属性新增一个对象。
Book.objects.update_or_create(name='西游记',defaults={'name':'新西游记','price':888.66})
如果表中有name字段为'西游记'的对象,将其修改成新西游记,并且修改price字段。如果没有就新增一个对象
"""
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)