源码版本:H版

一、简要回顾

对于请求:

地址:/dashboard/admin/instances/

方式:POST
参数:
instances_filter_q:
action:instances__soft_reboot__89a8849b-a3cd-4ce0-9158-c3dd69e8508e

URL绑定为:

openstack_dashboard/dashboards/admin/instances/urls.py

二、目录结构

三、请求的响应

  接下来主要分析如下代码:

openstack_dashboard/dashboards/admin/instances/views.py

views.AdminIndexView.as_view()

  先从Django的generic view说起.... generic view中的as_view()可以返回一个Django的view函数,该view函数会构造出类实例,将as_view()中传入的参数设置为该实例的属性,然后调用dispatch函数,dispatch函数通常会将request请求转给类中的post或get函数。generic view的主要使用方法是用子类重写其中的属性或方法。详细情况可以参考Django官方文档:https://docs.djangoproject.com/en/1.7/topics/class-based-views/。对Django框架的深入了解对于理解Horizon十分必要,as_view函数最终达到的效果还是将处理逻辑放入post函数或get函数中,这点和其他网络框架类似。

分析AdminIndexView.as_view(),由于请求的方式为POST,其会调用该类的post函数。先看看AdminIndexView类中的属性设置如下:

openstack_dashboard/dashboards/admin/instances/views.py

class AdminIndexView(tables.DataTableView):
table_class = project_tables.AdminInstancesTable
  template_name = 'admin/instances/index.html'

  由于AdminIndexView -> DataTableView -> MultiTableView,类关系如下图所示。追踪到MultiTableView.post,该post函数会调用该类的get函数。

1、  DataTableView、DataTable、Action三者的说明

  这里为了后面分析的方便,先对DataTableView、DataTable、Action进行一番说明,如下:

(参考:http://docs.openstack.org/developer/horizon/topics/tables.html

1)DataTableView簇有如下属性:

_data={

表名:data(通过get_data函数获得)

...

}

_tables={

表名:table实例

}

table=table实例

说明:本例中data为一个包含instance的list

  DataTableView可以通过table_class绑定具体的DataTable,通过get_data函数获取data,该函数通常调用openstack_dashboard/api模块获取数据,最后,DataTableView通过handle_table函数负责将data和table挂钩,或者处理table行为。DataTableView正如其名字一样,拥有table和data,负责处理data的获取,Table的创建,以及二者的绑定等。

2)DataTable簇:

  DataTable规定了table的column和action,可以处理和table绑定的data,take_action函数负责处理action。以AdminInstanceTable的创建过程为例,其中使用了metaclass对DataTable及其子类进行修改,具体解释如下:

  先观察AdminInstancesTable类和DataTableOptions类:

class AdminInstancesTable(tables.DataTable):
...
class Meta:
name = "instances"
verbose_name = _("Instances")
status_columns = ["status", "task"]
table_actions = (project_tables.TerminateInstance,
AdminInstanceFilterAction)
row_class = AdminUpdateRow
row_actions = (project_tables.ConfirmResize,
project_tables.RevertResize,
AdminEditInstance,
project_tables.ConsoleLink,
project_tables.LogLink,
project_tables.CreateSnapshot,
project_tables.TogglePause,
project_tables.ToggleSuspend,
MigrateInstance,
project_tables.SoftRebootInstance,
project_tables.RebootInstance,
project_tables.TerminateInstance)
class DataTableOptions(object):
def __init__(self, options):
self.name = getattr(options, 'name', self.__class__.__name__)
verbose_name = getattr(options, 'verbose_name', None) \
or self.name.title()
self.verbose_name = verbose_name
self.columns = getattr(options, 'columns', None)
self.status_columns = getattr(options, 'status_columns', [])
self.table_actions = getattr(options, 'table_actions', [])
self.row_actions = getattr(options, 'row_actions', [])
self.row_class = getattr(options, 'row_class', Row)
self.column_class = getattr(options, 'column_class', Column)
self.pagination_param = getattr(options, 'pagination_param', 'marker')
...

  接着分析metaclass对类的修改...

class DataTable(object):
  __metaclass__ = DataTableMetaclass
class DataTableMetaclass(type):
  def __new__(mcs, name, bases, attrs):
    # Process options from Meta
    class_name = name
    """将类中的Meta转变为DataTableOptions,添加为类的_meta属性"""
    attrs["_meta"] = opts = DataTableOptions(attrs.get("Meta", None))
    # Gather columns; this prevents the column from being an attribute
    # on the DataTable class and avoids naming conflicts.
    """将类中的column属性聚集作为新的列属性,阻止其作为类属性"""
    columns = []
    for attr_name, obj in attrs.items():
      if issubclass(type(obj), (opts.column_class, Column)):
        column_instance = attrs.pop(attr_name)
        column_instance.name = attr_name
        column_instance.classes.append('normal_column')
        columns.append((attr_name, column_instance))
    columns.sort(key=lambda x: x[1].creation_counter)     # Iterate in reverse to preserve final order
    for base in bases[::-1]:
      if hasattr(base, 'base_columns'):
        columns = base.base_columns.items() + columns
    attrs['base_columns'] = SortedDict(columns)     ...     """收集row_action和table_action对象"""
    actions = list(set(opts.row_actions) | set(opts.table_actions))
    actions.sort(key=attrgetter('name'))
    actions_dict = SortedDict([(action.name, action())
for action in actions])
    attrs['base_actions'] = actions_dict
    if opts._filter_action:
      # Replace our filter action with the instantiated version
      opts._filter_action = actions_dict[opts._filter_action.name]     # Create our new class!
    return type.__new__(mcs, name, bases, attrs)

  总结概况如下图:

