源码版本: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. php的大小写敏感问题整理

    php的大小写敏感问题整理 今天在开发php的过程中,因为命名大小写的问题导致代码错误,所以从网上整理了php的大小写敏感的一些资料,需要的朋友可以参考下.   PHP对大小写敏感问题的处理比较乱,写 ...

  2. WIN 7 发布项目

    本人工作都快两年了不知道web 怎么发布 今天我自己非常恼火!我就暗暗研究.希望能给新手指导 准备工作:WIN7 系统 .IIS 6.0.MVC (vs 新建mvc项目自带的项目) OK begin ...

  3. tensorboard入门

    Tensorboard tensorboard用以图形化展示我们的代码结构和图形化训练误差等,辅助优化程序 tensorboard实际上是tensorflow机器学习框架下的一个工具,需要先安装ten ...

  4. 关于String和StringBuffer的原理

    public class Foo {2.   public static void main (String [] args)  {3.      StringBuffer a = new Strin ...

  5. 整理sql server数据类型

    我们在平常开发过程中,在设计数据的时候,经常碰到数据类型选择的问题,为了更快,更合适地选择正确的数据类型,所以在这里做个总结. 分类 sql server 数据类型 c# 数据类型 描述 应用场景 字 ...

  6. HDU 2162 Add ‘em

    http://acm.hdu.edu.cn/showproblem.php?pid=2162 Problem Description Write a program to determine the ...

  7. 因为NLS_LANG 造成 Oracle数据库丢失 中文字符集兼容问题的处理.

    接着上一封blog. 因为sqlplus的 乱码问题 我修改了 注册表里面 NLS_LANG 的 value值.主要改动为: NLS_LANG source: SIMPLIFIED CHINESE_C ...

  8. session,cookie

    简单: cookie可以由客户端,服务端产生,保存在客户端,客户端可以更改cookie中的内容 session只能在服务端产生,保存在服务端,会产生一个session_id,一个域下,只有一个id,这 ...

  9. 【bzoj3203】[Sdoi2013]保护出题人 凸包+二分

    题目描述 输入 第一行两个空格隔开的正整数n和d,分别表示关数和相邻僵尸间的距离.接下来n行每行两个空格隔开的正整数,第i + 1行为Ai和 Xi,分别表示相比上一关在僵尸队列排头增加血量为Ai 点的 ...

  10. PHP 中数组获取不到元素

    早上看到 SO 上一个有关 PHP 的问题,提问者描述有一个数组,使用 print_r 可以看到索引 key 和相对应的 value 都是存在的,但是访问该元素,不管是使用 array[key] 还是 ...