¡@

Home 

OpenStack Study: base.py

OpenStack Index

**** CubicPower OpenStack Study ****

# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 Nebula, Inc.

#

# Licensed under the Apache License, Version 2.0 (the "License"); you may

# not use this file except in compliance with the License. You may obtain

# a copy of the License at

#

# http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

# License for the specific language governing permissions and limitations

# under the License.

import collections

import copy

import json

import logging

from operator import attrgetter

import sys

from django.core import exceptions as core_exceptions

from django.core import urlresolvers

from django import forms

from django.http import HttpResponse # noqa

from django import template

from django.template.defaultfilters import truncatechars # noqa

from django.template.loader import render_to_string

from django.utils.datastructures import SortedDict

from django.utils.html import escape

from django.utils import http

from django.utils.http import urlencode

from django.utils.safestring import mark_safe

from django.utils import termcolors

from django.utils.translation import ugettext_lazy as _

from horizon import conf

from horizon import exceptions

from horizon import messages

from horizon.tables.actions import FilterAction # noqa

from horizon.tables.actions import LinkAction # noqa

from horizon.utils import html

LOG = logging.getLogger(__name__)

PALETTE = termcolors.PALETTES[termcolors.DEFAULT_PALETTE]

STRING_SEPARATOR = "__"

**** CubicPower OpenStack Study ****

class Column(html.HTMLElement):

"""A class which represents a single column in a :class:`.DataTable`.

.. attribute:: transform

A string or callable. If ``transform`` is a string, it should be the

name of the attribute on the underlying data class which

should be displayed in this column. If it is a callable, it

will be passed the current row's data at render-time and should

return the contents of the cell. Required.

.. attribute:: verbose_name

The name for this column which should be used for display purposes.

Defaults to the value of ``transform`` with the first letter

of each word capitalized if the ``transform`` is not callable,

otherwise it

**** CubicPower OpenStack Study ****

    def __init__(self, transform, verbose_name=None, sortable=True,

                 link=None, allowed_data_types=[], hidden=False, attrs=None,

                 status=False, status_choices=None, display_choices=None,

                 empty_value=None, filters=None, classes=None, summation=None,

                 auto=None, truncate=None, link_classes=None, wrap_list=False,

                 form_field=None, form_field_attributes=None,

                 update_action=None):

        self.classes = list(classes or getattr(self, "classes", []))

        super(Column, self).__init__()

        self.attrs.update(attrs or {})

        if callable(transform):

            self.transform = transform

            self.name = "<%s callable>" % transform.__name__

        else:

            self.transform = unicode(transform)

            self.name = self.transform

        # Empty string is a valid value for verbose_name

        if verbose_name is None:

            if callable(transform):

                self.verbose_name = ''

            else:

                self.verbose_name = self.transform.title()

        else:

            self.verbose_name = verbose_name

        self.auto = auto

        self.sortable = sortable

        self.link = link

        self.allowed_data_types = allowed_data_types

        self.hidden = hidden

        self.status = status

        self.empty_value = empty_value or '-'

        self.filters = filters or []

        self.truncate = truncate

        self.link_classes = link_classes or []

        self.wrap_list = wrap_list

        self.form_field = form_field

        self.form_field_attributes = form_field_attributes or {}

        self.update_action = update_action

        if status_choices:

            self.status_choices = status_choices

        self.display_choices = display_choices

        if summation is not None and summation not in self.summation_methods:

            raise ValueError("Summation method %s must be one of %s."

                             % (summation,

                                ", ".join(self.summation_methods.keys())))

        self.summation = summation

        self.creation_counter = Column.creation_counter

        Column.creation_counter += 1

        if self.sortable and not self.auto:

            self.classes.append("sortable")

        if self.hidden:

            self.classes.append("hide")

        if self.link is not None:

            self.classes.append('anchor')

**** CubicPower OpenStack Study ****

    def __unicode__(self):

        return unicode(self.verbose_name)

**** CubicPower OpenStack Study ****

    def __repr__(self):

        return '<%s: %s>' % (self.__class__.__name__, self.name)

**** CubicPower OpenStack Study ****

    def get_raw_data(self, datum):

        """Returns the raw data for this column, before any filters or

        formatting are applied to it. This is useful when doing calculations

        on data in the table.

        """

        # Callable transformations

        if callable(self.transform):

            data = self.transform(datum)

        # Dict lookups

        elif isinstance(datum, collections.Mapping) and \

                self.transform in datum:

            data = datum.get(self.transform)

        else:

        # Basic object lookups

            try:

                data = getattr(datum, self.transform)

            except AttributeError:

                msg = _("The attribute %(attr)s doesn't exist on "

                        "%(obj)s.") % {'attr': self.transform, 'obj': datum}

                msg = termcolors.colorize(msg, **PALETTE['ERROR'])

                LOG.warning(msg)

                data = None

        return data

**** CubicPower OpenStack Study ****

    def get_data(self, datum):

        """Returns the final display data for this column from the given

        inputs.

        The return value will be either the attribute specified for this column

        or the return value of the attr:`~horizon.tables.Column.transform`

        method for this column.

        """

        datum_id = self.table.get_object_id(datum)

        if datum_id in self.table._data_cache[self]:

            return self.table._data_cache[self][datum_id]

        data = self.get_raw_data(datum)

        display_value = None

        if self.display_choices:

            display_value = [display for (value, display) in

                             self.display_choices

                             if value.lower() == (data or '').lower()]

        if display_value:

            data = display_value[0]

        else:

            for filter_func in self.filters:

                try:

                    data = filter_func(data)

                except Exception:

                    msg = ("Filter '%(filter)s' failed with data "

                           "'%(data)s' on column '%(col_name)s'")

                    LOG.warning(msg, {'filter': filter_func.func_name,

                                      'data': data,

                                      'col_name': unicode(self.verbose_name)})

        if data and self.truncate:

            data = truncatechars(data, self.truncate)

        self.table._data_cache[self][datum_id] = data

        return self.table._data_cache[self][datum_id]

**** CubicPower OpenStack Study ****

    def get_link_url(self, datum):

        """Returns the final value for the column's ``link`` property.

        If ``allowed_data_types`` of this column  is not empty and the datum

        has an assigned type, check if the datum's type is in the

        ``allowed_data_types`` list. If not, the datum won't be displayed

        as a link.

        If ``link`` is a callable, it will be passed the current data object

        and should return a URL. Otherwise ``get_link_url`` will attempt to

        call ``reverse`` on ``link`` with the object's id as a parameter.

        Failing that, it will simply return the value of ``link``.

        """

        if self.allowed_data_types:

            data_type_name = self.table._meta.data_type_name

            data_type = getattr(datum, data_type_name, None)

            if data_type and (data_type not in self.allowed_data_types):

                return None

        obj_id = self.table.get_object_id(datum)

        if callable(self.link):

            return self.link(datum)

        try:

            return urlresolvers.reverse(self.link, args=(obj_id,))

        except urlresolvers.NoReverseMatch:

            return self.link

**** CubicPower OpenStack Study ****

    def get_summation(self):

        """Returns the summary value for the data in this column if a

        valid summation method is specified for it. Otherwise returns ``None``.

        """

        if self.summation not in self.summation_methods:

            return None

        summation_function = self.summation_methods[self.summation]

        data = [self.get_raw_data(datum) for datum in self.table.data]

        data = filter(lambda datum: datum is not None, data)

        if len(data):

            summation = summation_function(data)

            for filter_func in self.filters:

                summation = filter_func(summation)

            return summation

        else:

            return None

**** CubicPower OpenStack Study ****

class Row(html.HTMLElement):

"""Represents a row in the table.

