Remove unused and unsupported table field
This commit removes any mention of table field from dynamic_ui and removes floppyforms from requirements. 'table' field was introduced for MsSqlClusterServer service, that changed significantly since then and does not use table field anymore. See https://github.com/murano-project/murano-app-incubator/blob/master/io.murano.windows.SqlServerCluster/UI/ui.yaml for more details (even though the repository is deprecated). Since no app available today uses this type of field, and no tests exist for this type of field. There is no way to test, that it works correctly, therefore it is very hard to transfer critical parts from floppyforms to murano-dashboard. 'table' field depends on django-floppyforms, which is not present in global-requirements, which is problematic and prevents us from enabling proposal bot in murano-dashboard. 'table' field was never documented, so no documentation change is required. Change-Id: I638c722c2bdde411aaefa02f3d4027119de0e0bd Closes-Bug: #1468342
This commit is contained in:
parent
69c40fa433
commit
b21538e2a3
@ -23,14 +23,11 @@ from django.core import validators as django_validator
|
||||
from django import forms
|
||||
from django.http import Http404
|
||||
from django.template import defaultfilters
|
||||
from django.template import loader
|
||||
from django.utils import html
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import floppyforms
|
||||
from horizon import exceptions
|
||||
from horizon import forms as hz_forms
|
||||
from horizon import messages
|
||||
from horizon import tables
|
||||
from openstack_dashboard.api import glance
|
||||
from openstack_dashboard.api import nova
|
||||
import yaql
|
||||
@ -278,183 +275,6 @@ class IntegerField(forms.IntegerField, CustomPropertiesField):
|
||||
pass
|
||||
|
||||
|
||||
class Column(tables.Column):
|
||||
template_name = 'common/form-fields/data-grid/input.html'
|
||||
|
||||
def __init__(self, transform, table_name=None, **kwargs):
|
||||
if hasattr(self, 'template_name'):
|
||||
def _transform(datum):
|
||||
context = {'data': getattr(datum, self.name, None),
|
||||
'row_index': str(datum.id),
|
||||
'table_name': table_name,
|
||||
'column_name': self.name}
|
||||
return loader.render_to_string(self.template_name, context)
|
||||
_transform.__name__ = transform
|
||||
transform = _transform
|
||||
super(Column, self).__init__(transform, **kwargs)
|
||||
|
||||
|
||||
class CheckColumn(Column):
|
||||
template_name = 'common/form-fields/data-grid/checkbox.html'
|
||||
|
||||
|
||||
class RadioColumn(Column):
|
||||
template_name = 'common/form-fields/data-grid/radio.html'
|
||||
|
||||
|
||||
# FixME: we need to have separated object until find out way to use the same
|
||||
# code for MS SQL Cluster datagrid
|
||||
class Object(object):
|
||||
def __init__(self, id, **kwargs):
|
||||
self.id = id
|
||||
for key, value in kwargs.iteritems():
|
||||
setattr(self, key, value)
|
||||
|
||||
def as_dict(self):
|
||||
item = {}
|
||||
for key, value in self.__dict__.iteritems():
|
||||
if key != 'id':
|
||||
item[key] = value
|
||||
return item
|
||||
|
||||
|
||||
def DataTableFactory(name, columns):
|
||||
class Object(object):
|
||||
row_name_re = re.compile(r'.*\{0}.*')
|
||||
|
||||
def __init__(self, id, **kwargs):
|
||||
self.id = id
|
||||
for key, value in kwargs.iteritems():
|
||||
if isinstance(value, basestring) and \
|
||||
re.match(self.row_name_re, value):
|
||||
setattr(self, key, value.format(id))
|
||||
else:
|
||||
setattr(self, key, value)
|
||||
|
||||
class DataTableBase(tables.DataTable):
|
||||
def __init__(self, request, data, **kwargs):
|
||||
if len(data) and isinstance(data[0], dict):
|
||||
objects = [Object(i, **item)
|
||||
for (i, item) in enumerate(data, 1)]
|
||||
else:
|
||||
objects = data
|
||||
super(DataTableBase, self).__init__(request, objects, **kwargs)
|
||||
|
||||
class Meta:
|
||||
template = 'common/form-fields/data-grid/data_table.html'
|
||||
name = ''
|
||||
footer = False
|
||||
|
||||
attrs = dict((col_id, cls(col_id, verbose_name=col_name, table_name=name))
|
||||
for (col_id, cls, col_name) in columns)
|
||||
attrs['Meta'] = Meta
|
||||
return tables.base.DataTableMetaclass('DataTable', (DataTableBase,), attrs)
|
||||
|
||||
|
||||
class TableWidget(floppyforms.widgets.Input):
|
||||
template_name = 'common/form-fields/data-grid/table_field.html'
|
||||
delimiter_re = re.compile('([\w-]*)@@([0-9]*)@@([\w-]*)')
|
||||
types = {'label': Column,
|
||||
'radio': RadioColumn,
|
||||
'checkbox': CheckColumn}
|
||||
|
||||
def __init__(self, columns_spec=None, table_class=None, js_buttons=True,
|
||||
min_value=None, max_value=None, max_sync=None,
|
||||
*args, **kwargs):
|
||||
assert columns_spec is not None or table_class is not None
|
||||
self.columns = []
|
||||
if columns_spec:
|
||||
for spec in columns_spec:
|
||||
name = spec['column_name']
|
||||
self.columns.append((name,
|
||||
self.types[spec['column_type']],
|
||||
spec.get('title', None) or name.title()))
|
||||
self.table_class = table_class
|
||||
self.js_buttons = js_buttons
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value
|
||||
self.max_sync = max_sync
|
||||
# FixME: we need to use this hack because TableField passes all kwargs
|
||||
# to TableWidget
|
||||
for kwarg in ('widget', 'description', 'description_title'):
|
||||
kwargs.pop(kwarg, None)
|
||||
super(TableWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_context(self, name, value, attrs=None):
|
||||
ctx = super(TableWidget, self).get_context_data()
|
||||
if value:
|
||||
if self.table_class:
|
||||
cls = self.table_class
|
||||
else:
|
||||
cls = DataTableFactory(name, self.columns)
|
||||
ctx.update({
|
||||
'data_table': cls(self.request, value),
|
||||
'js_buttons': self.js_buttons,
|
||||
'min_value': self.min_value,
|
||||
'max_value': self.max_value,
|
||||
'max_sync': self.max_sync
|
||||
})
|
||||
return ctx
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
def extract_value(row_key, col_id, col_cls):
|
||||
if col_cls == CheckColumn:
|
||||
val = data.get("{0}@@{1}@@{2}".format(name, row_key, col_id),
|
||||
False)
|
||||
return val and val == 'on'
|
||||
elif col_cls == RadioColumn:
|
||||
row_id = data.get("{0}@@@@{1}".format(name, col_id), False)
|
||||
return row_id == row_key
|
||||
else:
|
||||
return data.get("{0}@@{1}@@{2}".format(
|
||||
name, row_key, col_id), None)
|
||||
|
||||
def extract_keys():
|
||||
keys = set()
|
||||
regexp = re.compile('^{name}@@([^@]*)@@.*$'.format(name=name))
|
||||
for key in data.iterkeys():
|
||||
match = re.match(regexp, key)
|
||||
if match and match.group(1):
|
||||
keys.add(match.group(1))
|
||||
return keys
|
||||
|
||||
items = []
|
||||
if self.table_class:
|
||||
columns = [(_name, column.__class__, unicode(column.verbose_name))
|
||||
for (_name, column)
|
||||
in self.table_class.base_columns.items()]
|
||||
else:
|
||||
columns = self.columns
|
||||
|
||||
for row_key in extract_keys():
|
||||
item = {}
|
||||
for column_id, column_instance, column_name in columns:
|
||||
value = extract_value(row_key, column_id, column_instance)
|
||||
item[column_id] = value
|
||||
items.append(Object(row_key, **item))
|
||||
|
||||
return items
|
||||
|
||||
class Media:
|
||||
css = {'all': ('muranodashboard/css/tablefield.css',)}
|
||||
|
||||
|
||||
class TableField(CustomPropertiesField):
|
||||
def __init__(self, columns=None, label=None, table_class=None,
|
||||
initial=None,
|
||||
**kwargs):
|
||||
widget = TableWidget(columns, table_class, **kwargs)
|
||||
super(TableField, self).__init__(
|
||||
label=label, widget=widget, initial=initial)
|
||||
|
||||
@with_request
|
||||
def update(self, request, **kwargs):
|
||||
self.widget.request = request
|
||||
|
||||
def clean(self, objects):
|
||||
return [obj.as_dict() for obj in objects]
|
||||
|
||||
|
||||
class ChoiceField(forms.ChoiceField, CustomPropertiesField):
|
||||
pass
|
||||
|
||||
|
@ -44,7 +44,6 @@ TYPES.update({
|
||||
'password': fields.PasswordField,
|
||||
'integer': fields.IntegerField,
|
||||
'databaselist': fields.DatabaseListField,
|
||||
'table': fields.TableField,
|
||||
'flavor': fields.FlavorChoiceField,
|
||||
'keypair': fields.KeyPairChoiceField,
|
||||
'image': fields.ImageChoiceField,
|
||||
|
@ -8,7 +8,6 @@ DISABLED = False
|
||||
|
||||
ADD_INSTALLED_APPS = [
|
||||
'muranodashboard',
|
||||
'floppyforms'
|
||||
]
|
||||
|
||||
ADD_EXCEPTIONS = {
|
||||
|
@ -1,10 +0,0 @@
|
||||
input[type=radio].data-grid, input[type=checkbox].data-grid {
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.data-grid table {
|
||||
margin-bottom: 5px ! important;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
<input type="checkbox" class="data-grid"
|
||||
{% if data %}checked="checked"{% endif %}
|
||||
name="{{ table_name }}@@{{ row_index }}@@{{ column_name }}"/>
|
@ -1,2 +0,0 @@
|
||||
{% extends 'horizon/common/_data_table.html' %}
|
||||
{% block table_caption %}{% endblock table_caption %}
|
@ -1,4 +0,0 @@
|
||||
{{ data }}
|
||||
<input type="hidden" class="data-grid"
|
||||
name="{{ table_name }}@@{{ row_index }}@@{{ column_name }}"
|
||||
value="{{ data }}"/>
|
@ -1,4 +0,0 @@
|
||||
<input type="radio" class="data-grid"
|
||||
{% if data %}checked="checked"{% endif %}
|
||||
name="{{ table_name }}@@@@{{ column_name }}"
|
||||
value="{{ row_index }}"/>
|
@ -1,134 +0,0 @@
|
||||
<div class="data-grid">{{ data_table.render }}</div>
|
||||
{% if js_buttons %}
|
||||
<button type="button" id="node-add" class="btn btn-small">Add node</button>
|
||||
<button type="button" id="node-remove" class="btn btn-small">Remove node</button>
|
||||
<script>
|
||||
$(function() {
|
||||
var MAX_NODES, MIN_NODES, MAX_SYNC;
|
||||
|
||||
if ( {{ max_value }} )
|
||||
MAX_NODES = {{ max_value }};
|
||||
else
|
||||
MAX_NODES = Infinity;
|
||||
|
||||
if ( {{ min_value }} )
|
||||
MIN_NODES = {{ min_value }};
|
||||
else
|
||||
MIN_NODES = -Infinity;
|
||||
|
||||
if ( {{ max_sync }} )
|
||||
MAX_SYNC = {{ max_sync }};
|
||||
else
|
||||
MAX_SYNC = Infinity;
|
||||
|
||||
function trimLabel(label) {
|
||||
return $.trim(label.replace(/(\r\n|\r|\n|↵)/gm, ''));
|
||||
}
|
||||
|
||||
function getMaxLabel() {
|
||||
var labels = [],
|
||||
max = 0,
|
||||
base = '';
|
||||
$('.data-grid table tbody tr td:first-child').each(function(i, td) {
|
||||
labels.push(trimLabel($(td).text()));
|
||||
});
|
||||
labels.forEach(function(label) {
|
||||
var match = /([a-zA-z]+)-([0-9]+)/.exec(label),
|
||||
n = +match[2];
|
||||
base = match[1];
|
||||
if ( n > max )
|
||||
max = n;
|
||||
});
|
||||
return [base, max]
|
||||
}
|
||||
|
||||
function getNextLabel() {
|
||||
var baseMax = getMaxLabel();
|
||||
return baseMax[0]+'-'+(+baseMax[1]+1);
|
||||
}
|
||||
|
||||
function getNumOfSync() {
|
||||
var checked = 0;
|
||||
$('.data-grid table tbody td input:checkbox').each(function(index, cb) {
|
||||
if ( $(cb).attr('checked') )
|
||||
checked++;
|
||||
});
|
||||
return checked;
|
||||
}
|
||||
|
||||
function validate_sync(event) {
|
||||
var checkbox = $(event.target);
|
||||
|
||||
if ( checkbox.attr('checked') ) {
|
||||
if ( getNumOfSync() > MAX_SYNC ) {
|
||||
alert('No more than ' + MAX_SYNC + ' nodes can be in sync-mode!')
|
||||
checkbox.attr('checked', false);
|
||||
}
|
||||
} else if ( checkbox.parents().eq(1).find('input:radio').attr('checked') ) {
|
||||
alert('Primary node is always in sync-mode!');
|
||||
checkbox.attr('checked', true);
|
||||
}
|
||||
}
|
||||
|
||||
var primary = $('.data-grid table tbody input:radio[checked="checked"]').parents().eq(1);
|
||||
|
||||
function validate_primary(event) {
|
||||
var radio = $(event.target),
|
||||
checkbox = radio.parents().eq(1).find('input:checkbox');
|
||||
if ( !checkbox.attr('checked') ) {
|
||||
if ( getNumOfSync() == MAX_SYNC )
|
||||
primary.find('input:checkbox').attr('checked', false);
|
||||
checkbox.attr('checked', true);
|
||||
}
|
||||
primary = radio.parents().eq(1);
|
||||
}
|
||||
|
||||
$('.data-grid table tbody td input:checkbox').click(validate_sync);
|
||||
$('.data-grid table tbody td input:radio').click(validate_primary);
|
||||
|
||||
$('button#node-add').click(function() {
|
||||
debugger;
|
||||
if ( $('.data-grid table tbody tr').length >= MAX_NODES ) {
|
||||
alert('Maximum number of nodes ('+MAX_NODES+') already reached.');
|
||||
return;
|
||||
}
|
||||
var lastRow = $('.data-grid table tbody tr:last-child'),
|
||||
clone = lastRow.clone(),
|
||||
nameCell = clone.find('td:first-child'),
|
||||
hidden = nameCell.find('input:hidden'),
|
||||
match = /([^@]+)@@([0-9]+)@@(.+)/.exec(hidden.attr('name')),
|
||||
index = +match[2]+1,
|
||||
nextLabel = getNextLabel();
|
||||
nameCell.text(nextLabel);
|
||||
hidden.val(nextLabel);
|
||||
hidden.attr('name', match[1]+'@@'+index+'@@'+match[3]);
|
||||
nameCell.append(hidden);
|
||||
// toggle of sync and primary buttons of clone
|
||||
clone.find('input:checkbox').attr('checked', false);
|
||||
clone.find('input:checkbox').click(validate_sync);
|
||||
clone.find('input:radio').attr('checked', false);
|
||||
clone.find('input:radio').click(validate_primary);
|
||||
lastRow.after(clone);
|
||||
});
|
||||
|
||||
$('button#node-remove').click(function() {
|
||||
if ( $('.data-grid table tbody tr').length <= MIN_NODES ) {
|
||||
alert('There cannot be less than ' + MIN_NODES + ' nodes');
|
||||
return;
|
||||
}
|
||||
var labelNum = getMaxLabel(),
|
||||
label = labelNum.join('-'),
|
||||
rowRef = '.data-grid table tbody tr:contains('+label+')';
|
||||
if ( $(rowRef + ' :radio[checked="checked"]') ) {
|
||||
label = labelNum[0] + '-' + (labelNum[1] - 1);
|
||||
$('.data-grid table tbody tr:contains('+label+') :radio').attr(
|
||||
'checked', 'checked');
|
||||
}
|
||||
$(rowRef).remove();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
<script>
|
||||
$('.data-grid .table_caption').remove()
|
||||
</script>
|
@ -11,6 +11,5 @@ PyYAML>=3.1.0
|
||||
oslo.log>=1.2.0 # Apache-2.0
|
||||
|
||||
# not listed in global requirements
|
||||
django-floppyforms>=1.1
|
||||
yaql>=0.2.6,<0.3 # Apache 2.0 License
|
||||
python-muranoclient>=0.5.6
|
||||
|
Loading…
x
Reference in New Issue
Block a user