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