Package Gnumed :: Package wxpython :: Module gmDataMiningWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmDataMiningWidgets

  1  """GNUmed data mining related widgets. 
  2  """ 
  3  #================================================================ 
  4  # $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/wxpython/gmDataMiningWidgets.py,v $ 
  5  # $Id: gmDataMiningWidgets.py,v 1.14 2009-07-30 12:03:34 ncq Exp $ 
  6  __version__ = '$Revision: 1.14 $' 
  7  __author__ = 'karsten.hilbert@gmx.net' 
  8  __license__ = 'GPL v2 or later (details at http://www.gnu.org)' 
  9   
 10   
 11  # stdlib 
 12  import sys, os, fileinput, webbrowser, logging 
 13   
 14   
 15  # 3rd party 
 16  import wx 
 17   
 18   
 19  # GNUmed 
 20  if __name__ == '__main__': 
 21          sys.path.insert(0, '../../') 
 22  from Gnumed.pycommon import gmDispatcher, gmMimeLib, gmTools, gmPG2, gmMatchProvider, gmI18N 
 23  from Gnumed.business import gmPerson, gmDataMining, gmPersonSearch 
 24  from Gnumed.wxpython import gmGuiHelpers, gmListWidgets 
 25  from Gnumed.wxGladeWidgets import wxgPatientListingPnl, wxgDataMiningPnl 
 26   
 27   
 28  _log = logging.getLogger('gm.ui') 
 29  _log.info(__version__) 
 30  #================================================================ 