When iterated, the ``Row`` instance will yield each of its cells.

Rows are capable of AJAX updating, with a little added work:

The ``ajax`` property needs to be set to ``True``, and

subclasses need to

**** CubicPower OpenStack Study ****

    def __init__(self, table, datum=None):

        super(Row, self).__init__()

        self.table = table

        self.datum = datum

        self.selected = False

        if self.datum:

            self.load_cells()

        else:

            self.id = None

            self.cells = []

**** CubicPower OpenStack Study ****

    def load_cells(self, datum=None):

        """Load the row's data (either provided at initialization or as an

        argument to this function), initiailize all the cells contained

        by this row, and set the appropriate row properties which require

        the row's data to be determined.

        This function is called automatically by

        :meth:`~horizon.tables.Row.__init__` if the ``datum`` argument is

        provided. However, by not providing the data during initialization

        this function allows for the possibility of a two-step loading

        pattern when you need a row instance but don't yet have the data

        available.

        """

        # Compile all the cells on instantiation.

        table = self.table

        if datum:

            self.datum = datum

        else:

            datum = self.datum

        cells = []

        for column in table.columns.values():

            cell = table._meta.cell_class(datum, column, self)

            cells.append((column.name or column.auto, cell))

        self.cells = SortedDict(cells)

        if self.ajax:

            interval = conf.HORIZON_CONFIG['ajax_poll_interval']

            self.attrs['data-update-interval'] = interval

            self.attrs['data-update-url'] = self.get_ajax_update_url()

            self.classes.append("ajax-update")

        self.attrs['data-object-id'] = table.get_object_id(datum)

        # Add the row's status class and id to the attributes to be rendered.

        self.classes.append(self.status_class)

        id_vals = {"table": self.table.name,

                   "sep": STRING_SEPARATOR,

                   "id": table.get_object_id(datum)}

        self.id = "%(table)s%(sep)srow%(sep)s%(id)s" % id_vals

        self.attrs['id'] = self.id

        # Add the row's display name if available

        display_name = table.get_object_display(datum)

        if display_name:

            self.attrs['data-display'] = escape(display_name)

**** CubicPower OpenStack Study ****

    def __repr__(self):

        return '<%s: %s>' % (self.__class__.__name__, self.id)

**** CubicPower OpenStack Study ****

    def __iter__(self):

        return iter(self.cells.values())

    @property

**** CubicPower OpenStack Study ****

    def status(self):

        column_names = self.table._meta.status_columns

        if column_names:

            statuses = dict([(column_name, self.cells[column_name].status) for

                             column_name in column_names])

            return self.table.calculate_row_status(statuses)

    @property

**** CubicPower OpenStack Study ****

    def status_class(self):

        column_names = self.table._meta.status_columns

        if column_names:

            return self.table.get_row_status_class(self.status)

        else:

            return ''

**** CubicPower OpenStack Study ****

    def render(self):

        return render_to_string("horizon/common/_data_table_row.html",

                                {"row": self})

**** CubicPower OpenStack Study ****

    def get_cells(self):

        """Returns the bound cells for this row in order."""

        return self.cells.values()

**** CubicPower OpenStack Study ****

    def get_ajax_update_url(self):

        table_url = self.table.get_absolute_url()

        params = urlencode({"table": self.table.name,

                            "action": self.ajax_action_name,

                            "obj_id": self.table.get_object_id(self.datum)})

        return "%s?%s" % (table_url, params)

**** CubicPower OpenStack Study ****

    def can_be_selected(self, datum):

        """By default if multiselect enabled return True. You can remove the

        checkbox after an ajax update here if required.

        """

        return True

**** CubicPower OpenStack Study ****

    def get_data(self, request, obj_id):

        """Fetches the updated data for the row based on the object id

        passed in. Must be implemented by a subclass to allow AJAX updating.

        """

        raise NotImplementedError("You must define a get_data method on %s"

                                  % self.__class__.__name__)

**** CubicPower OpenStack Study ****

class Cell(html.HTMLElement):

"""Represents a single cell in the table."""

**** CubicPower OpenStack Study ****

    def __init__(self, datum, column, row, attrs=None, classes=None):

        self.classes = classes or getattr(self, "classes", [])

        super(Cell, self).__init__()

        self.attrs.update(attrs or {})

        self.datum = datum

        self.column = column

        self.row = row

        self.wrap_list = column.wrap_list

        self.inline_edit_available = self.column.update_action is not None

        # initialize the update action if available

        if self.inline_edit_available:

            self.update_action = self.column.update_action()

            self.attrs['data-cell-name'] = column.name

            self.attrs['data-update-url'] = self.get_ajax_update_url()

        self.inline_edit_mod = False

        self.data = self.get_data(datum, column, row)

**** CubicPower OpenStack Study ****

    def get_data(self, datum, column, row):

        """Fetches the data to be displayed in this cell."""

        table = row.table

        if column.auto == "multi_select":

            data = ""

            if row.can_be_selected(datum):

                widget = forms.CheckboxInput(check_test=lambda value: False)

                # Convert value to string to avoid accidental type conversion

                data = widget.render('object_ids',

                                     unicode(table.get_object_id(datum)),

                                     {'class': 'table-row-multi-select'})

            table._data_cache[column][table.get_object_id(datum)] = data

        elif column.auto == "form_field":

            widget = column.form_field

            if issubclass(widget.__class__, forms.Field):

                widget = widget.widget

            widget_name = "%s__%s" % \

                (column.name,

                 unicode(table.get_object_id(datum)))

            # Create local copy of attributes, so it don't change column

            # class form_field_attributes

            form_field_attributes = {}

            form_field_attributes.update(column.form_field_attributes)

            # Adding id of the input so it pairs with label correctly

            form_field_attributes['id'] = widget_name

            data = widget.render(widget_name,

                                 column.get_data(datum),

                                 form_field_attributes)

            table._data_cache[column][table.get_object_id(datum)] = data

        elif column.auto == "actions":

            data = table.render_row_actions(datum)

            table._data_cache[column][table.get_object_id(datum)] = data

        else:

            data = column.get_data(datum)

        return data

**** CubicPower OpenStack Study ****

    def __repr__(self):

        return '<%s: %s, %s>' % (self.__class__.__name__,

                                 self.column.name,

                                 self.row.id)

    @property

**** CubicPower OpenStack Study ****

    def id(self):

        return ("%s__%s" % (self.column.name,

                unicode(self.row.table.get_object_id(self.datum))))

    @property

**** CubicPower OpenStack Study ****

    def value(self):

        """Returns a formatted version of the data for final output.

        This takes into consideration the

        :attr:`~horizon.tables.Column.link`` and

        :attr:`~horizon.tables.Column.empty_value`

        attributes.

        """

        try:

            data = self.column.get_data(self.datum)

            if data is None:

                if callable(self.column.empty_value):

                    data = self.column.empty_value(self.datum)

                else:

                    data = self.column.empty_value

        except Exception:

            data = None

            exc_info = sys.exc_info()

            raise template.TemplateSyntaxError, exc_info[1], exc_info[2]

        if self.url:

            link_classes = ' '.join(self.column.link_classes)

            # Escape the data inside while allowing our HTML to render

            data = mark_safe('%s' %

                             (self.url, link_classes, escape(unicode(data))))

        return data

    @property

**** CubicPower OpenStack Study ****

    def url(self):

        if self.column.link:

            url = self.column.get_link_url(self.datum)

            if url:

                return url

        else:

            return None

    @property

**** CubicPower OpenStack Study ****

    def status(self):

        """Gets the status for the column based on the cell's data."""

        # Deal with status column mechanics based in this cell's data

        if hasattr(self, '_status'):

            return self._status

        if self.column.status or \

                self.column.name in self.column.table._meta.status_columns:

            #returns the first matching status found

            data_value_lower = unicode(self.data).lower()

            for status_name, status_value in self.column.status_choices:

                if unicode(status_name).lower() == data_value_lower:

                    self._status = status_value

                    return self._status

        self._status = None

        return self._status

**** CubicPower OpenStack Study ****

    def get_status_class(self, status):

        """Returns a css class name determined by the status value."""

        if status is True:

            return "status_up"

        elif status is False:

            return "status_down"

        else:

            return "status_unknown"

**** CubicPower OpenStack Study ****

    def get_default_classes(self):

        """Returns a flattened string of the cell's CSS classes."""

        if not self.url:

            self.column.classes = [cls for cls in self.column.classes

                                    if cls != "anchor"]

        column_class_string = self.column.get_final_attrs().get('class', "")

        classes = set(column_class_string.split(" "))

        if self.column.status:

            classes.add(self.get_status_class(self.status))

        if self.inline_edit_available:

            classes.add("inline_edit_available")

        return list(classes)

**** CubicPower OpenStack Study ****

    def get_ajax_update_url(self):

        column = self.column

        table_url = column.table.get_absolute_url()

        params = urlencode({"table": column.table.name,

                            "action": self.row.ajax_cell_action_name,

                            "obj_id": column.table.get_object_id(self.datum),

                            "cell_name": column.name})

        return "%s?%s" % (table_url, params)

    @property

**** CubicPower OpenStack Study ****

    def update_allowed(self):

        """Determines whether update of given cell is allowed.

        Calls allowed action of defined UpdateAction of the Column.

        """

        return self.update_action.allowed(self.column.table.request,

                                          self.datum,

                                          self)

**** CubicPower OpenStack Study ****

    def render(self):

        return render_to_string("horizon/common/_data_table_cell.html",

                                {"cell": self})

**** CubicPower OpenStack Study ****

class DataTableOptions(object):

