Ability to reference single object several times

This commit introduces ref(templateName [, parameterName] [, idOnly])
YAQL function was added to UI definition DSL. This function evaluates
template templateName and fixes the result in parameters under
parameterName key (or templateName if the second parameter was omitted).
Then it generates object ID and places it into ?/id field. On the first
use of parameterName or if idOnly is false the function will return
the whole object structure. On subsequent calls or if idOnly is true it
will return the ID that was generated upon the first call.
Thus the function brings ability to reference single object several
times.

ID generation is done in a safe way with a special temporary type that
wraps string ID and replaced with string representation upon
serialization. This way it is still impossible for a developer to
explicitly provide ID value in the template (it is still going to be
overwritten because it is not of an ObjectID type and it is impossible
to create instance of ObjectID from yaql expression)

Change-Id: Idf114113a73d00ab2132bd085157ca01e94dae3e
This commit is contained in:
Stan Lagun 2016-10-21 06:21:53 -07:00
parent cddd563994
commit 201eb75ae4
5 changed files with 63 additions and 8 deletions

View File

@ -24,6 +24,11 @@ from django.core import validators
_LOCALIZABLE_KEYS = set(['label', 'help_text', 'error_messages'])
class ObjectID(object):
def __init__(self):
self.object_id = str(uuid.uuid4())
def is_localizable(keys):
return set(keys).intersection(_LOCALIZABLE_KEYS)
@ -101,9 +106,12 @@ def evaluate(value, context):
def insert_hidden_ids(application):
def wrap(k, v):
if k == '?':
if k == '?' and isinstance(v, dict) and not isinstance(
v.get('id'), ObjectID):
v['id'] = str(uuid.uuid4())
return k, v
elif isinstance(v, ObjectID):
return k, v.object_id
else:
return rec(k), rec(v)

View File

@ -70,6 +70,7 @@ class Service(object):
self.application = application
self.context = legacy.create_context()
self.context['?service'] = self
yaql_functions.register(self.context)
params = parameters or {}
@ -123,15 +124,16 @@ class Service(object):
[])
def extract_attributes(self):
self.context['$'] = self.cleaned_data
context = self.context.create_child_context()
context['$'] = self.cleaned_data
for name, template in six.iteritems(self.templates):
self.context[name] = template
context[name] = template
if semantic_version.Version.coerce(self.spec_version) \
>= semantic_version.Version.coerce('2.2'):
management_form = catalog_forms.WF_MANAGEMENT_NAME
name = self.context['$'][management_form]['application_name']
name = self.cleaned_data[management_form]['application_name']
self.application['?']['name'] = name
attributes = helpers.evaluate(self.application, self.context)
attributes = helpers.evaluate(self.application, context)
return attributes
def get_data(self, form_name, expr, data=None):

View File

@ -71,7 +71,39 @@ def _name(context):
return name
@specs.parameter('template_name', yaqltypes.String())
@specs.parameter('parameter_name', yaqltypes.String(nullable=True))
@specs.parameter('id_only', yaqltypes.PythonType(bool, nullable=True))
def _ref(context, template_name, parameter_name=None, id_only=None):
service = context['?service']
data = None
if not parameter_name:
parameter_name = template_name
# add special symbol to avoid collisions with regular parameters
# and prevent it from overwriting '?service' context variable
parameter_name = '#' + parameter_name
if parameter_name in service.parameters:
data = service.parameters[parameter_name]
elif template_name in service.templates:
data = helpers.evaluate(service.templates[template_name], context)
service.parameters[parameter_name] = data
if not isinstance(data, dict):
return None
if not isinstance(data.get('?', {}).get('id'), helpers.ObjectID):
data.setdefault('?', {})['id'] = helpers.ObjectID()
if id_only is None:
id_only = False
elif id_only is None:
id_only = True
if id_only:
return data['?']['id']
else:
return data
def register(context):
context.register_function(_repeat, 'repeat')
context.register_function(_generate_hostname, 'generateHostname')
context.register_function(_name, 'name')
context.register_function(_ref, 'ref')

View File

@ -126,7 +126,6 @@ class TestService(helpers.APITestCase):
self.assertIsInstance(attributes, dict)
self.assertEqual(expected['?']['type'], attributes['?']['type'])
self.assertEqual(expected['?']['name'], attributes['?']['name'])
self.assertEqual(cleaned_data, service.context['$'])
self.assertEqual('foobar', service.application['?']['name'])
def test_get_and_set_cleaned_data(self):

View File

@ -9,6 +9,7 @@ features:
expressions. The difference between Templates and Parameters is that
Parameters are evaluated once before form render whereas Templates are
evaluated on each access.
- >
It is possible to specify static action (MuranoPL method) that is going to
be called before form is rendered. This allows MuranoPL class to provide
@ -22,10 +23,23 @@ features:
same package that was used to obtain UI definition file. The method
must return a dictionary which will be combined with Parameters that are
already present in the file.
- >
``ref(templateName [, parameterName] [, idOnly])`` YAQL function was added
to UI definition DSL. This function evaluates template ``templateName`` and
fixes the result in parameters under ``parameterName`` key (or
``templateName`` if the second parameter was omitted). Then it generates
object ID and places it into ``?/id`` field. On the first use of
``parameterName`` or if ``idOnly`` is ``false`` the function will return
the whole object structure. On subsequent calls or if ``idOnly`` is
``true`` it will return the ID that was generated upon the first call.
Thus the function brings ability to reference single object several times.
- >
``choice`` field type now can accept list of choices in a form of
dictionary. I.e. in addition to [[key1, value1], [key2, value2]] one can
provide {key1: value1, key2: value2}
dictionary. I.e. in addition to ``[[key1, value1], [key2, value2]]`` one
can provide ``{key1: value1, key2: value2}``
- >
UI definition version was bumped to ``2.4``. If application is going to
use Parameters it should indicate it by setting the version in UI file.