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 # act on text files only 183 mime_type = gmMimeLib.guess_mimetype(fname) 184 if not mime_type.startswith('text/'): 185 gmDispatcher.send(signal='statustext', msg = _('Cannot read SQL from [%s]. Not a text file.') % fname, beep = True) 186 return False 187 # act on "small" files only 188 stat_val = os.stat(fname) 189 if stat_val.st_size > 2000: 190 gmDispatcher.send(signal='statustext', msg = _('Cannot read SQL from [%s]. File too big (> 2000 bytes).') % fname, beep = True) 191 return False 192 # all checks passed 193 for line in fileinput.input(fname): 194 self._TCTRL_query.AppendText(line)
195 #-------------------------------------------------------- 196 # notebook plugin API 197 #--------------------------------------------------------
198 - def repopulate_ui(self):
199 pass
200 #-------------------------------------------------------- 201 # event handlers 202 #--------------------------------------------------------
203 - def _on_list_item_activated(self, evt):
204 data = self._LCTRL_result.get_selected_item_data() 205 206 try: 207 pk_pat = data['pk_patient'] 208 except KeyError: 209 gmGuiHelpers.gm_show_warning ( 210 _( 211 'Cannot activate patient.\n\n' 212 'The report result list does not contain\n' 213 'a column named "pk_patient".\n\n' 214 'You may want to use the SQL "AS" column alias\n' 215 'syntax to make your query return such a column.\n' 216 ), 217 _('Activating patient from report result') 218 ) 219 return 220 221 try: 222 pat = gmPerson.cPatient(aPK_obj = pk_pat) 223 except StandardError: 224 gmGuiHelpers.gm_show_warning ( 225 _( 226 'Cannot activate patient.\n' 227 '\n' 228 'There does not seem to exist a patient\n' 229 'with an internal ID of [%s].\n' 230 ) % pk_pat, 231 _('Activating patient from report result') 232 ) 233 return 234 235 from Gnumed.wxpython import gmPatSearchWidgets 236 gmPatSearchWidgets.set_active_patient(patient = pat)
237 #--------------------------------------------------------
238 - def _on_contribute_button_pressed(self, evt):
239 report = self._PRW_report_name.GetValue().strip() 240 if report == u'': 241 gmDispatcher.send(signal = 'statustext', msg = _('Report must have a name for contribution.'), beep = False) 242 return 243 244 query = self._TCTRL_query.GetValue().strip() 245 if query == u'': 246 gmDispatcher.send(signal = 'statustext', msg = _('Report must have a query for contribution.'), beep = False) 247 return 248 249 do_it = gmGuiHelpers.gm_show_question ( 250 _( 'Be careful that your contribution (the query itself) does\n' 251 'not contain any person-identifiable search parameters.\n' 252 '\n' 253 'Note, however, that no query result data whatsoever\n' 254 'is included in the contribution that will be sent.\n' 255 '\n' 256 'Are you sure you wish to send this query to\n' 257 'the gnumed community mailing list?\n' 258 ), 259 _('Contributing custom report') 260 ) 261 if not do_it: 262 return 263 264 auth = {'user': gmNetworkTools.default_mail_sender, 'password': u'gnumed-at-gmx-net'} 265 msg = u"""--- This is a report definition contributed by a GNUmed user: 266 267 ---------------------------------------- 268 269 --- %s 270 271 %s 272 273 ---------------------------------------- 274 275 --- The GNUmed client. 276 """ % (report, query) 277 278 if not gmNetworkTools.send_mail ( 279 sender = u'GNUmed Report Generator <gnumed@gmx.net>', 280 receiver = [u'gnumed-devel@gnu.org'], 281 subject = u'user contributed report', 282 message = msg, 283 encoding = gmI18N.get_encoding(), 284 server = gmNetworkTools.default_mail_server, 285 auth = auth 286 ): 287 gmDispatcher.send(signal = 'statustext', msg = _('Unable to send mail. Cannot contribute report [%s] to GNUmed community.') % report, beep = True) 288 return False 289 290 gmDispatcher.send(signal = 'statustext', msg = _('Thank you for your contribution to the GNUmed community!'), beep = False) 291 return True
292 #--------------------------------------------------------
293 - def _on_schema_button_pressed(self, evt):
294 # new=2: Python 2.5: open new tab 295 # will block when called in text mode (that is, from a terminal, too !) 296 webbrowser.open(u'http://wiki.gnumed.de/bin/view/Gnumed/DatabaseSchema', new=2, autoraise=1)
297 #--------------------------------------------------------
298 - def _on_delete_button_pressed(self, evt):
299 report = self._PRW_report_name.GetValue().strip() 300 if report == u'': 301 return True 302 if gmDataMining.delete_report_definition(name=report): 303 self._PRW_report_name.SetText() 304 self._TCTRL_query.SetValue(u'') 305 gmDispatcher.send(signal='statustext', msg = _('Deleted report definition [%s].') % report, beep=False) 306 return True 307 gmDispatcher.send(signal='statustext', msg = _('Error deleting report definition [%s].') % report, beep=True) 308 return False
309 #--------------------------------------------------------
310 - def _on_clear_button_pressed(self, evt):
311 self._PRW_report_name.SetText() 312 self._TCTRL_query.SetValue(u'') 313 self._LCTRL_result.set_columns() 314 self._LCTRL_result.patient_key = None
315 #--------------------------------------------------------
316 - def _on_save_button_pressed(self, evt):
317 report = self._PRW_report_name.GetValue().strip() 318 if report == u'': 319 gmDispatcher.send(signal='statustext', msg = _('Cannot save report definition without name.'), beep=True) 320 return False 321 query = self._TCTRL_query.GetValue().strip() 322 if query == u'': 323 gmDispatcher.send(signal='statustext', msg = _('Cannot save report definition without query.'), beep=True) 324 return False 325 # FIXME: check for exists and ask for permission 326 if gmDataMining.save_report_definition(name=report, query=query, overwrite=True): 327 gmDispatcher.send(signal='statustext', msg = _('Saved report definition [%s].') % report, beep=False) 328 return True 329 gmDispatcher.send(signal='statustext', msg = _('Error saving report definition [%s].') % report, beep=True) 330 return False
331 #--------------------------------------------------------
332 - def _on_visualize_button_pressed(self, evt):
333 334 try: 335 # better fail early 336 import Gnuplot 337 except ImportError: 338 gmGuiHelpers.gm_show_info ( 339 aMessage = _('Cannot import "Gnuplot" python module.'), 340 aTitle = _('Query result visualizer') 341 ) 342 return 343 344 x_col = gmListWidgets.get_choices_from_list ( 345 parent = self, 346 msg = _('Choose a column to be used as the X-Axis:'), 347 caption = _('Choose column from query results ...'), 348 choices = self.query_results[0].keys(), 349 columns = [_('column name')], 350 single_selection = True 351 ) 352 if x_col is None: 353 return 354 355 y_col = gmListWidgets.get_choices_from_list ( 356 parent = self, 357 msg = _('Choose a column to be used as the Y-Axis:'), 358 caption = _('Choose column from query results ...'), 359 choices = self.query_results[0].keys(), 360 columns = [_('column name')], 361 single_selection = True 362 ) 363 if y_col is None: 364 return 365 366 # FIXME: support debugging (debug=1) depending on --debug 367 gp = Gnuplot.Gnuplot(persist=1) 368 if self._PRW_report_name.GetValue().strip() != u'': 369 gp.title(_('GNUmed report: %s') % self._PRW_report_name.GetValue().strip()[:40]) 370 else: 371 gp.title(_('GNUmed report results')) 372 gp.xlabel(x_col) 373 gp.ylabel(y_col) 374 try: 375 gp.plot([ [r[x_col], r[y_col]] for r in self.query_results ]) 376 except StandardError: 377 _log.exception('unable to plot results from [%s:%s]' % (x_col, y_col)) 378 gmDispatcher.send(signal = 'statustext', msg = _('Error plotting data.'), beep = True) 379 380 return
381 #--------------------------------------------------------
382 - def _on_run_button_pressed(self, evt):
383 384 self._BTN_visualize.Enable(False) 385 386 user_query = self._TCTRL_query.GetValue().strip().strip(';') 387 if user_query == u'': 388 return True 389 390 # FIXME: make LIMIT configurable 391 limit = u'1001' 392 393 wrapper_query = u""" 394 SELECT * 395 FROM ( 396 %%s 397 ) AS user_query 398 LIMIT %s 399 """ % limit 400 401 # does user want to insert current patient ID ? 402 patient_id_token = u'$<ID_active_patient>$' 403 if user_query.find(patient_id_token) != -1: 404 # she does, but is it possible ? 405 curr_pat = gmPerson.gmCurrentPatient() 406 if not curr_pat.connected: 407 gmGuiHelpers.gm_show_error ( 408 aMessage = _( 409 'This query requires a patient to be\n' 410 'active in the client.\n' 411 '\n' 412 'Please activate the patient you are interested\n' 413 'in and re-run the query.\n' 414 ), 415 aTitle = _('Active patient query') 416 ) 417 return False 418 wrapper_query = u""" 419 SELECT 420 %s AS pk_patient, 421 * 422 FROM ( 423 %%s 424 ) AS user_query 425 LIMIT %s 426 """ % (str(curr_pat.ID), limit) 427 user_query = user_query.replace(patient_id_token, str(curr_pat.ID)) 428 429 self._LCTRL_result.set_columns() 430 self._LCTRL_result.patient_key = None 431 432 query = wrapper_query % user_query 433 try: 434 # read-only for safety reasons 435 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': query}], get_col_idx = True) 436 except StandardError: 437 _log.exception('report query failed') 438 self._LCTRL_result.set_columns([_('Error')]) 439 t, v = sys.exc_info()[:2] 440 rows = [ 441 [_('The query failed.')], 442 [u''], 443 [unicode(t)] 444 ] 445 for line in str(v).decode(gmI18N.get_encoding()).split('\n'): 446 rows.append([line]) 447 rows.append([u'']) 448 for line in user_query.split('\n'): 449 rows.append([line]) 450 self._LCTRL_result.set_string_items(rows) 451 self._LCTRL_result.set_column_widths() 452 gmDispatcher.send('statustext', msg = _('The query failed.'), beep = True) 453 return False 454 455 if len(rows) == 0: 456 self._LCTRL_result.set_columns([_('Results')]) 457 self._LCTRL_result.set_string_items([[_('Report returned no data.')]]) 458 self._LCTRL_result.set_column_widths() 459 gmDispatcher.send('statustext', msg = _('No data returned for this report.'), beep = True) 460 return True 461 462 gmDispatcher.send(signal = 'statustext', msg = _('Found %s results.') % len(rows)) 463 464 if len(rows) == 1001: 465 gmGuiHelpers.gm_show_info ( 466 aMessage = _( 467 'This query returned at least 1001 results.\n' 468 '\n' 469 'GNUmed will only show the first 1000 rows.\n' 470 '\n' 471 'You may want to narrow down the WHERE conditions\n' 472 'or use LIMIT and OFFSET to batchwise go through\n' 473 'all the matching rows.' 474 ), 475 aTitle = _('Report Generator') 476 ) 477 rows = rows[:-1] # make it true :-) 478 479 # swap (col_name, col_idx) to (col_idx, col_name) as needed by 480 # set_columns() and sort them according to position-in-query 481 cols = [ (value, key) for key, value in idx.items() ] 482 cols.sort() 483 cols = [ pair[1] for pair in cols ] 484 self._LCTRL_result.set_columns(cols) 485 for row in rows: 486 try: 487 label = unicode(gmTools.coalesce(row[0], u'')).replace('\n', '<LF>').replace('\r', '<CR>') 488 except UnicodeDecodeError: 489 label = _('not unicode()able') 490 if len(label) > 150: 491 label = label[:150] + gmTools.u_ellipsis 492 row_num = self._LCTRL_result.InsertStringItem(sys.maxint, label = label) 493 for col_idx in range(1, len(row)): 494 try: 495 label = unicode(gmTools.coalesce(row[col_idx], u'')).replace('\n', '<LF>').replace('\r', '<CR>')[:250] 496 except UnicodeDecodeError: 497 label = _('not unicode()able') 498 if len(label) > 150: 499 label = label[:150] + gmTools.u_ellipsis 500 self._LCTRL_result.SetStringItem ( 501 index = row_num, 502 col = col_idx, 503 label = label 504 ) 505 self._LCTRL_result.set_column_widths() 506 self._LCTRL_result.set_data(data = rows) 507 try: 508 self._LCTRL_result.patient_key = idx['pk_patient'] 509 except KeyError: 510 pass 511 512 self.query_results = rows 513 self._BTN_visualize.Enable(True) 514 515 return True
516 #================================================================ 517 # main 518 #---------------------------------------------------------------- 519 if __name__ == '__main__': 520 from Gnumed.pycommon import gmI18N, gmDateTime 521 522 gmI18N.activate_locale() 523 gmI18N.install_domain() 524 gmDateTime.init() 525 526 #------------------------------------------------------------
527 - def test_pat_list_ctrl():
528 app = wx.PyWidgetTester(size = (400, 500)) 529 lst = cPatientListingCtrl(app.frame, patient_key = 0) 530 lst.set_columns(['name', 'comment']) 531 lst.set_string_items([ 532 ['Kirk', 'Kirk by name'], 533 ['#12', 'Kirk by ID'], 534 ['unknown', 'unknown patient'] 535 ]) 536 # app.SetWidget(cPatientListingCtrl, patient_key = 0) 537 app.frame.Show() 538 app.MainLoop()
539 #------------------------------------------------------------ 540 541 test_pat_list_ctrl() 542 543 #================================================================ 544