"""Contains options for :class:`.DataTable` objects.

.. attribute:: name

A short name or slug for the table.

.. attribute:: verbose_name

A more verbose name for the table meant for display purposes.

.. attribute:: columns

A list of column objects or column names. Controls ordering/display

of the columns in the table.

.. attribute:: table_actions

A list of action classes derived from the

:class:`~horizon.tables.Action` class. These actions will handle tasks

such as bulk deletion, etc. for multiple objects at once.

.. attribute:: row_actions

A list similar to ``table_actions`` except tailored to appear for

each row. These actions act on a single object at a time.

.. attribute:: actions_column

Boolean value to control rendering of an additional column containing

the various actions for each row. Defaults to ``True`` if any actions

are specified in the ``row_actions`` option.

.. attribute:: multi_select

Boolean value to control rendering of an extra column with checkboxes

for selecting multiple objects in the table. Defaults to ``True`` if

any actions are specified in the ``table_actions`` option.

.. attribute:: filter

Boolean value to control the display of the "filter" search box

in the table actions. By

**** CubicPower OpenStack Study ****

    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.cell_class = getattr(options, 'cell_class', Cell)

        self.row_class = getattr(options, 'row_class', Row)

        self.column_class = getattr(options, 'column_class', Column)

        self.pagination_param = getattr(options, 'pagination_param', 'marker')

        self.browser_table = getattr(options, 'browser_table', None)

        self.footer = getattr(options, 'footer', True)

        self.no_data_message = getattr(options,

                                       "no_data_message",

                                       _("No items to display."))

        self.permissions = getattr(options, 'permissions', [])

        # Set self.filter if we have any FilterActions

        filter_actions = [action for action in self.table_actions if

                          issubclass(action, FilterAction)]

        if len(filter_actions) > 1:

            raise NotImplementedError("Multiple filter actions is not "

                                      "currently supported.")

        self.filter = getattr(options, 'filter', len(filter_actions) > 0)

        if len(filter_actions) == 1:

            self._filter_action = filter_actions.pop()

        else:

            self._filter_action = None

        self.template = getattr(options,

                                'template',

                                'horizon/common/_data_table.html')

        self.row_actions_template = \

                        'horizon/common/_data_table_row_actions.html'

        self.table_actions_template = \

                        'horizon/common/_data_table_table_actions.html'

        self.context_var_name = unicode(getattr(options,

                                                'context_var_name',

                                                'table'))

        self.actions_column = getattr(options,

                                     'actions_column',

                                     len(self.row_actions) > 0)

        self.multi_select = getattr(options,

                                    'multi_select',

                                    len(self.table_actions) > 0)

        # Set runtime table defaults; not configurable.

        self.has_more_data = False

        # Set mixed data type table attr

        self.mixed_data_type = getattr(options, 'mixed_data_type', False)

        self.data_types = getattr(options, 'data_types', [])

        # If the data_types has more than 2 elements, set mixed_data_type

        # to True automatically.

        if len(self.data_types) > 1:

            self.mixed_data_type = True

        # However, if the mixed_data_type is set to True manually and the

        # the data_types is empty, raise an error.

        if self.mixed_data_type and len(self.data_types) <= 1:

            raise ValueError("If mixed_data_type is set to True in class %s, "

                             "data_types should has more than one types" %

                             self.name)

        self.data_type_name = getattr(options,

                                      'data_type_name',

                                      "_table_data_type")

**** CubicPower OpenStack Study ****

class DataTableMetaclass(type):

"""Metaclass to add options to DataTable class and collect columns."""

**** CubicPower OpenStack Study ****

    def __new__(mcs, name, bases, attrs):

        # Process options from Meta

        class_name = name

        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.

        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)

        # If the table is in a ResourceBrowser, the column number must meet

        # these limits because of the width of the browser.

        if opts.browser_table == "navigation" and len(columns) > 3:

            raise ValueError("You can only assign three column to %s."

                             % class_name)

        if opts.browser_table == "content" and len(columns) > 2:

            raise ValueError("You can only assign two columns to %s."

                             % class_name)

        if opts.columns:

            # Remove any columns that weren't declared if we're being explicit

            # NOTE: we're iterating a COPY of the list here!

            for column_data in columns[:]:

                if column_data[0] not in opts.columns:

                    columns.pop(columns.index(column_data))

            # Re-order based on declared columns

            columns.sort(key=lambda x: attrs['_meta'].columns.index(x[0]))

        # Add in our auto-generated columns

        if opts.multi_select and opts.browser_table != "navigation":

            multi_select = opts.column_class("multi_select",

                                             verbose_name="",

                                             auto="multi_select")

            multi_select.classes.append('multi_select_column')

            columns.insert(0, ("multi_select", multi_select))

        if opts.actions_column:

            actions_column = opts.column_class("actions",

                                               verbose_name=_("Actions"),

                                               auto="actions")

            actions_column.classes.append('actions_column')

            columns.append(("actions", actions_column))

        # Store this set of columns internally so we can copy them per-instance

        attrs['_columns'] = SortedDict(columns)

        # Gather and register actions for later access since we only want

        # to instantiate them once.

        # (list() call gives deterministic sort order, which sets don't have.)

        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)

**** CubicPower OpenStack Study ****

class DataTable(object):

"""A class which

**** CubicPower OpenStack Study ****

    def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):

        self.request = request

        self.data = data

        self.kwargs = kwargs

        self._needs_form_wrapper = needs_form_wrapper

        self._no_data_message = self._meta.no_data_message

        self.breadcrumb = None

        self.current_item_id = None

        self.permissions = self._meta.permissions

        # Create a new set

        columns = []

        for key, _column in self._columns.items():

            column = copy.copy(_column)

            column.table = self

            columns.append((key, column))

        self.columns = SortedDict(columns)

        self._populate_data_cache()

        # Associate these actions with this table

        for action in self.base_actions.values():

            action.table = self

        self.needs_summary_row = any([col.summation

                                      for col in self.columns.values()])

**** CubicPower OpenStack Study ****

    def __unicode__(self):

        return unicode(self._meta.verbose_name)

**** CubicPower OpenStack Study ****

    def __repr__(self):

        return '<%s: %s>' % (self.__class__.__name__, self._meta.name)

    @property

**** CubicPower OpenStack Study ****

    def name(self):

        return self._meta.name

    @property

**** CubicPower OpenStack Study ****

    def footer(self):

        return self._meta.footer

    @property

**** CubicPower OpenStack Study ****

    def multi_select(self):

        return self._meta.multi_select

    @property

**** CubicPower OpenStack Study ****

    def filtered_data(self):

        # This function should be using django.utils.functional.cached_property

        # decorator, but unfortunately due to bug in Django

        # https://code.djangoproject.com/ticket/19872 it would make it fail

        # when being mocked by mox in tests.

        if not hasattr(self, '_filtered_data'):

            self._filtered_data = self.data

            if self._meta.filter and self._meta._filter_action:

                action = self._meta._filter_action

                filter_string = self.get_filter_string()

                request_method = self.request.method

                needs_preloading = (not filter_string

                                    and request_method == 'GET'

                                    and action.needs_preloading)

                valid_method = (request_method == action.method)

                if valid_method or needs_preloading:

                    if self._meta.mixed_data_type:

                        self._filtered_data = action.data_type_filter(self,

                                                                self.data,

                                                                filter_string)

                    else:

                        self._filtered_data = action.filter(self,

                                                            self.data,

                                                            filter_string)

        return self._filtered_data

