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, 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, gmNetworkTools 
 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 # will block when called in text mode (that is, from a terminal, too !) 298 gmNetworkTools.open_url_in_browser(url = u'http://wiki.gnumed.de/bin/view/Gnumed/DatabaseSchema')
299 #--------------------------------------------------------
300 - def _on_delete_button_pressed(self, evt):
301 report = self._PRW_report_name.GetValue().strip() 302 if report == u'': 303 return True 304 if gmDataMining.delete_report_definition(name=report): 305 self._PRW_report_name.SetText() 306 self._TCTRL_query.SetValue(u'') 307 gmDispatcher.send(signal='statustext', msg = _('Deleted report definition [%s].') % report, beep=False) 308 return True 309 gmDispatcher.send(signal='statustext', msg = _('Error deleting report definition [%s].') % report, beep=True) 310 return False
311 #--------------------------------------------------------
312 - def _on_clear_button_pressed(self, evt):
313 self._PRW_report_name.SetText() 314 self._TCTRL_query.SetValue(u'') 315 self._LCTRL_result.set_columns() 316 self._LCTRL_result.patient_key = None
317 #--------------------------------------------------------
318 - def _on_save_button_pressed(self, evt):
319 report = self._PRW_report_name.GetValue().strip() 320 if report == u'': 321 gmDispatcher.send(signal='statustext', msg = _('Cannot save report definition without name.'), beep=True) 322 return False 323 query = self._TCTRL_query.GetValue().strip() 324 if query == u'': 325 gmDispatcher.send(signal='statustext', msg = _('Cannot save report definition without query.'), beep=True) 326 return False 327 # FIXME: check for exists and ask for permission 328 if gmDataMining.save_report_definition(name=report, query=query, overwrite=True): 329 gmDispatcher.send(signal='statustext', msg = _('Saved report definition [%s].') % report, beep=False) 330 return True 331 gmDispatcher.send(signal='statustext', msg = _('Error saving report definition [%s].') % report, beep=True) 332 return False
333 #--------------------------------------------------------
334 - def _on_visualize_button_pressed(self, evt):
335 336 try: 337 # better fail early 338 import Gnuplot 339 except ImportError: 340 gmGuiHelpers.gm_show_info ( 341 aMessage = _('Cannot import "Gnuplot" python module.'), 342 aTitle = _('Query result visualizer') 343 ) 344 return 345 346 x_col = gmListWidgets.get_choices_from_list ( 347 parent = self, 348 msg = _('Choose a column to be used as the X-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 x_col is None: 355 return 356 357 y_col = gmListWidgets.get_choices_from_list ( 358 parent = self, 359 msg = _('Choose a column to be used as the Y-Axis:'), 360 caption = _('Choose column from query results ...'), 361 choices = self.query_results[0].keys(), 362 columns = [_('column name')], 363 single_selection = True 364 ) 365 if y_col is None: 366 return 367 368 # FIXME: support debugging (debug=1) depending on --debug 369 gp = Gnuplot.Gnuplot(persist=1) 370 if self._PRW_report_name.GetValue().strip() != u'': 371 gp.title(_('GNUmed report: %s') % self._PRW_report_name.GetValue().strip()[:40]) 372 else: 373 gp.title(_('GNUmed report results')) 374 gp.xlabel(x_col) 375 gp.ylabel(y_col) 376 try: 377 gp.plot([ [r[x_col], r[y_col]] for r in self.query_results ]) 378 except StandardError: 379 _log.exception('unable to plot results from [%s:%s]' % (x_col, y_col)) 380 gmDispatcher.send(signal = 'statustext', msg = _('Error plotting data.'), beep = True) 381 382 return
383 #--------------------------------------------------------
384 - def _on_run_button_pressed(self, evt):
385 386 self._BTN_visualize.Enable(False) 387 388 user_query = self._TCTRL_query.GetValue().strip().strip(';') 389 if user_query == u'': 390 return True 391 392 # FIXME: make LIMIT configurable 393 limit = u'1001' 394 395 wrapper_query = u""" 396 SELECT * 397 FROM ( 398 %%s 399 ) AS user_query 400 LIMIT %s 401 """ % limit 402 403 # does user want to insert current patient ID ? 404 patient_id_token = u'$<ID_active_patient>$' 405 if user_query.find(patient_id_token) != -1: 406 # she does, but is it possible ? 407 curr_pat = gmPerson.gmCurrentPatient() 408 if not curr_pat.connected: 409 gmGuiHelpers.gm_show_error ( 410 aMessage = _( 411 'This query requires a patient to be\n' 412 'active in the client.\n' 413 '\n' 414 'Please activate the patient you are interested\n' 415 'in and re-run the query.\n' 416 ), 417 aTitle = _('Active patient query') 418 ) 419 return False 420 wrapper_query = u""" 421 SELECT 422 %s AS pk_patient, 423 * 424 FROM ( 425 %%s 426 ) AS user_query 427 LIMIT %s 428 """ % (str(curr_pat.ID), limit) 429 user_query = user_query.replace(patient_id_token, str(curr_pat.ID)) 430 431 self._LCTRL_result.set_columns() 432 self._LCTRL_result.patient_key = None 433 434 query = wrapper_query % user_query 435 try: 436 # read-only for safety reasons 437 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': query}], get_col_idx = True) 438 except StandardError: 439 _log.exception('report query failed') 440 self._LCTRL_result.set_columns([_('Error')]) 441 t, v = sys.exc_info()[:2] 442 rows = [ 443 [_('The query failed.')], 444 [u''], 445 [unicode(t)] 446 ] 447 for line in str(v).decode(gmI18N.get_encoding()).split('\n'): 448 rows.append([line]) 449 rows.append([u'']) 450 for line in user_query.split('\n'): 451 rows.append([line]) 452 self._LCTRL_result.set_string_items(rows) 453 self._LCTRL_result.set_column_widths() 454 gmDispatcher.send('statustext', msg = _('The query failed.'), beep = True) 455 return False 456 457 if len(rows) == 0: 458 self._LCTRL_result.set_columns([_('Results')]) 459 self._LCTRL_result.set_string_items([[_('Report returned no data.')]]) 460 self._LCTRL_result.set_column_widths() 461 gmDispatcher.send('statustext', msg = _('No data returned for this report.'), beep = True) 462 return True 463 464 gmDispatcher.send(signal = 'statustext', msg = _('Found %s results.') % len(rows)) 465 466 if len(rows) == 1001: 467 gmGuiHelpers.gm_show_info ( 468 aMessage = _( 469 'This query returned at least 1001 results.\n' 470 '\n' 471 'GNUmed will only show the first 1000 rows.\n' 472 '\n' 473 'You may want to narrow down the WHERE conditions\n' 474 'or use LIMIT and OFFSET to batchwise go through\n' 475 'all the matching rows.' 476 ), 477 aTitle = _('Report Generator') 478 ) 479 rows = rows[:-1] # make it true :-) 480 481 # swap (col_name, col_idx) to (col_idx, col_name) as needed by 482 # set_columns() and sort them according to position-in-query 483 cols = [ (value, key) for key, value in idx.items() ] 484 cols.sort() 485 cols = [ pair[1] for pair in cols ] 486 self._LCTRL_result.set_columns(cols) 487 for row in rows: 488 try: 489 label = unicode(gmTools.coalesce(row[0], u'')).replace('\n', '<LF>').replace('\r', '<CR>') 490 except UnicodeDecodeError: 491 label = _('not unicode()able') 492 if len(label) > 150: 493 label = label[:150] + gmTools.u_ellipsis 494 row_num = self._LCTRL_result.InsertStringItem(sys.maxint, label = label) 495 for col_idx in range(1, len(row)): 496 try: 497 label = unicode(gmTools.coalesce(row[col_idx], u'')).replace('\n', '<LF>').replace('\r', '<CR>')[:250] 498 except UnicodeDecodeError: 499 label = _('not unicode()able') 500 if len(label) > 150: 501 label = label[:150] + gmTools.u_ellipsis 502 self._LCTRL_result.SetStringItem ( 503 index = row_num, 504 col = col_idx, 505 label = label 506 ) 507 self._LCTRL_result.set_column_widths() 508 self._LCTRL_result.set_data(data = rows) 509 try: 510 self._LCTRL_result.patient_key = idx['pk_patient'] 511 except KeyError: 512 pass 513 514 self.query_results = rows 515 self._BTN_visualize.Enable(True) 516 517 return True
518 #================================================================ 519 # main 520 #---------------------------------------------------------------- 521 if __name__ == '__main__': 522 from Gnumed.pycommon import gmI18N, gmDateTime 523 524 gmI18N.activate_locale() 525 gmI18N.install_domain() 526 gmDateTime.init() 527 528 #------------------------------------------------------------
529 - def test_pat_list_ctrl():
530 app = wx.PyWidgetTester(size = (400, 500)) 531 lst = cPatientListingCtrl(app.frame, patient_key = 0) 532 lst.set_columns(['name', 'comment']) 533 lst.set_string_items([ 534 ['Kirk', 'Kirk by name'], 535 ['#12', 'Kirk by ID'], 536 ['unknown', 'unknown patient'] 537 ]) 538 # app.SetWidget(cPatientListingCtrl, patient_key = 0) 539 app.frame.Show() 540 app.MainLoop()
541 #------------------------------------------------------------ 542 543 test_pat_list_ctrl() 544 545 #================================================================ 546