31 -class cPatientListingCtrl(gmListWidgets.cReportListCtrl):
32
33 - def __init__(self, *args, **kwargs):
34 """<patient_key> must index or name a column in self.__data""" 35 try: 36 self.patient_key = kwargs['patient_key'] 37 del kwargs['patient_key'] 38 except KeyError: 39 self.patient_key = None 40 41 gmListWidgets.cReportListCtrl.__init__(self, *args, **kwargs) 42 43 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated, self)
44 #------------------------------------------------------------ 45 # event handling 46 #------------------------------------------------------------
47 - def _on_list_item_activated(self, evt):
48 if self.patient_key is None: 49 gmDispatcher.send(signal = 'statustext', msg = _('List not known to be patient-related.')) 50 return 51 data = self.get_selected_item_data(only_one=True) 52 try: 53 pat_data = data[self.patient_key] 54 except (KeyError, IndexError, TypeError): 55 gmGuiHelpers.gm_show_info ( 56 _( 57 'Cannot activate patient.\n\n' 58 'The row does not contain a column\n' 59 'named or indexed "%s".\n\n' 60 ) % self.patient_key, 61 _('activating patient from list') 62 ) 63 return 64 try: 65 pat_pk = int(pat_data) 66 pat = gmPerson.cIdentity(aPK_obj = pat_pk) 67 except (ValueError, TypeError): 68 searcher = gmPersonSearch.cPatientSearcher_SQL() 69 idents = searcher.get_identities(pat_data) 70 if len(idents) == 0: 71 gmDispatcher.send(signal = 'statustext', msg = _('No matching patient found.')) 72 return 73 if len(idents) == 1: 74 pat = idents[0] 75 else: 76 from Gnumed.wxpython import gmPatSearchWidgets 77 dlg = gmPatSearchWidgets.cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1) 78 dlg.set_persons(persons=idents) 79 result = dlg.ShowModal() 80 if result == wx.ID_CANCEL: 81 dlg.Destroy() 82 return 83 pat = dlg.get_selected_person() 84 dlg.Destroy() 85 86 from Gnumed.wxpython import gmPatSearchWidgets 87 gmPatSearchWidgets.set_active_patient(patient = pat)
88 #================================================================
89 -class cPatientListingPnl(wxgPatientListingPnl.wxgPatientListingPnl):
90
91 - def __init__(self, *args, **kwargs):
92 93 try: 94 button_defs = kwargs['button_defs'][:5] 95 del kwargs['button_defs'] 96 except KeyError: 97 button_defs = [] 98 99 try: 100 msg = kwargs['message'] 101 del kwargs['message'] 102 except KeyError: 103 msg = None 104 105 wxgPatientListingPnl.wxgPatientListingPnl.__init__(self, *args, **kwargs) 106 107 if msg is not None: 108 self._lbl_msg.SetLabel(msg) 109 110 buttons = [self._BTN_1, self._BTN_2, self._BTN_3, self._BTN_4, self._BTN_5] 111 for idx in range(len(button_defs)): 112 button_def = button_defs[idx] 113 if button_def['label'].strip() == u'': 114 continue 115 buttons[idx].SetLabel(button_def['label']) 116 buttons[idx].SetToolTipString(button_def['tooltip']) 117 buttons[idx].Enable(True) 118 119 self.Fit()
120 #------------------------------------------------------------ 121 # event handling 122 #------------------------------------------------------------
123 - def _on_BTN_1_pressed(self, event):
124 event.Skip()
125 #------------------------------------------------------------
126 - def _on_BTN_2_pressed(self, event):
127 event.Skip()
128 #------------------------------------------------------------
129 - def _on_BTN_3_pressed(self, event):
130 event.Skip()
131 #------------------------------------------------------------
132 - def _on_BTN_4_pressed(self, event):
133 event.Skip()
134 #------------------------------------------------------------
135 - def _on_BTN_5_pressed(self, event):
136 event.Skip()
137 #================================================================
138 -class cDataMiningPnl(wxgDataMiningPnl.wxgDataMiningPnl):
139
140 - def __init__(self, *args, **kwargs):
141 wxgDataMiningPnl.wxgDataMiningPnl.__init__(self, *args, **kwargs) 142 143 self.__init_ui() 144 145 # make me a file drop target 146 dt = gmGuiHelpers.cFileDropTarget(self) 147 self.SetDropTarget(dt)
148 #--------------------------------------------------------
149 - def __init_ui(self):
150 mp = gmMatchProvider.cMatchProvider_SQL2 ( 151 queries = [u""" 152 SELECT DISTINCT ON (label) 153 cmd, 154 label 155 FROM cfg.report_query 156 WHERE 157 label %(fragment_condition)s 158 OR 159 cmd %(fragment_condition)s 160 """] 161 ) 162 mp.setThresholds(2,3,5) 163 self._PRW_report_name.matcher = mp 164 self._PRW_report_name.add_callback_on_selection(callback = self._on_report_selected) 165 self._PRW_report_name.add_callback_on_lose_focus(callback = self._auto_load_report)
166 #--------------------------------------------------------
167 - def _auto_load_report(self, *args, **kwargs):
168 if self._TCTRL_query.GetValue() == u'': 169 if self._PRW_report_name.GetData() is not None: 170 self._TCTRL_query.SetValue(self._PRW_report_name.GetData()) 171 self._BTN_run.SetFocus()
172 #--------------------------------------------------------
173 - def _on_report_selected(self, *args, **kwargs):
174 self._TCTRL_query.SetValue(self._PRW_report_name.GetData()) 175 self._BTN_run.SetFocus()
176 #-------------------------------------------------------- 177 # file drop target API 178 #--------------------------------------------------------
179 - def add_filenames(self, filenames):
180 # act on first file only 181 fname = filenames[0] 182 _log.debug('importing SQL from <%s>', fname) 183 # act on text files only 184 mime_type = gmMimeLib.guess_mimetype(fname) 185 _log.debug('mime type: %s', mime_type) 186 if not mime_type.startswith('text/'): 187 _log.debug('not a text file') 188 gmDispatcher.send(signal='statustext', msg = _('Cannot read SQL from [%s]. Not a text file.') % fname, beep = True) 189 return False 190 # act on "small" files only 191 stat_val = os.stat(fname) 192 if stat_val.st_size > 2000: 193 gmDispatcher.send(signal='statustext', msg = _('Cannot read SQL from [%s]. File too big (> 2000 bytes).') % fname, beep = True) 194 return False 195 # all checks passed 196 for line in fileinput.input(fname): 197 self._TCTRL_query.AppendText(line)
198 #-------------------------------------------------------- 199 # notebook plugin API 200 #--------------------------------------------------------
201 - def repopulate_ui(self):
202 pass
203 #-------------------------------------------------------- 204 # event handlers 205 #--------------------------------------------------------
206 - def _on_list_item_activated(self, evt):
207 data = self._LCTRL_result.get_selected_item_data() 208 209 try: 210 pk_pat = data['pk_patient'] 211 except KeyError: 212 gmGuiHelpers.gm_show_warning ( 213 _( 214 'Cannot activate patient.\n\n' 215 'The report result list does not contain\n' 216 'a column named "pk_patient".\n\n' 217 'You may want to use the SQL "AS" column alias\n' 218 'syntax to make your query return such a column.\n' 219 ), 220 _('Activating patient from report result') 221 ) 222 return 223 224 try: 225 pat = gmPerson.cPatient(aPK_obj = pk_pat) 226 except StandardError: 227 gmGuiHelpers.gm_show_warning ( 228 _( 229 'Cannot activate patient.\n' 230 '\n' 231 'There does not seem to exist a patient\n' 232 'with an internal ID of [%s].\n' 233 ) % pk_pat, 234 _('Activating patient from report result') 235 ) 236 return 237 238 from Gnumed.wxpython import gmPatSearchWidgets 239 gmPatSearchWidgets.set_active_patient(patient = pat)
240 #--------------------------------------------------------
241 - def _on_contribute_button_pressed(self, evt):
242 report = self._PRW_report_name.GetValue().strip() 243 if report == u'': 244 gmDispatcher.send(signal = 'statustext', msg = _('Report must have a name for contribution.'), beep = False) 245 return 246 247 query = self._TCTRL_query.GetValue().strip() 248 if query == u'': 249 gmDispatcher.send(signal = 'statustext', msg = _('Report must have a query for contribution.'), beep = False) 250 return 251 252 do_it = gmGuiHelpers.gm_show_question ( 253 _( 'Be careful that your contribution (the query itself) does\n' 254 'not contain any person-identifiable search parameters.\n' 255 '\n' 256 'Note, however, that no query result data whatsoever\n' 257 'is included in the contribution that will be sent.\n' 258 '\n' 259 'Are you sure you wish to send this query to\n' 260 'the gnumed community mailing list?\n' 261 ), 262 _('Contributing custom report') 263 ) 264 if not do_it: 265 return 266 267 auth = {'user': gmNetworkTools.default_mail_sender, 'password': u'gnumed-at-gmx-net'} 268 msg = u"""--- This is a report definition contributed by a GNUmed user: 269 270 ---------------------------------------- 271 272 --- %s 273 274 %s 275 276 ---------------------------------------- 277 278 --- The GNUmed client. 279 """ % (report, query) 280 281 if not gmNetworkTools.send_mail ( 282 sender = u'GNUmed Report Generator <gnumed@gmx.net>', 283 receiver = [u'gnumed-devel@gnu.org'], 284 subject = u'user contributed report', 285 message = msg, 286 encoding = gmI18N.get_encoding(), 287 server = gmNetworkTools.default_mail_server, 288 auth = auth 289 ): 290 gmDispatcher.send(signal = 'statustext', msg = _('Unable to send mail. Cannot contribute report [%s] to GNUmed community.') % report, beep = True) 291 return False 292 293 gmDispatcher.send(signal = 'statustext', msg = _('Thank you for your contribution to the GNUmed community!'), beep = False) 294 return True
295 #--------------------------------------------------------
296 - def _on_schema_button_pressed(self, evt):
297 # new=2: Python 2.5: open new tab 298 # will block when called in text mode (that is, from a terminal, too !) 299 webbrowser.open(u'http://wiki.gnumed.de/bin/view/Gnumed/DatabaseSchema', new=2, autoraise=1)
300 #--------------------------------------------------------
301 - def _on_delete_button_pressed(self, evt):
302 report = self._PRW_report_name.GetValue().strip() 303 if report == u'': 304 return True 305 if gmDataMining.delete_report_definition(name=report): 306 self._PRW_report_name.SetText() 307 self._TCTRL_query.SetValue(u'') 308 gmDispatcher.send(signal='statustext', msg = _('Deleted report definition [%s].') % report, beep=False) 309 return True 310 gmDispatcher.send(signal='statustext', msg = _('Error deleting report definition [%s].') % report, beep=True) 311 return False
312 #--------------------------------------------------------
313 - def _on_clear_button_pressed(self, evt):
314 self._PRW_report_name.SetText() 315 self._TCTRL_query.SetValue(u'') 316 self._LCTRL_result.set_columns() 317 self._LCTRL_result.patient_key = None
318 #--------------------------------------------------------
319 - def _on_save_button_pressed(self, evt):
320 report = self._PRW_report_name.GetValue().strip() 321 if report == u'': 322 gmDispatcher.send(signal='statustext', msg = _('Cannot save report definition without name.'), beep=True) 323 return False 324 query = self._TCTRL_query.GetValue().strip() 325 if query == u'': 326 gmDispatcher.send(signal='statustext', msg = _('Cannot save report definition without query.'), beep=True) 327 return False 328 # FIXME: check for exists and ask for permission 329 if gmDataMining.save_report_definition(name=report, query=query, overwrite=True): 330 gmDispatcher.send(signal='statustext', msg = _('Saved report definition [%s].') % report, beep=False) 331 return True 332 gmDispatcher.send(signal='statustext', msg = _('Error saving report definition [%s].') % report, beep=True) 333 return False
334 #--------------------------------------------------------
335 - def _on_visualize_button_pressed(self, evt):
336 337 try: 338 # better fail early 339 import Gnuplot 340 except ImportError: 341 gmGuiHelpers.gm_show_info ( 342 aMessage = _('Cannot import "Gnuplot" python module.'), 343 aTitle = _('Query result visualizer') 344 ) 345 return 346 347 x_col = gmListWidgets.get_choices_from_list ( 348 parent = self, 349 msg = _('Choose a column to be used as the X-Axis:'), 350 caption = _('Choose column from query results ...'), 351 choices = self.query_results[0].keys(), 352 columns = [_('column name')], 353 single_selection = True 354 ) 355 if x_col is None: 356 return 357 358 y_col = gmListWidgets.get_choices_from_list ( 359 parent = self, 360 msg = _('Choose a column to be used as the Y-Axis:'), 361 caption = _('Choose column from query results ...'), 362 choices = self.query_results[0].keys(), 363 columns = [_('column name')], 364 single_selection = True 365 ) 366 if y_col is None: 367 return 368 369 # FIXME: support debugging (debug=1) depending on --debug 370 gp = Gnuplot.Gnuplot(persist=1) 371 if self._PRW_report_name.GetValue().strip() != u'': 372 gp.title(_('GNUmed report: %s') % self._PRW_report_name.GetValue().strip()[:40]) 373 else: 374 gp.title(_('GNUmed report results')) 375 gp.xlabel(x_col) 376 gp.ylabel(y_col) 377 try: 378 gp.plot([ [r[x_col], r[y_col]] for r in self.query_results ]) 379 except StandardError: 380 _log.exception('unable to plot results from [%s:%s]' % (x_col, y_col)) 381 gmDispatcher.send(signal = 'statustext', msg = _('Error plotting data.'), beep = True) 382 383 return
384 #--------------------------------------------------------
385 - def _on_run_button_pressed(self, evt):
386 387 self._BTN_visualize.Enable(False) 388 389 user_query = self._TCTRL_query.GetValue().strip().strip(';') 390 if user_query == u'': 391 return True 392 393 # FIXME: make LIMIT configurable 394 limit = u'1001' 395 396 wrapper_query = u""" 397 SELECT * 398 FROM ( 399 %%s 400 ) AS user_query 401 LIMIT %s 402 """ % limit 403 404 # does user want to insert current patient ID ? 405 patient_id_token = u'$<ID_active_patient>$' 406 if user_query.find(patient_id_token) != -1: 407 # she does, but is it possible ? 408 curr_pat = gmPerson.gmCurrentPatient() 409 if not curr_pat.connected: 410 gmGuiHelpers.gm_show_error ( 411 aMessage = _( 412 'This query requires a patient to be\n' 413 'active in the client.\n' 414 '\n' 415 'Please activate the patient you are interested\n' 416 'in and re-run the query.\n' 417 ), 418 aTitle = _('Active patient query') 419 ) 420 return False 421 wrapper_query = u""" 422 SELECT 423 %s AS pk_patient, 424 * 425 FROM ( 426 %%s 427 ) AS user_query 428 LIMIT %s 429 """ % (str(curr_pat.ID), limit) 430 user_query = user_query.replace(patient_id_token, str(curr_pat.ID)) 431 432 self._LCTRL_result.set_columns() 433 self._LCTRL_result.patient_key = None 434 435 query = wrapper_query % user_query 436 try: 437 # read-only for safety reasons 438 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': query}], get_col_idx = True) 439 except StandardError: 440 _log.exception('report query failed') 441 self._LCTRL_result.set_columns([_('Error')]) 442 t, v = sys.exc_info()[:2] 443 rows = [ 444 [_('The query failed.')], 445 [u''], 446 [unicode(t)] 447 ] 448 for line in str(v).decode(gmI18N.get_encoding()).split('\n'): 449 rows.append([line]) 450 rows.append([u'']) 451 for line in user_query.split('\n'): 452 rows.append([line]) 453 self._LCTRL_result.set_string_items(rows) 454 self._LCTRL_result.set_column_widths() 455 gmDispatcher.send('statustext', msg = _('The query failed.'), beep = True) 456 return False 457 458 if len(rows) == 0: 459 self._LCTRL_result.set_columns([_('Results')]) 460 self._LCTRL_result.set_string_items([[_('Report returned no data.')]]) 461 self._LCTRL_result.set_column_widths() 462 gmDispatcher.send('statustext', msg = _('No data returned for this report.'), beep = True) 463 return True 464 465 gmDispatcher.send(signal = 'statustext', msg = _('Found %s results.') % len(rows)) 466 467 if len(rows) == 1001: 468 gmGuiHelpers.gm_show_info ( 469 aMessage = _( 470 'This query returned at least 1001 results.\n' 471 '\n' 472 'GNUmed will only show the first 1000 rows.\n' 473 '\n' 474 'You may want to narrow down the WHERE conditions\n' 475 'or use LIMIT and OFFSET to batchwise go through\n' 476 'all the matching rows.' 477 ), 478 aTitle = _('Report Generator') 479 ) 480 rows = rows[:-1] # make it true :-) 481 482 # swap (col_name, col_idx) to (col_idx, col_name) as needed by 483 # set_columns() and sort them according to position-in-query 484 cols = [ (value, key) for key, value in idx.items() ] 485 cols.sort() 486 cols = [ pair[1] for pair in cols ] 487 self._LCTRL_result.set_columns(cols) 488 for row in rows: 489 try: 490 label = unicode(gmTools.coalesce(row[0], u'')).replace('\n', '<LF>').replace('\r', '<CR>') 491 except UnicodeDecodeError: 492 label = _('not unicode()able') 493 if len(label) > 150: 494 label = label[:150] + gmTools.u_ellipsis 495 row_num = self._LCTRL_result.InsertStringItem(sys.maxint, label = label) 496 for col_idx in range(1, len(row)): 497 try: 498 label = unicode(gmTools.coalesce(row[col_idx], u'')).replace('\n', '<LF>').replace('\r', '<CR>')[:250] 499 except UnicodeDecodeError: 500 label = _('not unicode()able') 501 if len(label) > 150: 502 label = label[:150] + gmTools.u_ellipsis 503 self._LCTRL_result.SetStringItem ( 504 index = row_num, 505 col = col_idx, 506 label = label 507 ) 508 self._LCTRL_result.set_column_widths() 509 self._LCTRL_result.set_data(data = rows) 510 try: 511 self._LCTRL_result.patient_key = idx['pk_patient'] 512 except KeyError: 513 pass 514 515 self.query_results = rows 516 self._BTN_visualize.Enable(True) 517 518 return True
519 #================================================================ 520 # main 521 #---------------------------------------------------------------- 522 if __name__ == '__main__': 523 from Gnumed.pycommon import gmI18N, gmDateTime 524 525 gmI18N.activate_locale() 526 gmI18N.install_domain() 527 gmDateTime.init() 528 529 #------------------------------------------------------------
530 - def test_pat_list_ctrl():
531 app = wx.PyWidgetTester(size = (400, 500)) 532 lst = cPatientListingCtrl(app.frame, patient_key = 0) 533 lst.set_columns(['name', 'comment']) 534 lst.set_string_items([ 535 ['Kirk', 'Kirk by name'], 536 ['#12', 'Kirk by ID'], 537 ['unknown', 'unknown patient'] 538 ]) 539 # app.SetWidget(cPatientListingCtrl, patient_key = 0) 540 app.frame.Show() 541 app.MainLoop()
542 #------------------------------------------------------------ 543 544 test_pat_list_ctrl() 545 546 #================================================================ 547