**** CubicPower OpenStack Study ****

    def get_filter_string(self):

        filter_action = self._meta._filter_action

        param_name = filter_action.get_param_name()

        filter_string = self.request.POST.get(param_name, '')

        return filter_string

**** CubicPower OpenStack Study ****

    def _populate_data_cache(self):

        self._data_cache = {}

        # Set up hash tables to store data points for each column

        for column in self.get_columns():

            self._data_cache[column] = {}

**** CubicPower OpenStack Study ****

    def _filter_action(self, action, request, datum=None):

        try:

            # Catch user errors in permission functions here

            row_matched = True

            if self._meta.mixed_data_type:

                row_matched = action.data_type_matched(datum)

            return action._allowed(request, datum) and row_matched

        except Exception:

            LOG.exception("Error while checking action permissions.")

            return None

**** CubicPower OpenStack Study ****

    def is_browser_table(self):

        if self._meta.browser_table:

            return True

        return False

**** CubicPower OpenStack Study ****

    def render(self):

        """Renders the table using the template from the table options."""

        table_template = template.loader.get_template(self._meta.template)

        extra_context = {self._meta.context_var_name: self}

        context = template.RequestContext(self.request, extra_context)

        return table_template.render(context)

**** CubicPower OpenStack Study ****

    def get_absolute_url(self):

        """Returns the canonical URL for this table.

        This is used for the POST action attribute on the form element

        wrapping the table. In many cases it is also useful for redirecting

        after a successful action on the table.

        For convenience it defaults to the value of

        ``request.get_full_path()`` with any query string stripped off,

        e.g. the path at which the table was requested.

        """

        return self.request.get_full_path().partition('?')[0]

**** CubicPower OpenStack Study ****

    def get_full_url(self):

        """Returns the full URL path for this table.

        This is used for the POST action attribute on the form element

        wrapping the table. We use this method to persist the

        pagination marker.

        """

        return self.request.get_full_path()

**** CubicPower OpenStack Study ****

    def get_empty_message(self):

        """Returns the message to be displayed when there is no data."""

        return self._no_data_message

**** CubicPower OpenStack Study ****

    def get_object_by_id(self, lookup):

        """Returns the data object from the table's dataset which matches

        the ``lookup`` parameter specified. An error will be raised if

        the match is not a single data object.

        We will convert the object id and ``lookup`` to unicode before

        comparison.

        Uses :meth:`~horizon.tables.DataTable.get_object_id` internally.

        """

        if not isinstance(lookup, unicode):

            lookup = unicode(str(lookup), 'utf-8')

        matches = []

        for datum in self.data:

            obj_id = self.get_object_id(datum)

            if not isinstance(obj_id, unicode):

                obj_id = unicode(str(obj_id), 'utf-8')

            if obj_id == lookup:

                matches.append(datum)

        if len(matches) > 1:

            raise ValueError("Multiple matches were returned for that id: %s."

                           % matches)

        if not matches:

            raise exceptions.Http302(self.get_absolute_url(),

                                     _('No match returned for the id "%s".')

                                       % lookup)

        return matches[0]

    @property

**** CubicPower OpenStack Study ****

    def has_actions(self):

        """Boolean. Indicates whether there are any available actions on this

        table.

        """

        if not self.base_actions:

            return False

        return any(self.get_table_actions()) or any(self._meta.row_actions)

    @property

**** CubicPower OpenStack Study ****

    def needs_form_wrapper(self):

        """Boolean. Indicates whether this table should be rendered wrapped in

        a ``

`` tag or not.

        """

        # If needs_form_wrapper is explicitly set, defer to that.

        if self._needs_form_wrapper is not None:

            return self._needs_form_wrapper

        # Otherwise calculate whether or not we need a form element.

        return self.has_actions

**** CubicPower OpenStack Study ****

    def get_table_actions(self):

        """Returns a list of the action instances for this table."""

        bound_actions = [self.base_actions[action.name] for

                         action in self._meta.table_actions]

        return [action for action in bound_actions if

                self._filter_action(action, self.request)]

**** CubicPower OpenStack Study ****

    def get_row_actions(self, datum):

        """Returns a list of the action instances for a specific row."""

        bound_actions = []

        for action in self._meta.row_actions:

            # Copy to allow modifying properties per row

            bound_action = copy.copy(self.base_actions[action.name])

            bound_action.attrs = copy.copy(bound_action.attrs)

            bound_action.datum = datum

            # Remove disallowed actions.

            if not self._filter_action(bound_action,

                                       self.request,

                                       datum):

                continue

            # Hook for modifying actions based on data. No-op by default.

            bound_action.update(self.request, datum)

            # Pre-create the URL for this link with appropriate parameters

            if issubclass(bound_action.__class__, LinkAction):

                bound_action.bound_url = bound_action.get_link_url(datum)

            bound_actions.append(bound_action)

        return bound_actions

