restframework是django对restful架构扩展的第三方库
对应的中文是rest式的;Restful web service是一种常见的rest的应用,是遵守了rest风格的web服务,rest式的web服务是一种ROA(面向资源架构)
http方法 | 资源操作 | 幂等 | 安全 |
---|---|---|---|
GET | SELECT | 是 | 是 |
POST | INSERT | 否 | 否 |
PUT | UPDATE | 是 | 否 |
DELETE | DELETE | 是 | 否 |
注释: 幂等性:对同一个接口的多次访问,得到的资源状态是相同的。
安全性:对该REST 接口访问,不会使服务端资源状态发生改变
urls.py
urlpatterns = [
url(r'^books/$', views.BookViewSet.as_view({"get":"list","post":"create"}),name="book_list"),
url(r'^books/(?P\d+)$' , views.BookViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
}),name="book_detail"),
]
view.py
class BookSerializers(serializers.ModelSerializer):
class Meta:
model=Book
fields="__all__"
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializers
补充一个urls.py的另一种写法
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'books', views.BookViewSet)
urlpatterns = [
url(r'', include(router.urls)),
]
其实这种写法是官网给出的方法,第一种方法仅便于理解其执行了什么。
其实很容易看出来 restframework
是通过 CBV 的形式来实现的。 CBV 该如何入手呢?
思考:
django的执行顺序:
请求从客户端发出: 通过Django的WSGI 依次经过中间件 process_request 到 拿到视图 又通过process_view 到 url路由分发,到视图 在视图中获取数据渲染页面 又返回到 process_response 到 WSGI 最终返回给客户端
我们并没有设计到中间件部分,所以跳过中间件内容 直接到url的路由分发
所以可以得出应该从url路由分发开始入手
我们先看一些简单的方式
urlpatterns = [
url(r'^books/$', views.BookViewSet.as_view({"get":"list","post":"create"}),name="book_list"),
url(r'^books/(?P\d+)$' , views.BookViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
}),name="book_detail"),
]
as_view()
方法
如果找一个方法 一定要记住self是谁,这个方法是谁调用的,子类没有找父类
我们找到了自己写的这个类,我们并没有写as_view()方法 所以继续找它的继承类 ModelViewSet
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializers
ModelViewSet
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
没有发现as_view() 继续找
GenericViewSet
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass
ViewSetMinxin
终于我们找到了 as_view()方法 大体看一下
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
...
def view(request, *args, **kwargs):
self = cls(**initkwargs)
# We also store the mapping of request methods to actions,
# so that we can later set the action attribute.
# eg. `self.action = 'list'` on an incoming GET request.
self.action_map = actions
# Bind methods to actions
# This is the bit that's different to a standard view
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
# And continue as usual
return self.dispatch(request, *args, **kwargs)
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
# We need to set these on the view function, so that breadcrumb
# generation can pick out these bits of information from a
# resolved URL.
view.cls = cls
view.initkwargs = initkwargs
view.suffix = initkwargs.get('suffix', None)
view.actions = actions
return csrf_exempt(view)
其实找as_view()方法 我们主要看的内容是view()方法
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.action_map = actions
# 这里注意这个actions是什么 def as_view(cls, actions=None, **initkwargs):
# actions = {"get":"list","post":"create"}
for method, action in actions.items():
handler = getattr(self, action)
# handler = self.list ; self.create ===> 这里是将 getattr 写开后的样子 便于理解
# mixins.CreateModelMixin,
# mixins.RetrieveModelMixin,
# mixins.UpdateModelMixin,
# mixins.DestroyModelMixin,
# mixins.ListModelMixin,
# 最终可以从上面的这5个类中找到对应的方法, 可以自己看下
setattr(self, method, handler)
# setattr ===> self.get = list; self.post = create
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
# And continue as usual
return self.dispatch(request, *args, **kwargs) # 最终调用了self.dispatch()方法, 接下来找dispatch方法
ModelViewSet->GenericViewSet->GenericAPIView->APIView
APIView dispatch()
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
# 这里 是对request 做了一步封装, 后面说
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 这里进行 权限组件 认证组件 访问频率组件的验证
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
# request.method.lower() ==> [get, post, put, delete...请求方式]
# 请注意!!! 这里的handler是什么
# 在之前的view方法中 => self.get self.post 的值已经被替换成了 list create等方法
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
# response 就是self.list()的返回结果。具体self.list是怎么执行的 后面会说
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
ok到这里,已经拿到了response 思考一个问题:dispatch 是谁调用的, 最终返回到哪里
dispatch 是 view调用的 view是 as_view调用的 as_view 就是我们url路由分配的时候调用的方法
刚刚有个self.list没说,这个list是什么呢?
要记住self是谁
我们来找self.list()
从BookViewSet
开始找,没找到就找父类
ModelViewSet
看这个类的注释: 这是restframework 给出的注释,发现list方法了么
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
ListModelMixin
在说list之前先记住我们自己写的BookViewSet类
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializers
class ListModelMixin(object):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# 这里的queryset 其实就是我们在BookViewSet中写的那个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)
# 从page = 开始 这里是分页的内容, 后面会说
serializer = self.get_serializer(queryset, many=True)
"""
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
# get_serializer_class() == > return self.serializer_class == > 也就是我们自己写的serializer_class
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs) # 最终返回一个BookSerializers 的实例对象
serializer = BookSerializers(*args, **kwargs)
"""
return Response(serializer.data) # 返回了Response(serializer.data) 并最终给了as_view方法
其他的create 、delete方法都类似,这里就不进行赘述了
还记得url的另一种官方文档中的写法么?
其实仅仅是一种封装,使用起来更加简便
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'books', views.BookViewSet)
urlpatterns = [
url(r'', include(router.urls)),
]
首先我们先从DefaultRouter入手
DefaultRouter
class DefaultRouter(SimpleRouter):
"""
The default router extends the SimpleRouter, but also adds in a default
API root view, and adds format suffix patterns to the URLs.
"""
include_root_view = True
include_format_suffixes = True
root_view_name = 'api-root'
default_schema_renderers = None
APIRootView = APIRootView
APISchemaView = SchemaView
SchemaGenerator = SchemaGenerator
def __init__(self, *args, **kwargs):
if 'root_renderers' in kwargs:
self.root_renderers = kwargs.pop('root_renderers')
else:
self.root_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES)
# self.root_renderers = [
#'rest_framework.renderers.JSONRenderer',
# 'rest_framework.renderers.BrowsableAPIRenderer']
# 这个就是对restframework的一个初始化, 使用restframework时, 会有2种模式 一种是通过 浏览器访问的时候会得到一个html 页面 另一种是访问接口 得到一个Json数据
super(DefaultRouter, self).__init__(*args, **kwargs) # 执行了父类的 __init__
...
SimpleRoouter
def __init__(self, trailing_slash=True):
self.trailing_slash = trailing_slash and '/' or ''
# self.trailing_slash = '/'
super(SimpleRouter, self).__init__() # 执行了父类的 __init__
BaseRouter
class BaseRouter(object):
def __init__(self):
self.registry = [] # 初始化了一个东西
继续往下看
router.register(r'books', views.BookViewSet)
找register方法
class BaseRouter(object):
...
def register(self, prefix, viewset, base_name=None):
if base_name is None:
base_name = self.get_default_base_name(viewset)
"""
def get_default_base_name(self, viewset):
# viewset 是 views.BookViewSet
queryset = getattr(viewset, 'queryset', None)
# queryset = Book.objects.all()
assert queryset is not None, '`base_name` argument not specified, and could ' \
'not automatically determine the name from the viewset, as ' \
'it does not have a `.queryset` attribute.'
return queryset.model._meta.object_name.lower()
# 返回了model名字的小写
"""
# base_name = books
self.registry.append((prefix, viewset, base_name)) # 然后将(r'books', BookViewSet, 'books') 添加到self.registry
继续走
urlpatterns = [
url(r'', include(router.urls)),
]
# include 分发url 之类的是django自己做的,所以这里不作考虑
# 我们需要看的是 router.urls
DefaultRouter—>SimpleRouter->BaseRouter
BaseRouter
@property
def urls(self):
if not hasattr(self, '_urls'):
self._urls = self.get_urls()
return self._urls
其实就是执行了get_urls方法
DefaultRouter
# 这里执行的get_urls() 是 DefaultRouter 类下的方法
def get_urls(self):
urls = super(DefaultRouter, self).get_urls()
# 执行父类的get_urls() 父类的代码紧接下面
if self.include_root_view:
view = self.get_api_root_view(api_urls=urls)
root_url = url(r'^$', view, name=self.root_view_name)
urls.append(root_url)
if self.include_format_suffixes:
urls = format_suffix_patterns(urls)
return urls
SimpleRouter
def get_urls(self):
"""
Use the registered viewsets to generate a list of URL patterns.
"""
ret = []
for prefix, viewset, basename in self.registry:
# 还记得 self.registry 么
# (r'books', BookViewSet, 'books')
lookup = self.get_lookup_regex(viewset)
# lookup 是一个字符串的拼接样式
routes = self.get_routes(viewset)
# 开始拼接url并保存到ret中
for route in routes:
# Only actions which actually exist on the viewset will be bound
mapping = self.get_method_map(viewset, route.mapping)
if not mapping:
continue
# Build the url pattern
regex = route.url.format(
prefix=prefix,
lookup=lookup,
trailing_slash=self.trailing_slash
)
# If there is no prefix, the first part of the url is probably
# controlled by project's urls.py and the router is in an app,
# so a slash in the beginning will (A) cause Django to give
# warnings and (B) generate URLS that will require using '//'.
if not prefix and regex[:2] == '^/':
regex = '^' + regex[2:]
initkwargs = route.initkwargs.copy()
initkwargs.update({
'basename': basename,
})
view = viewset.as_view(mapping, **initkwargs)
name = route.name.format(basename=basename)
ret.append(url(regex, view, name=name))
# 最终ret = [, [^/.]+)/$>]
return ret
# 所以urls方法 得到的 [, [^/.]+)/$>] 这个列表 并放到 include中被执行