horizon源碼分析(二)


源碼版本: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()

  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/

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM