首页 > 代码库 > horizon源码分析(二)

horizon源码分析(二)

 

一、简要回顾

对于请求:

地址:/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函数中,这点和其他网络框架类似。

  

 

 

 

openstack_dashboard/dashboards/admin/instances/views.py

 

分析AdminIndexView.as_view(),其会调用该类的post函数

 

class AdminIndexView(tables.DataTableView):

    table_class = project_tables.AdminInstancesTable

template_name = ‘admin/instances/index.html‘

 

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

 

 

三、此处插入对DataTableDataTableViewAction三者的简要概括:

 

DataTableView簇:

_data=http://www.mamicode.com/{

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

...

}

 

_tables={

表名:table实例

}

table=table实例

 

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

 

可以通过table_class绑定具体的DataTable

通过get_data函数获取data,该函数通常调用openstack_dashboard/api模块获取数据

handle_table函数负责将datatable挂钩

 

综上,DataTableView正如其名字一样,拥有tabledata,负责处理data的获取,Table的创建,以及二者的绑定等

 

DataTable:

规定tablecolumnaction

可以处理和table绑定的data

take_action函数负责处理action

 

 

Action簇:

利用action函数进行处理

 

 

继续分析get函数...

 

 

 

horizon/tables/views.py

MultiTableView类:

def get(self, request, *args, **kwargs):

        handled = self.construct_tables()

        if handled:

            return handled

        """此处暂且不管 """

        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:

            """预先处理,此处暂且不管 """

            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

 

        # If we didn‘t already return a response, returning None continues

        # with the view as normal.

        return None

 

 

四、此处插入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_actiontable_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

 

 

继续分析handle_table函数...

 

 

 

MultiTableMixin类:

def handle_table(self, table):

        name = table.name

        """获取数据"""

        data = http://www.mamicode.com/self._get_data_dict()

 """获取与该DataTable相关的数据,并将数据和该DataTable挂钩"""

        self._tables[name].data = http://www.mamicode.com/data[table._meta.name]

        """暂且不管"""

        self._tables[name]._meta.has_more_data = http://www.mamicode.com/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):

        # See if we have a list of ids

        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:

            # We either didn‘t get an action or we‘re being hacked. Goodbye.

            return None

 

        # Meanhile, back in Gotham...

        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,由于BatchActionhandle函数,所以在Action__init__()中将属性handles_multiple设置为True"""

           # Single handling is easy

            if not action.handles_multiple:

                response = action.single(self, self.request, obj_id)

            # Otherwise figure out what to pass along

            else:#进入此项

                # Preference given to a specific id, since that implies

                # the user selected an action for just one row.

                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)

                #Call update to invoke changes if needed

                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:

                # Handle the exception but silence it since we‘ll display

                # an aggregate error message later. Otherwise we‘d get

                # multiple error messages displayed to the user.

                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)

 

 

 

 

参考文档:

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

 

<style></style>

horizon源码分析(二)