Start refactoring dynamic UI.
1. When using $value for form field attribute, first try cleaned_data. And only if it is absent, use raw data. 2. Move all service fields classes into separate module fields.py 3. Move some helpers to helpers.py and rename __common.py to forms.py. Change-Id: Iba27cb2f9bc1c017443e0666aa274b5f88373045
This commit is contained in:
parent
5998afd730
commit
09f3aca5b9
@ -11,7 +11,6 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@ -24,7 +23,7 @@ _all_services = OrderedDict()
|
|||||||
|
|
||||||
|
|
||||||
def import_all_services():
|
def import_all_services():
|
||||||
from muranodashboard.panel.services.__common import decamelize
|
import muranodashboard.panel.services.helpers as utils
|
||||||
directory = os.path.dirname(__file__)
|
directory = os.path.dirname(__file__)
|
||||||
for fname in sorted(os.listdir(directory)):
|
for fname in sorted(os.listdir(directory)):
|
||||||
try:
|
try:
|
||||||
@ -35,7 +34,7 @@ def import_all_services():
|
|||||||
if (not name in _all_services or
|
if (not name in _all_services or
|
||||||
_all_services[name][0] < modified_on):
|
_all_services[name][0] < modified_on):
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
kwargs = {decamelize(k): v
|
kwargs = {utils.decamelize(k): v
|
||||||
for k, v in yaml.load(f).iteritems()}
|
for k, v in yaml.load(f).iteritems()}
|
||||||
_all_services[name] = (modified_on,
|
_all_services[name] = (modified_on,
|
||||||
type(name, (), kwargs))
|
type(name, (), kwargs))
|
||||||
@ -46,14 +45,13 @@ def import_all_services():
|
|||||||
|
|
||||||
|
|
||||||
def iterate_over_services():
|
def iterate_over_services():
|
||||||
|
import muranodashboard.panel.services.forms as services
|
||||||
import_all_services()
|
import_all_services()
|
||||||
for id, service_data in _all_services.items():
|
for id, service_data in _all_services.items():
|
||||||
modified_on, service_cls = service_data
|
modified_on, service_cls = service_data
|
||||||
from muranodashboard.panel.services.__common import \
|
|
||||||
ServiceConfigurationForm
|
|
||||||
forms = []
|
forms = []
|
||||||
for fields in service_cls.forms:
|
for fields in service_cls.forms:
|
||||||
class Form(ServiceConfigurationForm):
|
class Form(services.ServiceConfigurationForm):
|
||||||
service = service_cls
|
service = service_cls
|
||||||
fields_template = fields
|
fields_template = fields
|
||||||
forms.append(Form)
|
forms.append(Form)
|
||||||
|
@ -25,11 +25,18 @@ from horizon import exceptions, messages
|
|||||||
from openstack_dashboard.api import glance
|
from openstack_dashboard.api import glance
|
||||||
from openstack_dashboard.api.nova import novaclient
|
from openstack_dashboard.api.nova import novaclient
|
||||||
from muranodashboard.datagrids import DataGridCompound
|
from muranodashboard.datagrids import DataGridCompound
|
||||||
import copy
|
|
||||||
from django.template.defaultfilters import pluralize
|
from django.template.defaultfilters import pluralize
|
||||||
|
import copy
|
||||||
|
|
||||||
|
|
||||||
CONFIRM_ERR_DICT = {'required': _('Please confirm your password')}
|
def with_request(func):
|
||||||
|
def update(self, initial):
|
||||||
|
request = initial.get('request')
|
||||||
|
if request:
|
||||||
|
func(self, request, initial)
|
||||||
|
else:
|
||||||
|
raise forms.ValidationError("Can't get a request information")
|
||||||
|
return update
|
||||||
|
|
||||||
|
|
||||||
class PasswordField(forms.CharField):
|
class PasswordField(forms.CharField):
|
||||||
@ -41,6 +48,16 @@ class PasswordField(forms.CharField):
|
|||||||
password_re, _('The password must contain at least one letter, one \
|
password_re, _('The password must contain at least one letter, one \
|
||||||
number and one special character'), 'invalid')
|
number and one special character'), 'invalid')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_clone_name(name):
|
||||||
|
return name + '-clone'
|
||||||
|
|
||||||
|
def compare(self, name, form_data):
|
||||||
|
if self.is_original(): # run compare only for original fields
|
||||||
|
if form_data.get(name) != form_data.get(self.get_clone_name(name)):
|
||||||
|
raise forms.ValidationError(_(u"{0}{1} don't match".format(
|
||||||
|
self.label, pluralize(2))))
|
||||||
|
|
||||||
class PasswordInput(forms.PasswordInput):
|
class PasswordInput(forms.PasswordInput):
|
||||||
class Media:
|
class Media:
|
||||||
js = ('muranodashboard/js/passwordfield.js',)
|
js = ('muranodashboard/js/passwordfield.js',)
|
||||||
@ -98,16 +115,6 @@ class InstanceCountField(forms.IntegerField):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def with_request(func):
|
|
||||||
def update(self, initial):
|
|
||||||
request = initial.get('request')
|
|
||||||
if request:
|
|
||||||
func(self, request, initial)
|
|
||||||
else:
|
|
||||||
raise forms.ValidationError("Can't get a request information")
|
|
||||||
return update
|
|
||||||
|
|
||||||
|
|
||||||
class DataGridField(forms.MultiValueField):
|
class DataGridField(forms.MultiValueField):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['widget'] = DataGridCompound
|
kwargs['widget'] = DataGridCompound
|
||||||
@ -214,64 +221,6 @@ class AZoneChoiceField(forms.ChoiceField):
|
|||||||
self.choices = az_choices
|
self.choices = az_choices
|
||||||
|
|
||||||
|
|
||||||
def get_clone_name(name):
|
|
||||||
return name + '-clone'
|
|
||||||
|
|
||||||
|
|
||||||
def camelize(name):
|
|
||||||
return ''.join([bit.capitalize() for bit in name.split('_')])
|
|
||||||
|
|
||||||
|
|
||||||
def decamelize(name):
|
|
||||||
pat = re.compile(r'([A-Z]*[^A-Z]*)(.*)')
|
|
||||||
bits = []
|
|
||||||
while True:
|
|
||||||
head, tail = re.match(pat, name).groups()
|
|
||||||
bits.append(head)
|
|
||||||
if tail:
|
|
||||||
name = tail
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return '_'.join([bit.lower() for bit in bits])
|
|
||||||
|
|
||||||
|
|
||||||
def explode(string):
|
|
||||||
if not string:
|
|
||||||
return string
|
|
||||||
bits = []
|
|
||||||
while True:
|
|
||||||
head, tail = string[0], string[1:]
|
|
||||||
bits.append(head)
|
|
||||||
if tail:
|
|
||||||
string = tail
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return bits
|
|
||||||
|
|
||||||
|
|
||||||
class UpdatableFieldsForm(forms.Form):
|
|
||||||
def update_fields(self):
|
|
||||||
# duplicate all password fields
|
|
||||||
while True:
|
|
||||||
index, inserted = 0, False
|
|
||||||
for name, field in self.fields.iteritems():
|
|
||||||
if isinstance(field, PasswordField) and not field.has_clone:
|
|
||||||
self.fields.insert(index + 1,
|
|
||||||
get_clone_name(name),
|
|
||||||
field.clone_field())
|
|
||||||
inserted = True
|
|
||||||
break
|
|
||||||
index += 1
|
|
||||||
if not inserted:
|
|
||||||
break
|
|
||||||
|
|
||||||
for name, field in self.fields.iteritems():
|
|
||||||
if hasattr(field, 'update'):
|
|
||||||
field.update(self.initial)
|
|
||||||
if not field.required:
|
|
||||||
field.widget.attrs['placeholder'] = 'Optional'
|
|
||||||
|
|
||||||
|
|
||||||
class BooleanField(forms.BooleanField):
|
class BooleanField(forms.BooleanField):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['widget'] = forms.CheckboxInput(attrs={'class': 'checkbox'})
|
kwargs['widget'] = forms.CheckboxInput(attrs={'class': 'checkbox'})
|
||||||
@ -345,216 +294,3 @@ class DatabaseListField(forms.CharField):
|
|||||||
super(DatabaseListField, self).validate(value)
|
super(DatabaseListField, self).validate(value)
|
||||||
for db_name in value:
|
for db_name in value:
|
||||||
self.validate_mssql_identifier(db_name)
|
self.validate_mssql_identifier(db_name)
|
||||||
|
|
||||||
|
|
||||||
class ServiceConfigurationForm(UpdatableFieldsForm):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(ServiceConfigurationForm, self).__init__(*args, **kwargs)
|
|
||||||
self.attribute_mappings = {}
|
|
||||||
self.insert_fields(self.fields_template)
|
|
||||||
self.initial = kwargs.get('initial', self.initial)
|
|
||||||
self.update_fields()
|
|
||||||
|
|
||||||
EVAL_PREFIX = '$'
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'string': forms.CharField,
|
|
||||||
'boolean': BooleanField,
|
|
||||||
'instance': InstanceCountField,
|
|
||||||
'clusterip': ClusterIPField,
|
|
||||||
'domain': DomainChoiceField,
|
|
||||||
'password': PasswordField,
|
|
||||||
'integer': forms.IntegerField,
|
|
||||||
'databaselist': DatabaseListField,
|
|
||||||
'datagrid': DataGridField,
|
|
||||||
'flavor': FlavorChoiceField,
|
|
||||||
'image': ImageChoiceField,
|
|
||||||
'azone': AZoneChoiceField,
|
|
||||||
'text': (forms.CharField, forms.Textarea)
|
|
||||||
}
|
|
||||||
|
|
||||||
localizable_keys = set(['label', 'help_text', 'error_messages'])
|
|
||||||
|
|
||||||
def init_attribute_mappings(self, field_name, kwargs):
|
|
||||||
def set_mapping(name, value):
|
|
||||||
"""Spawns new dictionaries for each dot found in name."""
|
|
||||||
bits = name.split('.')
|
|
||||||
head, tail, mapping = bits[0], bits[1:], self.attribute_mappings
|
|
||||||
while tail:
|
|
||||||
if not head in mapping:
|
|
||||||
mapping[head] = {}
|
|
||||||
head, tail, mapping = tail[0], tail[1:], mapping[head]
|
|
||||||
mapping[head] = value
|
|
||||||
|
|
||||||
if 'attribute_names' in kwargs:
|
|
||||||
attr_names = kwargs['attribute_names']
|
|
||||||
if type(attr_names) == list:
|
|
||||||
# allow pushing field value to multiple attributes
|
|
||||||
for attr_name in attr_names:
|
|
||||||
set_mapping(attr_name, field_name)
|
|
||||||
elif attr_names:
|
|
||||||
# if attributeNames = false, do not push field value
|
|
||||||
set_mapping(attr_names, field_name)
|
|
||||||
del kwargs['attribute_names']
|
|
||||||
else:
|
|
||||||
# default mapping: field to attr with same name
|
|
||||||
# do not spawn new dictionaries for any dot in field_name
|
|
||||||
self.attribute_mappings[field_name] = field_name
|
|
||||||
|
|
||||||
def init_field_descriptions(self, kwargs):
|
|
||||||
if 'description' in kwargs:
|
|
||||||
del kwargs['description']
|
|
||||||
if 'description_title' in kwargs:
|
|
||||||
del kwargs['description_title']
|
|
||||||
|
|
||||||
def insert_fields(self, field_specs):
|
|
||||||
def process_widget(kwargs, cls, widget):
|
|
||||||
widget = kwargs.get('widget', widget)
|
|
||||||
if widget is None:
|
|
||||||
widget = cls.widget
|
|
||||||
if 'widget_media' in kwargs:
|
|
||||||
media = kwargs['widget_media']
|
|
||||||
del kwargs['widget_media']
|
|
||||||
|
|
||||||
class Widget(widget):
|
|
||||||
class Media:
|
|
||||||
js = media.get('js', ())
|
|
||||||
css = media.get('css', {})
|
|
||||||
widget = Widget
|
|
||||||
if 'widget_attrs' in kwargs:
|
|
||||||
widget = widget(attrs=kwargs['widget_attrs'])
|
|
||||||
del kwargs['widget_attrs']
|
|
||||||
return widget
|
|
||||||
|
|
||||||
def append_properties(cls, kwargs):
|
|
||||||
props = {}
|
|
||||||
for key, value in kwargs.iteritems():
|
|
||||||
if isinstance(value, property):
|
|
||||||
props[key] = value
|
|
||||||
for key in props.keys():
|
|
||||||
del kwargs[key]
|
|
||||||
if props:
|
|
||||||
return type('cls_with_props', (cls,), props)
|
|
||||||
else:
|
|
||||||
return cls
|
|
||||||
|
|
||||||
def append_field(field_spec):
|
|
||||||
_, cls = parse_spec(field_spec['type'], 'type')
|
|
||||||
widget = None
|
|
||||||
if type(cls) == tuple:
|
|
||||||
cls, widget = cls
|
|
||||||
_, kwargs = parse_spec(field_spec)
|
|
||||||
kwargs['widget'] = process_widget(kwargs, cls, widget)
|
|
||||||
cls = append_properties(cls, kwargs)
|
|
||||||
|
|
||||||
self.init_attribute_mappings(field_spec['name'], kwargs)
|
|
||||||
self.init_field_descriptions(kwargs)
|
|
||||||
self.fields.insert(len(self.fields),
|
|
||||||
field_spec['name'],
|
|
||||||
cls(**kwargs))
|
|
||||||
|
|
||||||
def prepare_regexp(regexp):
|
|
||||||
if regexp[0] == '/':
|
|
||||||
groups = re.match(r'^/(.*)/([A-Za-z]*)$', regexp).groups()
|
|
||||||
regexp, flags_str = groups
|
|
||||||
flags = 0
|
|
||||||
for flag in explode(flags_str):
|
|
||||||
flag = flag.upper()
|
|
||||||
if hasattr(re, flag):
|
|
||||||
flags |= getattr(re, flag)
|
|
||||||
return RegexValidator(re.compile(regexp, flags))
|
|
||||||
else:
|
|
||||||
return RegexValidator(re.compile(regexp))
|
|
||||||
|
|
||||||
def is_localizable(keys):
|
|
||||||
return set(keys).intersection(self.localizable_keys)
|
|
||||||
|
|
||||||
def parse_spec(spec, keys=[]):
|
|
||||||
if not type(keys) == list:
|
|
||||||
keys = [keys]
|
|
||||||
key = keys and keys[-1] or None
|
|
||||||
if type(spec) == dict:
|
|
||||||
items = []
|
|
||||||
for k, v in spec.iteritems():
|
|
||||||
if not k in ('type', 'name'):
|
|
||||||
k = decamelize(k)
|
|
||||||
newKey, v = parse_spec(v, keys + [k])
|
|
||||||
if newKey:
|
|
||||||
k = newKey
|
|
||||||
items.append((k, v))
|
|
||||||
return key, dict(items)
|
|
||||||
elif type(spec) == list:
|
|
||||||
return key, [parse_spec(_spec, keys)[1] for _spec in spec]
|
|
||||||
elif type(spec) in (str, unicode) and is_localizable(keys):
|
|
||||||
return key, _(spec)
|
|
||||||
else:
|
|
||||||
if key == 'type':
|
|
||||||
return key, self.types[spec]
|
|
||||||
elif key == 'hidden' and spec is True:
|
|
||||||
return 'widget', forms.HiddenInput
|
|
||||||
elif key == 'regexp_validator':
|
|
||||||
return 'validators', [prepare_regexp(spec)]
|
|
||||||
elif (type(spec) in (str, unicode) and
|
|
||||||
spec[0] == self.EVAL_PREFIX):
|
|
||||||
def _get(field):
|
|
||||||
name = self.add_prefix(spec[1:])
|
|
||||||
return self.data.get(name, False)
|
|
||||||
|
|
||||||
def _set(field, value):
|
|
||||||
# doesn't work - why?
|
|
||||||
# super(field.__class__, field).__setattr__(key, value)
|
|
||||||
field.__dict__[key] = value
|
|
||||||
|
|
||||||
def _del(field):
|
|
||||||
# doesn't work - why?
|
|
||||||
# super(field.__class__, field).__delattr__(key)
|
|
||||||
del field.__dict__[key]
|
|
||||||
|
|
||||||
return key, property(_get, _set, _del)
|
|
||||||
else:
|
|
||||||
return key, spec
|
|
||||||
|
|
||||||
for spec in field_specs:
|
|
||||||
append_field(spec)
|
|
||||||
|
|
||||||
def get_unit_templates(self, data):
|
|
||||||
def parse_spec(spec):
|
|
||||||
if type(spec) == list:
|
|
||||||
return [parse_spec(_spec) for _spec in spec]
|
|
||||||
elif type(spec) == dict:
|
|
||||||
return {parse_spec(k): parse_spec(v)
|
|
||||||
for k, v in spec.iteritems()}
|
|
||||||
elif (type(spec) in (str, unicode) and
|
|
||||||
spec[0] == self.EVAL_PREFIX):
|
|
||||||
return data.get(spec[1:])
|
|
||||||
else:
|
|
||||||
return spec
|
|
||||||
return [parse_spec(spec) for spec in self.service.unit_templates]
|
|
||||||
|
|
||||||
def extract_attributes(self, attributes):
|
|
||||||
def get_data(name):
|
|
||||||
if type(name) == dict:
|
|
||||||
return {k: get_data(v) for k, v in name.iteritems()}
|
|
||||||
else:
|
|
||||||
return self.cleaned_data[name]
|
|
||||||
for attr_name, field_name in self.attribute_mappings.iteritems():
|
|
||||||
attributes[attr_name] = get_data(field_name)
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
form_data = self.cleaned_data
|
|
||||||
|
|
||||||
def compare(name, label):
|
|
||||||
if form_data.get(name) != form_data.get(get_clone_name(name)):
|
|
||||||
raise forms.ValidationError(_(u"{0}{1} don't match".format(
|
|
||||||
label, pluralize(2))))
|
|
||||||
|
|
||||||
for name, field in self.fields.iteritems():
|
|
||||||
if isinstance(field, PasswordField) and field.is_original():
|
|
||||||
compare(name, field.label)
|
|
||||||
|
|
||||||
if hasattr(field, 'postclean'):
|
|
||||||
value = field.postclean(self, form_data)
|
|
||||||
if value:
|
|
||||||
self.cleaned_data[name] = value
|
|
||||||
|
|
||||||
return self.cleaned_data
|
|
258
muranodashboard/panel/services/forms.py
Normal file
258
muranodashboard/panel/services/forms.py
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
# Copyright (c) 2013 Mirantis, 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 re
|
||||||
|
from django import forms
|
||||||
|
from django.core.validators import RegexValidator
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
import muranodashboard.panel.services.fields as fields
|
||||||
|
import muranodashboard.panel.services.helpers as helpers
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatableFieldsForm(forms.Form):
|
||||||
|
def update_fields(self):
|
||||||
|
# duplicate all password fields
|
||||||
|
while True:
|
||||||
|
index, inserted = 0, False
|
||||||
|
for name, field in self.fields.iteritems():
|
||||||
|
if isinstance(field, fields.PasswordField) and \
|
||||||
|
not field.has_clone:
|
||||||
|
self.fields.insert(index + 1,
|
||||||
|
field.get_clone_name(name),
|
||||||
|
field.clone_field())
|
||||||
|
inserted = True
|
||||||
|
break
|
||||||
|
index += 1
|
||||||
|
if not inserted:
|
||||||
|
break
|
||||||
|
|
||||||
|
for name, field in self.fields.iteritems():
|
||||||
|
if hasattr(field, 'update'):
|
||||||
|
field.update(self.initial)
|
||||||
|
if not field.required:
|
||||||
|
field.widget.attrs['placeholder'] = 'Optional'
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceConfigurationForm(UpdatableFieldsForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ServiceConfigurationForm, self).__init__(*args, **kwargs)
|
||||||
|
self.attribute_mappings = {}
|
||||||
|
self.insert_fields(self.fields_template)
|
||||||
|
self.initial = kwargs.get('initial', self.initial)
|
||||||
|
self.update_fields()
|
||||||
|
|
||||||
|
EVAL_PREFIX = '$'
|
||||||
|
|
||||||
|
types = {
|
||||||
|
'string': forms.CharField,
|
||||||
|
'boolean': fields.BooleanField,
|
||||||
|
'instance': fields.InstanceCountField,
|
||||||
|
'clusterip': fields.ClusterIPField,
|
||||||
|
'domain': fields.DomainChoiceField,
|
||||||
|
'password': fields.PasswordField,
|
||||||
|
'integer': forms.IntegerField,
|
||||||
|
'databaselist': fields.DatabaseListField,
|
||||||
|
'datagrid': fields.DataGridField,
|
||||||
|
'flavor': fields.FlavorChoiceField,
|
||||||
|
'image': fields.ImageChoiceField,
|
||||||
|
'azone': fields.AZoneChoiceField,
|
||||||
|
'text': (forms.CharField, forms.Textarea)
|
||||||
|
}
|
||||||
|
|
||||||
|
localizable_keys = set(['label', 'help_text', 'error_messages'])
|
||||||
|
|
||||||
|
def init_attribute_mappings(self, field_name, kwargs):
|
||||||
|
def set_mapping(name, value):
|
||||||
|
"""Spawns new dictionaries for each dot found in name."""
|
||||||
|
bits = name.split('.')
|
||||||
|
head, tail, mapping = bits[0], bits[1:], self.attribute_mappings
|
||||||
|
while tail:
|
||||||
|
if not head in mapping:
|
||||||
|
mapping[head] = {}
|
||||||
|
head, tail, mapping = tail[0], tail[1:], mapping[head]
|
||||||
|
mapping[head] = value
|
||||||
|
|
||||||
|
if 'attribute_names' in kwargs:
|
||||||
|
attr_names = kwargs['attribute_names']
|
||||||
|
if type(attr_names) == list:
|
||||||
|
# allow pushing field value to multiple attributes
|
||||||
|
for attr_name in attr_names:
|
||||||
|
set_mapping(attr_name, field_name)
|
||||||
|
elif attr_names:
|
||||||
|
# if attributeNames = false, do not push field value
|
||||||
|
set_mapping(attr_names, field_name)
|
||||||
|
del kwargs['attribute_names']
|
||||||
|
else:
|
||||||
|
# default mapping: field to attr with same name
|
||||||
|
# do not spawn new dictionaries for any dot in field_name
|
||||||
|
self.attribute_mappings[field_name] = field_name
|
||||||
|
|
||||||
|
def init_field_descriptions(self, kwargs):
|
||||||
|
if 'description' in kwargs:
|
||||||
|
del kwargs['description']
|
||||||
|
if 'description_title' in kwargs:
|
||||||
|
del kwargs['description_title']
|
||||||
|
|
||||||
|
def insert_fields(self, field_specs):
|
||||||
|
def process_widget(kwargs, cls, widget):
|
||||||
|
widget = kwargs.get('widget', widget)
|
||||||
|
if widget is None:
|
||||||
|
widget = cls.widget
|
||||||
|
if 'widget_media' in kwargs:
|
||||||
|
media = kwargs['widget_media']
|
||||||
|
del kwargs['widget_media']
|
||||||
|
|
||||||
|
class Widget(widget):
|
||||||
|
class Media:
|
||||||
|
js = media.get('js', ())
|
||||||
|
css = media.get('css', {})
|
||||||
|
widget = Widget
|
||||||
|
if 'widget_attrs' in kwargs:
|
||||||
|
widget = widget(attrs=kwargs['widget_attrs'])
|
||||||
|
del kwargs['widget_attrs']
|
||||||
|
return widget
|
||||||
|
|
||||||
|
def append_properties(cls, kwargs):
|
||||||
|
props = {}
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
if isinstance(value, property):
|
||||||
|
props[key] = value
|
||||||
|
for key in props.keys():
|
||||||
|
del kwargs[key]
|
||||||
|
if props:
|
||||||
|
return type('cls_with_props', (cls,), props)
|
||||||
|
else:
|
||||||
|
return cls
|
||||||
|
|
||||||
|
def append_field(field_spec):
|
||||||
|
cls = parse_spec(field_spec['type'], 'type')[1]
|
||||||
|
widget = None
|
||||||
|
if type(cls) == tuple:
|
||||||
|
cls, widget = cls
|
||||||
|
kwargs = parse_spec(field_spec)[1]
|
||||||
|
kwargs['widget'] = process_widget(kwargs, cls, widget)
|
||||||
|
cls = append_properties(cls, kwargs)
|
||||||
|
|
||||||
|
self.init_attribute_mappings(field_spec['name'], kwargs)
|
||||||
|
self.init_field_descriptions(kwargs)
|
||||||
|
self.fields.insert(len(self.fields),
|
||||||
|
field_spec['name'],
|
||||||
|
cls(**kwargs))
|
||||||
|
|
||||||
|
def prepare_regexp(regexp):
|
||||||
|
if regexp[0] == '/':
|
||||||
|
groups = re.match(r'^/(.*)/([A-Za-z]*)$', regexp).groups()
|
||||||
|
regexp, flags_str = groups
|
||||||
|
flags = 0
|
||||||
|
for flag in helpers.explode(flags_str):
|
||||||
|
flag = flag.upper()
|
||||||
|
if hasattr(re, flag):
|
||||||
|
flags |= getattr(re, flag)
|
||||||
|
return RegexValidator(re.compile(regexp, flags))
|
||||||
|
else:
|
||||||
|
return RegexValidator(re.compile(regexp))
|
||||||
|
|
||||||
|
def is_localizable(keys):
|
||||||
|
return set(keys).intersection(self.localizable_keys)
|
||||||
|
|
||||||
|
def parse_spec(spec, keys=[]):
|
||||||
|
if not type(keys) == list:
|
||||||
|
keys = [keys]
|
||||||
|
key = keys and keys[-1] or None
|
||||||
|
if type(spec) == dict:
|
||||||
|
items = []
|
||||||
|
for k, v in spec.iteritems():
|
||||||
|
if not k in ('type', 'name'):
|
||||||
|
k = helpers.decamelize(k)
|
||||||
|
newKey, v = parse_spec(v, keys + [k])
|
||||||
|
if newKey:
|
||||||
|
k = newKey
|
||||||
|
items.append((k, v))
|
||||||
|
return key, dict(items)
|
||||||
|
elif type(spec) == list:
|
||||||
|
return key, [parse_spec(_spec, keys)[1] for _spec in spec]
|
||||||
|
elif type(spec) in (str, unicode) and is_localizable(keys):
|
||||||
|
return key, _(spec)
|
||||||
|
else:
|
||||||
|
if key == 'type':
|
||||||
|
return key, self.types[spec]
|
||||||
|
elif key == 'hidden' and spec is True:
|
||||||
|
return 'widget', forms.HiddenInput
|
||||||
|
elif key == 'regexp_validator':
|
||||||
|
return 'validators', [prepare_regexp(spec)]
|
||||||
|
elif (type(spec) in (str, unicode) and
|
||||||
|
spec[0] == self.EVAL_PREFIX):
|
||||||
|
def _get(field):
|
||||||
|
"""First try to get value from cleaned data, if none
|
||||||
|
found, use raw data."""
|
||||||
|
data = getattr(self, 'cleaned_data', None)
|
||||||
|
value = data and data.get(spec[1:], None)
|
||||||
|
if value is None:
|
||||||
|
name = self.add_prefix(spec[1:])
|
||||||
|
value = self.data.get(name, None)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _set(field, value):
|
||||||
|
# doesn't work - why?
|
||||||
|
# super(field.__class__, field).__setattr__(key, value)
|
||||||
|
field.__dict__[key] = value
|
||||||
|
|
||||||
|
def _del(field):
|
||||||
|
# doesn't work - why?
|
||||||
|
# super(field.__class__, field).__delattr__(key)
|
||||||
|
del field.__dict__[key]
|
||||||
|
|
||||||
|
return key, property(_get, _set, _del)
|
||||||
|
else:
|
||||||
|
return key, spec
|
||||||
|
|
||||||
|
for spec in field_specs:
|
||||||
|
append_field(spec)
|
||||||
|
|
||||||
|
def get_unit_templates(self, data):
|
||||||
|
def parse_spec(spec):
|
||||||
|
if type(spec) == list:
|
||||||
|
return [parse_spec(_spec) for _spec in spec]
|
||||||
|
elif type(spec) == dict:
|
||||||
|
return {parse_spec(k): parse_spec(v)
|
||||||
|
for k, v in spec.iteritems()}
|
||||||
|
elif (type(spec) in (str, unicode) and
|
||||||
|
spec[0] == self.EVAL_PREFIX):
|
||||||
|
return data.get(spec[1:])
|
||||||
|
else:
|
||||||
|
return spec
|
||||||
|
return [parse_spec(spec) for spec in self.service.unit_templates]
|
||||||
|
|
||||||
|
def extract_attributes(self, attributes):
|
||||||
|
def get_data(name):
|
||||||
|
if type(name) == dict:
|
||||||
|
return {k: get_data(v) for k, v in name.iteritems()}
|
||||||
|
else:
|
||||||
|
return self.cleaned_data[name]
|
||||||
|
for attr_name, field_name in self.attribute_mappings.iteritems():
|
||||||
|
attributes[attr_name] = get_data(field_name)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
form_data = self.cleaned_data
|
||||||
|
|
||||||
|
for name, field in self.fields.iteritems():
|
||||||
|
if isinstance(field, fields.PasswordField):
|
||||||
|
field.compare(name, form_data)
|
||||||
|
|
||||||
|
if hasattr(field, 'postclean'):
|
||||||
|
value = field.postclean(self, form_data)
|
||||||
|
if value:
|
||||||
|
self.cleaned_data[name] = value
|
||||||
|
|
||||||
|
return self.cleaned_data
|
46
muranodashboard/panel/services/helpers.py
Normal file
46
muranodashboard/panel/services/helpers.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Copyright (c) 2013 Mirantis, 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 re
|
||||||
|
|
||||||
|
|
||||||
|
def camelize(name):
|
||||||
|
return ''.join([bit.capitalize() for bit in name.split('_')])
|
||||||
|
|
||||||
|
|
||||||
|
def decamelize(name):
|
||||||
|
pat = re.compile(r'([A-Z]*[^A-Z]*)(.*)')
|
||||||
|
bits = []
|
||||||
|
while True:
|
||||||
|
head, tail = re.match(pat, name).groups()
|
||||||
|
bits.append(head)
|
||||||
|
if tail:
|
||||||
|
name = tail
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return '_'.join([bit.lower() for bit in bits])
|
||||||
|
|
||||||
|
|
||||||
|
def explode(string):
|
||||||
|
if not string:
|
||||||
|
return string
|
||||||
|
bits = []
|
||||||
|
while True:
|
||||||
|
head, tail = string[0], string[1:]
|
||||||
|
bits.append(head)
|
||||||
|
if tail:
|
||||||
|
string = tail
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return bits
|
Loading…
Reference in New Issue
Block a user