说明:使用metaclass对类进行修改,这样极大地增加了程序的可扩展性和灵活性,但同时复杂度也增大。metaclass的理解可以参考:

http://blog.csdn.net/psh2009/article/details/10330747

http://jianpx.iteye.com/blog/908121

3)Action簇:

  利用action函数进行处理

  继续分析MultiTableView类的get函数,如下:

horizon/tables/views.py

MultiTableView类:
def get(self, request, *args, **kwargs):
  handled = self.construct_tables()
  if handled:
    return handled
  """如果handled不为空则表明只是处理table,无需再次用table渲染模板并返回;否则的话就需要渲染模板。具体渲染操作如下"""
  context = self.get_context_data(**kwargs)
  return self.render_to_response(context) def construct_tables(self):
  """根据类中的table_class属性绑定的DataTable类,创建或返回DataTable对象,此处为AdminInstancesTable对象 """
  tables = self.get_tables().values()
  # Early out before data is loaded
  for table in tables:
    """如果当前请求需要预处理或者是AJAX更新操作,将在如下函数中进行,特别注意,此处正是AJAX发送行更新请求的响应处"""
    preempted = table.maybe_preempt()
    if preempted:
      return preempted
  # Load data into each table and check for action handlers
  for table in tables:
    handled = self.handle_table(table)
    if handled:
      return handled
  return None
MultiTableMixin类:
def handle_table(self, table):
  name = table.name
  """获取数据,此处暂且不深入分析"""
  data = self._get_data_dict()
  """获取与该DataTable相关的数据,并将数据和该DataTable挂钩"""
  self._tables[name].data = data[table._meta.name]
  """有关翻页的设置,此处暂且不管"""
  self._tables[name]._meta.has_more_data = self.has_more_data(table)
  """此处为调用AdminInstancesTable.maybe_handle函数"""
  handled = self._tables[name].maybe_handle()
  return handled

horizon/tables/base.py

DataTable类:
def maybe_handle(self):
  """
  Determine whether the request should be handled by any action on this
  table after data has been loaded.
  """
  request = self.request
  """获取request中的数据,这里为
  table_name=’instances’
  action_name=’soft_reboot’
  obj_id=’89a8849b-a3cd-4ce0-9158-c3dd69e8508e’
  """
  table_name, action_name, obj_id = self.check_handler(request)
  if table_name == self.name and action_name:
    action_names = [action.name for action in
      self.base_actions.values() if not action.preempt]
    # do not run preemptive actions here
    if action_name in action_names:
      return self.take_action(action_name, obj_id)
  return None

  为了后面的继续分析,先看Action簇的类关系如下:

  继续分析take_action函数...

horizon/tables/base.py

DataTable类:
"""
action_name=’soft_reboot’
obj_id=’89a8849b-a3cd-4ce0-9158-c3dd69e8508e’
"""
def take_action(self, action_name, obj_id=None, obj_ids=None):
  obj_ids = obj_ids or self.request.POST.getlist('object_ids')
  """得到SoftRebootInstance实例"""
  action = self.base_actions.get(action_name, None)
  if not action or action.method != self.request.method:
    return None   if not action.requires_input or obj_id or obj_ids:
    if obj_id:
      obj_id = self.sanitize_id(obj_id)
    if obj_ids:
      obj_ids = [self.sanitize_id(i) for i in obj_ids]
    """SoftRebootInstance->RebootInstance->BatchAction->Action,由于BatchAction有handle函数,所以在Action的__init__()中将属性handles_multiple设置为True
    """
    if not action.handles_multiple:
      response = action.single(self, self.request, obj_id)
    else:#进入此项
      if obj_id:
        obj_ids = [obj_id]
      response = action.multiple(self, self.request, obj_ids)
    return response
  elif action and action.requires_input and not (obj_id or obj_ids):
    messages.info(self.request,
_("Please select a row before taking that action."))
  return None

  注意,这里使用了一个trick,如下:

horizon/tables/actions.py

Action类:
def __init__(...):
  ...
  if not has_multiple and self.handles_multiple:
    def multiple(self, data_table, request, object_ids):
      return self.handle(data_table, request, object_ids)
    """为该实例动态绑定multiple方法,其实质为调用handle方法"""
    self.multiple = new.instancemethod(multiple, self)

  所以,接下来分析BatchAction中的handle函数...

horizon/tables/actions.py

  BatchAction类:
