diff options
author | Jason Gerard DeRose <jderose@redhat.com> | 2009-12-09 09:09:53 -0700 |
---|---|---|
committer | Jason Gerard DeRose <jderose@redhat.com> | 2009-12-10 08:29:15 -0700 |
commit | b6e4972e7f6aa08e0392a2cf441b60ab0e7d88b7 (patch) | |
tree | 7e5329a51af169ce34a7d275a1bbd63c1e31c026 /ipawebui | |
parent | d08b8858ddc3bf6265f6ea8acae6661b9fff5112 (diff) | |
download | freeipa-b6e4972e7f6aa08e0392a2cf441b60ab0e7d88b7.tar.gz freeipa-b6e4972e7f6aa08e0392a2cf441b60ab0e7d88b7.tar.xz freeipa-b6e4972e7f6aa08e0392a2cf441b60ab0e7d88b7.zip |
Take 2: Extensible return values and validation; steps toward a single output_for_cli(); enable more webUI stuff
Diffstat (limited to 'ipawebui')
-rw-r--r-- | ipawebui/__init__.py | 2 | ||||
-rw-r--r-- | ipawebui/engine.py | 56 | ||||
-rw-r--r-- | ipawebui/widgets.py | 186 |
3 files changed, 222 insertions, 22 deletions
diff --git a/ipawebui/__init__.py b/ipawebui/__init__.py index 9f86da344..c7ebaa870 100644 --- a/ipawebui/__init__.py +++ b/ipawebui/__init__.py @@ -1,6 +1,6 @@ # Authors: Jason Gerard DeRose <jderose@redhat.com> # -# Copyright (C) 2008 Red Hat +# Copyright (C) 2009 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or diff --git a/ipawebui/engine.py b/ipawebui/engine.py index ea0905d49..a90a450d0 100644 --- a/ipawebui/engine.py +++ b/ipawebui/engine.py @@ -1,6 +1,6 @@ # Authors: Jason Gerard DeRose <jderose@redhat.com> # -# Copyright (C) 2008 Red Hat +# Copyright (C) 2009 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or @@ -21,6 +21,7 @@ Engine to map ipalib plugins to wehjit widgets. """ from controllers import Command +from ipalib import crud class ParamMapper(object): def __init__(self, api, app): @@ -28,7 +29,7 @@ class ParamMapper(object): self._app = app self.__methods = dict() for name in dir(self): - if name.startswith('_') or name.endswith('_'): + if name.startswith('_'): continue attr = getattr(self, name) if not callable(attr): @@ -40,13 +41,12 @@ class ParamMapper(object): if key in self.__methods: method = self.__methods[key] else: - #raise Warning('No ParamMapper for %r' % key) method = self.Str return method(param, cmd) def Str(self, param, cmd): return self._app.new('TextRow', - label=param.cli_name, + label=param.label, name=param.name, required=param.required, value=param.default, @@ -61,7 +61,7 @@ class ParamMapper(object): def Flag(self, param, cmd): return self._app.new('SelectRow', name=param.name, - label=param.cli_name, + label=param.label, ) @@ -128,23 +128,43 @@ class Engine(object): return page def build_page(self, cmd): - page = self.app.new('PageApp', + page = self.app.new('PageCmd', + cmd=cmd, id=cmd.name, title=cmd.summary.rstrip('.'), ) - #page.form.action = self.app.url + '__json__' - page.actions.add( - self.app.new('Submit') + page.form.action = page.url + page.form.method = 'GET' + page.form.add( + self.app.new('Hidden', name='__mode__', value='output') ) - table = self.app.new('FieldTable') - page.view.add(table) - for param in cmd.params(): - if param.exclude and 'webui' in param.exclude: - continue - field = self.param_mapper(param, cmd) - table.add(field) + page.notification = self.app.new('Notification') + page.view.add(page.notification) + page.prompt = self.make_prompt(cmd) + page.show = self.make_show(cmd) + self.conditional('input', page.actions, self.app.new('Submit')) + self.conditional('input', page.view, page.prompt) + self.conditional('output', page.view, page.show) + return page + + def conditional(self, mode, parent, *children): + conditional = self.app.new('Conditional', mode=mode) + conditional.add(*children) + parent.add(conditional) - page.form.action = '/'.join([self.jsonurl, cmd.name]) + def make_prompt(self, cmd): + table = self.app.new('FieldTable') + for param in self._iter_params(cmd.params): + table.add( + self.param_mapper(param, cmd) + ) + return table + def make_show(self, cmd): + return self.app.new('Output') - return page + def _iter_params(self, namespace): + for param in namespace(): + if param.exclude and 'webui' in param.exclude: + continue + yield param diff --git a/ipawebui/widgets.py b/ipawebui/widgets.py index 74b9d7e0c..1cf1adb97 100644 --- a/ipawebui/widgets.py +++ b/ipawebui/widgets.py @@ -1,6 +1,6 @@ # Authors: Jason Gerard DeRose <jderose@redhat.com> # -# Copyright (C) 2008 Red Hat +# Copyright (C) 2009 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or @@ -21,9 +21,10 @@ Custom IPA widgets. """ from textwrap import dedent -from wehjit import Collection, base, freeze +from wehjit import Collection, base, freeze, builtins from wehjit.util import Alternator from wehjit import Static, Dynamic, StaticProp, DynamicProp +from ipaserver.rpcserver import extract_query class IPAPlugins(base.Container): @@ -189,6 +190,14 @@ class Command(base.Widget): <td py:content="repr(option)" /> </tr> + <tr py:if="plugin.output" class="${row.next()}"> + <th colspan="2" py:content="'output (%d)' % len(plugin.output)" /> + </tr> + <tr py:for="param in plugin.output()" class="${row.next()}"> + <td py:content="param.name"/> + <td py:content="repr(param)" /> + </tr> + </table> """ @@ -211,7 +220,7 @@ class Object(base.Widget): <th colspan="2" py:content="'params (%d)' % len(plugin.params)" /> </tr> <tr py:for="param in plugin.params()" class="${row.next()}"> - <td py:content="param.name"/> + <td>${"param.name"}:</td> <td py:content="repr(param)" /> </tr> @@ -219,6 +228,171 @@ class Object(base.Widget): """ + +class Conditional(base.Container): + + mode = Static('mode', default='input') + + @DynamicProp + def page_mode(self): + if self.page is None: + return + return self.page.mode + + xml = """ + <div + xmlns:py="http://genshi.edgewall.org/" + py:if="mode == page_mode" + py:strip="True" + > + <child py:for="child in children" py:replace="child.generate()" /> + </div> + """ + + +class Output(base.Widget): + """ + Shows attributes form an LDAP entry. + """ + + order = Dynamic('order') + labels = Dynamic('labels') + result = Dynamic('result') + + xml = """ + <div + xmlns:py="http://genshi.edgewall.org/" + class="${klass}" + id="${id}" + > + <table py:if="isinstance(result, dict)"> + <tr py:for="key in order" py:if="key in result"> + <th py:content="labels[key]" /> + <td py:content="result[key]" /> + </tr> + </table> + + <table + py:if="isinstance(result, (list, tuple)) and len(result) > 0" + > + <tr> + <th + py:for="key in order" + py:if="key in result[0]" + py:content="labels[key]" + /> + </tr> + <tr py:for="entry in result"> + <td + py:for="key in order" + py:if="key in result[0]" + py:content="entry[key]" + /> + </tr> + </table> + </div> + """ + + style = ( + ('table', ( + ('empty-cells', 'show'), + ('border-collapse', 'collapse'), + )), + + ('th', ( + ('text-align', 'right'), + ('padding', '.25em 0.5em'), + ('line-height', '%(height_bar)s'), + ('vertical-align', 'top'), + )), + + ('td', ( + ('padding', '.25em'), + ('vertical-align', 'top'), + ('text-align', 'left'), + ('line-height', '%(height_bar)s'), + )), + ) + + +class Hidden(base.Field): + xml = """ + <input + xmlns:py="http://genshi.edgewall.org/" + type="hidden" + name="${name}" + /> + """ + + +class Notification(base.Widget): + message = Dynamic('message') + error = Dynamic('error', default=False) + + @property + def extra_css_classes(self): + if self.error: + yield 'error' + else: + yield 'okay' + + xml = """ + <p + xmlns:py="http://genshi.edgewall.org/" + class="${klass}" + id="${id}" + py:if="message" + py:content="message" + /> + """ + + style = ( + ('', ( + ('font-weight', 'bold'), + ('-moz-border-radius', '100%%'), + ('background-color', '#eee'), + ('border', '2px solid #966'), + ('padding', '0.5em'), + ('text-align', 'center'), + )), + ) + + +class PageCmd(builtins.PageApp): + cmd = Static('cmd') + mode = Dynamic('mode', default='input') + + def controller(self, environ): + query = extract_query(environ) + self.mode = query.pop('__mode__', 'input') + if self.mode == 'input': + return + soft = self.cmd.soft_validate(query) + errors = soft['errors'] + values = soft['values'] + if errors: + self.mode = 'input' + for key in self.form: + if key in errors: + self.form[key].error = errors[key] + if key in values: + self.form[key].value = values[key] + return + output = self.cmd(**query) + if isinstance(output, dict) and 'summary' in output: + self.notification.message = output['summary'] + params = self.cmd.output_params + if params: + order = list(params) + labels = dict((p.name, p.label) for p in params()) + else: + order = sorted(entry) + labels = dict((k, k) for k in order) + self.show.order = order + self.show.labels = labels + self.show.result = output.get('result') + + def create_widgets(): widgets = Collection('freeIPA') widgets.register_builtins() @@ -227,6 +401,12 @@ def create_widgets(): widgets.register(IPAPlugins) widgets.register(Command) widgets.register(Object) + widgets.register(Conditional) + widgets.register(Output) + widgets.register(Hidden) + widgets.register(Notification) + + widgets.register(PageCmd) freeze(widgets) |