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 (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'select distinct on (label) cmd, label from cfg.report_query where label %(fragment_condition)s or cmd %(fragment_condition)s'] 152 ) 153 mp.setThresholds(2,3,5) 154 self._PRW_report_name.matcher = mp 155 self._PRW_report_name.add_callback_on_selection(callback = self._on_report_selected) 156 self._PRW_report_name.add_callback_on_lose_focus(callback = self._auto_load_report)
157 #--------------------------------------------------------
158 - def _auto_load_report(self, *args, **kwargs):
159 if self._TCTRL_query.GetValue() == u'': 160 if self._PRW_report_name.GetData() is not None: 161 self._TCTRL_query.SetValue(self._PRW_report_name.GetData()) 162 self._BTN_run.SetFocus()
163 #--------------------------------------------------------
164 - def _on_report_selected(self, *args, **kwargs):
165 self._TCTRL_query.SetValue(self._PRW_report_name.GetData()) 166 self._BTN_run.SetFocus()
167 #-------------------------------------------------------- 168 # file drop target API 169 #--------------------------------------------------------
170 - def add_filenames(self, filenames):
171 # act on first file only 172 fname = filenames[0] 173 # act on text files only 174 mime_type = gmMimeLib.guess_mimetype(fname) 175 if not mime_type.startswith('text/'): 176 gmDispatcher.send(signal='statustext', msg = _('Cannot read SQL from [%s]. Not a text file.') % fname, beep = True) 177 return False 178 # act on "small" files only 179 stat_val = os.stat(fname) 180 if stat_val.st_size > 2000: 181 gmDispatcher.send(signal='statustext', msg = _('Cannot read SQL from [%s]. File too big (> 2000 bytes).') % fname, beep = True) 182 return False 183 # all checks passed 184 for line in fileinput.input(fname): 185 self._TCTRL_query.AppendText(line)
186 #-------------------------------------------------------- 187 # notebook plugin API 188 #--------------------------------------------------------
189 - def repopulate_ui(self):
190 pass
191 #-------------------------------------------------------- 192 # event handlers 193 #--------------------------------------------------------
194 - def _on_list_item_activated(self, evt):
195 data = self._LCTRL_result.get_selected_item_data() 196 197 try: 198 pk_pat = data['pk_patient'] 199 except KeyError: 200 gmGuiHelpers.gm_show_warning ( 201 _( 202 'Cannot activate patient.\n\n' 203 'The report result list does not contain\n' 204 'a column named "pk_patient".\n\n' 205 'You may want to use the SQL "AS" column alias\n' 206 'syntax to make your query return such a column.\n' 207 ), 208 _('Activating patient from report result') 209 ) 210 return 211 212 try: 213 pat = gmPerson.cPatient(aPK_obj = pk_pat) 214 except StandardError: 215 gmGuiHelpers.gm_show_warning ( 216 _( 217 'Cannot activate patient.\n' 218 '\n' 219 'There does not seem to exist a patient\n' 220 'with an internal ID of [%s].\n' 221 ) % pk_pat, 222 _('Activating patient from report result') 223 ) 224 return 225 226 from Gnumed.wxpython import gmPatSearchWidgets 227 gmPatSearchWidgets.set_active_patient(patient = pat)
228 #--------------------------------------------------------
229 - def _on_contribute_button_pressed(self, evt):
230 report = self._PRW_report_name.GetValue().strip() 231 if report == u'': 232 gmDispatcher.send(signal = 'statustext', msg = _('Report must have a name for contribution.'), beep = False) 233 return 234 235 query = self._TCTRL_query.GetValue().strip() 236 if query == u'': 237 gmDispatcher.send(signal = 'statustext', msg = _('Report must have a query for contribution.'), beep = False) 238 return 239 240 do_it = gmGuiHelpers.gm_show_question ( 241 _( 'Be careful that your contribution (the query itself) does\n' 242 'not contain any person-identifiable search parameters.\n' 243 '\n' 244 'Note, however, that no query result data whatsoever\n' 245 'is included in the contribution that will be sent.\n' 246 '\n' 247 'Are you sure you wish to send this query to\n' 248 'the gnumed community mailing list?\n' 249 ), 250 _('Contributing custom report') 251 ) 252 if not do_it: 253 return 254 255 auth = {'user': gmTools.default_mail_sender, 'password': u'gnumed-at-gmx-net'} 256 msg = u"""--- This is a report definition contributed by a GNUmed user: 257 258 ---------------------------------------- 259 260 --- %s 261 262 %s 263 264 ---------------------------------------- 265 266 --- The GNUmed client. 267 """ % (report, query) 268 269 if not gmTools.send_mail ( 270 sender = u'GNUmed Report Generator <gnumed@gmx.net>', 271 receiver = [u'gnumed-devel@gnu.org'], 272 subject = u'user contributed report', 273 message = msg, 274 encoding = gmI18N.get_encoding(), 275 server = gmTools.default_mail_server, 276 auth = auth 277 ): 278 gmDispatcher.send(signal = 'statustext', msg = _('Unable to send mail. Cannot contribute report [%s] to GNUmed community.') % report, beep = True) 279 return False 280 281 gmDispatcher.send(signal = 'statustext', msg = _('Thank you for your contribution to the GNUmed community!'), beep = False) 282 return True
283 #--------------------------------------------------------
284 - def _on_schema_button_pressed(self, evt):
285 # new=2: Python 2.5: open new tab 286 # will block when called in text mode (that is, from a terminal, too !) 287 webbrowser.open(u'http://wiki.gnumed.de/bin/view/Gnumed/DatabaseSchema', new=2, autoraise=1)
288 #--------------------------------------------------------
289 - def _on_delete_button_pressed(self, evt):
290 report = self._PRW_report_name.GetValue().strip() 291 if report == u'': 292 return True 293 if gmDataMining.delete_report_definition(name=report): 294 self._PRW_report_name.SetText() 295 self._TCTRL_query.SetValue(u'') 296 gmDispatcher.send(signal='statustext', msg = _('Deleted report definition [%s].') % report, beep=False) 297 return True 298 gmDispatcher.send(signal='statustext', msg = _('Error deleting report definition [%s].') % report, beep=True) 299 return False
300 #--------------------------------------------------------
301 - def _on_clear_button_pressed(self, evt):
302 self._PRW_report_name.SetText() 303 self._TCTRL_query.SetValue(u'') 304 self._LCTRL_result.set_columns() 305 self._LCTRL_result.patient_key = None
306 #--------------------------------------------------------
307 - def _on_save_button_pressed(self, evt):
308 report = self._PRW_report_name.GetValue().strip() 309 if report == u'': 310 gmDispatcher.send(signal='statustext', msg = _('Cannot save report definition without name.'), beep=True) 311 return False 312 query = self._TCTRL_query.GetValue().strip() 313 if query == u'': 314 gmDispatcher.send(signal='statustext', msg = _('Cannot save report definition without query.'), beep=True) 315 return False 316 # FIXME: check for exists and ask for permission 317 if gmDataMining.save_report_definition(name=report, query=query, overwrite=True): 318 gmDispatcher.send(signal='statustext', msg = _('Saved report definition [%s].') % report, beep=False) 319 return True 320 gmDispatcher.send(signal='statustext', msg = _('Error saving report definition [%s].') % report, beep=True) 321 return False
322 #--------------------------------------------------------
323 - def _on_visualize_button_pressed(self, evt):
324 325 try: 326 # better fail early 327 import Gnuplot 328 except ImportError: 329 gmGuiHelpers.gm_show_info ( 330 aMessage = _('Cannot import GNUplot python module.'), 331 aTitle = _('Query result visualizer') 332 ) 333 return 334 335 x_col = gmListWidgets.get_choices_from_list ( 336 parent = self, 337 msg = _('Choose a column to be used as the X-Axis:'), 338 caption = _('Choose column from query results ...'), 339 choices = self.query_results[0].keys(), 340 columns = [_('column name')], 341 single_selection = True 342 ) 343 if x_col is None: 344 return 345 346 y_col = gmListWidgets.get_choices_from_list ( 347 parent = self, 348 msg = _('Choose a column to be used as the Y-Axis:'), 349 caption = _('Choose column from query results ...'), 350 choices = self.query_results[0].keys(), 351 columns = [_('column name')], 352 single_selection = True 353 ) 354 if y_col is None: 355 return 356 357 # FIXME: support debugging (debug=1) depending on --debug 358 gp = Gnuplot.Gnuplot(persist=1) 359 if self._PRW_report_name.GetValue().strip() != u'': 360 gp.title(_('GNUmed report: %s') % self._PRW_report_name.GetValue().strip()[:40]) 361 else: 362 gp.title(_('GNUmed report results')) 363 gp.xlabel(x_col) 364 gp.ylabel(y_col) 365 try: 366 gp.plot([ [r[x_col], r[y_col]] for r in self.query_results ]) 367 except StandardError: 368 _log.exception('unable to plot results from [%s:%s]' % (x_col, y_col)) 369 gmDispatcher.send(signal = 'statustext', msg = _('Error plotting data.'), beep = True) 370 371 return
372 #--------------------------------------------------------
373 - def _on_run_button_pressed(self, evt):
374 375 self._BTN_visualize.Enable(False) 376 377 query = self._TCTRL_query.GetValue().strip().strip(';') 378 if query == u'': 379 return True 380 381 self._LCTRL_result.set_columns() 382 self._LCTRL_result.patient_key = None 383 384 # FIXME: make configurable 385 query = u'select * from (' + query + u'\n) as real_query limit 1024' 386 try: 387 # read-only for safety reasons 388 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': query}], get_col_idx = True) 389 except StandardError: 390 _log.exception('report query failed') 391 self._LCTRL_result.set_columns([_('Error')]) 392 t, v = sys.exc_info()[:2] 393 rows = [ 394 [_('The query failed.')], 395 [u''], 396 [unicode(t)] 397 ] 398 for line in str(v).decode(gmI18N.get_encoding()).split('\n'): 399 rows.append([line]) 400 rows.append([u'']) 401 for line in query.split('\n'): 402 rows.append([line]) 403 self._LCTRL_result.set_string_items(rows) 404 self._LCTRL_result.set_column_widths() 405 gmDispatcher.send('statustext', msg = _('The query failed.'), beep = True) 406 return False 407 408 if len(rows) == 0: 409 self._LCTRL_result.set_columns([_('Results')]) 410 self._LCTRL_result.set_string_items([[_('Report returned no data.')]]) 411 self._LCTRL_result.set_column_widths() 412 gmDispatcher.send('statustext', msg = _('No data returned for this report.'), beep = True) 413 return True 414 415 gmDispatcher.send(signal = 'statustext', msg = _('Found %s results.') % len(rows)) 416 417 # swap (col_name, col_idx) to (col_idx, col_name) as needed by 418 # set_columns() and sort them according to position-in-query 419 cols = [ (value, key) for key, value in idx.items() ] 420 cols.sort() 421 cols = [ pair[1] for pair in cols ] 422 self._LCTRL_result.set_columns(cols) 423 for row in rows: 424 label = unicode(gmTools.coalesce(row[0], u'')) 425 row_num = self._LCTRL_result.InsertStringItem(sys.maxint, label = label) 426 for col_idx in range(1, len(row)): 427 self._LCTRL_result.SetStringItem(index = row_num, col = col_idx, label = unicode(gmTools.coalesce(row[col_idx], u''))) 428 self._LCTRL_result.set_column_widths() 429 self._LCTRL_result.set_data(data = rows) 430 try: 431 self._LCTRL_result.patient_key = idx['pk_patient'] 432 except KeyError: 433 pass 434 435 self.query_results = rows 436 self._BTN_visualize.Enable(True) 437 438 return True
439 #================================================================ 440 # main 441 #---------------------------------------------------------------- 442 if __name__ == '__main__': 443 from Gnumed.pycommon import gmI18N, gmDateTime 444 445 gmI18N.activate_locale() 446 gmI18N.install_domain() 447 gmDateTime.init() 448 449 #------------------------------------------------------------
450 - def test_pat_list_ctrl():
451 app = wx.PyWidgetTester(size = (400, 500)) 452 lst = cPatientListingCtrl(app.frame, patient_key = 0) 453 lst.set_columns(['name', 'comment']) 454 lst.set_string_items([ 455 ['Kirk', 'Kirk by name'], 456 ['#12', 'Kirk by ID'], 457 ['unknown', 'unknown patient'] 458 ]) 459 # app.SetWidget(cPatientListingCtrl, patient_key = 0) 460 app.frame.Show() 461 app.MainLoop()
462 #------------------------------------------------------------ 463 464 test_pat_list_ctrl() 465 466 #================================================================ 467 # $Log: gmDataMiningWidgets.py,v $ 468 # Revision 1.14 2009-07-30 12:03:34 ncq 469 # - improved contribution email 470 # 471 # Revision 1.13 2009/07/18 11:46:53 ncq 472 # - be more robust in the face of non-existant patients being activated 473 # 474 # Revision 1.12 2009/07/15 12:21:10 ncq 475 # - auto-load report from db if name exists and query empty and name loses focus 476 # - clear results, too, on clear button 477 # - improved plot title 478 # - improved error handling around Gnuplot access 479 # - display # of results found 480 # 481 # Revision 1.11 2009/07/06 17:10:35 ncq 482 # - signal errors in sanity check of query before contributing 483 # - show warning before sending contribution 484 # - fix encoding of contribution email 485 # 486 # Revision 1.10 2009/06/04 16:30:30 ncq 487 # - use set active patient from pat search widgets 488 # 489 # Revision 1.9 2008/12/22 18:59:56 ncq 490 # - put \n before appended wrapper query because original query might have 491 # a line starting with "-- " as the last line ... 492 # 493 # Revision 1.8 2008/03/06 18:29:29 ncq 494 # - standard lib logging only 495 # 496 # Revision 1.7 2007/12/11 12:49:25 ncq 497 # - explicit signal handling 498 # 499 # Revision 1.6 2007/11/21 14:33:40 ncq 500 # - fix use of send_mail() 501 # 502 # Revision 1.5 2007/09/24 18:31:16 ncq 503 # - support visualizing data mining results 504 # 505 # Revision 1.4 2007/09/10 13:50:05 ncq 506 # - missing import 507 # 508 # Revision 1.3 2007/08/12 00:07:18 ncq 509 # - no more gmSignals.py 510 # 511 # Revision 1.2 2007/07/09 11:06:24 ncq 512 # - missing import 513 # 514 # Revision 1.1 2007/07/09 11:03:49 ncq 515 # - new file 516 # 517 # 518