**** CubicPower OpenStack Study ****

    def render_table_actions(self):

        """Renders the actions specified in ``Meta.table_actions``."""

        template_path = self._meta.table_actions_template

        table_actions_template = template.loader.get_template(template_path)

        bound_actions = self.get_table_actions()

        extra_context = {"table_actions": bound_actions}

        if self._meta.filter and \

           self._filter_action(self._meta._filter_action, self.request):

            extra_context["filter"] = self._meta._filter_action

        context = template.RequestContext(self.request, extra_context)

        return table_actions_template.render(context)

**** CubicPower OpenStack Study ****

    def render_row_actions(self, datum):

        """Renders the actions specified in ``Meta.row_actions`` using the

        current row data.

        """

        template_path = self._meta.row_actions_template

        row_actions_template = template.loader.get_template(template_path)

        bound_actions = self.get_row_actions(datum)

        extra_context = {"row_actions": bound_actions,

                         "row_id": self.get_object_id(datum)}

        context = template.RequestContext(self.request, extra_context)

        return row_actions_template.render(context)

    @staticmethod

**** CubicPower OpenStack Study ****

    def parse_action(action_string):

        """Parses the ``action`` parameter (a string) sent back with the

        POST data. By default this parses a string formatted as

        ``{{ table_name }}__{{ action_name }}__{{ row_id }}`` and returns

        each of the pieces. The ``row_id`` is optional.

        """

        if action_string:

            bits = action_string.split(STRING_SEPARATOR)

            bits.reverse()

            table = bits.pop()

            action = bits.pop()

            try:

                object_id = bits.pop()

            except IndexError:

                object_id = None

            return table, action, object_id

**** CubicPower OpenStack Study ****

    def take_action(self, action_name, obj_id=None, obj_ids=None):

        """Locates the appropriate action and routes the object

        data to it. The action should return an HTTP redirect

        if successful, or a value which evaluates to ``False``

        if unsuccessful.

        """

        # See if we have a list of ids

        obj_ids = obj_ids or self.request.POST.getlist('object_ids')

        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

        # Meanwhile, 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]

            # 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

    @classmethod

**** CubicPower OpenStack Study ****

    def check_handler(cls, request):

        """Determine whether the request should be handled by this table."""

        if request.method == "POST" and "action" in request.POST:

            table, action, obj_id = cls.parse_action(request.POST["action"])

        elif "table" in request.GET and "action" in request.GET:

            table = request.GET["table"]

            action = request.GET["action"]

            obj_id = request.GET.get("obj_id", None)

        else:

            table = action = obj_id = None

        return table, action, obj_id

**** CubicPower OpenStack Study ****

    def maybe_preempt(self):

        """Determine whether the request should be handled by a preemptive

        action on this table or by an AJAX row update before loading any data.

        """

        request = self.request

        table_name, action_name, obj_id = self.check_handler(request)

        if table_name == self.name:

            # Handle AJAX row updating.

            new_row = self._meta.row_class(self)

            if new_row.ajax and new_row.ajax_action_name == action_name:

                try:

                    datum = new_row.get_data(request, obj_id)

                    new_row.load_cells(datum)

                    error = False

                except Exception:

                    datum = None

                    error = exceptions.handle(request, ignore=True)

                if request.is_ajax():

                    if not error:

                        return HttpResponse(new_row.render())

                    else:

                        return HttpResponse(status=error.status_code)

            elif new_row.ajax_cell_action_name == action_name:

                # inline edit of the cell actions

                return self.inline_edit_handle(request, table_name,

                                               action_name, obj_id,

                                               new_row)

            preemptive_actions = [action for action in

                                  self.base_actions.values() if action.preempt]

            if action_name:

                for action in preemptive_actions:

                    if action.name == action_name:

                        handled = self.take_action(action_name, obj_id)

                        if handled:

                            return handled

        return None

**** CubicPower OpenStack Study ****

    def inline_edit_handle(self, request, table_name, action_name, obj_id,

                           new_row):

        """Inline edit handler.

        Showing form or handling update by POST of the cell.

        """

        try:

            cell_name = request.GET['cell_name']

            datum = new_row.get_data(request, obj_id)

            # TODO(lsmola) extract load cell logic to Cell and load

            # only 1 cell. This is kind of ugly.

            if request.GET.get('inline_edit_mod') == "true":

                new_row.table.columns[cell_name].auto = "form_field"

                inline_edit_mod = True

            else:

                inline_edit_mod = False

            # Load the cell and set the inline_edit_mod.

            new_row.load_cells(datum)

            cell = new_row.cells[cell_name]

            cell.inline_edit_mod = inline_edit_mod

            # If not allowed, neither edit mod or updating is allowed.

            if not cell.update_allowed:

                datum_display = (self.get_object_display(datum) or

                                 _("N/A"))

                LOG.info('Permission denied to %s: "%s"' %

                         ("Update Action", datum_display))

                return HttpResponse(status=401)

            # If it is post request, we are updating the cell.

            if request.method == "POST":

                return self.inline_update_action(request,

                                                 datum,

                                                 cell,

                                                 obj_id,

                                                 cell_name)

            error = False

        except Exception:

            datum = None

            error = exceptions.handle(request, ignore=True)

        if request.is_ajax():

            if not error:

                return HttpResponse(cell.render())

            else:

                return HttpResponse(status=error.status_code)

