Enabled CRUDS in webUI using wehjit 0.2.0

This commit is contained in:
Jason Gerard DeRose 2010-01-26 06:39:00 -07:00 committed by Rob Crittenden
parent e9dc9de23a
commit 7b571e3693
6 changed files with 231 additions and 191 deletions

View File

@ -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)

View File

@ -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

View File

@ -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',

View File

@ -273,4 +273,5 @@ 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())
return (method, args, options, _id)

View File

@ -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

View File

@ -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
class LandingPage(base.Widget):
pages = Static('pages', default=tuple())
xml = """
<div
xmlns:py="http://genshi.edgewall.org/"
py:if="mode == page_mode"
py:strip="True"
class="${css_classes}"
id="${id}"
>
<child py:for="child in children" py:replace="child.generate()" />
<a
py:for="p in pages"
py:content="p.title"
href="${relurl(p.url)}"
/>
</div>
"""
class Output(base.Widget):
"""
Shows attributes form an LDAP entry.
"""
class Form(builtins.Form):
js_class = 'Form'
order = Dynamic('order')
labels = Dynamic('labels')
result = Dynamic('result')
javascript = """
Wehjit.bases.Form = new Class({
Extends: Wehjit.bases.Widget,
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>
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;
},
<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>
"""
on_focus: function(field, event) {
this.focused = field;
},
style = (
('table', (
('empty-cells', 'show'),
('border-collapse', 'collapse'),
)),
on_run: function(dialog, params) {
console.assert(dialog == this.parent);
this.refocus();
},
('th', (
('text-align', 'right'),
('padding', '.25em 0.5em'),
('line-height', '%(height_bar)s'),
('vertical-align', 'top'),
)),
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;
},
('td', (
('padding', '.25em'),
('vertical-align', 'top'),
('text-align', 'left'),
('line-height', '%(height_bar)s'),
)),
)
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);
}
class Hidden(base.Field):
xml = """
<input
xmlns:py="http://genshi.edgewall.org/"
type="hidden"
name="${name}"
/>
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)
class CRUDS(builtins.CRUDS):
display_cols = Static('display_cols', json=True, default=tuple())
@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 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)