zoukankan      html  css  js  c++  java
  • horizon源码分析(二)

    源码版本:H版

    一、简要回顾

    对于请求:

    地址:/dashboard/admin/instances/
    方式:POST
    参数:
    instances_filter_q
    actioninstances__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()

      Djangogeneric view说起.... generic view中的as_view()可以返回一个Djangoview函数,该view函数会构造出类实例,将as_view()中传入的参数设置为该实例的属性,然后调用dispatch函数,dispatch函数通常会将request请求转给类中的postget函数。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、DataTableAction三者的说明

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

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

    1)DataTableView簇有如下属性:

    _data={

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

    ...

    }

     

    _tables={

    表名:table实例

    }

    table=table实例

     

    说明:本例中data为一个包含instancelist

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

    2)DataTable:

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

      先观察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/

     

     

  • 相关阅读:
    Android仿网易client实现抽屉式拖拉菜单界面
    使用SRVCTL时报错:error while loading shared libraries
    permission denied for this window type
    pytest文档22-fixture详细介绍-作为参数传入,error和failed区别
    pytest文档22-fixture详细介绍-作为参数传入,error和failed区别
    pytest文档22-fixture详细介绍-作为参数传入,error和failed区别
    xml文件错误
    xml文件错误
    xml文件错误
    mysql replace into 的使用情况
  • 原文地址:https://www.cnblogs.com/littlebugfish/p/3986722.html
Copyright © 2011-2022 走看看