**** CubicPower OpenStack Study ****

    def inline_update_action(self, request, datum, cell, obj_id, cell_name):

        """Handling update by POST of the cell.

        """

        new_cell_value = request.POST.get(

            cell_name + '__' + obj_id, None)

        if issubclass(cell.column.form_field.__class__,

                      forms.Field):

            try:

                # using Django Form Field to parse the

                # right value from POST and to validate it

                new_cell_value = (

                    cell.column.form_field.clean(

                        new_cell_value))

                cell.update_action.action(

                    self.request, datum, obj_id, cell_name, new_cell_value)

                response = {

                    'status': 'updated',

                    'message': ''

                }

                return HttpResponse(

                    json.dumps(response),

                    status=200,

                    content_type="application/json")

            except core_exceptions.ValidationError:

                # if there is a validation error, I will

                # return the message to the client

                exc_type, exc_value, exc_traceback = (

                    sys.exc_info())

                response = {

                    'status': 'validation_error',

                    'message': ' '.join(exc_value.messages)}

                return HttpResponse(

                    json.dumps(response),

                    status=400,

                    content_type="application/json")

**** CubicPower OpenStack Study ****

    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

        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

**** CubicPower OpenStack Study ****

    def sanitize_id(self, obj_id):

        """Override to modify an incoming obj_id to match existing

        API data types or modify the format.

        """

        return obj_id

**** CubicPower OpenStack Study ****

    def get_object_id(self, datum):

        """Returns the identifier for the object this row will represent.

        By default this returns an ``id`` attribute on the given object,

        but this can be overridden to return other values.

        .. warning::

            Make sure that the value returned is a unique value for the id

            otherwise rendering issues can occur.

        """

        return datum.id

**** CubicPower OpenStack Study ****

    def get_object_display(self, datum):

        """Returns a display name that identifies this object.

        By default, this returns a ``name`` attribute from the given object,

        but this can be overridden to return other values.

        """

        if hasattr(datum, 'name'):

            return datum.name

        return None

**** CubicPower OpenStack Study ****

    def has_more_data(self):

        """Returns a boolean value indicating whether there is more data

        available to this table from the source (generally an API).

        The method is largely meant for internal use, but if you want to

        override it to provide custom behavior you can do so at your own risk.

        """

        return self._meta.has_more_data

**** CubicPower OpenStack Study ****

    def get_marker(self):

        """Returns the identifier for the last object in the current data set

        for APIs that use marker/limit-based paging.

        """

        return http.urlquote_plus(self.get_object_id(self.data[-1]))

**** CubicPower OpenStack Study ****

    def get_pagination_string(self):

        """Returns the query parameter string to paginate this table."""

        return "=".join([self._meta.pagination_param, self.get_marker()])

**** CubicPower OpenStack Study ****

    def calculate_row_status(self, statuses):

        """Returns a boolean value determining the overall row status

        based on the dictionary of column name to status mappings passed in.

        By default, it uses the following logic:

        #. If any statuses are ``False``, return ``False``.

        #. If no statuses are ``False`` but any or ``None``, return ``None``.

        #. If all statuses are ``True``, return ``True``.

        This provides the greatest protection against false positives without

        weighting any particular columns.

        The ``statuses`` parameter is passed in as a dictionary mapping

        column names to their statuses in order to allow this function to

        be overridden in such a way as to weight one column's status over

        another should that behavior be desired.

        """

        values = statuses.values()

        if any([status is False for status in values]):

            return False

        elif any([status is None for status in values]):

            return None

        else:

            return True

**** CubicPower OpenStack Study ****

    def get_row_status_class(self, status):

        """Returns a css class name determined by the status value. This class

        name is used to indicate the status of the rows in the table if

        any ``status_columns`` have been specified.

        """

        if status is True:

            return "status_up"

        elif status is False:

            return "status_down"

        else:

            return "status_unknown"

**** CubicPower OpenStack Study ****

    def get_columns(self):

        """Returns this table's columns including auto-generated ones."""

        return self.columns.values()

**** CubicPower OpenStack Study ****

    def get_rows(self):

        """Return the row data for this table broken out by columns."""

        rows = []

        try:

            for datum in self.filtered_data:

                row = self._meta.row_class(self, datum)

                if self.get_object_id(datum) == self.current_item_id:

                    self.selected = True

                    row.classes.append('current_selected')

                rows.append(row)

        except Exception:

            # Exceptions can be swallowed at the template level here,

            # re-raising as a TemplateSyntaxError makes them visible.

            LOG.exception("Error while rendering table rows.")

            exc_info = sys.exc_info()

            raise template.TemplateSyntaxError, exc_info[1], exc_info[2]

        return rows