From 2857c8f4a3eeb6c2db1d2572422ca4c0673ce1ba Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Thu, 26 May 2016 00:49:07 +0300 Subject: [PATCH] Fetch class descendants in the dynamic_ui from glare Before this change whenever a murano-class dependency was specified in the ui definition of a package, only packages that contained those specific fqns were fetched from API, even though the information about inheritance is available in the glare back-end. With this change dashboard also fetches all the packages, that have classes, that inherit from the specified package. This change also alters the way absence of a package is handled. It's now specified as an empty field with explanation, rather then a django message. This allows having only partial dependencies and skipping optional dependencies altogether Change-Id: I6632f72ca260c26a116b3aa4608d9592d0f1cbce Closes-Bug: #1585812 --- muranodashboard/api/packages.py | 8 ++ muranodashboard/dynamic_ui/fields.py | 92 ++++++++++++++----- ...tract-base-class-fix-7cb06a0924b973f3.yaml | 4 + 3 files changed, 80 insertions(+), 24 deletions(-) create mode 100644 releasenotes/notes/abstract-base-class-fix-7cb06a0924b973f3.yaml diff --git a/muranodashboard/api/packages.py b/muranodashboard/api/packages.py index 95820e7a7..df691db2f 100644 --- a/muranodashboard/api/packages.py +++ b/muranodashboard/api/packages.py @@ -54,6 +54,14 @@ def package_list(request, marker=None, filters=None, paginate=False, return packages, has_more_data +def apps_that_inherit(request, fqn): + glare = getattr(settings, 'MURANO_USE_GLARE', False) + if not glare: + return [] + apps = api.muranoclient(request).packages.filter(inherits=fqn) + return apps + + def app_by_fqn(request, fqn, catalog=True): apps = api.muranoclient(request).packages.filter(fqn=fqn, catalog=catalog) try: diff --git a/muranodashboard/dynamic_ui/fields.py b/muranodashboard/dynamic_ui/fields.py index 4b04bf2e0..a15a3b95d 100644 --- a/muranodashboard/dynamic_ui/fields.py +++ b/muranodashboard/dynamic_ui/fields.py @@ -20,7 +20,7 @@ import re from django.core.urlresolvers import reverse from django.core import validators as django_validator from django import forms -from django.http import Http404 +from django.forms import widgets from django.template import defaultfilters from django.utils.encoding import force_text from django.utils import html @@ -524,24 +524,37 @@ class DatabaseListField(CharField): self.validate_mssql_identifier(db_name) +class ErrorWidget(widgets.Widget): + + def __init__(self, *args, **kwargs): + self.message = kwargs.pop( + 'message', _("There was an error initialising this field.")) + super(ErrorWidget, self).__init__(*args, **kwargs) + + def render(self, name, value, attrs=None): + return "
{message}
".format( + name=name, message=self.message) + + +class MuranoTypeWidget(hz_forms.fields.DynamicSelectWidget): + def __init__(self, attrs=None, **kwargs): + if attrs is None: + attrs = {'class': 'murano_add_select'} + else: + attrs.setdefault('class', '') + attrs['class'] += ' murano_add_select' + super(MuranoTypeWidget, self).__init__(attrs=attrs, **kwargs) + + class Media(object): + js = ('muranodashboard/js/add-select.js',) + + def make_select_cls(fqns): if not isinstance(fqns, (tuple, list)): fqns = (fqns,) - class Widget(hz_forms.fields.DynamicSelectWidget): - def __init__(self, attrs=None, **kwargs): - if attrs is None: - attrs = {'class': 'murano_add_select'} - else: - attrs.setdefault('class', '') - attrs['class'] += ' murano_add_select' - super(Widget, self).__init__(attrs=attrs, **kwargs) - - class Media(object): - js = ('muranodashboard/js/add-select.js',) - class DynamicSelect(hz_forms.DynamicChoiceField, CustomPropertiesField): - widget = Widget + widget = MuranoTypeWidget def __init__(self, empty_value_message=None, *args, **kwargs): super(DynamicSelect, self).__init__(*args, **kwargs) @@ -552,21 +565,52 @@ def make_select_cls(fqns): @with_request def update(self, request, environment_id, **kwargs): + matching_classes = [] + fqns_seen = set() + # NOTE(kzaitsev): it's possible to have a private + # and public apps with the same fqn, however the engine would + # currently favor private package. Therefore we should squash + # these until we devise a better way to work with this + # situation and versioning + + for class_fqn in fqns: + app_found = pkg_api.app_by_fqn(request, class_fqn) + if app_found: + fqns_seen.add(app_found.fully_qualified_name) + matching_classes.append(app_found) + + apps_found = pkg_api.apps_that_inherit(request, class_fqn) + for app in apps_found: + if app.fully_qualified_name in fqns_seen: + continue + fqns_seen.add(app.fully_qualified_name) + matching_classes.append(app) + + if not matching_classes: + msg = _( + "Couldn't find any apps, required for this field.\n" + "Tried: {fqns}").format(fqns=', '.join(fqns)) + self.widget = ErrorWidget(message=msg) + + # NOTE(kzaitsev): this closure is needed to allow us have custom + # logic when clicking add button def _make_link(): ns_url = 'horizon:murano:catalog:add' + ns_url_args = (environment_id, False, True) - def _reverse(_fqn): - _app = pkg_api.app_by_fqn(request, _fqn) - if _app is None: - msg = "Application with FQN='{0}' doesn't exist" - messages.error(request, msg.format(_fqn)) - raise Http404(msg.format(_fqn)) - args = (_app.id, environment_id, False, True) - return _app.name, reverse(ns_url, args=args) - return json.dumps([_reverse(cls) for cls in fqns]) + # This will prevent horizon from adding an extra '+' button + if not matching_classes: + return '' + + return json.dumps([ + (app.name, reverse(ns_url, args=((app.id,) + ns_url_args))) + for app in matching_classes]) self.widget.add_item_link = _make_link - apps = env_api.service_list_by_fqns(request, environment_id, fqns) + + apps = env_api.service_list_by_fqns( + request, environment_id, + [app.fully_qualified_name for app in matching_classes]) choices = [('', self.empty_value_message)] choices.extend([(app['?']['id'], html.escape(app.name)) for app in apps]) diff --git a/releasenotes/notes/abstract-base-class-fix-7cb06a0924b973f3.yaml b/releasenotes/notes/abstract-base-class-fix-7cb06a0924b973f3.yaml new file mode 100644 index 000000000..fc9f08d04 --- /dev/null +++ b/releasenotes/notes/abstract-base-class-fix-7cb06a0924b973f3.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Specifying a base class in the ui definition now also fetches all the packages with classes + that inherit from that class, when glare is used.