This patch enables webUI Create-Retrieve-Updated-Delete-Search
operations for all api.Object plugins that:

  1. implement all the required CRUDS methods
  2. have a primary_key

Last night I realized that the upgrade to wehjit 0.2.0 broke the
installer, so I hurried this patch a bit, left out some niceties that
still need a bit more testing and tweaking.
>From 073cea91cca082ec0f8d4d0644ff9db1961bfba9 Mon Sep 17 00:00:00 2001
From: Jason Gerard DeRose <jder...@redhat.com>
Date: Tue, 26 Jan 2010 06:39:00 -0700
Subject: [PATCH] Enabled CRUDS in webUI using wehjit 0.2.0

---
 ipalib/plugable.py         |    2 +
 ipalib/plugins/baseldap.py |    7 +-
 ipalib/plugins/user.py     |    3 +
 ipaserver/rpcserver.py     |    3 +
 ipawebui/engine.py         |  124 +++++++++++-------
 ipawebui/widgets.py        |  301 ++++++++++++++++++++++----------------------
 6 files changed, 241 insertions(+), 199 deletions(-)

diff --git a/ipalib/plugable.py b/ipalib/plugable.py
index 3ee2bd5..ecccb79 100644
--- a/ipalib/plugable.py
+++ b/ipalib/plugable.py
@@ -531,6 +531,8 @@ class API(DictProxy):
             value = getattr(options, key, None)
             if value is not None:
                 overrides[key] = value
+        if hasattr(options, 'prod'):
+            overrides['webui_prod'] = options.prod
         if context is not None:
             overrides['context'] = context
         self.bootstrap(**overrides)
diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py
index 17db048..eeea7a6 100644
--- a/ipalib/plugins/baseldap.py
+++ b/ipalib/plugins/baseldap.py
@@ -133,6 +133,7 @@ class LDAPCreate(crud.Create):
     """
     Create a new entry in LDAP.
     """
+
     takes_options = (
         Flag('raw',
             cli_name='raw',
@@ -142,6 +143,7 @@ class LDAPCreate(crud.Create):
         Flag('all',
             cli_name='all',
             doc='retrieve all attributes',
+            exclude='webui',
         ),
         Str('addattr*', validate_add_attribute,
             cli_name='addattr',
@@ -291,14 +293,17 @@ class LDAPUpdate(LDAPQuery, crud.Update):
     """
     Update an LDAP entry.
     """
+
     takes_options = (
         Flag('raw',
             cli_name='raw',
             doc='print entries as they are stored in LDAP',
+            exclude='webui',
         ),
         Flag('all',
             cli_name='all',
             doc='retrieve all attributes',
+            exclude='webui',
         ),
         Str('addattr*', validate_add_attribute,
             cli_name='addattr',
@@ -456,6 +461,7 @@ class LDAPModMember(LDAPQuery):
         Flag('raw',
             cli_name='raw',
             doc='print entries as they are stored in LDAP',
+            exclude='webui',
         ),
     )
 
@@ -751,4 +757,3 @@ class LDAPSearch(crud.Search):
 
     def post_callback(self, ldap, entries, truncated, *args, **options):
         pass
-
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
index 97641a4..1686d67 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -113,6 +113,9 @@ class user(LDAPObject):
             cli_name='password',
             label='Password',
             doc='Set the user password',
+            # FIXME: This is temporary till bug is fixed causing updates to
+            # bomb out via the webUI.
+            exclude='webui',
         ),
         Int('uidnumber?',
             cli_name='uid',
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index a42c3d0..e84cb07 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -273,4 +273,7 @@ class jsonserver(WSGIExecutioner):
             raise JSONError(
                 error='params[1] (aka options) must be a dict'
             )
+        options = dict((str(k), v) for (k, v) in options.iteritems())
+        print 'args = %r' % (args,)
+        print 'options = %r' % (options,)
         return (method, args, options, _id)
diff --git a/ipawebui/engine.py b/ipawebui/engine.py
index a90a450..01b271a 100644
--- a/ipawebui/engine.py
+++ b/ipawebui/engine.py
@@ -65,7 +65,17 @@ class ParamMapper(object):
         )
 
 
+def filter_params(namespace):
+    for param in namespace():
+        if param.exclude and 'webui' in param.exclude:
+            continue
+        yield param
+
+
 class Engine(object):
+
+    cruds = frozenset(['add', 'show', 'mod', 'del', 'find'])
+
     def __init__(self, api, app):
         self.api = api
         self.app = app
@@ -86,11 +96,21 @@ class Engine(object):
             )
 
     def build(self):
-        for cmd in self.api.Object.user.methods():
-            self.pages[cmd.name] = self.build_page(cmd)
-        for page in self.pages.itervalues():
-            page.menu.label = 'Users'
-            self.add_object_menuitems(page.menu, 'user')
+        for obj in self.api.Object():
+            if self.cruds.issubset(obj.methods) and obj.primary_key is not None:
+                self.pages[obj.name] = self.build_cruds_page(obj)
+
+        # Add landing page:
+        landing = self.app.new('PageApp', id='', title='Welcome to FreeIPA')
+
+        for page in self.pages.values() + [landing]:
+            page.menu.label = 'FreeIPA'
+            for name in sorted(self.pages):
+                p = self.pages[name]
+                page.menu.new_child('MenuItem', label=p.title, href=p.url)
+
+
+
 
         # Add in the info pages:
         page = self.app.new('PageApp', id='api', title='api')
@@ -110,6 +130,58 @@ class Engine(object):
                     )
                 )
 
