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

Source Code for Module Gnumed.wxpython.gmMedicationWidgets

   1  """GNUmed medication/substances handling widgets.""" 
   2   
   3  #================================================================ 
   4  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
   5  __license__ = "GPL v2 or later" 
   6   
   7  import logging 
   8  import sys 
   9  import os.path 
  10  import decimal 
  11   
  12   
  13  import wx 
  14  import wx.grid 
  15   
  16   
  17  if __name__ == '__main__': 
  18          sys.path.insert(0, '../../') 
  19          from Gnumed.pycommon import gmI18N 
  20          gmI18N.activate_locale() 
  21          gmI18N.install_domain(domain = 'gnumed') 
  22   
  23  from Gnumed.pycommon import gmDispatcher 
  24  from Gnumed.pycommon import gmCfg 
  25  from Gnumed.pycommon import gmTools 
  26  from Gnumed.pycommon import gmDateTime 
  27  from Gnumed.pycommon import gmMatchProvider 
  28  from Gnumed.pycommon import gmI18N 
  29  from Gnumed.pycommon import gmPrinting 
  30  from Gnumed.pycommon import gmCfg2 
  31  from Gnumed.pycommon import gmNetworkTools 
  32   
  33  from Gnumed.business import gmPerson 
  34  from Gnumed.business import gmATC 
  35  from Gnumed.business import gmPraxis 
  36  from Gnumed.business import gmMedication 
  37  from Gnumed.business import gmForms 
  38  from Gnumed.business import gmStaff 
  39  from Gnumed.business import gmDocuments 
  40  from Gnumed.business import gmLOINC 
  41  from Gnumed.business import gmClinicalRecord 
  42  from Gnumed.business import gmClinicalCalculator 
  43   
  44  from Gnumed.wxpython import gmGuiHelpers 
  45  from Gnumed.wxpython import gmRegetMixin 
  46  from Gnumed.wxpython import gmAuthWidgets 
  47  from Gnumed.wxpython import gmEditArea 
  48  from Gnumed.wxpython import gmMacro 
  49  from Gnumed.wxpython import gmCfgWidgets 
  50  from Gnumed.wxpython import gmListWidgets 
  51  from Gnumed.wxpython import gmPhraseWheel 
  52  from Gnumed.wxpython import gmFormWidgets 
  53  from Gnumed.wxpython import gmAllergyWidgets 
  54  from Gnumed.wxpython import gmDocumentWidgets 
  55   
  56   
  57  _log = logging.getLogger('gm.ui') 
  58   
  59  #============================================================ 
  60  # generic drug database access 
  61  #============================================================ 
