neutron-fwaas-dashboard/neutron_fwaas_dashboard/dashboards/project/firewalls_v2/widgets.py

261 lines
8.3 KiB
Python

# 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 itertools
from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
from horizon.forms import fields
"""A custom Horizon Forms Select widget that displays select choices as a table
The widgets is meant as an optional replacement for the existing Horizon
ThemableDynamicSelectWidget which it extends and is compatible with.
Columns
-------
Columns are defined by setting the widgets 'column' attribute, which is
expected to be an iterable of strings, each one corresponding to one column and
used for that columns heading.
Rows
----
Each row corresponds to one choice/select option with a defined value in
each column.
The values displayed in each column are derived using the 'build_columns'
attribute, which is expected to be a function that:
- takes a choice tuple of the form (value, label) as defined
for the Django SelectField instances as it's only parameter
- returns an iterable of Strings which are rendered as column
values for the given choice row in the same order as in the
iterable
The default implementation simply uses the provided value and label as separate
column values.
See the default implementation and example bellow for more details.
Condensed values
----------------
To maintain visual consistency, the currently selected value is displayed in
the 'standard' ThemableDynamicSelectWidget HTML setup. To accommodate this, a
condensed, single string value is created from the individual columns and
displayed in the select box.
This behavior can be modified by setting the 'condense' attribute. This is
expected to be a function that:
- Takes the column iterable returned by 'build_columns' function
- Returns a single string representation of the choice
By default, the condensed value is created by joining all of the provided
columns and joining them using commas as a delimiter.
See the default implementation and example bellow for more details.
Small screen reactivity
-----------------------
Support for small screens (< 768px) is turned on by setting the attribute
'alternate_xs' to True. When on, a condesned version of the popup table
us used for small screens, where a single column is used with the condensed
row values used instead of the full table rows.
The 'condense' function described above is used to construct this table.
Example
-------
port_id = forms.ThemableDynamicChoiceField(
label=_("Ports"),
widget=TableSelectWidget(
columns=[
'ID',
'Name'
],
build_columns=lambda choice: return (choice[1], choice[0]),
choices=[
('port 1', 'id1'),
('port 2', 'id2')
],
alternate_xs=True,
condense=lambda columns: return ",".join(columns)
)
)
Produces:
+------+--------+
| ID | Name |
+------+--------+
| id1 | port 1 |
| id2 | port 2 |
+------+--------+
on normal screens and
+-------------+
| ID, Name |
+-------------+
| id1, port 1 |
| id2, port 2 |
+-------------+
on xs screens.
"""
class TableSelectWidget(fields.ThemableDynamicSelectWidget):
def __init__(self,
attrs=None,
columns=None,
alternate_xs=False,
empty_text=_("No options available"),
other_html=None,
condense=None,
build_columns=None, *args, **kwargs
):
"""Initializer for TableSelectWidget
:param attrs: A { attribute: value } dictionary which is attached to
the hidden select element; see
ThemableDynamicSelectWidget for further information
:param columns: An iterable of column headers/names
:param alternate_xs: A truth-y value which enables/disables an
alternate rendering method for small screens
:param empty_text: The text to be displayed in case no options are
available
:param other_html: A method for adding custom HTML to the hidden option
HTML.
NOTE: This mimics the behavior of
ThemableDynamicSelectWidget and is retained to
maintain compatibility with any related, potential
functionality
:param condense: A function callback that produces a condensed label
for each option
:param build_columns: A function used to populate the individual
columns in the pop up table for each option
"""
super(TableSelectWidget, self).__init__(attrs, *args, **kwargs)
self.columns = columns or [_('Label'), _('Value'), 'Nothing']
self.alternate_xs = alternate_xs
self.empty_text = empty_text
if other_html:
self.other_html = other_html
if condense:
self.condense = condense
if build_columns:
self.build_columns = build_columns
@staticmethod
def build_columns(choice):
"""Default column building method
Overwrite this method when initializing this widget or using
self.fields[name].widget.build_columns in a parent form initialization
to customize the behavior (see above for details)
:param choice:
:return:
"""
return choice
@staticmethod
def condense(choice_columns):
"""The default condense method
Overwrite this method when initializing this widget or using
self.fields[name].widget.condense in a parent form initialization to
customize the behavior (see above for details)
:param choice_columns:
:return:
"""
return " / ".join([str(c) for c in choice_columns])
# Implements the parent 'other_html' construction for compatibility reasons
# Can be set in initializer to change the behavior as needed
def other_html(self, choice):
opt_label = choice[1]
other_html = self.transform_option_html_attrs(opt_label)
data_attr_html = self.get_data_attrs(opt_label)
if data_attr_html:
other_html += ' ' + data_attr_html
return other_html
def render(self, name, value, attrs=None, choices=None):
new_choices = []
initial_value = value
choices = choices or []
for opt in itertools.chain(self.choices, choices):
other_html = self.other_html(opt)
choice_columns = self.build_columns(opt)
condensed_label = self.condense(choice_columns)
built_choice = (
opt[0], condensed_label, choice_columns, other_html
)
new_choices.append(built_choice)
# Initial selection
if opt[0] == value:
initial_value = built_choice
if not initial_value and new_choices:
initial_value = new_choices[0]
element_id = attrs.pop('id', 'id_%s' % name)
# Size of individual columns in terms of the bootstrap grid - used
# for styling purposes
column_size = 12 // len(self.columns)
# Creates a single string label for all columns for use with small
# screens
condensed_headers = self.condense(self.columns)
template = get_template('project/firewalls_v2/table_select.html')
select_attrs = self.build_attrs(attrs)
context = {
'name': name,
'options': new_choices,
'id': element_id,
'value': value,
'initial_value': initial_value,
'select_attrs': select_attrs,
'column_size': column_size,
'columns': self.columns,
'condensed_headers': condensed_headers,
'alternate_xs': self.alternate_xs,
'empty_text': self.empty_text
}
return template.render(context)