Horizon selects are now themable: Launch Instance

Horizon was using a standard select input.  Unfortunately, this type
of input is only customizable to a small extent.

Co-Authored-By: Ryan Peters <rjpeter2@gmail.com>
Co-Authored-By: Matthew Wood <woodm1979@gmail.com>
Co-Authored-By: Brian Tully <brian.tully@hp.com>

Change-Id: Iaf8427c3fff6d2fbfae944731fd9f6f754ed7c31
Partially-implements: blueprint horizon-theme-css-reorg
This commit is contained in:
Diana Whitten 2016-03-07 17:03:44 -07:00
parent 377da87e3c
commit 2b67ae681b
6 changed files with 125 additions and 38 deletions

View File

@ -235,6 +235,7 @@ class ThemableSelectWidget(SelectWidget):
# or both.
new_choices = []
initial_value = value
for opt_value, opt_label in itertools.chain(self.choices, choices):
other_html = self.transform_option_html_attrs(opt_label)
@ -244,12 +245,15 @@ class ThemableSelectWidget(SelectWidget):
opt_label = self.transform_option_label(opt_label)
# If value exists, save off its label for use
if opt_value == value:
initial_value = opt_label
if other_html:
new_choices.append((opt_value, opt_label, other_html))
else:
new_choices.append((opt_value, opt_label))
initial_value = value
if value is None and new_choices:
initial_value = new_choices[0][1]

View File

@ -38,7 +38,7 @@ class MinifiedNode(Node):
def render(self, context):
return ' '.join(
force_text(self.nodelist.render(context).strip()).split()
)
).replace(' > ', '>').replace(' <', '<')
@register.filter

View File