+    def build_cruds_page(self, obj):
+        page = self.app.new('PageGrid', title=obj.name, id=obj.name)
+
+        # Setup CRUDS widget:
+        page.cruds.autoload = True
+        page.cruds.jsonrpc_url = self.api.Backend.jsonserver.url
+        page.cruds.key = obj.primary_key.name
+        page.cruds.method_create = obj.methods['add'].name
+        page.cruds.method_retrieve = obj.methods['show'].name
+        page.cruds.method_update = obj.methods['mod'].name
+        page.cruds.method_delete = obj.methods['del'].name
+        page.cruds.method_search = obj.methods['find'].name
+        page.cruds.display_cols = tuple(
+            dict(
+                name=p.name,
+                label=p.label,
+                css_classes=None,
+            )
+            for p in obj.params()
+        )
+
+        # Setup the Grid widget:
+        page.grid.cols = tuple(
+            dict(
+                name=p.name,
+                label=p.label,
+                css_classes=None,
+            )
+            for p in obj.params() if p.required
+        )
+
+
+        # Setup the create Dialog:
+        cmd = obj.methods['add']
+        page.create.title = cmd.summary.rstrip('.')
+        for p in filter_params(cmd.params):
+            page.create.fieldtable.add(self.param_mapper(p, cmd))
+
+        # Setup the retrieve Dialog
+        page.retrieve.title = 'Showing "{value}"'
+
+        # Setup the update Dialog:
+        page.update.title = 'Updating "{value}"'
+        cmd = obj.methods['mod']
+        for p in filter_params(cmd.options):
+            page.update.fieldtable.add(self.param_mapper(p, cmd))
+
+        # Setup the delete Dialog
+        page.delete.title = 'Delete "{value}"?'
+
+        return page
+
     def build_info_page(self, kind):
         # Add in the Object page:
         plugins = tuple(self.api[kind]())
@@ -126,45 +198,3 @@ class Engine(object):
                 self.app.new(kind)
             )
         return page