def handle(self, table, request, obj_ids):
action_success = []
action_failure = []
action_not_allowed = []
for datum_id in obj_ids:
datum = table.get_object_by_id(datum_id)
datum_display = table.get_object_display(datum) or _("N/A")
if not table._filter_action(self, request, datum):
action_not_allowed.append(datum_display)
LOG.info('Permission denied to %s: "%s"' %
(self._conjugate(past=True).lower(), datum_display))
continue
try:
self.action(request, datum_id)
self.update(request, datum)
action_success.append(datum_display)
self.success_ids.append(datum_id)
LOG.info('%s: "%s"' %
(self._conjugate(past=True), datum_display))
except Exception as ex:
if getattr(ex, "_safe_message", None):
ignore = False
else:
ignore = True
action_failure.append(datum_display)
exceptions.handle(request, ignore=ignore) ...
return shortcuts.redirect(self.get_success_url(request))

openstack_dashboard/dashboards/project/instances/tables.py

SoftRebootInstance类:
class SoftRebootInstance(RebootInstance):
name = "soft_reboot"
action_present = _("Soft Reboot")
action_past = _("Soft Rebooted") def action(self, request, obj_id):
api.nova.server_reboot(request, obj_id, soft_reboot=True)

  在此总结一下,处理的流程大概是DataTableView首先获取Data和Table,然后将Data和Table绑定,如果有对Table的处理则调用Table的函数进行处理,通常最终会落实到Table中Row所对应的Action。补充一下关于返回Table的渲染,首先在template中使用Table对象进行模板渲染,然后Table使用Row进行渲染,Row使用Cell进行渲染,和表格的形式一致。在Row的构造中会绑定Ajax信息,用来对Row进行轮询更新。

四、workflows处理流程

  一般Dashboard都不只包含DataTableView,还有很多其他View类,其中WorkflowView比较常见。这里简单说明一下,主要以POST请求为例。经过对DataTableView的分析,很容易明白WorkflowView的处理流程,主要见下图。其中普遍存在用类属性来表明绑定关系的特点,所以图中上面一排的虚线表示类的相互绑定关系,下面的虚线则表明类的调用关系。注意Workflow的finalize函数会先依次调用各个Step的Action的handle方法,然后会调用自己的handle方法做最后的处理!更加详细的说明可以参考:http://docs.openstack.org/developer/horizon/ref/workflows.html

参考文档:

http://docs.openstack.org/developer/horizon/

horizon源码分析(二)的更多相关文章

  1. horizon源码分析(一)

    源码版本:H版 一.写在前面 本来应该搭建horizon的development环境的,这样方便debug,但是由于各种报错,本人没有搭建成功,这也导致有很多源码疑问没有解决,后续可以继续补充这一部分 ...

  2. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  3. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  4. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  5. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  6. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  7. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

随机推荐

  1. CSS Grid布局指南

    简介 CSS Grid布局 (又名"网格"),是一个基于二维网格布局的系统,主要目的是改变我们基于网格设计的用户接口方式.如我们所知,CSS 总是用于网页的样式设置,但它并没有起到 ...

  2. 第四周作业——C语言自评

    1.你对自己的未来有什么规划?做了哪些准备?以目前的现状来说,希望至少能够掌握专业所要求的基本操作,然后一步步去深入.提升,毕业之后不会灰溜溜的一次次求职失败.目前更多的是利用闲暇时间补回过去老师同学 ...

  3. influxdb 命令

    写入数据: curl -X POST -d '[{"name":"foo","columns":["val"],&quo ...

  4. jstack笔记

    遇到java程序跑不动怎么办,jstack是比较容易想到的一个工具,利用jstack来dump出一个线程堆栈快照,然后具体分析. 一般的堆栈大概是由下面的部分组成的: "resin-2212 ...

  5. Django之contenttypes的应用

    Django contenttypes 应用 简介 contenttypes 是Django内置的一个应用,可以追踪项目中所有app和model的对应关系,并记录在ContentType表中. 每当我 ...

  6. PHP中与类有关的几个魔术常量

    与类有关的魔术常量: 以前学过的魔术常量: __FILE__ __DIR__ __LINE__ 现在: __CLASS__: 代表当前其所在的类的类名: __METHOD__:代表其当前所在的方法名:

  7. set集合,深浅拷贝以及部分知识点补充

    目录: 1.基础数据类型补充 2.set集合 3.深浅拷贝 一,基础数据类型补充 字符串的基本操作 li = ["李李嘉诚", "麻花藤", "⻩黄海 ...

  8. 【.Net】win10 uwp unix timestamp 时间戳 转 DateTime

    有时候需要把网络的 unix timestamp 转为 C# 的 DateTime ,在 UWP 可以如何转换? 转换函数可以使用下面的代码 private static DateTime UnixT ...

  9. 【Django】Django迁移数据库

    我们已经编写了博客数据库模型的代码,但那还只是 Python 代码而已,Django 还没有把它翻译成数据库语言,因此实际上这些数据库表还没有真正的在数据库中创建 为了让 Django 完成翻译,创建 ...

  10. hdu 6434 Count (欧拉函数)

    题目链接 Problem Description Multiple query, for each n, you need to get $$$$$$ \sum_{i=1}^{n} \sum_{j=1 ...