@ -113,17 +113,19 @@ class TestChoiceFieldForm(forms.SelfHandlingForm):
name = forms.CharField(max_length=255,
label="Test Name",
help_text="Please enter a name")
test_choices = forms.ChoiceField(label="Test Choices",
required=False,
help_text="Testing drop down choices",
widget=forms.fields.SelectWidget(
attrs={
'class': 'switchable',
'data-slug': 'source'},
transform_html_attrs=title_dic.get))
test_choices = forms.ChoiceField(
label="Test Choices",
required=False,
help_text="Testing drop down choices",
widget=forms.fields.SelectWidget(
attrs={
'class': 'switchable',
'data-slug': 'source'},
transform_html_attrs=title_dic.get))
def __init__(self, request, *args, **kwargs):
super(TestChoiceFieldForm, self).__init__(request, *args, **kwargs)
super(TestChoiceFieldForm, self).__init__(request, *args,
**kwargs)
choices = ([('choice1', 'label1'),
('choice2', 'label2')])
self.fields['test_choices'].choices = choices
@ -144,7 +146,78 @@ class ChoiceFieldTests(test.TestCase):
return shortcuts.render(self.request, self.template,
{'form': self.form})
def test_choicefield_title(self):
def test_legacychoicefield_title(self):
resp = self._render_form()
self.assertContains(
resp,
'<option value="choice1" title="This is choice 1">label1</option>',
count=1, html=True)
self.assertContains(
resp,
'<option value="choice2" title="This is choice 2">label2</option>',
count=1, html=True)
class TestThemableChoiceFieldForm(forms.SelfHandlingForm):
# It's POSSIBLE to combine this with the test helper form above, but
# I fear we'd run into collisions where one test's desired output is
# actually within a separate widget's output.
title_dic = {"label1": {"title": "This is choice 1"},
"label2": {"title": "This is choice 2"},
"label3": {"title": "This is choice 3"}}
name = forms.CharField(max_length=255,
label="Test Name",
help_text="Please enter a name")
test_choices = forms.ThemableChoiceField(
label="Test Choices",
required=False,
help_text="Testing drop down choices",
widget=forms.fields.ThemableSelectWidget(
attrs={
'class': 'switchable',
'data-slug': 'source'},
transform_html_attrs=title_dic.get))
def __init__(self, request, *args, **kwargs):
super(TestThemableChoiceFieldForm, self).__init__(request, *args,
**kwargs)
choices = ([('choice1', 'label1'),
('choice2', 'label2')])
self.fields['test_choices'].choices = choices
def handle(self, request, data):
return True
class ThemableChoiceFieldTests(test.TestCase):
template = 'horizon/common/_form_fields.html'
def setUp(self):
super(ThemableChoiceFieldTests, self).setUp()
self.form = TestThemableChoiceFieldForm(self.request)
def _render_form(self):
return shortcuts.render(self.request, self.template,
{'form': self.form})
def test_choicefield_labels_and_title_attr(self):
resp = self._render_form()
self.assertContains(
resp,
'<a data-select-value="choice1" title="This is choice 1">'
'label1</a>',
count=1,
html=True)
self.assertContains(
resp,
'<a data-select-value="choice2" title="This is choice 2">'
'label2</a>',
count=1,
html=True)
def test_choicefield_title_select_compatible(self):
resp = self._render_form()
self.assertContains(
resp,

View File

@ -1639,7 +1639,7 @@ class InstanceTests(helpers.TestCase):
('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', 'm1.metadata'),
)
select_options = '\n'.join([
select_options = ''.join([
'<option value="%s">%s</option>' % (f[0], f[1])
for f in sorted_flavors
])

View File

@ -52,8 +52,8 @@ LOG = logging.getLogger(__name__)
class SelectProjectUserAction(workflows.Action):
project_id = forms.ChoiceField(label=_("Project"))
user_id = forms.ChoiceField(label=_("User"))
project_id = forms.ThemableChoiceField(label=_("Project"))
user_id = forms.ThemableChoiceField(label=_("User"))
def __init__(self, request, *args, **kwargs):
super(SelectProjectUserAction, self).__init__(request, *args, **kwargs)
@ -79,35 +79,37 @@ class SelectProjectUser(workflows.Step):
class SetInstanceDetailsAction(workflows.Action):
availability_zone = forms.ChoiceField(label=_("Availability Zone"),
required=False)
availability_zone = forms.ThemableChoiceField(label=_("Availability Zone"),
required=False)
name = forms.CharField(label=_("Instance Name"),
max_length=255)
flavor = forms.ChoiceField(label=_("Flavor"),
help_text=_("Size of image to launch."))
flavor = forms.ThemableChoiceField(label=_("Flavor"),
help_text=_("Size of image to launch."))
count = forms.IntegerField(label=_("Number of Instances"),
min_value=1,
initial=1)
source_type = forms.ChoiceField(label=_("Instance Boot Source"),
help_text=_("Choose Your Boot Source "
"Type."))
source_type = forms.ThemableChoiceField(
label=_("Instance Boot Source"),
help_text=_("Choose Your Boot Source "
"Type."))
instance_snapshot_id = forms.ChoiceField(label=_("Instance Snapshot"),
required=False)
instance_snapshot_id = forms.ThemableChoiceField(
label=_("Instance Snapshot"),
required=False)
volume_id = forms.ChoiceField(label=_("Volume"), required=False)
volume_id = forms.ThemableChoiceField(label=_("Volume"), required=False)
volume_snapshot_id = forms.ChoiceField(label=_("Volume Snapshot"),
required=False)
volume_snapshot_id = forms.ThemableChoiceField(label=_("Volume Snapshot"),
required=False)
image_id = forms.ChoiceField(
label=_("Image Name"),
required=False,
widget=forms.SelectWidget(
widget=forms.ThemableSelectWidget(
data_attrs=('volume_size',),
transform=lambda x: ("%s (%s)" % (x.name,
filesizeformat(x.bytes)))))
@ -536,10 +538,11 @@ KEYPAIR_IMPORT_URL = "horizon:project:access_and_security:keypairs:import"
class SetAccessControlsAction(workflows.Action):
keypair = forms.DynamicChoiceField(label=_("Key Pair"),
help_text=_("Key pair to use for "
"authentication."),
add_item_link=KEYPAIR_IMPORT_URL)
keypair = forms.ThemableDynamicChoiceField(
label=_("Key Pair"),
help_text=_("Key pair to use for "
"authentication."),
add_item_link=KEYPAIR_IMPORT_URL)
admin_pass = forms.RegexField(
label=_("Admin Password"),
required=False,
@ -627,10 +630,11 @@ class CustomizeAction(workflows.Action):
('file', _('File'))]
attributes = {'class': 'switchable', 'data-slug': 'scriptsource'}
script_source = forms.ChoiceField(label=_('Customization Script Source'),
choices=source_choices,
widget=forms.Select(attrs=attributes),
required=False)
script_source = forms.ChoiceField(
label=_('Customization Script Source'),
choices=source_choices,
widget=forms.ThemableSelectWidget(attrs=attributes),
required=False)
script_help = _("A script or set of commands to be executed after the "
"instance has been built (max 16kb).")
@ -812,7 +816,7 @@ class SetNetworkPorts(workflows.Step):
class SetAdvancedAction(workflows.Action):
disk_config = forms.ChoiceField(
disk_config = forms.ThemableChoiceField(
label=_("Disk Partition"), required=False,
help_text=_("Automatic: The entire disk is a single partition and "
"automatically resizes. Manual: Results in faster build "
@ -822,7 +826,7 @@ class SetAdvancedAction(workflows.Action):
required=False, help_text=_("Configure OpenStack to write metadata to "
"a special configuration drive that "
"attaches to the instance when it boots."))
server_group = forms.ChoiceField(
server_group = forms.ThemableChoiceField(
label=_("Server Group"), required=False,
help_text=_("Server group to associate with this instance."))

View File

@ -53,6 +53,12 @@
}
}
// Input groups need to look like button groups
.input-group .themable-select .btn {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
// For vertical forms, we'll want the button to take on the entire width.
// think old-school launch-instance.
form:not(.form-inline) .form-group .themable-select {