Attachment 'Form-0.8alpha.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 Form.py - MoinMoin macro to display database based forms
4
5 @copyright: 2008 Wolfgang Fischer
6 @license: GNU GPL, see COPYING for details.
7
8 Information on prerequisites, installation and usage can be found at
9 http://moinmo.in/MacroMarket/Form
10 """
11 Dependencies = ['external'] # Can't be cached as it depends on external data
12
13 def execute(macro, args):
14 args = [ arg.strip() for arg in args.split(',') ]
15 request = macro.request
16 if not hasattr(request, 'form_macro_form'):
17 request.form_macro_form = Form(request)
18 return request.form_macro_form.render(args)
19
20
21 class Form:
22 import datetime
23 from MoinMoin import wikiutil
24
25 # fake _ function to get gettext recognize those texts:
26 _ = lambda x: x
27 ui_text = {
28 # TODO: Button labels should be defined here
29 }
30 del _
31
32 datetime_types = [ datetime.datetime, datetime.date, datetime.time ]
33
34
35 def __init__(self, request):
36 self.request = request
37 self.formatter = request.formatter
38 self.post_args = request.form
39 self.date_format = request.user.date_fmt or request.cfg.date_fmt
40 self.datetime_format = request.user.datetime_fmt or request.cfg.datetime_fmt
41 today = self.datetime.date.today()
42 self.variables = {
43 'PAGE': request.page.page_name,
44 'NOW' : self.datetime.datetime.now(),
45 'TODAY' : today,
46 'TODAY_DATETIME' : self.datetime.datetime.fromordinal(today.toordinal()),
47 'ME': request.user.name,
48 }
49 self.forms = {}
50 self.dict_name_stack = []
51 self.last_form_key = ''
52
53 # Extract information on post action
54 self.action = u''
55 self.action_form = u''
56 self.action_index = 0
57 for k in self.post_args.keys():
58 if k.startswith('_Action_') and len(k) > 18:
59 self.action = k[8:11]
60 self.action_index = int(k[12:16])
61 self.action_form = k[17:]
62
63
64
65 # =============================
66 # Render form elements
67 # =============================
68
69 def render(self, args):
70 if not args:
71 return self.error_message('Missing arguments')
72 element = args[0].lower()
73 if element == 'start':
74 return self.render_start()
75 elif element == 'end':
76 return self.render_end()
77 request = self.request
78 post_args = self.post_args
79
80 if len(args) > 1: # key specified
81 key = self.key = args[1]
82 else:
83 key = u''
84 self.dict_name, self.form_name, self.field_name = self.parse_element_key(key, element)
85 dict_name = self.dict_name
86 form_name = self.form_name
87 self.form_key = form_key = u'%s.%s' % (dict_name, form_name)
88 self.last_form_key = self.form_key
89 if form_key not in self.forms: # First element of this form
90 form = self.form = self.forms[form_key] = {}
91 form['form_name'] = form_name
92 form['dict_name'] = dict_name
93 # Get form dict
94 form_dict_obj = request.dicts.dict(dict_name)
95 if not hasattr(form_dict_obj, 'get_dict'):
96 form['dict'] = { }
97 return self.error_message('The form dictionary "%s" does not exist or is not valid' % dict_name)
98 form['dict'] = form_dict_obj.get_dict()
99 form['addonly'] = self.get_stripped_from_form_dict('addonly') == u'1'
100 # Connect to data source
101 error = self.connect()
102 if error:
103 return self.error_message(error)
104
105 # Fetch data
106 error = self.fetch_data()
107 if error:
108 return self.error_message(error)
109
110 if form_key == self.action_form:
111 # Do form action
112 self.do_action()
113 # Fetch updated data
114 self.fetch_data()
115 # Close connection
116 self.connection.close()
117
118 records = form['records']
119 form['add_mode'] = (post_args.has_key('_Action_New_0000_%s' % form_key) or not records) and self.get_stripped_from_form_dict('insert')
120 # Set index of current record
121 self.set_record_index()
122 # Add record values to variables dict
123 # if form_key not in post_args.get('form', []):
124 index = form['index']
125 if 0 < index <= len(records):
126 record = records[index-1]
127 else:
128 record = value = None
129 for (field, field_data) in form['fields'].iteritems():
130 field_type = field_data[1]
131 if record:
132 value = record[field_data[0]]
133 if field_type not in self.datetime_types and field_type:
134 value = field_type(value)
135 self.variables['%s.%s' % (form_key, field)] = value
136
137 self.form = self.forms[form_key]
138
139 if element == 'form':
140 return self.render_form()
141 elif element == 'buttons':
142 return self.render_buttons()
143 elif element == 'navigation':
144 return self.render_navigation()
145 elif element == 'filter':
146 return self.render_filter()
147 elif element == 'next':
148 return self.render_next()
149 else:
150 return self.render_field(element, args)
151
152
153 def render_form(self):
154 # TODO: handle recursion
155 # TODO: handle form body doesn't exist
156 form = self.form
157 request = self.request
158 form_text = [ self.get_stripped_from_form_dict('header').replace('\\n', '\n') % self.variables ]
159 form_body = self.get_stripped_from_form_dict('body').replace('\\n', '\n') % self.variables
160 if self.get_stripped_from_form_dict('all'):
161 records = form['records']
162 count = [len(records), len(records) + 1][self.get_stripped_from_form_dict('insert') != u'']
163 for i in range(count):
164 form_text.append(form_body)
165 else:
166 form_text.append(form_body)
167 form_text.append(self.get_stripped_from_form_dict('footer').replace('\\n', '\n') % self.variables)
168 # Load the parser
169 Parser = self.wikiutil.searchAndImportPlugin(request.cfg, "parser", 'wiki')
170 self.dict_name_stack.append(self.dict_name)
171 result = self.wikiutil.renderText(request, Parser, u''.join(form_text), line_anchors=False)
172 self.dict_name_stack.pop()
173 return result
174
175
176 def render_start(self):
177 return self.formatter.rawHTML('<span><form action="" method="POST" enctype="multipart/form-data">')
178
179
180 def render_end(self):
181 return self.formatter.rawHTML('</form></span>')
182
183
184 def render_next(self):
185 form = self.form
186 form['index'] += 1
187 form['add_mode'] = form['index'] > len(form['records'])
188 return u''
189
190
191 def render_field(self, element, args):
192 form = self.form
193 field_name = self.field_name
194 field_key = '%s.%s' % (self.form_key, field_name)
195 value = self.wikiutil.escape(self.get_field_value(self.form_key, field_name, field_key) or '', 1)
196
197 if element == 'value':
198 return self.formatter.rawHTML(u'%s<input type="hidden" name="%s" value="%s">' % (value, field_key, value))
199
200 elif element == 'textarea':
201 if len(args) > 2:
202 cols = args[2]
203 else:
204 cols = u''
205 if len(args) > 3:
206 rows = args[3]
207 else:
208 rows = u''
209 return self.formatter.rawHTML(u'<textarea name="%s" cols="%s" rows="%s">%s</textarea>' % (field_key, cols, rows, value))
210
211 elif element == 'hidden':
212 return self.formatter.rawHTML(u'<input type="hidden" name="%s" value="%s">' % (field_key, value))
213
214 else:
215 if len(args) > 2:
216 size = args[2]
217 else:
218 size = u''
219 return self.formatter.rawHTML(u'<input type="text" name="%s" size="%s" value="%s">' % (field_key, size, value))
220
221
222 def get_field_value(self, form_key, field_name, field_key, for_action = 0):
223 if field_name.startswith('@'):
224 value = self.post_args.get(field_key, [None])[0]
225 elif not for_action:
226 form = self.forms[form_key]
227 index = form['index']
228 records = form['records']
229 if index > len(records):
230 value = None
231 else:
232 value = records[index-1][form['fields'][field_name][0]]
233 else:
234 if form_key == self.action_form:
235 index = self.action_index
236 else:
237 index = 0
238 value = self.post_args.get(field_key, [u''])[index]
239 form = self.forms[form_key]
240 field_type = form['fields'][field_name][1]
241 if field_type:
242 value = field_type(value)
243 if value == '':
244 value = None
245 return value
246
247
248 def render_navigation(self):
249 form = self.form
250 form_key = self.form_key
251 return self.formatter.rawHTML("""
252 <input type="submit" name="First_%s" value="|<">
253 <input type="submit" name="Previous_%s" value="<">
254 <input type="text" name="GoTo_%s" size="3" value="%s">
255 <input type="submit" name="Next_%s" value=">">
256 <input type="submit" name="Last_%s" value=">|"> of %s
257 """ % (form_key, form_key, form_key, str(form['index']), form_key, form_key, len(form['records'])))
258
259
260 def render_filter(self):
261 formatter = self.formatter
262 return formatter.rawHTML('<input type="submit" name="_Action_App_0000" value="Filter"><input type="reset">')
263
264
265 def render_buttons(self):
266 formatter = self.formatter
267 if self.get_stripped_from_form_dict('all'):
268 action_index = self.form['index'] - 1
269 else:
270 action_index = 0
271 action_key = ('0000%i_' % action_index)[-5:]
272 action_key += self.form_key
273 result = []
274 # if self.form['unbound']:
275 # result.append(formatter.rawHTML('<input type="submit" name="_Action_App_%s" value="Filter"><input type="reset">' % (action_key)))
276 if self.form['add_mode']:
277 result.append(formatter.rawHTML('<input type="submit" name="_Action_Add_%s" value="Add"><input type="submit" name="_Action_Can_%s" value="Cancel">' % (action_key, action_key)))
278 else:
279 if self.get_stripped_from_form_dict('update'):
280 result.append(formatter.rawHTML('<input type="submit" name="_Action_Sav_%s" value="Save">' % action_key))
281 if self.get_stripped_from_form_dict('delete'):
282 result.append(formatter.rawHTML('<input type="submit" name="_Action_Del_%s" value="Delete">' % action_key))
283 if self.get_stripped_from_form_dict('insert') and not self.get_stripped_from_form_dict('all'):
284 result.append(formatter.rawHTML('<input type="submit" name="_Action_New_%s" value="New">' % action_key))
285 return u''.join(result)
286
287
288
289 # =============================
290 # DataBase communication
291 # =============================
292
293 def fetch_data(self):
294 select_query = self.get_stripped_from_form_dict('select')
295 if select_query:
296 parameters = self.get_query_parameters('select')
297 else:
298 return 'The form "%s" does not contain a select query' % self.form_key
299 cursor = self.connection.cursor()
300 cursor.execute(select_query, parameters)
301 description = cursor.description
302 form = self.form
303 form['fields'] = dict([(description[i][0], [i, description[i][1]]) for i in range(len(description))])
304 form['records'] = [cursor.fetchall(), []][form['addonly']]
305 cursor.close()
306 return None
307
308
309 def set_record_index(self):
310 form = self.form
311 form_key = self.form_key
312 post_args = self.post_args
313 records = form['records']
314 # Calculate index of current record (first record is 1)
315 if form['add_mode']:
316 index = len(records) + 1
317 elif 'GoTo_%s' % form_key in post_args:
318 index = int(post_args['GoTo_%s' % form_key][0])
319 if 'First_%s' % form_key in post_args:
320 index = 1
321 elif 'Previous_%s' % form_key in post_args:
322 index -= 1
323 elif 'Next_%s' % form_key in post_args:
324 index +=1
325 elif 'Last_%s' % form_key in post_args:
326 index = len(records)
327 if index < 1:
328 index = 1
329 elif index > len(records):
330 index = len(records)
331 else:
332 goto_fields = self.get_stripped_from_form_dict('goto_fields')
333 if goto_fields:
334 fields = form['fields']
335 goto_fields = [ field.strip() for field in goto_fields.split(',') ]
336 variables = self.variables
337 def get_value(value, type, variables):
338 value = value.strip().strip('"\'')
339 if value in variables:
340 return variables[value]
341 else:
342 value = value % variables
343 if type == self.datetime.datetime:
344 return self.datetime.datetime.strptime(value, self.datetime_format)
345 elif type == self.datetime.date:
346 return self.datetime.datetime.strptime(value, self.date_format)
347 return type(value)
348 goto_values_list = self.get_stripped_from_form_dict('goto_values').split(',')
349 goto_values = [ (get_value(goto_values_list[i], fields[goto_fields[i]][1], variables), fields[goto_fields[i]][0]) for i in range(len(goto_values_list)) ]
350 index = 0
351 for record_index in range(len(records)):
352 equal = True
353 for value in goto_values:
354 equal = value[0] == records[record_index][value[1]]
355 if not equal:
356 break
357 if equal:
358 index = record_index + 1
359 break
360 if not index: # No matching record found
361 index = 1 # Set to first record (default)
362 else:
363 index = 1 # Set to first record (default)
364 form['index'] = index
365
366
367 def do_action(self):
368 cursor = self.connection.cursor()
369 action = self.action
370 action_query_map = {'Add' : 'insert', 'Sav' : 'update', 'Del' : 'delete'}
371 if action in action_query_map:
372 query_type =action_query_map[action]
373 query = self.get_stripped_from_form_dict(query_type)
374 if query:
375 parameters = self.get_query_parameters(query_type, for_action = 1)
376 count = cursor.execute(query, parameters)
377 self.connection.commit()
378 cursor.close()
379
380
381 def connect(self):
382 dict_name, self.connection_name = self.parse_connection_key(self.get_stripped_from_form_dict('connection'))
383 connection_dict_obj = self.request.dicts.dict(dict_name)
384 if hasattr(connection_dict_obj, 'get_dict'):
385 self.connection_dict = connection_dict = connection_dict_obj.get_dict()
386 else:
387 return 'The connection dictionary "%s" does not exist or is not valid' % dict_name
388
389 connection_name_prefix = '%s.' % self.connection_name
390 connection_type = self.get_stripped_from_connection_dict('type').lower()
391
392 if connection_type == 'sqlite':
393 import sqlite3
394 connection_string = self.get_stripped_from_connection_dict('file')
395 if connection_string.startswith('attachment:'):
396 from MoinMoin.action import AttachFile
397 pagename, filename = AttachFile.absoluteName(connection_string[11:], dict_name)
398 connection_string = AttachFile.getFilename(self.request, pagename, filename)
399 self.connection = sqlite3.connect(connection_string)
400
401 elif connection_type == 'odbc':
402 import pyodbc
403 connection_string = self.get_stripped_from_connection_dict('connection_string')
404 self.connection = pyodbc.connect(connection_string)
405
406 elif connection_type == 'mysql':
407 import MySQLdb
408 port = connection_dict.get('port', '3306').strip('`')
409 self.connection = MySQLdb.connect(host = self.get_stripped_from_connection_dict('server'), port = port, user = self.get_stripped_from_connection_dict('uid'), passwd = self.get_stripped_from_connection_dict('pwd'),db = self.get_stripped_from_connection_dict('database'))
410
411 elif connection_type == 'oracle':
412 import cx_Oracle
413 if 'port' in parameters:
414 port = parameters['%sport' % connection_name_prefix]
415 else:
416 port = 1521
417 self.connection = cx_Oracle.connect(self.get_stripped_from_connection_dict('uid'), self.get_stripped_from_connection_dict('pwd'), cx_Oracle.makedsn(self.get_stripped_from_connection_dict('server'), port, self.get_stripped_from_connection_dict('database')))
418
419 else:
420 return 'Connection type "%s" specified in "%s" is unknown' % (connection_type, dict_name)
421
422 return None
423
424
425
426 # =============================
427 # Handling Form Dictionaries
428 # =============================
429
430 def parse_element_key(self, key, element):
431 key_parts = key.split('.')
432 if element in ['form', 'buttons', 'navigation', 'filter', 'next']:
433 key_parts.extend([u'', u'', u''])
434 else:
435 if len(key_parts) == 2:
436 key_parts.insert(0, u'')
437 elif len(key_parts) < 2:
438 pass # TODO: raise error - field not specified correctly
439 dict_name, form_name, field_name = key_parts[:3]
440 if not (form_name or dict_name):
441 if self.last_form_key:
442 form_key = self.last_form_key
443 form = self.forms[form_key]
444 dict_name, form_name = form['dict_name'], form['form_name']
445 else:
446 pass # TODO: raise error - unspecified form
447 elif not dict_name:
448 if self.dict_name_stack:
449 dict_name = self.dict_name_stack[-1]
450 else:
451 dict_name = self.request.page.page_name
452 return dict_name, form_name, field_name
453
454
455 def parse_connection_key(self, key):
456 key_parts = key.split('.')
457 key_parts.extend([u'', u''])
458 dict_name, connection_name = key_parts[:2]
459 if not dict_name:
460 dict_name = self.dict_name
461 return dict_name, connection_name
462
463
464 def get_query_parameters(self, key, for_action = 0):
465 # In this order:
466 # 1. variables values
467 # 2. form values (if not current)
468 # 3. post values
469 form_key = self.form_key
470 variables = self.variables
471 result = []
472 parameters = self.get_stripped_from_form_dict('%s_parameters' % key)
473 if parameters:
474 for par in parameters.split(','):
475 par = par.strip()
476 if variables.has_key(par):
477 value = variables[par]
478 else:
479 key_parts = par.strip().split('.')
480 if len(key_parts) == 2:
481 key_parts.insert(0, u'')
482 elif len(key_parts) < 2:
483 pass # TODO: raise error - parameter not specified correctly
484 dict_name, form_name, field_name = key_parts[:3]
485 if not (form_name or dict_name):
486 dict_name, form_name = self.dict_name, self.form_name
487 elif not dict_name:
488 dict_name = self.dict_name
489 parameter_form_key = u'%s.%s' % (dict_name, form_name)
490 parameter_key = u'%s.%s' % (parameter_form_key, field_name)
491 if self.forms.has_key(parameter_form_key):
492 value = self.get_field_value(parameter_form_key, field_name, parameter_key, for_action = for_action)
493 result.append(value)
494 return result
495
496
497
498 def get_stripped_from_form_dict(self, key):
499 return self.form['dict'].get('%s.%s' % (self.form_name, key), u'').strip('`')
500
501
502 def get_stripped_from_connection_dict(self, key):
503 return self.connection_dict.get('%s.%s' % (self.connection_name, key), u'').strip('`')
504
505
506
507 # =============================
508 # Error Message
509 # =============================
510
511 def error_message(self, message):
512 formatter = self.formatter
513 result = [ '', formatter.preformatted(True) ]
514 message = self.wikiutil.escape(message, 1)
515 result.append(formatter.rawHTML("Form macro error: %s" % message))
516 result.append(formatter.preformatted(False))
517 return u'\n'.join(result)
518
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.