62 -def configure_drug_data_source(parent=None):
63 gmCfgWidgets.configure_string_from_list_option ( 64 parent = parent, 65 message = _( 66 '\n' 67 'Please select the default drug data source from the list below.\n' 68 '\n' 69 'Note that to actually use it you need to have the database installed, too.' 70 ), 71 option = 'external.drug_data.default_source', 72 bias = 'user', 73 default_value = None, 74 choices = gmMedication.drug_data_source_interfaces.keys(), 75 columns = [_('Drug data source')], 76 data = gmMedication.drug_data_source_interfaces.keys(), 77 caption = _('Configuring default drug data source') 78 )
79 #============================================================
80 -def get_drug_database(parent = None):
81 dbcfg = gmCfg.cCfgSQL() 82 83 # load from option 84 default_db = dbcfg.get2 ( 85 option = 'external.drug_data.default_source', 86 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 87 bias = 'workplace' 88 ) 89 90 # not configured -> try to configure 91 if default_db is None: 92 gmDispatcher.send('statustext', msg = _('No default drug database configured.'), beep = True) 93 configure_drug_data_source(parent = parent) 94 default_db = dbcfg.get2 ( 95 option = 'external.drug_data.default_source', 96 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 97 bias = 'workplace' 98 ) 99 # still not configured -> return 100 if default_db is None: 101 gmGuiHelpers.gm_show_error ( 102 aMessage = _('There is no default drug database configured.'), 103 aTitle = _('Jumping to drug database') 104 ) 105 return None 106 107 # now it MUST be configured (either newly or previously) 108 # but also *validly* ? 109 try: 110 drug_db = gmMedication.drug_data_source_interfaces[default_db]() 111 except KeyError: 112 # not valid 113 _log.error('faulty default drug data source configuration: %s', default_db) 114 # try to configure 115 configure_drug_data_source(parent = parent) 116 default_db = dbcfg.get2 ( 117 option = 'external.drug_data.default_source', 118 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 119 bias = 'workplace' 120 ) 121 # deconfigured or aborted (and thusly still misconfigured) ? 122 try: 123 drug_db = gmMedication.drug_data_source_interfaces[default_db]() 124 except KeyError: 125 _log.error('still faulty default drug data source configuration: %s', default_db) 126 return None 127 128 pat = gmPerson.gmCurrentPatient() 129 if pat.connected: 130 drug_db.patient = pat 131 132 return drug_db
133 #============================================================
134 -def jump_to_drug_database():
135 dbcfg = gmCfg.cCfgSQL() 136 drug_db = get_drug_database() 137 if drug_db is None: 138 return 139 drug_db.switch_to_frontend(blocking = False)
140 141 #============================================================
142 -def jump_to_ifap(import_drugs=False):
143 144 dbcfg = gmCfg.cCfgSQL() 145 146 ifap_cmd = dbcfg.get2 ( 147 option = 'external.ifap-win.shell_command', 148 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 149 bias = 'workplace', 150 default = 'wine "C:\Ifapwin\WIAMDB.EXE"' 151 ) 152 found, binary = gmShellAPI.detect_external_binary(ifap_cmd) 153 if not found: 154 gmDispatcher.send('statustext', msg = _('Cannot call IFAP via [%s].') % ifap_cmd) 155 return False 156 ifap_cmd = binary 157 158 if import_drugs: 159 transfer_file = os.path.expanduser(dbcfg.get2 ( 160 option = 'external.ifap-win.transfer_file', 161 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 162 bias = 'workplace', 163 default = '~/.wine/drive_c/Ifapwin/ifap2gnumed.csv' 164 )) 165 # file must exist for Ifap to write into it 166 try: 167 f = open(transfer_file, 'w+b').close() 168 except IOError: 169 _log.exception('Cannot create IFAP <-> GNUmed transfer file [%s]', transfer_file) 170 gmDispatcher.send('statustext', msg = _('Cannot create IFAP <-> GNUmed transfer file [%s].') % transfer_file) 171 return False 172 173 wx.BeginBusyCursor() 174 gmShellAPI.run_command_in_shell(command = ifap_cmd, blocking = import_drugs) 175 wx.EndBusyCursor() 176 177 if import_drugs: 178 # COMMENT: this file must exist PRIOR to invoking IFAP 179 # COMMENT: or else IFAP will not write data into it ... 180 try: 181 csv_file = open(transfer_file, 'rb') # FIXME: encoding 182 except: 183 _log.exception('cannot access [%s]', fname) 184 csv_file = None 185 186 if csv_file is not None: 187 import csv 188 csv_lines = csv.DictReader ( 189 csv_file, 190 fieldnames = u'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split(), 191 delimiter = ';' 192 ) 193 pat = gmPerson.gmCurrentPatient() 194 emr = pat.get_emr() 195 # dummy episode for now 196 epi = emr.add_episode(episode_name = _('Current medication')) 197 for line in csv_lines: 198 narr = u'%sx %s %s %s (\u2258 %s %s) von %s (%s)' % ( 199 line['Packungszahl'].strip(), 200 line['Handelsname'].strip(), 201 line['Form'].strip(), 202 line[u'Packungsgr\xf6\xdfe'].strip(), 203 line['Abpackungsmenge'].strip(), 204 line['Einheit'].strip(), 205 line['Hersteller'].strip(), 206 line['PZN'].strip() 207 ) 208 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi) 209 csv_file.close() 210 211 return True
212 213 #============================================================ 214 # ATC related widgets 215 #============================================================ 216
217 -def browse_atc_reference(parent=None):
218 219 if parent is None: 220 parent = wx.GetApp().GetTopWindow() 221 #------------------------------------------------------------ 222 def refresh(lctrl): 223 atcs = gmATC.get_reference_atcs() 224 225 items = [ [ 226 a['atc'], 227 a['term'], 228 u'%s' % gmTools.coalesce(a['ddd'], u''), 229 gmTools.coalesce(a['unit'], u''), 230 gmTools.coalesce(a['administrative_route'], u''), 231 gmTools.coalesce(a['comment'], u''), 232 a['version'], 233 a['lang'] 234 ] for a in atcs ] 235 lctrl.set_string_items(items) 236 lctrl.set_data(atcs)
237 #------------------------------------------------------------ 238 gmListWidgets.get_choices_from_list ( 239 parent = parent, 240 msg = _('\nThe ATC codes as known to GNUmed.\n'), 241 caption = _('Showing ATC codes.'), 242 columns = [ u'ATC', _('Term'), u'DDD', _('Unit'), _(u'Route'), _('Comment'), _('Version'), _('Language') ], 243 single_selection = True, 244 refresh_callback = refresh 245 ) 246 247 #============================================================
248 -def update_atc_reference_data():
249 250 dlg = wx.FileDialog ( 251 parent = None, 252 message = _('Choose an ATC import config file'), 253 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')), 254 defaultFile = '', 255 wildcard = "%s (*.conf)|*.conf|%s (*)|*" % (_('config files'), _('all files')), 256 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST 257 ) 258 259 result = dlg.ShowModal() 260 if result == wx.ID_CANCEL: 261 return 262 263 cfg_file = dlg.GetPath() 264 dlg.Destroy() 265 266 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing ATC reference data')) 267 if conn is None: 268 return False 269 270 wx.BeginBusyCursor() 271 272 if gmATC.atc_import(cfg_fname = cfg_file, conn = conn): 273 gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported ATC reference data.')) 274 else: 275 gmDispatcher.send(signal = 'statustext', msg = _('Importing ATC reference data failed.'), beep = True) 276 277 wx.EndBusyCursor() 278 return True
279 280 #============================================================ 281
282 -class cATCPhraseWheel(gmPhraseWheel.cPhraseWheel):
283
284 - def __init__(self, *args, **kwargs):
285 286 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 287 288 query = u""" 289 290 SELECT DISTINCT ON (label) 291 atc_code, 292 label 293 FROM ( 294 295 SELECT 296 code as atc_code, 297 (code || ': ' || term || coalesce(' (' || ddd || unit || ')', '')) 298 AS label 299 FROM ref.atc 300 WHERE 301 term %(fragment_condition)s 302 OR 303 code %(fragment_condition)s 304 305 UNION ALL 306 307 SELECT 308 atc_code, 309 (atc_code || ': ' || description) 310 AS label 311 FROM ref.consumable_substance 312 WHERE 313 description %(fragment_condition)s 314 OR 315 atc_code %(fragment_condition)s 316 317 UNION ALL 318 319 SELECT 320 atc_code, 321 (atc_code || ': ' || description || ' (' || preparation || ')') 322 AS label 323 FROM ref.branded_drug 324 WHERE 325 description %(fragment_condition)s 326 OR 327 atc_code %(fragment_condition)s 328 329 -- it would be nice to be able to include clin.vacc_indication but that's hard to do in SQL 330 331 ) AS candidates 332 WHERE atc_code IS NOT NULL 333 ORDER BY label 334 LIMIT 50""" 335 336 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 337 mp.setThresholds(1, 2, 4) 338 # mp.word_separators = '[ \t=+&:@]+' 339 self.SetToolTipString(_('Select an ATC (Anatomical-Therapeutic-Chemical) code.')) 340 self.matcher = mp 341 self.selection_only = True
342 343 #============================================================ 344 # consumable substances widgets 345 #------------------------------------------------------------
346 -def manage_consumable_substances(parent=None):
347 348 if parent is None: 349 parent = wx.GetApp().GetTopWindow() 350 #------------------------------------------------------------ 351 def add_from_db(substance): 352 drug_db = get_drug_database(parent = parent) 353 if drug_db is None: 354 return False 355 drug_db.import_drugs() 356 return True
357 #------------------------------------------------------------ 358 def edit(substance=None): 359 return edit_consumable_substance(parent = parent, substance = substance, single_entry = (substance is not None)) 360 #------------------------------------------------------------ 361 def delete(substance): 362 if substance.is_in_use_by_patients: 363 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this substance. It is in use.'), beep = True) 364 return False 365 366 return gmMedication.delete_consumable_substance(substance = substance['pk']) 367 #------------------------------------------------------------ 368 def refresh(lctrl): 369 substs = gmMedication.get_consumable_substances(order_by = 'description') 370 items = [ [ 371 s['description'], 372 s['amount'], 373 s['unit'], 374 gmTools.coalesce(s['atc_code'], u''), 375 s['pk'] 376 ] for s in substs ] 377 lctrl.set_string_items(items) 378 lctrl.set_data(substs) 379 #------------------------------------------------------------ 380 msg = _('\nThese are the consumable substances registered with GNUmed.\n') 381 382 gmListWidgets.get_choices_from_list ( 383 parent = parent, 384 msg = msg, 385 caption = _('Showing consumable substances.'), 386 columns = [_('Substance'), _('Amount'), _('Unit'), 'ATC', u'#'], 387 single_selection = True, 388 new_callback = edit, 389 edit_callback = edit, 390 delete_callback = delete, 391 refresh_callback = refresh, 392 left_extra_button = (_('Import'), _('Import consumable substances from a drug database.'), add_from_db) 393 ) 394 395 #------------------------------------------------------------
396 -def edit_consumable_substance(parent=None, substance=None, single_entry=False):
397 398 if substance is not None: 399 if substance.is_in_use_by_patients: 400 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit this substance. It is in use.'), beep = True) 401 return False 402 403 ea = cConsumableSubstanceEAPnl(parent = parent, id = -1) 404 ea.data = substance 405 ea.mode = gmTools.coalesce(substance, 'new', 'edit') 406 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry) 407 dlg.SetTitle(gmTools.coalesce(substance, _('Adding new consumable substance'), _('Editing consumable substance'))) 408 if dlg.ShowModal() == wx.ID_OK: 409 dlg.Destroy() 410 return True 411 dlg.Destroy() 412 return False
413 414 #============================================================ 415 from Gnumed.wxGladeWidgets import wxgConsumableSubstanceEAPnl 416
417 -class cConsumableSubstanceEAPnl(wxgConsumableSubstanceEAPnl.wxgConsumableSubstanceEAPnl, gmEditArea.cGenericEditAreaMixin):
418
419 - def __init__(self, *args, **kwargs):
420 421 try: 422 data = kwargs['substance'] 423 del kwargs['substance'] 424 except KeyError: 425 data = None 426 427 wxgConsumableSubstanceEAPnl.wxgConsumableSubstanceEAPnl.__init__(self, *args, **kwargs) 428 gmEditArea.cGenericEditAreaMixin.__init__(self) 429 430 # Code using this mixin should set mode and data 431 # after instantiating the class: 432 self.mode = 'new' 433 self.data = data 434 if data is not None: 435 self.mode = 'edit'
436 437 # self.__init_ui() 438 #---------------------------------------------------------------- 439 # def __init_ui(self): 440 # self._PRW_atc.selection_only = False 441 #---------------------------------------------------------------- 442 # generic Edit Area mixin API 443 #----------------------------------------------------------------
444 - def _valid_for_save(self):
445 446 validity = True 447 448 if self._TCTRL_substance.GetValue().strip() == u'': 449 validity = False 450 self.display_tctrl_as_valid(tctrl = self._TCTRL_substance, valid = False) 451 self._TCTRL_substance.SetFocus() 452 else: 453 self.display_tctrl_as_valid(tctrl = self._TCTRL_substance, valid = True) 454 455 try: 456 decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', '.')) 457 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True) 458 except (TypeError, decimal.InvalidOperation): 459 validity = False 460 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False) 461 self._TCTRL_amount.SetFocus() 462 463 if self._PRW_unit.GetValue().strip() == u'': 464 validity = False 465 self._PRW_unit.display_as_valid(valid = False) 466 self._TCTRL_substance.SetFocus() 467 else: 468 self._PRW_unit.display_as_valid(valid = True) 469 470 if validity is False: 471 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save consumable substance. Missing essential input.')) 472 473 return validity
474 #----------------------------------------------------------------
475 - def _save_as_new(self):
476 subst = gmMedication.create_consumable_substance ( 477 substance = self._TCTRL_substance.GetValue().strip(), 478 atc = self._PRW_atc.GetData(), 479 amount = decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', '.')), 480 unit = gmTools.coalesce(self._PRW_unit.GetData(), self._PRW_unit.GetValue().strip(), function_initial = ('strip', None)) 481 ) 482 success, data = subst.save() 483 if not success: 484 err, msg = data 485 _log.error(err) 486 _log.error(msg) 487 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save consumable substance. %s') % msg, beep = True) 488 return False 489 490 self.data = subst 491 return True
492 #----------------------------------------------------------------
493 - def _save_as_update(self):
494 self.data['description'] = self._TCTRL_substance.GetValue().strip() 495 self.data['atc_code'] = self._PRW_atc.GetData() 496 self.data['amount'] = decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', '.')) 497 self.data['unit'] = gmTools.coalesce(self._PRW_unit.GetData(), self._PRW_unit.GetValue().strip(), function_initial = ('strip', None)) 498 success, data = self.data.save() 499 500 if not success: 501 err, msg = data 502 _log.error(err) 503 _log.error(msg) 504 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save consumable substance. %s') % msg, beep = True) 505 return False 506 507 return True
508 #----------------------------------------------------------------
509 - def _refresh_as_new(self):
510 self._TCTRL_substance.SetValue(u'') 511 self._TCTRL_amount.SetValue(u'') 512 self._PRW_unit.SetText(u'', None) 513 self._PRW_atc.SetText(u'', None) 514 515 self._TCTRL_substance.SetFocus()
516 #----------------------------------------------------------------
517 - def _refresh_from_existing(self):
518 self._TCTRL_substance.SetValue(self.data['description']) 519 self._TCTRL_amount.SetValue(u'%s' % self.data['amount']) 520 self._PRW_unit.SetText(self.data['unit'], self.data['unit']) 521 self._PRW_atc.SetText(gmTools.coalesce(self.data['atc_code'], u''), self.data['atc_code']) 522 523 self._TCTRL_substance.SetFocus()
524 #----------------------------------------------------------------
526 self._refresh_as_new()
527 528 #============================================================ 529 # drug component widgets 530 #------------------------------------------------------------
531 -def manage_drug_components(parent=None):
532 533 if parent is None: 534 parent = wx.GetApp().GetTopWindow() 535 536 #------------------------------------------------------------ 537 def edit(component=None): 538 substance = gmMedication.cConsumableSubstance(aPK_obj = component['pk_consumable_substance']) 539 return edit_consumable_substance(parent = parent, substance = substance, single_entry = True)
540 #------------------------------------------------------------ 541 def delete(component): 542 if component.is_in_use_by_patients: 543 gmDispatcher.send(signal = 'statustext', msg = _('Cannot remove this component from the drug. It is in use.'), beep = True) 544 return False 545 546 return component.containing_drug.remove_component(substance = component['pk_component']) 547 #------------------------------------------------------------ 548 def refresh(lctrl): 549 comps = gmMedication.get_drug_components() 550 items = [ [ 551 u'%s%s' % (c['brand'], gmTools.coalesce(c['atc_brand'], u'', u' [%s]')), 552 u'%s%s' % (c['substance'], gmTools.coalesce(c['atc_substance'], u'', u' [%s]')), 553 u'%s %s' % (c['amount'], c['unit']), 554 c['preparation'], 555 gmTools.coalesce(c['external_code_brand'], u'', u'%%s [%s]' % c['external_code_type_brand']), 556 c['pk_component'] 557 ] for c in comps ] 558 lctrl.set_string_items(items) 559 lctrl.set_data(comps) 560 #------------------------------------------------------------ 561 msg = _('\nThese are the components in the drug brands known to GNUmed.\n') 562 563 gmListWidgets.get_choices_from_list ( 564 parent = parent, 565 msg = msg, 566 caption = _('Showing drug brand components.'), 567 columns = [_('Brand'), _('Substance'), _('Strength'), _('Preparation'), _('Code'), u'#'], 568 single_selection = True, 569 #new_callback = edit, 570 edit_callback = edit, 571 delete_callback = delete, 572 refresh_callback = refresh 573 ) 574 575 #------------------------------------------------------------
576 -def edit_drug_component(parent=None, drug_component=None, single_entry=False):
577 ea = cDrugComponentEAPnl(parent = parent, id = -1) 578 ea.data = drug_component 579 ea.mode = gmTools.coalesce(drug_component, 'new', 'edit') 580 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry) 581 dlg.SetTitle(gmTools.coalesce(drug_component, _('Adding new drug component'), _('Editing drug component'))) 582 if dlg.ShowModal() == wx.ID_OK: 583 dlg.Destroy() 584 return True 585 dlg.Destroy() 586 return False
587 588 #============================================================ 589 from Gnumed.wxGladeWidgets import wxgDrugComponentEAPnl 590
591 -class cDrugComponentEAPnl(wxgDrugComponentEAPnl.wxgDrugComponentEAPnl, gmEditArea.cGenericEditAreaMixin):
592
593 - def __init__(self, *args, **kwargs):
594 595 try: 596 data = kwargs['component'] 597 del kwargs['component'] 598 except KeyError: 599 data = None 600 601 wxgDrugComponentEAPnl.wxgDrugComponentEAPnl.__init__(self, *args, **kwargs) 602 gmEditArea.cGenericEditAreaMixin.__init__(self) 603 604 # Code using this mixin should set mode and data 605 # after instantiating the class: 606 self.mode = 'new' 607 self.data = data 608 if data is not None: 609 self.mode = 'edit'
610 611 #self.__init_ui() 612 #---------------------------------------------------------------- 613 # def __init_ui(self): 614 # # adjust phrasewheels etc 615 #---------------------------------------------------------------- 616 # generic Edit Area mixin API 617 #----------------------------------------------------------------
618 - def _valid_for_save(self):
619 if self.data is not None: 620 if self.data['is_in_use']: 621 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit drug component. It is in use.'), beep = True) 622 return False 623 624 validity = True 625 626 if self._PRW_substance.GetData() is None: 627 validity = False 628 self._PRW_substance.display_as_valid(False) 629 else: 630 self._PRW_substance.display_as_valid(True) 631 632 val = self._TCTRL_amount.GetValue().strip().replace(',', u'.', 1) 633 try: 634 decimal.Decimal(val) 635 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True) 636 except: 637 validity = False 638 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False) 639 640 if self._PRW_unit.GetValue().strip() == u'': 641 validity = False 642 self._PRW_unit.display_as_valid(False) 643 else: 644 self._PRW_unit.display_as_valid(True) 645 646 if validity is False: 647 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save drug component. Invalid or missing essential input.')) 648 649 return validity
650 #----------------------------------------------------------------
651 - def _save_as_new(self):
652 # save the data as a new instance 653 data = 1 654 data[''] = 1 655 data[''] = 1 656 # data.save() 657 658 # must be done very late or else the property access 659 # will refresh the display such that later field 660 # access will return empty values 661 # self.data = data 662 return False 663 return True
664 #----------------------------------------------------------------
665 - def _save_as_update(self):
666 self.data['pk_consumable_substance'] = self._PRW_substance.GetData() 667 self.data['amount'] = decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', u'.', 1)) 668 self.data['unit'] = self._PRW_unit.GetValue().strip() 669 return self.data.save()
670 #----------------------------------------------------------------
671 - def _refresh_as_new(self):
672 self._TCTRL_brand.SetValue(u'') 673 self._TCTRL_components.SetValue(u'') 674 self._TCTRL_codes.SetValue(u'') 675 self._PRW_substance.SetText(u'', None) 676 self._TCTRL_amount.SetValue(u'') 677 self._PRW_unit.SetText(u'', None) 678 679 self._PRW_substance.SetFocus()
680 #----------------------------------------------------------------
681 - def _refresh_from_existing(self):
682 self._TCTRL_brand.SetValue(u'%s (%s)' % (self.data['brand'], self.data['preparation'])) 683 self._TCTRL_components.SetValue(u' / '.join(self.data.containing_drug['components'])) 684 details = [] 685 if self.data['atc_brand'] is not None: 686 details.append(u'ATC: %s' % self.data['atc_brand']) 687 if self.data['external_code_brand'] is not None: 688 details.append(u'%s: %s' % (self.data['external_code_type_brand'], self.data['external_code_brand'])) 689 self._TCTRL_codes.SetValue(u'; '.join(details)) 690 691 self._PRW_substance.SetText(self.data['substance'], self.data['pk_consumable_substance']) 692 self._TCTRL_amount.SetValue(u'%s' % self.data['amount']) 693 self._PRW_unit.SetText(self.data['unit'], self.data['unit']) 694 695 self._PRW_substance.SetFocus()
696 #----------------------------------------------------------------
698 #self._PRW_brand.SetText(u'', None) 699 #self._TCTRL_prep.SetValue(u'') 700 #self._TCTRL_brand_details.SetValue(u'') 701 self._PRW_substance.SetText(u'', None) 702 self._TCTRL_amount.SetValue(u'') 703 self._PRW_unit.SetText(u'', None) 704 705 self._PRW_substance.SetFocus()
706 707 #============================================================
708 -class cDrugComponentPhraseWheel(gmPhraseWheel.cPhraseWheel):
709
710 - def __init__(self, *args, **kwargs):
711 712 mp = gmMedication.cDrugComponentMatchProvider() 713 mp.setThresholds(2, 3, 4) 714 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 715 self.SetToolTipString(_('A drug component with optional strength.')) 716 self.matcher = mp 717 self.selection_only = False
718 #--------------------------------------------------------
719 - def _data2instance(self):
720 return gmMedication.cDrugComponent(aPK_obj = self.GetData(as_instance = False, can_create = False))
721 722 #============================================================ 723 #============================================================
724 -class cSubstancePreparationPhraseWheel(gmPhraseWheel.cPhraseWheel):
725
726 - def __init__(self, *args, **kwargs):
727 728 query = u""" 729 ( 730 SELECT DISTINCT ON (list_label) 731 preparation AS data, 732 preparation AS list_label, 733 preparation AS field_label 734 FROM ref.branded_drug 735 WHERE preparation %(fragment_condition)s 736 ) UNION ( 737 SELECT DISTINCT ON (list_label) 738 preparation AS data, 739 preparation AS list_label, 740 preparation AS field_label 741 FROM clin.substance_intake 742 WHERE preparation %(fragment_condition)s 743 ) 744 ORDER BY list_label 745 LIMIT 30""" 746 747 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 748 mp.setThresholds(1, 2, 4) 749 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 750 self.SetToolTipString(_('The preparation (form) of the substance or brand.')) 751 self.matcher = mp 752 self.selection_only = False
753 754 #============================================================
755 -class cSubstancePhraseWheel(gmPhraseWheel.cPhraseWheel):
756
757 - def __init__(self, *args, **kwargs):
758 759 mp = gmMedication.cSubstanceMatchProvider() 760 mp.setThresholds(1, 2, 4) 761 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 762 self.SetToolTipString(_('The substance with optional strength.')) 763 self.matcher = mp 764 self.selection_only = False 765 self.phrase_separators = None
766 767 #--------------------------------------------------------
768 - def _data2instance(self):
769 return gmMedication.cConsumableSubstance(aPK_obj = self.GetData(as_instance = False, can_create = False))
770 771 #============================================================ 772 # branded drugs widgets 773 #------------------------------------------------------------
774 -def manage_components_of_branded_drug(parent=None, brand=None):
775 776 if brand is not None: 777 if brand.is_in_use_by_patients: 778 gmGuiHelpers.gm_show_info ( 779 aTitle = _('Managing components of a drug'), 780 aMessage = _( 781 'Cannot manage the components of the branded drug product\n' 782 '\n' 783 ' "%s" (%s)\n' 784 '\n' 785 'because it is currently taken by patients.\n' 786 ) % (brand['brand'], brand['preparation']) 787 ) 788 return False 789 #-------------------------------------------------------- 790 if parent is None: 791 parent = wx.GetApp().GetTopWindow() 792 #-------------------------------------------------------- 793 # def manage_substances(): 794 # pass 795 #-------------------------------------------------------- 796 if brand is None: 797 msg = _('Pick the substances which are components of this drug.') 798 right_col = _('Components of drug') 799 comp_substs = [] 800 else: 801 right_col = u'%s (%s)' % (brand['brand'], brand['preparation']) 802 msg = _( 803 'Adjust the components of "%s"\n' 804 '\n' 805 'The drug must contain at least one component. Any given\n' 806 'substance can only be included once per drug.' 807 ) % right_col 808 comp_substs = [ c.substance for c in brand.components ] 809 810 substs = gmMedication.get_consumable_substances(order_by = 'description') 811 choices = [ u'%s %s %s' % (s['description'], s['amount'], s['unit']) for s in substs ] 812 picks = [ u'%s %s %s' % (c['description'], c['amount'], c['unit']) for c in comp_substs ] 813 814 picker = gmListWidgets.cItemPickerDlg ( 815 parent, 816 -1, 817 title = _('Managing components of a drug ...'), 818 msg = msg 819 ) 820 picker.set_columns(['Substances'], [right_col]) 821 picker.set_choices(choices = choices, data = substs) 822 picker.set_picks(picks = picks, data = comp_substs) 823 # picker.extra_button = ( 824 # _('Substances'), 825 # _('Manage list of consumable substances'), 826 # manage_substances 827 # ) 828 829 btn_pressed = picker.ShowModal() 830 substs = picker.get_picks() 831 picker.Destroy() 832 833 if btn_pressed != wx.ID_OK: 834 return (False, None) 835 836 if brand is not None: 837 brand.set_substances_as_components(substances = substs) 838 839 return (True, substs)
840 #------------------------------------------------------------
841 -def manage_branded_drugs(parent=None, ignore_OK_button=False):
842 843 if parent is None: 844 parent = wx.GetApp().GetTopWindow() 845 #------------------------------------------------------------ 846 def add_from_db(brand): 847 drug_db = get_drug_database(parent = parent) 848 if drug_db is None: 849 return False 850 drug_db.import_drugs() 851 return True
852 #------------------------------------------------------------ 853 def get_tooltip(brand=None): 854 tt = u'%s %s\n' % (brand['brand'], brand['preparation']) 855 tt += u'\n' 856 tt += u'%s%s%s\n' % ( 857 gmTools.bool2subst(brand.is_vaccine, u'%s, ' % _('Vaccine'), u''), 858 u'%s, ' % gmTools.bool2subst(brand.is_in_use_by_patients, _('in use'), _('not in use')), 859 gmTools.bool2subst(brand['is_fake_brand'], _('fake'), u'') 860 ) 861 tt += gmTools.coalesce(brand['atc'], u'', _('ATC: %s\n')) 862 tt += gmTools.coalesce(brand['external_code'], u'', u'%s: %%s\n' % brand['external_code_type']) 863 if brand['components'] is not None: 864 tt += u'- %s' % u'\n- '.join(brand['components']) 865 return tt 866 #------------------------------------------------------------ 867 def edit(brand): 868 if brand is not None: 869 if brand.is_vaccine: 870 gmGuiHelpers.gm_show_info ( 871 aTitle = _('Editing medication'), 872 aMessage = _( 873 'Cannot edit the medication\n' 874 '\n' 875 ' "%s" (%s)\n' 876 '\n' 877 'because it is a vaccine. Please edit it\n' 878 'from the vaccine management section !\n' 879 ) % (brand['brand'], brand['preparation']) 880 ) 881 return False 882 883 return edit_branded_drug(parent = parent, branded_drug = brand, single_entry = True) 884 #------------------------------------------------------------ 885 def delete(brand): 886 if brand.is_vaccine: 887 gmGuiHelpers.gm_show_info ( 888 aTitle = _('Deleting medication'), 889 aMessage = _( 890 'Cannot delete the medication\n' 891 '\n' 892 ' "%s" (%s)\n' 893 '\n' 894 'because it is a vaccine. Please delete it\n' 895 'from the vaccine management section !\n' 896 ) % (brand['brand'], brand['preparation']) 897 ) 898 return False 899 gmMedication.delete_branded_drug(brand = brand['pk_brand']) 900 return True 901 #------------------------------------------------------------ 902 def new(): 903 return edit_branded_drug(parent = parent, branded_drug = None, single_entry = False) 904 #------------------------------------------------------------ 905 def refresh(lctrl): 906 drugs = gmMedication.get_branded_drugs() 907 items = [ [ 908 u'%s%s' % ( 909 d['brand'], 910 gmTools.bool2subst(d['is_fake_brand'], ' (%s)' % _('fake'), u'') 911 ), 912 d['preparation'], 913 gmTools.coalesce(d['atc'], u''), 914 gmTools.coalesce(d['components'], u''), 915 gmTools.coalesce(d['external_code'], u'', u'%%s [%s]' % d['external_code_type']), 916 d['pk_brand'] 917 ] for d in drugs ] 918 lctrl.set_string_items(items) 919 lctrl.set_data(drugs) 920 #------------------------------------------------------------ 921 msg = _('\nThese are the drug brands known to GNUmed.\n') 922 923 gmListWidgets.get_choices_from_list ( 924 parent = parent, 925 msg = msg, 926 caption = _('Showing branded drugs.'), 927 columns = [_('Name'), _('Preparation'), _('ATC'), _('Components'), _('Code'), u'#'], 928 single_selection = True, 929 ignore_OK_button = ignore_OK_button, 930 refresh_callback = refresh, 931 new_callback = new, 932 edit_callback = edit, 933 delete_callback = delete, 934 list_tooltip_callback = get_tooltip, 935 left_extra_button = (_('Import'), _('Import substances and brands from a drug database.'), add_from_db) 936 #, middle_extra_button = (_('Clone'), _('Clone selected drug into a new entry for editing.'), clone_from_existing) 937 #, right_extra_button = (_('Reassign'), _('Reassign all patients taking the selected drug to another drug.'), reassign_patients) 938 ) 939 940 #------------------------------------------------------------
941 -def edit_branded_drug(parent=None, branded_drug=None, single_entry=False):
942 943 if branded_drug is not None: 944 if branded_drug.is_in_use_by_patients: 945 gmGuiHelpers.gm_show_info ( 946 aTitle = _('Editing drug'), 947 aMessage = _( 948 'Cannot edit the branded drug product\n' 949 '\n' 950 ' "%s" (%s)\n' 951 '\n' 952 'because it is currently taken by patients.\n' 953 ) % (branded_drug['brand'], branded_drug['preparation']) 954 ) 955 return False 956 957 if parent is None: 958 parent = wx.GetApp().GetTopWindow() 959 #-------------------------------------------- 960 def manage_substances(drug): 961 manage_consumable_substances(parent = parent)
962 #-------------------------------------------- 963 ea = cBrandedDrugEAPnl(parent = parent, id = -1) 964 ea.data = branded_drug 965 ea.mode = gmTools.coalesce(branded_drug, 'new', 'edit') 966 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry) 967 dlg.SetTitle(gmTools.coalesce(branded_drug, _('Adding new drug brand'), _('Editing drug brand'))) 968 dlg.left_extra_button = ( 969 _('Substances'), 970 _('Manage consumable substances'), 971 manage_substances 972 ) 973 if dlg.ShowModal() == wx.ID_OK: 974 dlg.Destroy() 975 return True 976 dlg.Destroy() 977 return False 978 979 #============================================================ 980 from Gnumed.wxGladeWidgets import wxgBrandedDrugEAPnl 981
982 -class cBrandedDrugEAPnl(wxgBrandedDrugEAPnl.wxgBrandedDrugEAPnl, gmEditArea.cGenericEditAreaMixin):
983
984 - def __init__(self, *args, **kwargs):
985 986 try: 987 data = kwargs['drug'] 988 del kwargs['drug'] 989 except KeyError: 990 data = None 991 992 wxgBrandedDrugEAPnl.wxgBrandedDrugEAPnl.__init__(self, *args, **kwargs) 993 gmEditArea.cGenericEditAreaMixin.__init__(self) 994 995 self.mode = 'new' 996 self.data = data 997 if data is not None: 998 self.mode = 'edit' 999 self.__component_substances = data.components_as_substances
1000 1001 #self.__init_ui() 1002 #---------------------------------------------------------------- 1003 # def __init_ui(self): 1004 # adjust external type PRW 1005 #---------------------------------------------------------------- 1006 # generic Edit Area mixin API 1007 #----------------------------------------------------------------
1008 - def _valid_for_save(self):
1009 1010 if self.data is not None: 1011 if self.data.is_in_use_by_patients: 1012 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit drug brand. It is in use.'), beep = True) 1013 return False 1014 1015 validity = True 1016 1017 brand_name = self._PRW_brand.GetValue().strip() 1018 if brand_name == u'': 1019 validity = False 1020 self._PRW_brand.display_as_valid(False) 1021 else: 1022 self._PRW_brand.display_as_valid(True) 1023 1024 preparation = self._PRW_preparation.GetValue().strip() 1025 if preparation == u'': 1026 validity = False 1027 self._PRW_preparation.display_as_valid(False) 1028 else: 1029 self._PRW_preparation.display_as_valid(True) 1030 1031 if validity is True: 1032 # dupe ? 1033 drug = gmMedication.get_drug_by_brand(brand_name = brand_name, preparation = preparation) 1034 if drug is not None: 1035 validity = False 1036 self._PRW_brand.display_as_valid(False) 1037 self._PRW_preparation.display_as_valid(False) 1038 gmGuiHelpers.gm_show_error ( 1039 title = _('Checking brand data'), 1040 error = _( 1041 'The brand information you entered:\n' 1042 '\n' 1043 ' [%s %s]\n' 1044 '\n' 1045 'already exists as a drug product.' 1046 ) % (brand_name, preparation) 1047 ) 1048 1049 else: 1050 # lacking components ? 1051 self._TCTRL_components.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND)) 1052 if len(self.__component_substances) == 0: 1053 wants_empty = gmGuiHelpers.gm_show_question ( 1054 title = _('Checking brand data'), 1055 question = _( 1056 'You have not selected any substances\n' 1057 'as drug components.\n' 1058 '\n' 1059 'Without components you will not be able to\n' 1060 'use this drug for documenting patient care.\n' 1061 '\n' 1062 'Are you sure you want to save\n' 1063 'it without components ?' 1064 ) 1065 ) 1066 if not wants_empty: 1067 validity = False 1068 self.display_ctrl_as_valid(ctrl = self._TCTRL_components, valid = False) 1069 1070 if validity is False: 1071 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save branded drug. Invalid or missing essential input.')) 1072 1073 return validity
1074 #----------------------------------------------------------------
1075 - def _save_as_new(self):
1076 1077 drug = gmMedication.create_branded_drug ( 1078 brand_name = self._PRW_brand.GetValue().strip(), 1079 preparation = gmTools.coalesce ( 1080 self._PRW_preparation.GetData(), 1081 self._PRW_preparation.GetValue() 1082 ).strip(), 1083 return_existing = True 1084 ) 1085 drug['is_fake_brand'] = self._CHBOX_is_fake.GetValue() 1086 drug['atc'] = self._PRW_atc.GetData() 1087 code = self._TCTRL_external_code.GetValue().strip() 1088 if code != u'': 1089 drug['external_code'] = code 1090 drug['external_code_type'] = self._PRW_external_code_type.GetData().strip() 1091 1092 drug.save() 1093 1094 if len(self.__component_substances) > 0: 1095 drug.set_substances_as_components(substances = self.__component_substances) 1096 1097 self.data = drug 1098 1099 return True
1100 #----------------------------------------------------------------
1101 - def _save_as_update(self):
1102 self.data['brand'] = self._PRW_brand.GetValue().strip() 1103 self.data['preparation'] = gmTools.coalesce ( 1104 self._PRW_preparation.GetData(), 1105 self._PRW_preparation.GetValue() 1106 ).strip() 1107 self.data['is_fake_brand'] = self._CHBOX_is_fake.GetValue() 1108 self.data['atc'] = self._PRW_atc.GetData() 1109 code = self._TCTRL_external_code.GetValue().strip() 1110 if code != u'': 1111 self.data['external_code'] = code 1112 self.data['external_code_type'] = self._PRW_external_code_type.GetData().strip() 1113 success, data = self.data.save() 1114 if not success: 1115 err, msg = data 1116 _log.error('problem saving') 1117 _log.error('%s', err) 1118 _log.error('%s', msg) 1119 return (success is True)
1120 #----------------------------------------------------------------
1121 - def _refresh_as_new(self):
1122 self._PRW_brand.SetText(u'', None) 1123 self._PRW_preparation.SetText(u'', None) 1124 self._CHBOX_is_fake.SetValue(False) 1125 self._TCTRL_components.SetValue(u'') 1126 self._PRW_atc.SetText(u'', None) 1127 self._TCTRL_external_code.SetValue(u'') 1128 self._PRW_external_code_type.SetText(u'', None) 1129 1130 self._PRW_brand.SetFocus() 1131 1132 self.__component_substances = []
1133 #----------------------------------------------------------------
1135 self._refresh_as_new()
1136 #----------------------------------------------------------------
1137 - def _refresh_from_existing(self):
1138 self._PRW_brand.SetText(self.data['brand'], self.data['pk_brand']) 1139 self._PRW_preparation.SetText(self.data['preparation'], self.data['preparation']) 1140 self._CHBOX_is_fake.SetValue(self.data['is_fake_brand']) 1141 comps = u'' 1142 if self.data['components'] is not None: 1143 comps = u'- %s' % u'\n- '.join(self.data['components']) 1144 self._TCTRL_components.SetValue(comps) 1145 self._PRW_atc.SetText(gmTools.coalesce(self.data['atc'], u''), self.data['atc']) 1146 self._TCTRL_external_code.SetValue(gmTools.coalesce(self.data['external_code'], u'')) 1147 t = gmTools.coalesce(self.data['external_code_type'], u'') 1148 self._PRW_external_code_type.SetText(t, t) 1149 1150 self._PRW_brand.SetFocus() 1151 1152 self.__component_substances = self.data.components_as_substances
1153 #---------------------------------------------------------------- 1154 # event handler 1155 #----------------------------------------------------------------
1156 - def _on_manage_components_button_pressed(self, event):
1157 event.Skip() 1158 if self.mode == 'new_from_existing': 1159 brand = None 1160 else: 1161 brand = self.data 1162 OKed, substs = manage_components_of_branded_drug(parent = self, brand = brand) 1163 if OKed is True: 1164 self.__component_substances = substs 1165 comps = u'' 1166 if len(substs) > 0: 1167 comps = u'- %s' % u'\n- '.join([ u'%s %s %s' % (s['description'], s['amount'], s['unit']) for s in substs ]) 1168 self._TCTRL_components.SetValue(comps)
1169 #============================================================
1170 -class cBrandedDrugPhraseWheel(gmPhraseWheel.cPhraseWheel):
1171
1172 - def __init__(self, *args, **kwargs):
1173 1174 query = u""" 1175 SELECT 1176 pk 1177 AS data, 1178 (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', '')) 1179 AS list_label, 1180 (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', '')) 1181 AS field_label 1182 FROM ref.branded_drug 1183 WHERE description %(fragment_condition)s 1184 ORDER BY list_label 1185 LIMIT 50""" 1186 1187 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 1188 mp.setThresholds(2, 3, 4) 1189 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1190 self.SetToolTipString(_( 1191 'The brand name of the drug.\n' 1192 '\n' 1193 'Note: a brand name will need to be linked to\n' 1194 'one or more components before it can be used,\n' 1195 'except in the case of fake (generic) vaccines.' 1196 )) 1197 self.matcher = mp 1198 self.selection_only = False
1199 1200 #============================================================ 1201 # current substance intake widgets 1202 #------------------------------------------------------------
1203 -class cSubstanceSchedulePhraseWheel(gmPhraseWheel.cPhraseWheel):
1204
1205 - def __init__(self, *args, **kwargs):
1206 1207 query = u""" 1208 SELECT DISTINCT ON (sched) 1209 schedule as sched, 1210 schedule 1211 FROM clin.substance_intake 1212 WHERE schedule %(fragment_condition)s 1213 ORDER BY sched 1214 LIMIT 50""" 1215 1216 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 1217 mp.setThresholds(1, 2, 4) 1218 mp.word_separators = '[ \t=+&:@]+' 1219 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1220 self.SetToolTipString(_('The schedule for taking this substance.')) 1221 self.matcher = mp 1222 self.selection_only = False
1223 1224 #============================================================
1225 -class cSubstanceAimPhraseWheel(gmPhraseWheel.cPhraseWheel):
1226
1227 - def __init__(self, *args, **kwargs):
1228 1229 query = u""" 1230 ( 1231 SELECT DISTINCT ON (field_label) 1232 aim 1233 AS data, 1234 aim || ' (' || substance || ' ' || amount || ' ' || unit || ')' 1235 AS list_label, 1236 aim 1237 AS field_label 1238 FROM clin.v_substance_intakes 1239 WHERE 1240 aim %(fragment_condition)s 1241 %(ctxt_substance)s 1242 ) UNION ( 1243 SELECT DISTINCT ON (field_label) 1244 aim 1245 AS data, 1246 aim || ' (' || substance || ' ' || amount || ' ' || unit || ')' 1247 AS list_label, 1248 aim 1249 AS field_label 1250 FROM clin.v_substance_intakes 1251 WHERE 1252 aim %(fragment_condition)s 1253 ) 1254 ORDER BY list_label 1255 LIMIT 30""" 1256 1257 context = {'ctxt_substance': { 1258 'where_part': u'AND substance = %(substance)s', 1259 'placeholder': u'substance' 1260 }} 1261 1262 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = context) 1263 mp.setThresholds(1, 2, 4) 1264 #mp.word_separators = '[ \t=+&:@]+' 1265 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1266 self.SetToolTipString(_('The medical aim for consuming this substance.')) 1267 self.matcher = mp 1268 self.selection_only = False
1269 1270 #============================================================
1271 -def turn_substance_intake_into_allergy(parent=None, intake=None, emr=None):
1272 1273 if intake['is_currently_active']: 1274 intake['discontinued'] = gmDateTime.pydt_now_here() 1275 if intake['discontinue_reason'] is None: 1276 intake['discontinue_reason'] = u'%s %s' % (_('not tolerated:'), _('discontinued due to allergy or intolerance')) 1277 else: 1278 if not intake['discontinue_reason'].startswith(_('not tolerated:')): 1279 intake['discontinue_reason'] = u'%s %s' % (_('not tolerated:'), intake['discontinue_reason']) 1280 if not intake.save(): 1281 return False 1282 1283 allg = intake.turn_into_allergy(encounter_id = emr.active_encounter['pk_encounter']) 1284 1285 brand = intake.containing_drug 1286 if brand is not None: 1287 comps = [ c['substance'] for c in brand.components ] 1288 if len(comps) > 1: 1289 gmGuiHelpers.gm_show_info ( 1290 aTitle = _(u'Documented an allergy'), 1291 aMessage = _( 1292 u'An allergy was documented against the substance:\n' 1293 u'\n' 1294 u' [%s]\n' 1295 u'\n' 1296 u'This substance was taken with the multi-component brand:\n' 1297 u'\n' 1298 u' [%s (%s)]\n' 1299 u'\n' 1300 u'Note that ALL components of this brand were discontinued.' 1301 ) % ( 1302 intake['substance'], 1303 intake['brand'], 1304 u' & '.join(comps) 1305 ) 1306 ) 1307 1308 if parent is None: 1309 parent = wx.GetApp().GetTopWindow() 1310 1311 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent = parent, id = -1) 1312 dlg.ShowModal() 1313 1314 return True
1315 1316 #============================================================
1317 -def manage_substance_intakes(parent=None, emr=None):
1318 1319 if parent is None: 1320 parent = wx.GetApp().GetTopWindow() 1321 1322 if emr is None: 1323 emr = gmPerson.gmCurrentPatient().emr 1324 # #------------------------------------------------------------ 1325 # def add_from_db(substance): 1326 # drug_db = get_drug_database(parent = parent) 1327 # if drug_db is None: 1328 # return False 1329 # drug_db.import_drugs() 1330 # return True 1331 # #------------------------------------------------------------ 1332 # def edit(substance=None): 1333 # return edit_consumable_substance(parent = parent, substance = substance, single_entry = (substance is not None)) 1334 # #------------------------------------------------------------ 1335 # def delete(substance): 1336 # if substance.is_in_use_by_patients: 1337 # gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this substance. It is in use.'), beep = True) 1338 # return False 1339 # 1340 # return gmMedication.delete_consumable_substance(substance = substance['pk']) 1341 #------------------------------------------------------------ 1342 def get_tooltip(intake=None): 1343 return intake.format(one_line = False, show_all_brand_components = True)
1344 #------------------------------------------------------------ 1345 def refresh(lctrl): 1346 intakes = emr.get_current_substance_intakes ( 1347 include_inactive = False, 1348 include_unapproved = True, 1349 order_by = u'substance, brand, started' 1350 ) 1351 items = [] 1352 for i in intakes: 1353 if i['started'] is None: 1354 started = u'' 1355 else: 1356 started = u'%s:' % gmDateTime.pydt_strftime(i['started'], '%Y %b %d') 1357 items.append ([ 1358 u'%s%s %s %s %s%s' % ( 1359 i['substance'], 1360 gmTools.coalesce(i['brand'], u'', u' (%s)'), 1361 i['amount'], 1362 i['unit'], 1363 i['preparation'], 1364 gmTools.coalesce(i['external_code_brand'], u'', u' [%s::%s]' % (i['external_code_type_brand'], i['external_code_brand'])) 1365 ), 1366 u'%s%s%s' % ( 1367 started, 1368 gmTools.coalesce(i['schedule'], u'', u' %%s %s' % gmTools.u_right_arrow), 1369 gmTools.coalesce(i['duration'], u'', u' %s') 1370 ), 1371 u'%s' % ( 1372 gmTools.bool2subst ( 1373 i['intake_is_approved_of'], 1374 u'', 1375 _('disapproved') 1376 ) 1377 ) 1378 ]) 1379 lctrl.set_string_items(items) 1380 lctrl.set_data(intakes) 1381 #------------------------------------------------------------ 1382 msg = _('Substances consumed by the patient:') 1383 1384 return gmListWidgets.get_choices_from_list ( 1385 parent = parent, 1386 msg = msg, 1387 caption = _('Showing consumable substances.'), 1388 columns = [ _('Intake'), _('Application'), _('Status') ], 1389 single_selection = False, 1390 # new_callback = edit, 1391 # edit_callback = edit, 1392 # delete_callback = delete, 1393 refresh_callback = refresh, 1394 list_tooltip_callback = get_tooltip 1395 # ,left_extra_button = (_('Import'), _('Import consumable substances from a drug database.'), add_from_db) 1396 ) 1397 1398 #============================================================ 1399 from Gnumed.wxGladeWidgets import wxgCurrentMedicationEAPnl 1400
1401 -class cSubstanceIntakeEAPnl(wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl, gmEditArea.cGenericEditAreaMixin):
1402
1403 - def __init__(self, *args, **kwargs):
1404 1405 try: 1406 data = kwargs['substance'] 1407 del kwargs['substance'] 1408 except KeyError: 1409 data = None 1410 1411 self.calc = gmClinicalCalculator.cClinicalCalculator() 1412 1413 wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl.__init__(self, *args, **kwargs) 1414 gmEditArea.cGenericEditAreaMixin.__init__(self) 1415 1416 self.mode = 'new' 1417 self.data = data 1418 if data is not None: 1419 self.mode = 'edit' 1420 1421 self.__init_ui()
1422 #----------------------------------------------------------------
1423 - def __init_ui(self):
1424 1425 self._PRW_component.add_callback_on_lose_focus(callback = self._on_leave_component) 1426 self._PRW_component.selection_only = True 1427 1428 self._PRW_substance.add_callback_on_lose_focus(callback = self._on_leave_substance) 1429 self._PRW_substance.selection_only = True 1430 1431 self._PRW_duration.display_accuracy = gmDateTime.acc_days 1432 1433 self._PRW_aim.add_callback_on_set_focus(callback = self._on_enter_aim)
1434 #----------------------------------------------------------------
1435 - def __refresh_allergies(self):
1436 curr_pat = gmPerson.gmCurrentPatient() 1437 emr = curr_pat.emr 1438 1439 state = emr.allergy_state 1440 if state['last_confirmed'] is None: 1441 confirmed = _('never') 1442 else: 1443 confirmed = gmDateTime.pydt_strftime(state['last_confirmed'], '%Y %b %d') 1444 msg = _(u'%s, last confirmed %s\n') % (state.state_string, confirmed) 1445 msg += gmTools.coalesce(state['comment'], u'', _('Comment (%s): %%s\n') % state['modified_by']) 1446 1447 tt = u'' 1448 1449 allgs = emr.get_allergies() 1450 if len(allgs) > 0: 1451 msg += u'\n' 1452 for allergy in allgs: 1453 msg += u'%s: %s (%s)\n' % ( 1454 allergy['descriptor'], 1455 allergy['l10n_type'], 1456 gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'), u'?') 1457 ) 1458 tt += u'%s: %s\n' % ( 1459 allergy['descriptor'], 1460 gmTools.coalesce(allergy['reaction'], _('reaction not recorded')) 1461 ) 1462 1463 if len(allgs) > 0: 1464 msg += u'\n' 1465 tt += u'\n' 1466 1467 gfr = emr.get_most_recent_results(loinc = gmLOINC.LOINC_gfr_quantity, no_of_results = 1) 1468 if gfr is None: 1469 self.calc.patient = curr_pat 1470 gfr = self.calc.eGFR 1471 if gfr.numeric_value is None: 1472 msg += _('GFR: unknown') 1473 else: 1474 msg += gfr.message 1475 tt += gfr.format ( 1476 left_margin = 0, 1477 width = 50, 1478 eol = u'\n', 1479 with_formula = True, 1480 with_warnings = True, 1481 with_variables = False, 1482 with_sub_results = True, 1483 return_list = False 1484 ) 1485 else: 1486 msg += u'%s: %s %s (%s)\n' % ( 1487 gfr['unified_abbrev'], 1488 gfr['unified_val'], 1489 gmTools.coalesce(gfr['abnormality_indicator'], u'', u' (%s)'), 1490 gmDateTime.pydt_strftime ( 1491 gfr['clin_when'], 1492 format = '%Y %b %d' 1493 ) 1494 ) 1495 tt += _('GFR reported by path lab') 1496 1497 self._LBL_allergies.SetLabel(msg) 1498 self._LBL_allergies.SetToolTipString(tt)
1499 #---------------------------------------------------------------- 1500 # generic Edit Area mixin API 1501 #----------------------------------------------------------------
1502 - def _valid_for_save(self):
1503 1504 validity = True 1505 1506 has_component = (self._PRW_component.GetData() is not None) 1507 has_substance = (self._PRW_substance.GetValue().strip() != u'') 1508 1509 self._PRW_component.display_as_valid(True) 1510 1511 # cannot add duplicate components 1512 if self.mode == 'new': 1513 msg = _( 1514 'The patient is already taking\n' 1515 '\n' 1516 ' %s\n' 1517 '\n' 1518 'You will want to adjust the schedule\n' 1519 'rather than document the intake twice.' 1520 ) 1521 title = _('Adding substance intake entry') 1522 if has_component: 1523 emr = gmPerson.gmCurrentPatient().get_emr() 1524 if emr.substance_intake_exists(pk_component = self._PRW_component.GetData()): 1525 gmGuiHelpers.gm_show_warning ( 1526 aTitle = title, 1527 aMessage = msg % self._PRW_component.GetValue().strip() 1528 ) 1529 self._PRW_component.display_as_valid(False) 1530 validity = False 1531 pk_substance = self._PRW_substance.GetData() 1532 if pk_substance is not None: 1533 emr = gmPerson.gmCurrentPatient().get_emr() 1534 if emr.substance_intake_exists(pk_substance = pk_substance): 1535 gmGuiHelpers.gm_show_warning ( 1536 aTitle = title, 1537 aMessage = msg % self._PRW_substance.GetValue().strip() 1538 ) 1539 self._PRW_substance.display_as_valid(False) 1540 validity = False 1541 1542 # must have either brand or substance 1543 if (has_component is False) and (has_substance is False): 1544 self._PRW_substance.display_as_valid(False) 1545 self._PRW_component.display_as_valid(False) 1546 validity = False 1547 else: 1548 self._PRW_substance.display_as_valid(True) 1549 1550 # brands already have a preparation, so only required for substances 1551 if not has_component: 1552 if self._PRW_preparation.GetValue().strip() == u'': 1553 self._PRW_preparation.display_as_valid(False) 1554 validity = False 1555 else: 1556 self._PRW_preparation.display_as_valid(True) 1557 1558 # episode must be set if intake is to be approved of 1559 if self._CHBOX_approved.IsChecked(): 1560 if self._PRW_episode.GetValue().strip() == u'': 1561 self._PRW_episode.display_as_valid(False) 1562 validity = False 1563 else: 1564 self._PRW_episode.display_as_valid(True) 1565 1566 if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]: 1567 self._PRW_duration.display_as_valid(True) 1568 else: 1569 if self._PRW_duration.GetData() is None: 1570 # no data ... 1571 if gmDateTime.str2interval(self._PRW_duration.GetValue()) is None: 1572 self._PRW_duration.display_as_valid(False) 1573 validity = False 1574 # ... but valid string 1575 else: 1576 self._PRW_duration.display_as_valid(True) 1577 # has data 1578 else: 1579 self._PRW_duration.display_as_valid(True) 1580 1581 # start must exist 1582 start = self._DP_started.GetData() 1583 if start is None: 1584 self._DP_started.display_as_valid(False) 1585 validity = False 1586 else: 1587 self._DP_started.display_as_valid(True) 1588 1589 # end must be "< now()" AND "> start" if at all 1590 end = self._DP_discontinued.GetData() 1591 if end is not None: 1592 if end > gmDateTime.pydt_now_here(): 1593 self._DP_discontinued.display_as_valid(False) 1594 validity = False 1595 else: 1596 if start > end: 1597 self._DP_started.display_as_valid(False) 1598 self._DP_discontinued.display_as_valid(False) 1599 validity = False 1600 else: 1601 self._DP_started.display_as_valid(True) 1602 self._DP_discontinued.display_as_valid(True) 1603 1604 if validity is False: 1605 gmDispatcher.send(signal = 'statustext', msg = _('Input incomplete/invalid for saving as substance intake.')) 1606 1607 return validity
1608 #----------------------------------------------------------------
1609 - def _save_as_new(self):
1610 1611 emr = gmPerson.gmCurrentPatient().get_emr() 1612 epi = self._PRW_episode.GetData(can_create = True) 1613 1614 if self._PRW_substance.GetData() is None: 1615 # auto-creates all components as intakes 1616 intake = emr.add_substance_intake ( 1617 pk_component = self._PRW_component.GetData(), 1618 episode = epi 1619 ) 1620 else: 1621 intake = emr.add_substance_intake ( 1622 pk_substance = self._PRW_substance.GetData(), 1623 episode = epi, 1624 preparation = self._PRW_preparation.GetValue().strip() 1625 ) 1626 1627 if intake is None: 1628 gmDispatcher.send('statustext', msg = _('Cannot add duplicate of (maybe inactive) substance intake.'), beep = True) 1629 return False 1630 1631 intake['started'] = self._DP_started.GetData() 1632 intake['discontinued'] = self._DP_discontinued.GetData() 1633 if intake['discontinued'] is None: 1634 intake['discontinue_reason'] = None 1635 else: 1636 intake['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip() 1637 intake['schedule'] = self._PRW_schedule.GetValue().strip() 1638 intake['aim'] = self._PRW_aim.GetValue().strip() 1639 intake['notes'] = self._PRW_notes.GetValue().strip() 1640 intake['is_long_term'] = self._CHBOX_long_term.IsChecked() 1641 intake['intake_is_approved_of'] = self._CHBOX_approved.IsChecked() 1642 if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]: 1643 intake['duration'] = None 1644 else: 1645 if self._PRW_duration.GetData() is None: 1646 intake['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue()) 1647 else: 1648 intake['duration'] = self._PRW_duration.GetData() 1649 intake.save() 1650 1651 self.data = intake 1652 1653 return True
1654 #----------------------------------------------------------------
1655 - def _save_as_update(self):
1656 1657 # auto-applies to all components of drug if any: 1658 self.data['started'] = self._DP_started.GetData() 1659 self.data['discontinued'] = self._DP_discontinued.GetData() 1660 if self.data['discontinued'] is None: 1661 self.data['discontinue_reason'] = None 1662 else: 1663 self.data['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip() 1664 self.data['schedule'] = self._PRW_schedule.GetValue() 1665 self.data['is_long_term'] = self._CHBOX_long_term.IsChecked() 1666 self.data['intake_is_approved_of'] = self._CHBOX_approved.IsChecked() 1667 if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]: 1668 self.data['duration'] = None 1669 else: 1670 # self.data['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue()) 1671 if self._PRW_duration.GetData() is None: 1672 self.data['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue()) 1673 else: 1674 self.data['duration'] = self._PRW_duration.GetData() 1675 1676 # applies to non-component substances only 1677 self.data['preparation'] = self._PRW_preparation.GetValue() 1678 1679 # per-component 1680 self.data['aim'] = self._PRW_aim.GetValue() 1681 self.data['notes'] = self._PRW_notes.GetValue() 1682 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True) 1683 1684 self.data.save() 1685 1686 return True
1687 #----------------------------------------------------------------
1688 - def _refresh_as_new(self):
1689 self._PRW_component.SetText(u'', None) 1690 self._LBL_component.Enable(True) 1691 self._PRW_component.Enable(True) 1692 self._TCTRL_brand_ingredients.SetValue(u'') 1693 self._TCTRL_brand_ingredients.SetToolTipString(u'') 1694 1695 self._LBL_or.Enable(True) 1696 1697 self._PRW_substance.SetText(u'', None) 1698 self._PRW_substance.Enable(True) 1699 1700 self._PRW_preparation.SetText(u'', None) 1701 self._PRW_preparation.Enable(True) 1702 1703 self._PRW_schedule.SetText(u'', None) 1704 self._PRW_duration.SetText(u'', None) 1705 self._PRW_aim.SetText(u'', None) 1706 self._PRW_notes.SetText(u'', None) 1707 self._PRW_episode.SetText(u'', None) 1708 1709 self._CHBOX_long_term.SetValue(False) 1710 self._CHBOX_approved.SetValue(True) 1711 1712 self._DP_started.SetData(gmDateTime.pydt_now_here()) 1713 self._DP_discontinued.SetData(None) 1714 self._PRW_discontinue_reason.SetValue(u'') 1715 1716 self.__refresh_allergies() 1717 1718 self._PRW_component.SetFocus()
1719 #----------------------------------------------------------------
1720 - def _refresh_from_existing(self):
1721 1722 self._TCTRL_brand_ingredients.SetValue(u'') 1723 self._TCTRL_brand_ingredients.SetToolTipString(u'') 1724 1725 if self.data['pk_brand'] is None: 1726 self.__refresh_from_existing_substance() 1727 else: 1728 self.__refresh_from_existing_component() 1729 1730 # no editing of substance or component 1731 self._LBL_component.Enable(False) 1732 self._PRW_component.Enable(False) 1733 self._LBL_or.Enable(False) 1734 self._PRW_substance.Enable(False) 1735 1736 if self.data['is_long_term']: 1737 self._CHBOX_long_term.SetValue(True) 1738 self._PRW_duration.Enable(False) 1739 self._PRW_duration.SetText(gmTools.u_infinity, None) 1740 self._BTN_discontinued_as_planned.Enable(False) 1741 else: 1742 self._CHBOX_long_term.SetValue(False) 1743 self._PRW_duration.Enable(True) 1744 self._BTN_discontinued_as_planned.Enable(True) 1745 self._PRW_duration.SetData(self.data['duration']) 1746 # if self.data['duration'] is None: 1747 # self._PRW_duration.SetText(u'', None) 1748 # else: 1749 # self._PRW_duration.SetText(gmDateTime.format_interval(self.data['duration'], gmDateTime.acc_days), self.data['duration']) 1750 self._PRW_aim.SetText(gmTools.coalesce(self.data['aim'], u''), self.data['aim']) 1751 self._PRW_notes.SetText(gmTools.coalesce(self.data['notes'], u''), self.data['notes']) 1752 self._PRW_episode.SetData(self.data['pk_episode']) 1753 self._PRW_schedule.SetText(gmTools.coalesce(self.data['schedule'], u''), self.data['schedule']) 1754 1755 self._CHBOX_approved.SetValue(self.data['intake_is_approved_of']) 1756 1757 self._DP_started.SetData(self.data['started']) 1758 self._DP_discontinued.SetData(self.data['discontinued']) 1759 self._PRW_discontinue_reason.SetValue(gmTools.coalesce(self.data['discontinue_reason'], u'')) 1760 if self.data['discontinued'] is not None: 1761 self._PRW_discontinue_reason.Enable() 1762 1763 self.__refresh_allergies() 1764 1765 self._PRW_schedule.SetFocus()
1766 #----------------------------------------------------------------
1768 self._LBL_component.Enable(False) 1769 self._PRW_component.Enable(False) 1770 self._PRW_component.SetText(u'', None) 1771 self._PRW_component.display_as_valid(True) 1772 1773 self._LBL_or.Enable(False) 1774 1775 self._PRW_substance.Enable(True) 1776 self._PRW_substance.SetText ( 1777 u'%s %s %s' % (self.data['substance'], self.data['amount'], self.data['unit']), 1778 self.data['pk_substance'] 1779 ) 1780 1781 # self._PRW_preparation.SetText(gmTools.coalesce(self.data['preparation'], u''), self.data['preparation']) 1782 self._PRW_preparation.SetText(self.data['preparation'], self.data['preparation']) 1783 self._PRW_preparation.Enable(True)
1784 #----------------------------------------------------------------
1786 self._LBL_component.Enable(True) 1787 self._PRW_component.Enable(True) 1788 self._PRW_component.SetText ( 1789 u'%s %s %s (%s)' % (self.data['substance'], self.data['amount'], self.data['unit'], self.data['brand']), 1790 self.data['pk_drug_component'] 1791 ) 1792 1793 brand = gmMedication.cBrandedDrug(aPK_obj = self.data['pk_brand']) 1794 if brand['components'] is not None: 1795 self._TCTRL_brand_ingredients.SetValue(u'; '.join(brand['components'])) 1796 tt = u'%s:\n\n- %s' % ( 1797 self.data['brand'], 1798 u'\n- '.join(brand['components']) 1799 ) 1800 self._TCTRL_brand_ingredients.SetToolTipString(tt) 1801 1802 self._LBL_or.Enable(False) 1803 self._LBL_substance.Enable(False) 1804 self._PRW_substance.SetText(u'', None) 1805 self._PRW_substance.display_as_valid(True) 1806 1807 self._PRW_preparation.SetText(self.data['preparation'], self.data['preparation']) 1808 self._PRW_preparation.Enable(False)
1809 #----------------------------------------------------------------
1811 self._refresh_as_new() 1812 1813 self._PRW_episode.SetData(self.data['pk_episode']) 1814 1815 self._PRW_component.SetFocus()
1816 #---------------------------------------------------------------- 1817 # event handlers 1818 #----------------------------------------------------------------
1819 - def _on_leave_component(self):
1820 if self._PRW_component.GetData() is None: 1821 self._LBL_or.Enable(True) 1822 self._PRW_component.SetText(u'', None) 1823 self._LBL_substance.Enable(True) 1824 self._PRW_substance.Enable(True) 1825 self._LBL_preparation.Enable(True) 1826 self._PRW_preparation.Enable(True) 1827 #self._PRW_preparation.SetText(u'', None) 1828 self._TCTRL_brand_ingredients.SetValue(u'') 1829 self._TCTRL_brand_ingredients.SetToolTipString(u'') 1830 else: 1831 self._LBL_or.Enable(False) 1832 self._LBL_substance.Enable(False) 1833 self._PRW_substance.SetText(u'', None) 1834 self._PRW_substance.display_as_valid(True) 1835 self._PRW_substance.Enable(False) 1836 self._LBL_preparation.Enable(False) 1837 self._PRW_preparation.Enable(False) 1838 comp = gmMedication.cDrugComponent(aPK_obj = self._PRW_component.GetData()) 1839 self._PRW_preparation.SetText(comp['preparation'], comp['preparation']) 1840 brand = comp.containing_drug 1841 if brand['components'] is not None: 1842 self._TCTRL_brand_ingredients.SetValue(u'; '.join(brand['components'])) 1843 tt = u'%s:\n\n- %s' % ( 1844 brand['brand'], 1845 u'\n- '.join(brand['components']) 1846 ) 1847 self._TCTRL_brand_ingredients.SetToolTipString(tt)
1848 #----------------------------------------------------------------
1849 - def _on_leave_substance(self):
1850 if self._PRW_substance.GetData() is None: 1851 self._LBL_or.Enable(True) 1852 self._LBL_component.Enable(True) 1853 self._PRW_component.Enable(True) 1854 self._PRW_substance.SetText(u'', None) 1855 else: 1856 self._LBL_or.Enable(False) 1857 self._LBL_component.Enable(False) 1858 self._PRW_component.SetText(u'', None) 1859 self._PRW_component.display_as_valid(True) 1860 self._PRW_component.Enable(False) 1861 self._LBL_preparation.Enable(True) 1862 self._PRW_preparation.Enable(True) 1863 self._TCTRL_brand_ingredients.SetValue(u'') 1864 self._TCTRL_brand_ingredients.SetToolTipString(u'')
1865 #----------------------------------------------------------------
1866 - def _on_enter_aim(self):
1867 # when a drug component/substance is selected (that is, when .GetData() 1868 # returns not None) then we do not want to use the GetValue().strip() 1869 # result because that will also have amount and unit appended, hence 1870 # create the real component or substance instance and take the canonical 1871 # substance name from there 1872 subst = self._PRW_component.GetValue().strip() 1873 if subst != u'': 1874 comp = self._PRW_component.GetData(as_instance = True) 1875 if comp is None: 1876 self._PRW_aim.set_context(context = u'substance', val = subst) 1877 return 1878 self._PRW_aim.set_context(context = u'substance', val = comp['substance']) 1879 return 1880 1881 subst = self._PRW_substance.GetValue().strip() 1882 if subst == u'': 1883 self._PRW_aim.unset_context(context = u'substance') 1884 return 1885 comp = self._PRW_substance.GetData(as_instance = True) 1886 if comp is None: 1887 self._PRW_aim.set_context(context = u'substance', val = subst) 1888 return 1889 self._PRW_aim.set_context(context = u'substance', val = comp['description'])
1890 #----------------------------------------------------------------
1891 - def _on_discontinued_date_changed(self, event):
1892 if self._DP_discontinued.GetData() is None: 1893 self._PRW_discontinue_reason.Enable(False) 1894 else: 1895 self._PRW_discontinue_reason.Enable(True)
1896 #----------------------------------------------------------------
1897 - def _on_manage_brands_button_pressed(self, event):
1898 manage_branded_drugs(parent = self, ignore_OK_button = True)
1899 #----------------------------------------------------------------
1900 - def _on_manage_substances_button_pressed(self, event):
1901 manage_consumable_substances(parent = self)
1902 #----------------------------------------------------------------
1903 - def _on_heart_button_pressed(self, event):
1904 gmNetworkTools.open_url_in_browser(url = u'http://qtdrugs.org')
1905 #----------------------------------------------------------------
1906 - def _on_kidneys_button_pressed(self, event):
1907 if self._PRW_component.GetData() is not None: 1908 search_term = self._PRW_component.GetData(as_instance = True) 1909 elif self._PRW_substance.GetData() is not None: 1910 search_term = self._PRW_substance.GetData(as_instance = True) 1911 elif self._PRW_component.GetValue().strip() != u'': 1912 search_term = self._PRW_component.GetValue().strip() 1913 else: 1914 search_term = self._PRW_substance.GetValue().strip() 1915 1916 gmNetworkTools.open_url_in_browser(url = gmMedication.drug2renal_insufficiency_url(search_term = search_term))
1917 #----------------------------------------------------------------
1919 1920 now = gmDateTime.pydt_now_here() 1921 1922 self.__refresh_allergies() 1923 1924 if self.data is None: 1925 return 1926 1927 # do we have a (full) plan ? 1928 if None not in [self.data['started'], self.data['duration']]: 1929 planned_end = self.data['started'] + self.data['duration'] 1930 # the plan hasn't ended so [Per plan] can't apply ;-) 1931 if planned_end > now: 1932 return 1933 self._DP_discontinued.SetData(planned_end) 1934 self._PRW_discontinue_reason.Enable(True) 1935 self._PRW_discontinue_reason.SetValue(u'') 1936 return 1937 1938 # we know started but not duration: apparently the plan is to stop today 1939 if self.data['started'] is not None: 1940 # but we haven't started yet so we can't stop 1941 if self.data['started'] > now: 1942 return 1943 1944 self._DP_discontinued.SetData(now) 1945 self._PRW_discontinue_reason.Enable(True) 1946 self._PRW_discontinue_reason.SetValue(u'')
1947 #----------------------------------------------------------------
1948 - def _on_chbox_long_term_checked(self, event):
1949 if self._CHBOX_long_term.IsChecked() is True: 1950 self._PRW_duration.Enable(False) 1951 self._BTN_discontinued_as_planned.Enable(False) 1952 self._PRW_discontinue_reason.Enable(False) 1953 else: 1954 self._PRW_duration.Enable(True) 1955 self._BTN_discontinued_as_planned.Enable(True) 1956 self._PRW_discontinue_reason.Enable(True) 1957 1958 self.__refresh_allergies()
1959 #----------------------------------------------------------------
1960 - def turn_into_allergy(self, data=None):
1961 if not self.save(): 1962 return False 1963 1964 return turn_substance_intake_into_allergy ( 1965 parent = self, 1966 intake = self.data, 1967 emr = gmPerson.gmCurrentPatient().get_emr() 1968 )
1969 1970 #============================================================
1971 -def delete_substance_intake(parent=None, substance=None):
1972 1973 subst = gmMedication.cSubstanceIntakeEntry(aPK_obj = substance) 1974 msg = _( 1975 '\n' 1976 '[%s]\n' 1977 '\n' 1978 'It may be prudent to edit (before deletion) the details\n' 1979 'of this substance intake entry so as to leave behind\n' 1980 'some indication of why it was deleted.\n' 1981 ) % subst.format() 1982 1983 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 1984 parent, 1985 -1, 1986 caption = _('Deleting medication / substance intake'), 1987 question = msg, 1988 button_defs = [ 1989 {'label': _('&Edit'), 'tooltip': _('Allow editing of substance intake entry before deletion.'), 'default': True}, 1990 {'label': _('&Delete'), 'tooltip': _('Delete immediately without editing first.')}, 1991 {'label': _('&Cancel'), 'tooltip': _('Abort. Do not delete or edit substance intake entry.')} 1992 ] 1993 ) 1994 1995 edit_first = dlg.ShowModal() 1996 dlg.Destroy() 1997 1998 if edit_first == wx.ID_CANCEL: 1999 return 2000 2001 if edit_first == wx.ID_YES: 2002 edit_intake_of_substance(parent = parent, substance = subst) 2003 delete_it = gmGuiHelpers.gm_show_question ( 2004 aMessage = _('Now delete substance intake entry ?'), 2005 aTitle = _('Deleting medication / substance intake') 2006 ) 2007 else: 2008 delete_it = True 2009 2010 if not delete_it: 2011 return 2012 2013 gmMedication.delete_substance_intake(substance = substance)
2014 #------------------------------------------------------------
2015 -def edit_intake_of_substance(parent = None, substance=None):
2016 ea = cSubstanceIntakeEAPnl(parent = parent, id = -1, substance = substance) 2017 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (substance is not None)) 2018 dlg.SetTitle(gmTools.coalesce(substance, _('Adding medication/non-medication substance intake'), _('Editing medication/non-medication substance intake'))) 2019 dlg.left_extra_button = ( 2020 _('Allergy'), 2021 _('Document an allergy against this substance.'), 2022 ea.turn_into_allergy 2023 ) 2024 if dlg.ShowModal() == wx.ID_OK: 2025 dlg.Destroy() 2026 return True 2027 dlg.Destroy() 2028 return False
2029 2030 #============================================================ 2031 # current substances grid 2032 #------------------------------------------------------------
2033 -def configure_medication_list_template(parent=None):
2034 2035 if parent is None: 2036 parent = wx.GetApp().GetTopWindow() 2037 2038 template = gmFormWidgets.manage_form_templates ( 2039 parent = parent, 2040 template_types = ['current medication list'] 2041 ) 2042 option = u'form_templates.medication_list' 2043 2044 if template is None: 2045 gmDispatcher.send(signal = 'statustext', msg = _('No medication list template configured.'), beep = True) 2046 return None 2047 2048 if template['engine'] not in [u'L', u'X', u'T']: 2049 gmDispatcher.send(signal = 'statustext', msg = _('No medication list template configured.'), beep = True) 2050 return None 2051 2052 dbcfg = gmCfg.cCfgSQL() 2053 dbcfg.set ( 2054 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2055 option = option, 2056 value = u'%s - %s' % (template['name_long'], template['external_version']) 2057 ) 2058 2059 return template
2060 #------------------------------------------------------------ 2118 2119 #------------------------------------------------------------
2120 -def configure_prescription_template(parent=None):
2121 2122 if parent is None: 2123 parent = wx.GetApp().GetTopWindow() 2124 2125 template = gmFormWidgets.manage_form_templates ( 2126 parent = parent, 2127 msg = _('Select the default prescription template:'), 2128 template_types = ['prescription', 'current medication list'] 2129 ) 2130 2131 if template is None: 2132 gmDispatcher.send(signal = 'statustext', msg = _('No prescription template configured.'), beep = True) 2133 return None 2134 2135 if template['engine'] not in [u'L', u'X', u'T']: 2136 gmDispatcher.send(signal = 'statustext', msg = _('No prescription template configured.'), beep = True) 2137 return None 2138 2139 option = u'form_templates.prescription' 2140 dbcfg = gmCfg.cCfgSQL() 2141 dbcfg.set ( 2142 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2143 option = option, 2144 value = u'%s - %s' % (template['name_long'], template['external_version']) 2145 ) 2146 2147 return template
2148 #------------------------------------------------------------
2149 -def get_prescription_template(parent=None):
2150 2151 if parent is None: 2152 parent = wx.GetApp().GetTopWindow() 2153 2154 dbcfg = gmCfg.cCfgSQL() 2155 option = u'form_templates.prescription' 2156 template_name = dbcfg.get2 ( 2157 option = option, 2158 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2159 bias = 'user' 2160 ) 2161 2162 if template_name is None: 2163 template = configure_prescription_template(parent = parent) 2164 if template is None: 2165 gmGuiHelpers.gm_show_error ( 2166 aMessage = _('There is no prescription template configured.'), 2167 aTitle = _('Printing prescription') 2168 ) 2169 return None 2170 return template 2171 2172 try: 2173 name, ver = template_name.split(u' - ') 2174 except: 2175 _log.exception('problem splitting prescription template name [%s]', template_name) 2176 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading prescription template.'), beep = True) 2177 return False 2178 template = gmForms.get_form_template(name_long = name, external_version = ver) 2179 if template is None: 2180 gmGuiHelpers.gm_show_error ( 2181 aMessage = _('Cannot load prescription template [%s - %s]') % (name, ver), 2182 aTitle = _('Printing prescription') 2183 ) 2184 return None 2185 return template
2186 #------------------------------------------------------------ 2213 2214 #------------------------------------------------------------
2215 -def prescribe_drugs(parent=None, emr=None):
2216 2217 dbcfg = gmCfg.cCfgSQL() 2218 rx_mode = dbcfg.get2 ( 2219 option = u'horst_space.default_prescription_mode', 2220 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2221 bias = u'user', 2222 default = u'form' # set to 'database' to access database 2223 ) 2224 2225 if parent is None: 2226 parent = wx.GetApp().GetTopWindow() 2227 2228 if rx_mode == 'form': 2229 return print_prescription(parent = parent, emr = emr) 2230 2231 if rx_mode == 'database': 2232 drug_db = get_drug_database() 2233 if drug_db is None: 2234 return 2235 drug_db.reviewer = gmStaff.gmCurrentProvider() 2236 prescribed_drugs = drug_db.prescribe() 2237 update_substance_intake_list_from_prescription ( 2238 parent = parent, 2239 prescribed_drugs = prescribed_drugs, 2240 emr = emr 2241 )
2242 2243 #------------------------------------------------------------
2244 -def update_substance_intake_list_from_prescription(parent=None, prescribed_drugs=None, emr=None):
2245 2246 if len(prescribed_drugs) == 0: 2247 return 2248 2249 curr_brands = [ i['pk_brand'] for i in emr.get_current_substance_intakes() if i['pk_brand'] is not None ] 2250 new_drugs = [] 2251 for drug in prescribed_drugs: 2252 if drug['pk_brand'] not in curr_brands: 2253 new_drugs.append(drug) 2254 2255 if len(new_drugs) == 0: 2256 return 2257 2258 if parent is None: 2259 parent = wx.GetApp().GetTopWindow() 2260 2261 dlg = gmListWidgets.cItemPickerDlg ( 2262 parent, 2263 -1, 2264 msg = _( 2265 'These brands have been prescribed but are not listed\n' 2266 'in the current medication list of this patient.\n' 2267 '\n' 2268 'Please select those you want added to the medication list.' 2269 ) 2270 ) 2271 dlg.set_columns ( 2272 columns = [_('Newly prescribed drugs')], 2273 columns_right = [_('Add to medication list')] 2274 ) 2275 choices = [ (u'%s %s (%s)' % (d['brand'], d['preparation'], u'; '.join(d['components']))) for d in new_drugs ] 2276 dlg.set_choices ( 2277 choices = choices, 2278 data = new_drugs 2279 ) 2280 dlg.ShowModal() 2281 drugs2add = dlg.get_picks() 2282 dlg.Destroy() 2283 2284 if drugs2add is None: 2285 return 2286 2287 if len(drugs2add) == 0: 2288 return 2289 2290 for drug in drugs2add: 2291 # only add first component since all other components get added by a trigger ... 2292 intake = emr.add_substance_intake ( 2293 pk_component = drug['pk_components'][0], 2294 episode = emr.add_episode(episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE)['pk_episode'], 2295 ) 2296 if intake is None: 2297 continue 2298 intake['intake_is_approved_of'] = True 2299 intake.save() 2300 2301 return
2302 #------------------------------------------------------------
2303 -class cCurrentSubstancesGrid(wx.grid.Grid):
2304 """A grid class for displaying current substance intake. 2305 2306 - does NOT listen to the currently active patient 2307 - thereby it can display any patient at any time 2308 """
2309 - def __init__(self, *args, **kwargs):
2310 2311 wx.grid.Grid.__init__(self, *args, **kwargs) 2312 2313 self.__patient = None 2314 self.__row_data = {} 2315 self.__prev_row = None 2316 self.__prev_tooltip_row = None 2317 self.__prev_cell_0 = None 2318 self.__grouping_mode = u'issue' 2319 self.__filter_show_unapproved = True 2320 self.__filter_show_inactive = True 2321 2322 self.__grouping2col_labels = { 2323 u'issue': [ 2324 _('Health issue'), 2325 _('Substance'), 2326 _('Strength'), 2327 _('Schedule'), 2328 _('Started'), 2329 _('Duration / Until'), 2330 _('Brand'), 2331 _('Advice') 2332 ], 2333 u'brand': [ 2334 _('Brand'), 2335 _('Schedule'), 2336 _('Substance'), 2337 _('Strength'), 2338 _('Started'), 2339 _('Duration / Until'), 2340 _('Health issue'), 2341 _('Advice') 2342 ], 2343 u'episode': [ 2344 _('Episode'), 2345 _('Substance'), 2346 _('Strength'), 2347 _('Schedule'), 2348 _('Started'), 2349 _('Duration / Until'), 2350 _('Brand'), 2351 _('Advice') 2352 ] 2353 } 2354 2355 self.__grouping2order_by_clauses = { 2356 u'issue': u'pk_health_issue nulls first, substance, started', 2357 u'episode': u'pk_health_issue nulls first, episode, substance, started', 2358 u'brand': u'brand nulls last, substance, started' 2359 } 2360 2361 self.__init_ui() 2362 self.__register_events()
2363 #------------------------------------------------------------ 2364 # external API 2365 #------------------------------------------------------------
2366 - def get_selected_cells(self):
2367 2368 sel_block_top_left = self.GetSelectionBlockTopLeft() 2369 sel_block_bottom_right = self.GetSelectionBlockBottomRight() 2370 sel_cols = self.GetSelectedCols() 2371 sel_rows = self.GetSelectedRows() 2372 2373 selected_cells = [] 2374 2375 # individually selected cells (ctrl-click) 2376 selected_cells += self.GetSelectedCells() 2377 2378 # selected rows 2379 selected_cells += list ( 2380 (row, col) 2381 for row in sel_rows 2382 for col in xrange(self.GetNumberCols()) 2383 ) 2384 2385 # selected columns 2386 selected_cells += list ( 2387 (row, col) 2388 for row in xrange(self.GetNumberRows()) 2389 for col in sel_cols 2390 ) 2391 2392 # selection blocks 2393 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()): 2394 selected_cells += [ 2395 (row, col) 2396 for row in xrange(top_left[0], bottom_right[0] + 1) 2397 for col in xrange(top_left[1], bottom_right[1] + 1) 2398 ] 2399 2400 return set(selected_cells)
2401 #------------------------------------------------------------
2402 - def get_selected_rows(self):
2403 rows = {} 2404 2405 for row, col in self.get_selected_cells(): 2406 rows[row] = True 2407 2408 return rows.keys()
2409 #------------------------------------------------------------
2410 - def get_selected_data(self):
2411 return [ self.__row_data[row] for row in self.get_selected_rows() ]
2412 #------------------------------------------------------------
2413 - def repopulate_grid(self):
2414 2415 self.empty_grid() 2416 2417 if self.__patient is None: 2418 return 2419 2420 emr = self.__patient.get_emr() 2421 meds = emr.get_current_substance_intakes ( 2422 order_by = self.__grouping2order_by_clauses[self.__grouping_mode], 2423 include_unapproved = self.__filter_show_unapproved, 2424 include_inactive = self.__filter_show_inactive 2425 ) 2426 if not meds: 2427 return 2428 2429 self.BeginBatch() 2430 2431 # columns 2432 labels = self.__grouping2col_labels[self.__grouping_mode] 2433 if self.__filter_show_unapproved: 2434 self.AppendCols(numCols = len(labels) + 1) 2435 else: 2436 self.AppendCols(numCols = len(labels)) 2437 for col_idx in range(len(labels)): 2438 self.SetColLabelValue(col_idx, labels[col_idx]) 2439 if self.__filter_show_unapproved: 2440 self.SetColLabelValue(len(labels), u'OK?') 2441 self.SetColSize(len(labels), 40) 2442 2443 self.AppendRows(numRows = len(meds)) 2444 2445 # loop over data 2446 for row_idx in range(len(meds)): 2447 med = meds[row_idx] 2448 self.__row_data[row_idx] = med 2449 2450 if med['is_currently_active'] is True: 2451 atcs = [] 2452 if med['atc_substance'] is not None: 2453 atcs.append(med['atc_substance']) 2454 # if med['atc_brand'] is not None: 2455 # atcs.append(med['atc_brand']) 2456 # allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],), brand = med['brand']) 2457 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],)) 2458 if allg not in [None, False]: 2459 attr = self.GetOrCreateCellAttr(row_idx, 0) 2460 if allg['type'] == u'allergy': 2461 attr.SetTextColour('red') 2462 else: 2463 #attr.SetTextColour('yellow') # too light 2464 #attr.SetTextColour('pink') # too light 2465 #attr.SetTextColour('dark orange') # slightly better 2466 attr.SetTextColour('magenta') 2467 self.SetRowAttr(row_idx, attr) 2468 else: 2469 attr = self.GetOrCreateCellAttr(row_idx, 0) 2470 attr.SetTextColour('grey') 2471 self.SetRowAttr(row_idx, attr) 2472 2473 if self.__grouping_mode == u'episode': 2474 if med['pk_episode'] is None: 2475 self.__prev_cell_0 = None 2476 epi = gmTools.u_diameter 2477 else: 2478 if self.__prev_cell_0 == med['episode']: 2479 epi = u'' 2480 else: 2481 self.__prev_cell_0 = med['episode'] 2482 epi = gmTools.coalesce(med['episode'], u'') 2483 self.SetCellValue(row_idx, 0, gmTools.wrap(text = epi, width = 40)) 2484 2485 self.SetCellValue(row_idx, 1, med['substance']) 2486 self.SetCellValue(row_idx, 2, u'%s %s' % (med['amount'], med['unit'])) 2487 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], u'')) 2488 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d')) 2489 2490 if med['is_long_term']: 2491 self.SetCellValue(row_idx, 5, gmTools.u_infinity) 2492 else: 2493 if med['discontinued'] is None: 2494 if med['duration'] is None: 2495 self.SetCellValue(row_idx, 5, u'') 2496 else: 2497 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days)) 2498 else: 2499 self.SetCellValue(row_idx, 5, med['discontinued'].strftime('%Y-%m-%d')) 2500 2501 if med['pk_brand'] is None: 2502 brand = u'%s (%s)' % (gmTools.u_diameter, med['preparation']) 2503 else: 2504 if med['fake_brand']: 2505 brand = u'%s (%s)' % ( 2506 gmTools.coalesce(med['brand'], u'', _('%s <fake>')), 2507 med['preparation'] 2508 ) 2509 else: 2510 brand = u'%s (%s)' % ( 2511 gmTools.coalesce(med['brand'], u''), 2512 med['preparation'] 2513 ) 2514 self.SetCellValue(row_idx, 6, gmTools.wrap(text = brand, width = 35)) 2515 2516 elif self.__grouping_mode == u'issue': 2517 if med['pk_health_issue'] is None: 2518 self.__prev_cell_0 = None 2519 issue = u'%s%s' % ( 2520 gmTools.u_diameter, 2521 gmTools.coalesce(med['episode'], u'', u' (%s)') 2522 ) 2523 else: 2524 if self.__prev_cell_0 == med['health_issue']: 2525 issue = u'' 2526 else: 2527 self.__prev_cell_0 = med['health_issue'] 2528 issue = med['health_issue'] 2529 self.SetCellValue(row_idx, 0, gmTools.wrap(text = issue, width = 40)) 2530 2531 self.SetCellValue(row_idx, 1, med['substance']) 2532 self.SetCellValue(row_idx, 2, u'%s %s' % (med['amount'], med['unit'])) 2533 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], u'')) 2534 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d')) 2535 2536 if med['is_long_term']: 2537 self.SetCellValue(row_idx, 5, gmTools.u_infinity) 2538 else: 2539 if med['discontinued'] is None: 2540 if med['duration'] is None: 2541 self.SetCellValue(row_idx, 5, u'') 2542 else: 2543 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days)) 2544 else: 2545 self.SetCellValue(row_idx, 5, med['discontinued'].strftime('%Y-%m-%d')) 2546 2547 if med['pk_brand'] is None: 2548 brand = u'%s (%s)' % (gmTools.u_diameter, med['preparation']) 2549 else: 2550 if med['fake_brand']: 2551 brand = u'%s (%s)' % ( 2552 gmTools.coalesce(med['brand'], u'', _('%s <fake>')), 2553 med['preparation'] 2554 ) 2555 else: 2556 brand = u'%s (%s)' % ( 2557 gmTools.coalesce(med['brand'], u''), 2558 med['preparation'] 2559 ) 2560 self.SetCellValue(row_idx, 6, gmTools.wrap(text = brand, width = 35)) 2561 2562 elif self.__grouping_mode == u'brand': 2563 2564 if med['pk_brand'] is None: 2565 self.__prev_cell_0 = None 2566 brand = u'%s (%s)' % ( 2567 gmTools.u_diameter, 2568 med['preparation'] 2569 ) 2570 else: 2571 if self.__prev_cell_0 == med['brand']: 2572 brand = u'' 2573 else: 2574 self.__prev_cell_0 = med['brand'] 2575 if med['fake_brand']: 2576 brand = u'%s (%s)' % ( 2577 gmTools.coalesce(med['brand'], u'', _('%s <fake>')), 2578 med['preparation'] 2579 ) 2580 else: 2581 brand = u'%s (%s)' % ( 2582 gmTools.coalesce(med['brand'], u''), 2583 med['preparation'] 2584 ) 2585 self.SetCellValue(row_idx, 0, gmTools.wrap(text = brand, width = 35)) 2586 2587 self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], u'')) 2588 self.SetCellValue(row_idx, 2, med['substance']) 2589 self.SetCellValue(row_idx, 3, u'%s %s' % (med['amount'], med['unit'])) 2590 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d')) 2591 2592 if med['is_long_term']: 2593 self.SetCellValue(row_idx, 5, gmTools.u_infinity) 2594 else: 2595 if med['discontinued'] is None: 2596 if med['duration'] is None: 2597 self.SetCellValue(row_idx, 5, u'') 2598 else: 2599 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days)) 2600 else: 2601 self.SetCellValue(row_idx, 5, med['discontinued'].strftime('%Y-%m-%d')) 2602 2603 if med['pk_health_issue'] is None: 2604 issue = u'%s%s' % ( 2605 gmTools.u_diameter, 2606 gmTools.coalesce(med['episode'], u'', u' (%s)') 2607 ) 2608 else: 2609 issue = gmTools.coalesce(med['health_issue'], u'') 2610 self.SetCellValue(row_idx, 6, gmTools.wrap(text = issue, width = 40)) 2611 2612 else: 2613 raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode) 2614 2615 if med['notes'] is not None: 2616 self.SetCellValue(row_idx, 7, gmTools.wrap(text = med['notes'], width = 50)) 2617 2618 if self.__filter_show_unapproved: 2619 self.SetCellValue ( 2620 row_idx, 2621 len(labels), 2622 gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, u'', u'?') 2623 ) 2624 2625 #self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE) 2626 2627 self.AutoSize() 2628 self.EndBatch()
2629 #------------------------------------------------------------
2630 - def empty_grid(self):
2631 self.BeginBatch() 2632 self.ClearGrid() 2633 # Windows cannot do "nothing", it rather decides to assert() 2634 # on thinking it is supposed to do nothing 2635 if self.GetNumberRows() > 0: 2636 self.DeleteRows(pos = 0, numRows = self.GetNumberRows()) 2637 if self.GetNumberCols() > 0: 2638 self.DeleteCols(pos = 0, numCols = self.GetNumberCols()) 2639 self.EndBatch() 2640 self.__row_data = {} 2641 self.__prev_cell_0 = None
2642 #------------------------------------------------------------
2643 - def show_info_on_entry(self):
2644 2645 if len(self.__row_data) == 0: 2646 return 2647 2648 sel_rows = self.get_selected_rows() 2649 if len(sel_rows) != 1: 2650 return 2651 2652 drug_db = get_drug_database() 2653 if drug_db is None: 2654 return 2655 2656 intake = self.get_selected_data()[0] # just in case 2657 if intake['brand'] is None: 2658 drug_db.show_info_on_substance(substance_intake = intake) 2659 else: 2660 drug_db.show_info_on_drug(substance_intake = intake)
2661 #------------------------------------------------------------
2663 search_term = None 2664 if len(self.__row_data) > 0: 2665 sel_rows = self.get_selected_rows() 2666 if len(sel_rows) == 1: 2667 search_term = self.get_selected_data()[0] 2668 gmNetworkTools.open_url_in_browser(url = gmMedication.drug2renal_insufficiency_url(search_term = search_term))
2669 #------------------------------------------------------------
2670 - def show_cardiac_info(self):
2671 gmNetworkTools.open_url_in_browser(url = u'http://qtdrugs.org')
2672 #------------------------------------------------------------
2673 - def report_ADR(self):
2674 dbcfg = gmCfg.cCfgSQL() 2675 url = dbcfg.get2 ( 2676 option = u'external.urls.report_ADR', 2677 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2678 bias = u'user', 2679 default = u'https://dcgma.org/uaw/meldung.php' # http://www.akdae.de/Arzneimittelsicherheit/UAW-Meldung/UAW-Meldung-online.html 2680 ) 2681 gmNetworkTools.open_url_in_browser(url = url)
2682 #------------------------------------------------------------
2683 - def prescribe(self):
2684 prescribe_drugs ( 2685 parent = self, 2686 emr = self.__patient.emr 2687 )
2688 #------------------------------------------------------------
2689 - def check_interactions(self):
2690 2691 if len(self.__row_data) == 0: 2692 return 2693 2694 drug_db = get_drug_database() 2695 if drug_db is None: 2696 return 2697 2698 if len(self.get_selected_rows()) > 1: 2699 drug_db.check_interactions(substance_intakes = self.get_selected_data()) 2700 else: 2701 drug_db.check_interactions(substance_intakes = self.__row_data.values())
2702 #------------------------------------------------------------
2703 - def add_substance(self):
2704 edit_intake_of_substance(parent = self, substance = None)
2705 #------------------------------------------------------------
2706 - def edit_substance(self):
2707 2708 rows = self.get_selected_rows() 2709 2710 if len(rows) == 0: 2711 return 2712 2713 if len(rows) > 1: 2714 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit more than one substance at once.'), beep = True) 2715 return 2716 2717 subst = self.get_selected_data()[0] 2718 edit_intake_of_substance(parent = self, substance = subst)
2719 #------------------------------------------------------------
2720 - def delete_substance(self):
2721 2722 rows = self.get_selected_rows() 2723 2724 if len(rows) == 0: 2725 return 2726 2727 if len(rows) > 1: 2728 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete more than one substance at once.'), beep = True) 2729 return 2730 2731 subst = self.get_selected_data()[0] 2732 delete_substance_intake(parent = self, substance = subst['pk_substance_intake'])
2733 #------------------------------------------------------------
2735 rows = self.get_selected_rows() 2736 2737 if len(rows) == 0: 2738 return 2739 2740 if len(rows) > 1: 2741 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create allergy from more than one substance at once.'), beep = True) 2742 return 2743 2744 return turn_substance_intake_into_allergy ( 2745 parent = self, 2746 intake = self.get_selected_data()[0], 2747 emr = self.__patient.get_emr() 2748 )
2749 #------------------------------------------------------------
2750 - def print_medication_list(self):
2751 # there could be some filtering/user interaction going on here 2752 print_medication_list(parent = self)
2753 #------------------------------------------------------------
2754 - def get_row_tooltip(self, row=None):
2755 2756 try: 2757 entry = self.__row_data[row] 2758 except KeyError: 2759 return u' ' 2760 2761 emr = self.__patient.get_emr() 2762 atcs = [] 2763 if entry['atc_substance'] is not None: 2764 atcs.append(entry['atc_substance']) 2765 # if entry['atc_brand'] is not None: 2766 # atcs.append(entry['atc_brand']) 2767 # allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],), brand = entry['brand']) 2768 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],)) 2769 2770 tt = _('Substance intake entry (%s, %s) [#%s] \n') % ( 2771 gmTools.bool2subst ( 2772 boolean = entry['is_currently_active'], 2773 true_return = gmTools.bool2subst ( 2774 boolean = entry['seems_inactive'], 2775 true_return = _('active, needs check'), 2776 false_return = _('active'), 2777 none_return = _('assumed active') 2778 ), 2779 false_return = _('inactive') 2780 ), 2781 gmTools.bool2subst ( 2782 boolean = entry['intake_is_approved_of'], 2783 true_return = _('approved'), 2784 false_return = _('unapproved') 2785 ), 2786 entry['pk_substance_intake'] 2787 ) 2788 2789 if allg not in [None, False]: 2790 certainty = gmTools.bool2subst(allg['definite'], _('definite'), _('suspected')) 2791 tt += u'\n' 2792 tt += u' !! ---- Cave ---- !!\n' 2793 tt += u' %s (%s): %s (%s)\n' % ( 2794 allg['l10n_type'], 2795 certainty, 2796 allg['descriptor'], 2797 gmTools.coalesce(allg['reaction'], u'')[:40] 2798 ) 2799 tt += u'\n' 2800 2801 tt += u' ' + _('Substance: %s [#%s]\n') % (entry['substance'], entry['pk_substance']) 2802 tt += u' ' + _('Preparation: %s\n') % entry['preparation'] 2803 tt += u' ' + _('Amount per dose: %s %s') % (entry['amount'], entry['unit']) 2804 if entry.ddd is not None: 2805 tt += u' (DDD: %s %s)' % (entry.ddd['ddd'], entry.ddd['unit']) 2806 tt += u'\n' 2807 tt += gmTools.coalesce(entry['atc_substance'], u'', _(' ATC (substance): %s\n')) 2808 2809 tt += u'\n' 2810 2811 tt += gmTools.coalesce ( 2812 entry['brand'], 2813 u'', 2814 _(' Brand name: %%s [#%s]\n') % entry['pk_brand'] 2815 ) 2816 tt += gmTools.coalesce(entry['atc_brand'], u'', _(' ATC (brand): %s\n')) 2817 2818 tt += u'\n' 2819 2820 tt += gmTools.coalesce(entry['schedule'], u'', _(' Regimen: %s\n')) 2821 2822 if entry['is_long_term']: 2823 duration = u' %s %s' % (gmTools.u_right_arrow, gmTools.u_infinity) 2824 else: 2825 if entry['duration'] is None: 2826 duration = u'' 2827 else: 2828 duration = u' %s %s' % (gmTools.u_right_arrow, gmDateTime.format_interval(entry['duration'], gmDateTime.acc_days)) 2829 2830 tt += _(' Started %s%s%s\n') % ( 2831 gmDateTime.pydt_strftime(entry['started'], '%Y %b %d'), 2832 duration, 2833 gmTools.bool2subst(entry['is_long_term'], _(' (long-term)'), _(' (short-term)'), u'') 2834 ) 2835 2836 if entry['discontinued'] is not None: 2837 tt += _(' Discontinued %s\n') % gmDateTime.pydt_strftime(entry['discontinued'], '%Y %b %d') 2838 tt += _(' Reason: %s\n') % entry['discontinue_reason'] 2839 2840 tt += u'\n' 2841 2842 tt += gmTools.coalesce(entry['aim'], u'', _(' Aim: %s\n')) 2843 tt += gmTools.coalesce(entry['episode'], u'', _(' Episode: %s\n')) 2844 tt += gmTools.coalesce(entry['health_issue'], u'', _(' Health issue: %s\n')) 2845 tt += gmTools.coalesce(entry['notes'], u'', _(' Advice: %s\n')) 2846 2847 tt += u'\n' 2848 2849 tt += _(u'Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % ({ 2850 'row_ver': entry['row_version'], 2851 'mod_when': gmDateTime.pydt_strftime(entry['modified_when'], '%Y %b %d %H:%M:%S'), 2852 'mod_by': entry['modified_by'] 2853 }) 2854 2855 return tt
2856 #------------------------------------------------------------ 2857 # internal helpers 2858 #------------------------------------------------------------
2859 - def __init_ui(self):
2860 self.CreateGrid(0, 1) 2861 self.EnableEditing(0) 2862 self.EnableDragGridSize(1) 2863 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows) 2864 2865 self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER) 2866 2867 self.SetRowLabelSize(0) 2868 self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
2869 #------------------------------------------------------------ 2870 # properties 2871 #------------------------------------------------------------
2872 - def _get_patient(self):
2873 return self.__patient
2874
2875 - def _set_patient(self, patient):
2876 self.__patient = patient 2877 self.repopulate_grid()
2878 2879 patient = property(_get_patient, _set_patient) 2880 #------------------------------------------------------------
2881 - def _get_grouping_mode(self):
2882 return self.__grouping_mode
2883
2884 - def _set_grouping_mode(self, mode):
2885 self.__grouping_mode = mode 2886 self.repopulate_grid()
2887 2888 grouping_mode = property(_get_grouping_mode, _set_grouping_mode) 2889 #------------------------------------------------------------
2891 return self.__filter_show_unapproved
2892
2893 - def _set_filter_show_unapproved(self, val):
2894 self.__filter_show_unapproved = val 2895 self.repopulate_grid()
2896 2897 filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved) 2898 #------------------------------------------------------------
2899 - def _get_filter_show_inactive(self):
2900 return self.__filter_show_inactive
2901
2902 - def _set_filter_show_inactive(self, val):
2903 self.__filter_show_inactive = val 2904 self.repopulate_grid()
2905 2906 filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive) 2907 #------------------------------------------------------------ 2908 # event handling 2909 #------------------------------------------------------------
2910 - def __register_events(self):
2911 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow 2912 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells) 2913 #self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels) 2914 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels) 2915 2916 # editing cells 2917 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2918 #------------------------------------------------------------
2919 - def __on_mouse_over_cells(self, evt):
2920 """Calculate where the mouse is and set the tooltip dynamically.""" 2921 2922 # Use CalcUnscrolledPosition() to get the mouse position within the 2923 # entire grid including what's offscreen 2924 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 2925 2926 # use this logic to prevent tooltips outside the actual cells 2927 # apply to GetRowSize, too 2928 # tot = 0 2929 # for col in xrange(self.NumberCols): 2930 # tot += self.GetColSize(col) 2931 # if xpos <= tot: 2932 # self.tool_tip.Tip = 'Tool tip for Column %s' % ( 2933 # self.GetColLabelValue(col)) 2934 # break 2935 # else: # mouse is in label area beyond the right-most column 2936 # self.tool_tip.Tip = '' 2937 2938 row, col = self.XYToCell(x, y) 2939 2940 if row == self.__prev_tooltip_row: 2941 return 2942 2943 self.__prev_tooltip_row = row 2944 2945 try: 2946 evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row)) 2947 except KeyError: 2948 pass
2949 #------------------------------------------------------------
2950 - def __on_cell_left_dclicked(self, evt):
2951 row = evt.GetRow() 2952 data = self.__row_data[row] 2953 edit_intake_of_substance(parent = self, substance = data)
2954 #============================================================ 2955 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl 2956
2957 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
2958 2959 """Panel holding a grid with current substances. Used as notebook page.""" 2960
2961 - def __init__(self, *args, **kwargs):
2962 2963 wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl.__init__(self, *args, **kwargs) 2964 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 2965 2966 self.__register_interests()
2967 #----------------------------------------------------- 2968 # reget-on-paint mixin API 2969 #-----------------------------------------------------
2970 - def _populate_with_data(self):
2971 """Populate cells with data from model.""" 2972 pat = gmPerson.gmCurrentPatient() 2973 if pat.connected: 2974 self._grid_substances.patient = pat 2975 self.__refresh_gfr(pat) 2976 else: 2977 self._grid_substances.patient = None 2978 self.__clear_gfr() 2979 return True
2980 #--------------------------------------------------------
2981 - def __refresh_gfr(self, patient):
2982 gfr = patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_gfr_quantity, no_of_results = 1) 2983 if gfr is None: 2984 calc = gmClinicalCalculator.cClinicalCalculator() 2985 calc.patient = patient 2986 gfr = calc.eGFR 2987 if gfr.numeric_value is None: 2988 msg = _('GFR: ?') 2989 tt = gfr.message 2990 else: 2991 msg = _('eGFR: %.1f (%s)') % ( 2992 gfr.numeric_value, 2993 gmDateTime.pydt_strftime ( 2994 gfr.date_valid, 2995 format = '%b %Y' 2996 ) 2997 ) 2998 tt = gfr.format ( 2999 left_margin = 0, 3000 width = 50, 3001 eol = u'\n', 3002 with_formula = True, 3003 with_warnings = True, 3004 with_variables = False, 3005 with_sub_results = True, 3006 return_list = False 3007 ) 3008 else: 3009 msg = u'%s: %s %s (%s)\n' % ( 3010 gfr['unified_abbrev'], 3011 gfr['unified_val'], 3012 gmTools.coalesce(gfr['abnormality_indicator'], u'', u' (%s)'), 3013 gmDateTime.pydt_strftime ( 3014 gfr['clin_when'], 3015 format = '%b %Y' 3016 ) 3017 ) 3018 tt = _('GFR reported by path lab') 3019 3020 self._LBL_gfr.SetLabel(msg) 3021 self._LBL_gfr.SetToolTipString(tt) 3022 self._LBL_gfr.Refresh() 3023 self.Layout()
3024 #--------------------------------------------------------
3025 - def __clear_gfr(self):
3026 self._LBL_gfr.SetLabel(_('GFR: ?')) 3027 self._LBL_gfr.Refresh() 3028 self.Layout()
3029 #-------------------------------------------------------- 3030 # event handling 3031 #--------------------------------------------------------
3032 - def __register_interests(self):
3033 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 3034 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 3035 gmDispatcher.connect(signal = u'substance_intake_mod_db', receiver = self._schedule_data_reget)
3036 # active_substance_mod_db 3037 # substance_brand_mod_db 3038 #--------------------------------------------------------
3039 - def _on_pre_patient_selection(self):
3040 wx.CallAfter(self.__on_pre_patient_selection)
3041
3042 - def __on_pre_patient_selection(self):
3043 self._grid_substances.patient = None
3044 #--------------------------------------------------------
3045 - def _on_post_patient_selection(self):
3046 wx.CallAfter(self.__on_post_patient_selection)
3047
3048 - def __on_post_patient_selection(self):
3049 self._schedule_data_reget()
3050 #--------------------------------------------------------
3051 - def _on_add_button_pressed(self, event):
3052 self._grid_substances.add_substance()
3053 #--------------------------------------------------------
3054 - def _on_edit_button_pressed(self, event):
3055 self._grid_substances.edit_substance()
3056 #--------------------------------------------------------
3057 - def _on_delete_button_pressed(self, event):
3058 self._grid_substances.delete_substance()
3059 #--------------------------------------------------------
3060 - def _on_info_button_pressed(self, event):
3061 self._grid_substances.show_info_on_entry()
3062 #--------------------------------------------------------
3063 - def _on_interactions_button_pressed(self, event):
3064 self._grid_substances.check_interactions()
3065 #--------------------------------------------------------
3066 - def _on_issue_grouping_selected(self, event):
3067 self._grid_substances.grouping_mode = 'issue'
3068 #--------------------------------------------------------
3069 - def _on_episode_grouping_selected(self, event):
3070 self._grid_substances.grouping_mode = 'episode'
3071 #--------------------------------------------------------
3072 - def _on_brand_grouping_selected(self, event):
3073 self._grid_substances.grouping_mode = 'brand'
3074 #--------------------------------------------------------
3075 - def _on_show_unapproved_checked(self, event):
3076 self._grid_substances.filter_show_unapproved = self._CHBOX_show_unapproved.GetValue()
3077 #--------------------------------------------------------
3078 - def _on_show_inactive_checked(self, event):
3079 self._grid_substances.filter_show_inactive = self._CHBOX_show_inactive.GetValue()
3080 #--------------------------------------------------------
3081 - def _on_print_button_pressed(self, event):
3082 self._grid_substances.print_medication_list()
3083 #--------------------------------------------------------
3084 - def _on_allergy_button_pressed(self, event):
3085 self._grid_substances.create_allergy_from_substance()
3086 #--------------------------------------------------------
3087 - def _on_button_kidneys_pressed(self, event):
3088 self._grid_substances.show_renal_insufficiency_info()
3089 #--------------------------------------------------------
3090 - def _on_button_heart_pressed(self, event):
3091 self._grid_substances.show_cardiac_info()
3092 #--------------------------------------------------------
3093 - def _on_adr_button_pressed(self, event):
3094 self._grid_substances.report_ADR()
3095 #--------------------------------------------------------
3096 - def _on_rx_button_pressed(self, event):
3097 self._grid_substances.prescribe()
3098 #============================================================ 3099 # main 3100 #------------------------------------------------------------ 3101 if __name__ == '__main__': 3102 3103 if len(sys.argv) < 2: 3104 sys.exit() 3105 3106 if sys.argv[1] != 'test': 3107 sys.exit() 3108 3109 from Gnumed.business import gmPersonSearch 3110 3111 pat = gmPersonSearch.ask_for_patient() 3112 if pat is None: 3113 sys.exit() 3114 gmPerson.set_active_patient(patient = pat) 3115 3116 #---------------------------------------- 3117 app = wx.PyWidgetTester(size = (600, 600)) 3118 # #app.SetWidget(cATCPhraseWheel, -1) 3119 # app.SetWidget(cSubstancePhraseWheel, -1) 3120 # app.MainLoop() 3121 manage_substance_intakes() 3122 3123 #============================================================ 3124