基于 rest_framework 的 url 路由 跟 数据 跟 前端交互。
为了要让 django 支持 RESTful 风格 的 接口 需要 用到 第三方组件,来 分析下rest_framework 对 request 做了什么 ,又对 context 做了什么?
按照 惯例,先贴出源码
router = BulkRouter()
router.register(r\'v1/assets\', api.AssetViewSet, \'asset\')
router.register(r\'v1/admin-user\', api.AdminUserViewSet, \'admin-user\')
router.register(r\'v1/system-user\', api.SystemUserViewSet, \'system-user\')
router.register(r\'v1/labels\', api.LabelViewSet, \'label\')
router.register(r\'v1/nodes\', api.NodeViewSet, \'node\')
router.register(r\'v1/domain\', api.DomainViewSet, \'domain\')
router.register(r\'v1/gateway\', api.GatewayViewSet, \'gateway\')
jumpserver 大量使用了 这种形式 的 路由。但事实 what the fuck 基于类的视图为什么没有as_view() 方法? 其实as_view 方法 依然实现了,只不过又 封装了几层,更加抽象了一点。 这个 register 方法什么样,首先看源码。
class BaseRouter(six.with_metaclass(RenameRouterMethods)):
def __init__(self):
self.registry = []
def register(self, prefix, viewset, basename=None, base_name=None):
if base_name is not None:
msg = \"The `base_name` argument is pending deprecation in favor of `basename`.\"
warnings.warn(msg, PendingDeprecationWarning, 2)
assert not (basename and base_name), (
\"Do not provide both the `basename` and `base_name` arguments.\")
if basename is None:
basename = base_name
if basename is None:
basename = self.get_default_basename(viewset)
self.registry.append((prefix, viewset, basename))
其实register 方法 是调用了父类BaseRouter 的 register ,这个 方法 其实就是在内部 维持了一个 列表registry,这个 列表放了 prefix,viewset ,basename。接着往下看,
urlpatterns += router.urls
源码中用到了 router的urls 属性。
接下来 看 urls 属性应该怎么看?
@property
def urls(self):
if not hasattr(self, \'_urls\'):
self._urls = self.get_urls()
return self._urls
这个 urls 属性调用了 get_urls 方法。而 get_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:
lookup = self.get_lookup_regex(viewset)
routes = self.get_routes(viewset)
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,
\'detail\': route.detail,
})
view = viewset.as_view(mapping, **initkwargs)
name = route.name.format(basename=basename)
ret.append(url(regex, view, name=name))
return ret
这个 get_url 方法 就是 取出刚才 在 regester 方法中放进registry列表中 中的 东西,然后遍历 routes 列表,route列表存放了Route容器,源码如下。
outes = [
# List route.
Route(
url=r\'^{prefix}{trailing_slash}$\',
mapping={
\'get\': \'list\',
\'post\': \'create\'
},
name=\'{basename}-list\',
detail=False,
initkwargs={\'suffix\': \'List\'}
),
# Dynamically generated list routes. Generated using
# @action(detail=False) decorator on methods of the viewset.
DynamicRoute(
url=r\'^{prefix}/{url_path}{trailing_slash}$\',
name=\'{basename}-{url_name}\',
detail=False,
initkwargs={}
),
# Detail route.
Route(
url=r\'^{prefix}/{lookup}{trailing_slash}$\',
mapping={
\'get\': \'retrieve\',
\'put\': \'update\',
\'patch\': \'partial_update\',
\'delete\': \'destroy\'
},
name=\'{basename}-detail\',
detail=True,
initkwargs={\'suffix\': \'Instance\'}
),
# Dynamically generated detail routes. Generated using
# @action(detail=True) decorator on methods of the viewset.
DynamicRoute(
url=r\'^{prefix}/{lookup}/{url_path}{trailing_slash}$\',
name=\'{basename}-{url_name}\',
detail=True,
initkwargs={}
),
]
遍历route 列表,调用 get_method_map 这个方法,这个方法源码如下。
def get_method_map(self, viewset, method_map):
\"\"\"
Given a viewset, and a mapping of http methods to actions,
return a new mapping which only includes any mappings that
are actually implemented by the viewset.
\"\"\"
bound_methods = {}
for method, action in method_map.items():
if hasattr(viewset, action):
bound_methods[method] = action
return bound_methods
其实这个方法 就是 返回了一个 method 跟 action 的 映射,mapping。然后把这个mapping 作为参数 放进 视图类的 as_view 方法中。那 基于 rest_framework 的 as_view 跟普通 类视图 有什么不一样?
举个例子,jumpserver 中 常用的BulkModelViewSet 类,当中的as_view 方法,需要在父类中找,按照python 最新版,多继承的寻址顺序为 从左 至右,广度优先的原则。比如
class A (object):
pass
class B(A):
pass
class C(A):
pass
class E(B,C):
pass
if __name__ == \'__main__\':
print(E.__mro__)
就是 多继承 寻找方法顺序为 E >> B>>C>>A>>object.
class BulkModelViewSet(bulk_mixins.BulkCreateModelMixin,
bulk_mixins.BulkUpdateModelMixin,
bulk_mixins.BulkDestroyModelMixin,
ModelViewSet):
pass
最后在BulkModelViewSet 的 父类 ModelViewSet 的父类 GenericViewSet 的父类 ViewSetMixin 找到了 as_view 方法。
源码贴出 如下,这个方法 ,actions 就是刚才传进去的mapping 参数,然后dispaher 方法,ViewSetMixin 本身没有实现,老办法找 父类,发现,其实父类 使用得是 ajdango 得 base 类View dispath 方法,这里就不贴出了,然后调用视图类 经行映射得那个方法比如 get——list ,或者 post-create 方法。
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
\"\"\"
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
\"\"\"
# The name and description initkwargs may be explicitly overridden for
# certain route confiugurations. eg, names of extra actions.
cls.name = None
cls.description = None
# The suffix initkwarg is reserved for displaying the viewset type.
# This initkwarg should have no effect if the name is provided.
# eg. \'List\' or \'Instance\'.
cls.suffix = None
# The detail initkwarg is reserved for introspecting the viewset type.
cls.detail = None
# Setting a basename allows a view to reverse its action urls. This
# value is provided by the router through the initkwargs.
cls.basename = None
# actions must not be empty
if not actions:
raise TypeError(\"The `actions` argument must be provided when \"
\"calling `.as_view()` on a ViewSet. For example \"
\"`.as_view({\'get\': \'list\'})`\")
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(\"You tried to pass in the %s method name as a \"
\"keyword argument to %s(). Don\'t do that.\"
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError(\"%s() received an invalid keyword %r\" % (
cls.__name__, key))
# name and suffix are mutually exclusive
if \'name\' in initkwargs and \'suffix\' in initkwargs:
raise TypeError(\"%s() received both `name` and `suffix`, which are \"
\"mutually exclusive arguments.\" % (cls.__name__))
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.actions = actions
return csrf_exempt(view)
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。