-
-    def build_page(self, cmd):
-        page = self.app.new('PageCmd',
-            cmd=cmd,
-            id=cmd.name,
-            title=cmd.summary.rstrip('.'),
-        )
-        page.form.action = page.url
-        page.form.method = 'GET'
-        page.form.add(
-            self.app.new('Hidden', name='__mode__', value='output')
-        )
-        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)
-
-    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')
-
-    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 1cf1adb..d05b5b4 100644
--- a/ipawebui/widgets.py
+++ b/ipawebui/widgets.py
@@ -228,169 +228,170 @@ 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')
+class LandingPage(base.Widget):
+    pages = Static('pages', default=tuple())
 
     xml = """
     <div
         xmlns:py="http://genshi.edgewall.org/";
-        class="${klass}"
+        class="${css_classes}"
         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]"
+    <a
+        py:for="p in pages"
+        py:content="p.title"
+        href="${relurl(p.url)}"
     />
-    </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 Form(builtins.Form):
+    js_class = 'Form'
+
+    javascript = """
+    Wehjit.bases.Form = new Class({
+        Extends: Wehjit.bases.Widget,
+
+        post_init: function() {
+            this.focused = null;
+            $each(this.el.elements, function(field) {
+                field.connect('focus', this);
+            }, this);
+            var parent = this.get_parent();
+            if (parent && parent.klass == 'Dialog') {
+                parent.addEvent('run', this.on_run.bind(this));
+                this.parent = parent;
+            }
+            this.formdata = null;
+        },
+
+        on_focus: function(field, event) {
+            this.focused = field;
+        },
+
+        on_run: function(dialog, params) {
+            console.assert(dialog == this.parent);
+            this.refocus();
+        },
+
+        refocus: function() {
+            console.log('refocus', this.id, this.focused);
+            if (this.focused) {
+                this.focused.focus();
+                return true;
+            }
+            if (this.el.elements.length > 0) {
+                this.el.elements[0].focus();
+                return true;
+            }
+            return false;
+        },
+
+        get_data: function() {
+            console.log('Form.get_data');
+            var rawdata = this.el.get_data();
+            var data = {};
+
+            if (this.formdata == null) {
+                $each(rawdata, function(value, key) {
+                    if (value !== '') {
+                        data[key] = value;
+                    }
+                });
+            }
+            else {
+                $each(rawdata, function(value, key) {
+                    var old = this.formdata[key];
+                    if (old  == undefined && value === '') {
+                        return;
+                    }
+                    if (old != value) {
+                        console.log('changed: %s = %s', key, value);
+                        data[key] = value;
+                    }
+                }, this);
+            }
+
+            return data;
+
+        },
+
+        set_data: function(data) {
+            console.log('Form.set_data', data);
+            this.focused = null;
+            if ($type(data) == 'object') {
+                this.formdata = data;
+            }
+            else {
+                this.formdata = null;
+            }
+            this.el.set_data(data);
+        },
+
+        reset: function() {
+            this.formdata = null;
+            this.focused = null;
+            this.el.reset();
+        },
+
+    });
     """
 
 
-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"
-    />
+class CRUDS(builtins.CRUDS):
+    display_cols = Static('display_cols', json=True, default=tuple())
+
+
+class Display(builtins.Display):
+    cols = None
+
+    javascript = """
+    Wehjit.bases.Display = new Class({
+        Extends: Wehjit.bases.Widget,
+
+        post_init: function() {
+            var parent = this.get_parent();
+            console.assert(parent);
+            parent.addEvent('run', this.on_run.bind(this));
+            this.cruds = Wehjit.get('cruds');
+            this.cols = this.cruds.data.display_cols;
+            console.assert(this.cols);
+            if (this.cols.length == 0) {
+                this.cols = Wehjit.data.grid.cols;
+            }
+        },
+
+        on_run: function(dialog, row) {
+            console.log('Display.on_run(%s, %s)', dialog, row);
+            this.el.empty();
+            if ($type(row) != 'object') {
+                return;
+            }
+            this.cols.each(function(col) {
+                var tr = new Element('tr');
+                var th = new Element('th');
+                th.textContent = col.label + ':';
+                tr.appendChild(th);
+                this.el.appendChild(tr);
+                var td = new Element('td');
+                var value = row[col.name];
+                if ($type(value) == 'array') {
+                    var value = value.join(',');
+                }
+                if ($type(value) != 'string') {
+                    var value = '';
+                }
+                td.textContent = value;
+                tr.appendChild(td);
+            }, this);
+        },
+
+    });
     """
 
-    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():
@@ -401,12 +402,10 @@ 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)
+    widgets.register(LandingPage)
+    widgets.register(Form, override=True)
+    widgets.register(CRUDS, override=True)
+    widgets.register(Display, override=True)
 
 
     freeze(widgets)
-- 
1.6.3.3

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to