root/cherokee/trunk/admin/Form.py

Revision 2515, 15.6 kB (checked in by alo, 3 days ago)

--

Line 
1 import re
2 import types
3
4 from Entry import *
5 from Module import *
6
7 FORM_TEMPLATE = """
8 <form action="%(action)s" method="%(method)s">
9   %(content)s
10   %(submit)s
11   <input type="hidden" name="is_submit" value="1" />
12 </form>
13 """
14
15 AUTO_SUBMIT_JS = """
16 <script type="text/javascript">
17   $(document).ready(autosubmit);
18 </script>
19 """
20
21 AUTOFORM_TEMPLATE = FORM_TEMPLATE.replace('<form','<form class="auto"') + AUTO_SUBMIT_JS
22
23 SUBMIT_BUTTON = """
24 <input type="submit" %(submit_props)s />
25 """
26
27 DEFAULT_SUBMIT_VALUE= 'value="Submit"'
28
29 class WebComponent:
30     def __init__ (self, id, cfg):
31         self._id  = id
32         self._cfg = cfg
33
34     def _op_handler (self, uri, post):
35         if post.get_val('is_submit'):
36             tmp = self._op_apply_changes (uri, post)
37             if tmp:
38                 return tmp
39         return self._op_render()
40
41     def _op_render (self):
42         raise "Should have been overridden"
43
44     def _apply_changes (self, uri, post):
45         raise "Should have been overridden"
46
47     def HandleRequest (self, uri, post):
48         # Is this a form submit
49         is_submit = post.get_val('is_submit')
50
51         # Check the URL
52         parts = uri.split('/')[2:]
53         if parts:
54             ruri = '/' + '/'.join(parts)
55         else:
56             ruri = '/'
57
58         if len(ruri) <= 1 and not is_submit:
59             return self._op_render()
60
61         return self._op_handler(ruri, post)
62
63 class Form:
64     def __init__ (self, action, method='post', add_submit=True, auto=True):
65         self._action       = action
66         self._method       = method
67         self._add_submit   = add_submit
68         self._auto         = auto
69
70     def Render (self, content='', submit_props='' ):
71         keys = {'submit':       '',
72                 'submit_props': submit_props,
73                 'content':      content,
74                 'action':       self._action,
75                 'method':       self._method}
76
77         if self._add_submit:
78             keys['submit'] = SUBMIT_BUTTON
79
80         if self._auto:
81             render = AUTOFORM_TEMPLATE
82         else:
83             render = FORM_TEMPLATE
84
85         while '%(' in render:
86             for replacement in re.findall (r'\%\((\w+)\)s', render):
87                 macro = '%('+replacement+')s'
88                 render = render.replace (macro, keys[replacement])
89
90         return render
91
92
93 class FormHelper (WebComponent):
94     options_wrap_num = 1
95
96     def __init__ (self, id, cfg):
97         WebComponent.__init__ (self, id, cfg)
98
99         self.errors      = {}
100         self.submit_url  = None
101
102     def set_submit_url (self, url):
103         self.submit_url = url
104
105     def Indent (self, content):
106         return '<div class="indented">%s</div>' %(content)
107
108     def Dialog (self, txt, type_='information'):
109         return '<div class="dialog-%s">%s</div>' % (type_, txt)
110
111     def HiddenInput (self, key, value):
112         return '<input type="hidden" value="%s" id="%s" name="%s" />' % (value, key, key)
113
114     def InstanceEntry (self, cfg_key, type, **kwargs):
115         # Instance an Entry
116         entry = Entry (cfg_key, type, self._cfg, **kwargs)
117         txt = str(entry)
118
119         # Check whether there is a related error
120         if cfg_key in self.errors.keys():
121             msg, val = self.errors[cfg_key]
122             if val:
123                 txt += '<div class="error"><b>%s</b>: %s</div>' % (val, msg)
124             else:
125                 txt += '<div class="error">%s</div>' % (msg)
126         return txt
127
128     def Label(self, title, for_id):
129         txt = '<label for="%s">%s</label>' % (for_id, title)
130         return txt
131
132     def InstanceTab (self, entries):
133         # HTML
134         txt = '<dl class="tab" id="tab_%s">' % (self._id)
135         num = 0
136         for title, content in entries:
137             error_inside = 'class="error"' in content
138             if error_inside:
139                 txt += '<dt num="%d"><div class="error">%s</div></dt>\n' % (num, title)
140             else:
141                 txt += '<dt num="%d">%s</dt>\n' % (num, title)
142             txt += '<dd>%s</dd>\n' % (content)
143             num += 1
144         txt += '</dl>'
145
146         # Javascript
147         txt += '''
148         <script type="text/javascript">
149           var settings = {
150              alwaysOpen: true,
151              animated:   false
152           };
153
154           open_tab = get_cookie('open_tab');
155           if (open_tab) {
156             settings['active'] = parseInt(open_tab);
157           }
158
159           jQuery("#tab_%s").Accordion(settings).change(
160             function (event, newHeader, oldHeader) {
161               if (! newHeader) return;
162               document.cookie = "open_tab=" + newHeader.attr("num");
163           });
164         </script>
165         ''' % (self._id)
166         return txt
167
168     def AddTableEntry (self, table, title, cfg_key, extra_cols=None):
169         # Get the entry
170         txt = self.InstanceEntry (cfg_key, 'text')
171
172         # Add to the table
173         label = self.Label(title, cfg_key);
174         tup = (label, txt)
175         if extra_cols:
176             tup += extra_cols
177         table += tup
178
179     def InstanceButton (self, name, **kwargs):
180         extra = ""
181         for karg in kwargs:
182             extra += '%s="%s" '%(karg, kwargs[karg])
183         return '<input type="button" value="%s" %s/>' % (name, extra)
184
185     def InstanceImage (self, name, alt, **kwargs):
186         extra = ""
187         for karg in kwargs:
188             extra += '%s="%s" '%(karg, kwargs[karg])
189         return '<img src="/static/images/%s" alt="%s" title="%s" %s/>' % (name, alt, alt, extra)
190
191     def _get_auto_wrap_id (self):
192         return "options_wrap_%d" % (FormHelper.options_wrap_num)
193
194     def InstanceOptions (self, cfg_key, options, *args, **kwargs):
195         value = self._cfg.get_val (cfg_key)
196         if value != None:
197             ops = EntryOptions (cfg_key, options, selected=value, *args, **kwargs)
198         else:
199             ops = EntryOptions (cfg_key, options, *args, **kwargs)
200
201         # Auto wrap
202         auto_wrap_id = self._get_auto_wrap_id()
203         FormHelper.options_wrap_num += 1
204
205         ops = '<div id="%s" name="%s">%s</div>'%(auto_wrap_id, auto_wrap_id, ops)
206         return (ops, value, auto_wrap_id)
207
208     def AddTableOptions (self, table, title, cfg_key, options, *args, **kwargs):
209         entry, value, wrap = self.InstanceOptions (cfg_key, options, *args, **kwargs)
210
211         label = self.Label(title, cfg_key)
212         table += (label, entry)
213
214         return value
215
216     def AddTableOptions_Ajax (self, table, title, cfg_key, options, *args, **kwargs):
217         wrap_id = self._get_auto_wrap_id()
218         js = "options_changed('/ajax/update','%s','%s');" % (cfg_key, wrap_id)
219         kwargs['onChange'] = js
220
221         return self.AddTableOptions (table, title, cfg_key, options, *args, **kwargs)
222
223     def AddPropOptions_Ajax (self, table, title, cfg_key, options, comment, *args, **kwargs):
224         wrap_id = self._get_auto_wrap_id()
225         js = "options_changed('/ajax/update','%s','%s');" % (cfg_key, wrap_id)
226         kwargs['onChange'] = js
227
228         return self.AddPropOptions (table, title, cfg_key, options, comment, *args, **kwargs)
229
230     def AddPropOptions_Reload (self, table, title, cfg_key, options, comment, **kwargs):
231         assert (self.submit_url)
232
233         # The Table entry itself
234         auto_wrap_id = self._get_auto_wrap_id()
235         js = "options_changed('/ajax/update','%s','%s');" % (cfg_key, auto_wrap_id)
236         kwargs['onChange'] = js
237         kwargs['noautosubmit'] = True
238         name = self.AddPropOptions (table, title, cfg_key, options, comment, **kwargs)
239
240         # If there was no cfg value, pick the first
241         if not name:
242             name = options[0][0]
243
244         # Render active option
245         if name:
246             try:
247                 # Inherit the errors, if any
248                 kwargs['errors'] = self.errors
249                 props_widget = module_obj_factory (name, self._cfg, cfg_key,
250                                                    self.submit_url, **kwargs)
251                 render = props_widget._op_render()
252             except IOError:
253                 render = "Couldn't load the properties module: %s" % (name)
254         else:
255             render = ''
256
257         return render
258
259     def AddTableOptions_Reload (self, table, title, cfg_key, options, **kwargs):
260         assert (self.submit_url)
261         print "DEPRECATED: AddTableOptions_Reload"
262
263         # The Table entry itself
264         auto_wrap_id = self._get_auto_wrap_id()
265         js = "options_changed('/ajax/update','%s','%s');" % (cfg_key, auto_wrap_id)
266         name = self.AddTableOptions (table, title, cfg_key, options, onChange=js)
267
268         # If there was no cfg value, pick the first
269         if not name:
270             name = options[0][0]
271
272         # Render active option
273         if name:
274             try:
275                 # Inherit the errors, if any
276                 kwargs['errors'] = self.errors
277                 props_widget = module_obj_factory (name, self._cfg, cfg_key,
278                                                    self.submit_url, **kwargs)
279                 render = props_widget._op_render()
280             except IOError:
281                 render = "Couldn't load the properties module: %s" % (name)
282         else:
283             render = ''
284
285         return render
286
287     def InstanceCheckbox (self, cfg_key, default=None, quiet=False):
288         try:
289             tmp = self._cfg[cfg_key].value.lower()
290             if tmp in ["on", "1", "true"]:
291                 value = '1'
292             else:
293                 value = '0'
294         except:
295             value = None
296
297         if value == '1':
298             entry = Entry (cfg_key, 'checkbox', quiet=quiet, checked=value)
299         elif value == '0':
300             entry = Entry (cfg_key, 'checkbox', quiet=quiet)
301         else:
302             if default == True:
303                 entry = Entry (cfg_key, 'checkbox', quiet=quiet, checked='1')
304             elif default == False:
305                 entry = Entry (cfg_key, 'checkbox', quiet=quiet)
306             else:
307                 entry = Entry (cfg_key, 'checkbox', quiet=quiet)
308
309         return entry
310
311     def AddTableCheckbox (self, table, title, cfg_key, default=None):
312         entry = self.InstanceCheckbox (cfg_key, default)
313         label = self.Label(title, cfg_key);
314         table += (label, entry)
315
316     # Errors
317     #
318
319     def _error_add (self, key, wrong_val, msg):
320         self.errors[key] = (msg, str(wrong_val))
321
322     def has_errors (self):
323         return len(self.errors) > 0
324
325     # Applying changes
326     #
327
328     def Validate_NotEmpty (self, post, cfg_key, error_msg):
329         try:
330             cfg_val = self._cfg[cfg_key].value
331         except:
332             cfg_val = None
333
334         if not cfg_val and \
335            not post.get_val(cfg_key):
336             self._error_add (cfg_key, '', error_msg)
337
338     def ValidateChange_SingleKey (self, key, post, validation):
339         for regex, tmp in validation:
340             pass_cfg = False
341             nochroot = False
342
343             if type(tmp) == types.FunctionType:
344                 validation_func = tmp
345
346             elif type(tmp) == types.TupleType:
347                 validation_func = tmp[0]
348                 for k in tmp[1:]:
349                     if k == 'cfg':
350                         pass_cfg = True
351                     elif k == 'nochroot':
352                         nochroot = True
353                     else:
354                         print "UNKNOWN validation option:", k
355
356             p = re.compile (regex)
357             if p.match (key):
358                 value = post.get_val(key)
359                 if not value:
360                     continue
361                 try:
362                     if pass_cfg:
363                         tmp = validation_func (value, self._cfg, nochroot)
364                     else:
365                         tmp = validation_func (value)
366                     post[key] = [tmp]
367                 except ValueError, error:
368                     self._error_add (key, value, error)
369
370     def _ValidateChanges (self, post, validation):
371         for post_entry in post:
372             self.ValidateChange_SingleKey (post_entry, post, validation)
373
374     def ApplyCheckbox (self, post, cfg_key):
375         if cfg_key in self.errors:
376             return
377
378         if cfg_key in post:
379             value = post.pop(cfg_key)
380             if value.lower() in ['on', '1']:
381                 self._cfg[cfg_key] = '1'
382             else:
383                 self._cfg[cfg_key] = '0'
384         else:
385             self._cfg[cfg_key] = "0"
386
387     def ApplyChanges (self, checkboxes, post, validation=None):
388         # Validate changes
389         if validation:
390             self._ValidateChanges (post, validation)
391
392         # Apply checkboxes
393         for key in checkboxes:
394             self.ApplyCheckbox (post, key)
395
396         # Apply text entries
397         for confkey in post:
398             if not '!' in confkey:
399                 continue
400
401             if confkey in self.errors:
402                 continue
403             if not confkey in checkboxes:
404                 value = post[confkey][0]
405                 if not value:
406                     del (self._cfg[confkey])
407                 else:
408                     self._cfg[confkey] = value
409
410     def ApplyChangesDirectly (self, post):
411         for confkey in post:
412             if not '!' in confkey:
413                 continue
414
415             if confkey in self.errors:
416                 continue
417
418             value = post[confkey][0]
419             if not value:
420                 del (self._cfg[confkey])
421             else:
422                 self._cfg[confkey] = value
423
424     def ApplyChangesPrefix (self, prefix, checkboxes, post, validation=None):
425         checkboxes_pre = ["%s!%s"%(prefix, x) for x in checkboxes]
426         return self.ApplyChanges (checkboxes_pre, post, validation)
427
428     def ApplyChanges_OptionModule (self, cfg_key, uri, post):
429         # Read the option entry value
430         name = self._cfg.get_val(cfg_key)
431         if not name: return
432
433         # Instance module and apply the changes
434         module = module_obj_factory (name, self._cfg, cfg_key, self.submit_url)
435         module._op_apply_changes (uri, post)
436
437         # Include module errors
438         for error in module.errors:
439             self.errors[error] = module.errors[error]
440
441         # Clean up properties
442         props = module.__class__.PROPERTIES
443
444         to_be_deleted = []
445         for entry in self._cfg[cfg_key]:
446             prop_cfg = "%s!%s" % (cfg_key, entry)
447             if entry not in props:
448                 to_be_deleted.append (prop_cfg)
449
450         for entry in to_be_deleted:
451             del(self._cfg[entry])
452
453     def AddProp (self, table, title, cfg_key, entry, comment=None):
454         label = self.Label (title, cfg_key);
455         table += (label, entry, comment)
456
457     def AddPropEntry (self, table, title, cfg_key, comment=None, **kwargs):
458         entry = self.InstanceEntry (cfg_key, 'text', **kwargs)
459         self.AddProp (table, title, cfg_key, entry, comment)
460
461     def AddPropCheck (self, table, title, cfg_key, default, comment=None):
462         entry = self.InstanceCheckbox (cfg_key, default)
463         self.AddProp (table, title, cfg_key, entry, comment)
464
465     def AddPropOptions (self, table, title, cfg_key, options, comment=None, **kwargs):
466         entry, v, w = self.InstanceOptions (cfg_key, options, **kwargs)
467         self.AddProp (table, title, cfg_key, entry, comment)
468         return v
469
470
471 class TableProps:
472     def __init__ (self):
473         self._content = []
474
475     def __add__ (self, (title, content, comment)):
476         self._content.append((title, content, comment))
477
478     def _render_entry (self, (title, content, comment)):
479         txt  = '<table class="tableprop">'
480         txt += '  <tr><th class="title">%s</th><td>%s</td></tr>' % (title, content)
481         txt += '  <tr><td colspan="2"><div class="comment">%s</div></td></tr>' % (comment)
482         txt += '</table>'
483         return txt
484
485     def __str__ (self):
486         tmp = []
487         for entry in self._content:
488             tmp.append (self._render_entry(entry))
489
490         txt = "<hr />".join(tmp)
491         return '<div class="tableprop_block">%s</div>' % (txt)
492                        
Note: See TracBrowser for help on using the browser.