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