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  __version__ = "$Revision: 1.33 $" 
   5  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
   6   
   7  import logging, sys, os.path, webbrowser 
   8   
   9   
  10  import wx, wx.grid 
  11   
  12   
  13  if __name__ == '__main__': 
  14          sys.path.insert(0, '../../') 
  15  from Gnumed.pycommon import gmDispatcher, gmCfg, gmShellAPI, gmTools, gmDateTime 
  16  from Gnumed.pycommon import gmMatchProvider, gmI18N, gmPrinting, gmCfg2 
  17  from Gnumed.business import gmPerson, gmATC, gmSurgery, gmMedication, gmForms 
  18  from Gnumed.wxpython import gmGuiHelpers, gmRegetMixin, gmAuthWidgets, gmEditArea, gmMacro 
  19  from Gnumed.wxpython import gmCfgWidgets, gmListWidgets, gmPhraseWheel, gmFormWidgets 
  20  from Gnumed.wxpython import gmAllergyWidgets 
  21   
  22   
  23  _log = logging.getLogger('gm.ui') 
  24  _log.info(__version__) 
  25   
  26  #============================================================ 
  27  # ATC related widgets 
  28  #============================================================ 
  29   
30 -def browse_atc_reference(parent=None):
31 32 if parent is None: 33 parent = wx.GetApp().GetTopWindow() 34 #------------------------------------------------------------ 35 def refresh(lctrl): 36 atcs = gmATC.get_reference_atcs() 37 38 items = [ [ 39 a['atc'], 40 a['term'], 41 u'%s' % gmTools.coalesce(a['ddd'], u''), 42 gmTools.coalesce(a['unit'], u''), 43 gmTools.coalesce(a['administrative_route'], u''), 44 gmTools.coalesce(a['comment'], u''), 45 a['version'], 46 a['lang'] 47 ] for a in atcs ] 48 lctrl.set_string_items(items) 49 lctrl.set_data(atcs)
50 #------------------------------------------------------------ 51 gmListWidgets.get_choices_from_list ( 52 parent = parent, 53 msg = _('\nThe ATC codes as known to GNUmed.\n'), 54 caption = _('Showing ATC codes.'), 55 columns = [ u'ATC', _('Term'), u'DDD', _('Unit'), _(u'Route'), _('Comment'), _('Version'), _('Language') ], 56 single_selection = True, 57 refresh_callback = refresh 58 ) 59 60 #============================================================ 61
62 -def update_atc_reference_data():
63 64 dlg = wx.FileDialog ( 65 parent = None, 66 message = _('Choose an ATC import config file'), 67 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')), 68 defaultFile = '', 69 wildcard = "%s (*.conf)|*.conf|%s (*)|*" % (_('config files'), _('all files')), 70 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST 71 ) 72 73 result = dlg.ShowModal() 74 if result == wx.ID_CANCEL: 75 return 76 77 cfg_file = dlg.GetPath() 78 dlg.Destroy() 79 80 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing ATC reference data')) 81 if conn is None: 82 return False 83 84 wx.BeginBusyCursor() 85 86 if gmATC.atc_import(cfg_fname = cfg_file, conn = conn): 87 gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported ATC reference data.')) 88 else: 89 gmDispatcher.send(signal = 'statustext', msg = _('Importing ATC reference data failed.'), beep = True) 90 91 wx.EndBusyCursor() 92 return True
93 94 #============================================================ 95
96 -class cATCPhraseWheel(gmPhraseWheel.cPhraseWheel):
97
98 - def __init__(self, *args, **kwargs):
99 100 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 101 102 query = u""" 103 104 SELECT DISTINCT ON (label) 105 atc_code, 106 label 107 FROM ( 108 109 SELECT 110 code as atc_code, 111 (code || ': ' || term || coalesce(' (' || ddd || unit || ')', '')) 112 AS label 113 FROM ref.atc 114 WHERE 115 term %(fragment_condition)s 116 OR 117 code %(fragment_condition)s 118 119 UNION ALL 120 121 SELECT 122 atc_code, 123 (atc_code || ': ' || description) 124 AS label 125 FROM ref.substance_in_brand 126 WHERE 127 description %(fragment_condition)s 128 OR 129 atc_code %(fragment_condition)s 130 131 UNION ALL 132 133 SELECT 134 atc_code, 135 (atc_code || ': ' || description || ' (' || preparation || ')') 136 AS label 137 FROM ref.branded_drug 138 WHERE 139 description %(fragment_condition)s 140 OR 141 atc_code %(fragment_condition)s 142 143 -- it would be nice to be able to include clin.vacc_indication but that's hard to do in SQL 144 145 ) AS candidates 146 147 ORDER BY label 148 LIMIT 50""" 149 150 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 151 mp.setThresholds(1, 2, 4) 152 # mp.word_separators = '[ \t=+&:@]+' 153 self.SetToolTipString(_('Select an ATC (Anatomical-Therapeutic-Chemical) code.')) 154 self.matcher = mp 155 self.selection_only = True
156 157 #============================================================ 158 #============================================================ 159
160 -def manage_substances_in_brands(parent=None):
161 162 if parent is None: 163 parent = wx.GetApp().GetTopWindow() 164 165 #------------------------------------------------------------ 166 def delete(component): 167 gmMedication.delete_component_from_branded_drug ( 168 brand = component['pk_brand'], 169 component = component['pk_substance_in_brand'] 170 ) 171 return True
172 #------------------------------------------------------------ 173 def refresh(lctrl): 174 substs = gmMedication.get_substances_in_brands() 175 items = [ [ 176 u'%s%s' % (s['brand'], gmTools.coalesce(s['atc_brand'], u'', u' (%s)')), 177 s['substance'], 178 gmTools.coalesce(s['atc_substance'], u''), 179 s['preparation'], 180 gmTools.coalesce(s['external_code_brand'], u'', u'%%s [%s]' % s['external_code_type_brand']), 181 s['pk_substance_in_brand'] 182 ] for s in substs ] 183 lctrl.set_string_items(items) 184 lctrl.set_data(substs) 185 #------------------------------------------------------------ 186 msg = _('\nThese are the substances in the drug brands known to GNUmed.\n') 187 188 gmListWidgets.get_choices_from_list ( 189 parent = parent, 190 msg = msg, 191 caption = _('Showing drug brand components (substances).'), 192 columns = [_('Brand'), _('Substance'), u'ATC', _('Preparation'), _('Code'), u'#'], 193 single_selection = True, 194 #new_callback = new, 195 #edit_callback = edit, 196 delete_callback = delete, 197 refresh_callback = refresh 198 ) 199 #============================================================
200 -def manage_branded_drugs(parent=None):
201 202 if parent is None: 203 parent = wx.GetApp().GetTopWindow() 204 #------------------------------------------------------------ 205 def delete(brand): 206 if brand.is_vaccine: 207 gmGuiHelpers.gm_show_info ( 208 aTitle = _('Deleting medication'), 209 aMessage = _( 210 'Cannot delete the medication\n' 211 '\n' 212 ' "%s" (%s)\n' 213 '\n' 214 'because it is a vaccine. Please delete it\n' 215 'from the vaccine management section !\n' 216 ) % (brand['description'], brand['preparation']) 217 ) 218 return False 219 gmMedication.delete_branded_drug(brand = brand['pk']) 220 return True
221 #------------------------------------------------------------ 222 def new(): 223 drug_db = get_drug_database(parent = parent) 224 225 if drug_db is None: 226 return False 227 228 drug_db.import_drugs() 229 230 return True 231 #------------------------------------------------------------ 232 def refresh(lctrl): 233 drugs = gmMedication.get_branded_drugs() 234 items = [ [ 235 d['description'], 236 d['preparation'], 237 gmTools.coalesce(d['atc_code'], u''), 238 gmTools.coalesce(d['external_code'], u'', u'%%s [%s]' % d['external_code_type']), 239 d['pk'] 240 ] for d in drugs ] 241 lctrl.set_string_items(items) 242 lctrl.set_data(drugs) 243 #------------------------------------------------------------ 244 msg = _('\nThese are the drug brands known to GNUmed.\n') 245 246 gmListWidgets.get_choices_from_list ( 247 parent = parent, 248 msg = msg, 249 caption = _('Showing branded drugs.'), 250 columns = [_('Name'), _('Preparation'), _('ATC'), _('Code'), u'#'], 251 single_selection = True, 252 refresh_callback = refresh, 253 new_callback = new, 254 #edit_callback = edit, 255 delete_callback = delete 256 ) 257 #============================================================
258 -def manage_substances_in_use(parent=None):
259 260 if parent is None: 261 parent = wx.GetApp().GetTopWindow() 262 #------------------------------------------------------------ 263 def delete(substance): 264 gmMedication.delete_used_substance(substance = substance['pk']) 265 return True
266 #------------------------------------------------------------ 267 # def new(): 268 # drug_db = get_drug_database(parent = parent) 269 # 270 # if drug_db is None: 271 # return False 272 # 273 # drug_db.import_drugs() 274 # 275 # return True 276 #------------------------------------------------------------ 277 def refresh(lctrl): 278 substs = gmMedication.get_substances_in_use() 279 items = [ [ 280 s['description'], 281 gmTools.coalesce(s['atc_code'], u''), 282 s['pk'] 283 ] for s in substs ] 284 lctrl.set_string_items(items) 285 lctrl.set_data(substs) 286 #------------------------------------------------------------ 287 msg = _('Substances currently or previously consumed across all patients.') 288 289 gmListWidgets.get_choices_from_list ( 290 parent = parent, 291 msg = msg, 292 caption = _('Showing consumed substances.'), 293 columns = [_('Name'), _('ATC'), u'#'], 294 single_selection = True, 295 refresh_callback = refresh, 296 #new_callback = new, 297 #edit_callback = edit, 298 delete_callback = delete 299 ) 300 #============================================================ 301 # generic drug database access 302 #============================================================
303 -def configure_drug_data_source(parent=None):
304 gmCfgWidgets.configure_string_from_list_option ( 305 parent = parent, 306 message = _( 307 '\n' 308 'Please select the default drug data source from the list below.\n' 309 '\n' 310 'Note that to actually use it you need to have the database installed, too.' 311 ), 312 option = 'external.drug_data.default_source', 313 bias = 'user', 314 default_value = None, 315 choices = gmMedication.drug_data_source_interfaces.keys(), 316 columns = [_('Drug data source')], 317 data = gmMedication.drug_data_source_interfaces.keys(), 318 caption = _('Configuring default drug data source') 319 )
320 #============================================================
321 -def get_drug_database(parent = None):
322 dbcfg = gmCfg.cCfgSQL() 323 324 default_db = dbcfg.get2 ( 325 option = 'external.drug_data.default_source', 326 workplace = gmSurgery.gmCurrentPractice().active_workplace, 327 bias = 'workplace' 328 ) 329 330 if default_db is None: 331 gmDispatcher.send('statustext', msg = _('No default drug database configured.'), beep = True) 332 configure_drug_data_source(parent = parent) 333 default_db = dbcfg.get2 ( 334 option = 'external.drug_data.default_source', 335 workplace = gmSurgery.gmCurrentPractice().active_workplace, 336 bias = 'workplace' 337 ) 338 if default_db is None: 339 gmGuiHelpers.gm_show_error ( 340 aMessage = _('There is no default drug database configured.'), 341 aTitle = _('Jumping to drug database') 342 ) 343 return None 344 345 try: 346 return gmMedication.drug_data_source_interfaces[default_db]() 347 except KeyError: 348 _log.error('faulty default drug data source configuration: %s', default_db) 349 return None
350 351 #============================================================
352 -def jump_to_drug_database():
353 dbcfg = gmCfg.cCfgSQL() 354 drug_db = get_drug_database() 355 if drug_db is None: 356 return 357 pat = gmPerson.gmCurrentPatient() 358 if pat.connected: 359 drug_db.patient = pat 360 drug_db.switch_to_frontend(blocking = False)
361 362 #============================================================
363 -def jump_to_ifap(import_drugs=False):
364 365 dbcfg = gmCfg.cCfgSQL() 366 367 ifap_cmd = dbcfg.get2 ( 368 option = 'external.ifap-win.shell_command', 369 workplace = gmSurgery.gmCurrentPractice().active_workplace, 370 bias = 'workplace', 371 default = 'wine "C:\Ifapwin\WIAMDB.EXE"' 372 ) 373 found, binary = gmShellAPI.detect_external_binary(ifap_cmd) 374 if not found: 375 gmDispatcher.send('statustext', msg = _('Cannot call IFAP via [%s].') % ifap_cmd) 376 return False 377 ifap_cmd = binary 378 379 if import_drugs: 380 transfer_file = os.path.expanduser(dbcfg.get2 ( 381 option = 'external.ifap-win.transfer_file', 382 workplace = gmSurgery.gmCurrentPractice().active_workplace, 383 bias = 'workplace', 384 default = '~/.wine/drive_c/Ifapwin/ifap2gnumed.csv' 385 )) 386 # file must exist for Ifap to write into it 387 try: 388 f = open(transfer_file, 'w+b').close() 389 except IOError: 390 _log.exception('Cannot create IFAP <-> GNUmed transfer file [%s]', transfer_file) 391 gmDispatcher.send('statustext', msg = _('Cannot create IFAP <-> GNUmed transfer file [%s].') % transfer_file) 392 return False 393 394 wx.BeginBusyCursor() 395 gmShellAPI.run_command_in_shell(command = ifap_cmd, blocking = import_drugs) 396 wx.EndBusyCursor() 397 398 if import_drugs: 399 # COMMENT: this file must exist PRIOR to invoking IFAP 400 # COMMENT: or else IFAP will not write data into it ... 401 try: 402 csv_file = open(transfer_file, 'rb') # FIXME: encoding 403 except: 404 _log.exception('cannot access [%s]', fname) 405 csv_file = None 406 407 if csv_file is not None: 408 import csv 409 csv_lines = csv.DictReader ( 410 csv_file, 411 fieldnames = u'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split(), 412 delimiter = ';' 413 ) 414 pat = gmPerson.gmCurrentPatient() 415 emr = pat.get_emr() 416 # dummy episode for now 417 epi = emr.add_episode(episode_name = _('Current medication')) 418 for line in csv_lines: 419 narr = u'%sx %s %s %s (\u2258 %s %s) von %s (%s)' % ( 420 line['Packungszahl'].strip(), 421 line['Handelsname'].strip(), 422 line['Form'].strip(), 423 line[u'Packungsgr\xf6\xdfe'].strip(), 424 line['Abpackungsmenge'].strip(), 425 line['Einheit'].strip(), 426 line['Hersteller'].strip(), 427 line['PZN'].strip() 428 ) 429 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi) 430 csv_file.close() 431 432 return True
433 434 #============================================================ 435 # current substance intake handling 436 #============================================================
437 -class cSubstanceSchedulePhraseWheel(gmPhraseWheel.cPhraseWheel):
438
439 - def __init__(self, *args, **kwargs):
440 441 query = u""" 442 SELECT DISTINCT ON (sched) 443 schedule as sched, 444 schedule 445 FROM clin.substance_intake 446 WHERE schedule %(fragment_condition)s 447 ORDER BY sched 448 LIMIT 50""" 449 450 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 451 mp.setThresholds(1, 2, 4) 452 mp.word_separators = '[ \t=+&:@]+' 453 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 454 self.SetToolTipString(_('The schedule for taking this substance.')) 455 self.matcher = mp 456 self.selection_only = False
457 #============================================================
458 -class cSubstancePreparationPhraseWheel(gmPhraseWheel.cPhraseWheel):
459
460 - def __init__(self, *args, **kwargs):
461 462 query = u""" 463 ( 464 SELECT DISTINCT ON (preparation) 465 preparation as prep, preparation 466 FROM ref.branded_drug 467 WHERE preparation %(fragment_condition)s 468 ) UNION ( 469 SELECT DISTINCT ON (preparation) 470 preparation as prep, preparation 471 FROM clin.substance_intake 472 WHERE preparation %(fragment_condition)s 473 ) 474 ORDER BY prep 475 limit 30""" 476 477 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 478 mp.setThresholds(1, 2, 4) 479 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 480 self.SetToolTipString(_('The preparation (form) of the substance the patient is taking.')) 481 self.matcher = mp 482 self.selection_only = False
483 #============================================================
484 -class cSubstancePhraseWheel(gmPhraseWheel.cPhraseWheel):
485
486 - def __init__(self, *args, **kwargs):
487 488 query = u""" 489 ( 490 SELECT 491 pk::text, 492 description as subst 493 --(description || coalesce(' [' || atc_code || ']', '')) as subst 494 FROM clin.consumed_substance 495 WHERE description %(fragment_condition)s 496 497 ) UNION ( 498 499 SELECT 500 description, 501 description as subst 502 --NULL, 503 --(description || coalesce(' [' || atc_code || ']', '')) as subst 504 FROM ref.substance_in_brand 505 WHERE description %(fragment_condition)s 506 507 ) UNION ( 508 509 SELECT 510 term, 511 term as subst 512 --NULL, 513 --(term || ' [' || atc || ']') as subst 514 FROM ref.v_atc 515 WHERE 516 is_group_code IS FALSE 517 AND 518 term %(fragment_condition)s 519 ) 520 ORDER BY subst 521 LIMIT 50""" 522 523 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 524 mp.setThresholds(1, 2, 4) 525 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 526 self.SetToolTipString(_('The INN / substance the patient is taking.')) 527 self.matcher = mp 528 self.selection_only = False
529 #---------------------------------------------------------
530 - def GetData(self, can_create=False, as_instance=False):
531 532 if self.data is not None: 533 try: 534 int(self.data) 535 except ValueError: 536 self.data = None 537 538 return super(cSubstancePhraseWheel, self).GetData(can_create = can_create, as_instance = as_instance)
539 #============================================================
540 -class cBrandedDrugPhraseWheel(gmPhraseWheel.cPhraseWheel):
541
542 - def __init__(self, *args, **kwargs):
543 544 query = u""" 545 SELECT 546 pk, 547 (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', '')) 548 AS brand 549 FROM ref.branded_drug 550 WHERE description %(fragment_condition)s 551 ORDER BY brand 552 LIMIT 50""" 553 554 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 555 mp.setThresholds(2, 3, 4) 556 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 557 self.SetToolTipString(_('The brand name of the drug the patient is taking.')) 558 self.matcher = mp 559 self.selection_only = False
560 561 #============================================================ 562 from Gnumed.wxGladeWidgets import wxgCurrentMedicationEAPnl 563
564 -class cCurrentMedicationEAPnl(wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl, gmEditArea.cGenericEditAreaMixin):
565
566 - def __init__(self, *args, **kwargs):
567 568 try: 569 data = kwargs['substance'] 570 del kwargs['substance'] 571 except KeyError: 572 data = None 573 574 wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl.__init__(self, *args, **kwargs) 575 gmEditArea.cGenericEditAreaMixin.__init__(self) 576 self.mode = 'new' 577 self.data = data 578 if data is not None: 579 self.mode = 'edit' 580 581 self.__init_ui()
582 #----------------------------------------------------------------
583 - def __init_ui(self):
584 585 self._PRW_brand.add_callback_on_lose_focus(callback = self._on_leave_brand) 586 self._PRW_substance.add_callback_on_lose_focus(callback = self._on_leave_substance)
587 #----------------------------------------------------------------
588 - def __refresh_allergies(self):
589 emr = gmPerson.gmCurrentPatient().get_emr() 590 591 state = emr.allergy_state 592 if state['last_confirmed'] is None: 593 confirmed = _('never') 594 else: 595 confirmed = state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding()) 596 msg = _(u'%s, last confirmed %s\n') % (state.state_string, confirmed) 597 msg += gmTools.coalesce(state['comment'], u'', _('Comment (%s): %%s\n') % state['modified_by']) 598 msg += u'\n' 599 600 for allergy in emr.get_allergies(): 601 msg += u'%s (%s, %s): %s\n' % ( 602 allergy['descriptor'], 603 allergy['l10n_type'], 604 gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'), u'?'), 605 gmTools.coalesce(allergy['reaction'], _('reaction not recorded')) 606 ) 607 608 self._LBL_allergies.SetLabel(msg)
609 #----------------------------------------------------------------
611 612 if self._PRW_brand.GetData() is None: 613 self._TCTRL_brand_ingredients.SetValue(u'') 614 if self.data is None: 615 return 616 if self.data['pk_brand'] is None: 617 return 618 self._PRW_brand.SetText(self.data['brand'], self.data['pk_brand']) 619 620 brand = gmMedication.cBrandedDrug(aPK_obj = self._PRW_brand.GetData()) 621 622 if self.data is None: 623 self._PRW_preparation.SetText(brand['preparation'], None) 624 else: 625 self._PRW_preparation.SetText ( 626 gmTools.coalesce(self.data['preparation'], brand['preparation']), 627 self.data['preparation'] 628 ) 629 630 comps = brand.components 631 632 if comps is None: 633 return 634 635 if len(comps) == 0: 636 return 637 638 comps = u' / '.join([ u'%s%s' % (c['description'], gmTools.coalesce(c['atc_code'], u'', u' (%s)')) for c in comps ]) 639 self._TCTRL_brand_ingredients.SetValue(comps)
640 #---------------------------------------------------------------- 641 # generic Edit Area mixin API 642 #----------------------------------------------------------------
643 - def _valid_for_save(self):
644 645 validity = True 646 647 has_brand = (self._PRW_brand.GetData() is not None) 648 has_substance = (self._PRW_substance.GetValue().strip() == u'') 649 650 # must have either brand or substance 651 if not (has_brand or has_substance): 652 self._PRW_substance.display_as_valid(False) 653 self._PRW_brand.display_as_valid(False) 654 validity = False 655 else: 656 self._PRW_substance.display_as_valid(True) 657 self._PRW_brand.display_as_valid(True) 658 659 # brands must have components or they cannot be used 660 if has_brand: 661 brand = gmMedication.cBrandedDrug(aPK_obj = self._PRW_brand.GetData()) 662 if len(brand.components) == 0: 663 self._PRW_brand.display_as_valid(False) 664 validity = False 665 666 # brands already have a preparation, so only required for substances 667 if not has_brand: 668 if self._PRW_preparation.GetValue().strip() == u'': 669 self._PRW_preparation.display_as_valid(False) 670 validity = False 671 else: 672 self._PRW_preparation.display_as_valid(True) 673 674 # episode must be set if intake is to be approved of 675 if self._CHBOX_approved.IsChecked(): 676 if self._PRW_episode.GetValue().strip() == u'': 677 self._PRW_episode.display_as_valid(False) 678 validity = False 679 else: 680 self._PRW_episode.display_as_valid(True) 681 682 # huh ? 683 if self._CHBOX_approved.IsChecked() is True: 684 self._PRW_duration.display_as_valid(True) 685 else: 686 if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]: 687 self._PRW_duration.display_as_valid(True) 688 else: 689 if gmDateTime.str2interval(self._PRW_duration.GetValue()) is None: 690 self._PRW_duration.display_as_valid(False) 691 validity = False 692 else: 693 self._PRW_duration.display_as_valid(True) 694 695 # end must be > start if at all 696 end = self._DP_discontinued.GetValue(as_pydt = True, invalid_as_none = True) 697 if end is not None: 698 start = self._DP_started.GetValue(as_pydt = True) 699 if start > end: 700 self._DP_started.display_as_valid(False) 701 self._DP_discontinued.display_as_valid(False) 702 validity = False 703 else: 704 self._DP_started.display_as_valid(True) 705 self._DP_discontinued.display_as_valid(True) 706 707 if validity is False: 708 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save substance intake. Invalid or missing essential input.')) 709 710 return validity
711 #----------------------------------------------------------------
712 - def _save_as_new(self):
713 714 pk_brand = self._PRW_brand.GetData() 715 brand = None 716 717 if pk_brand is None: 718 prep = self._PRW_preparation.GetValue() 719 if self._PRW_substance.GetData() is None: 720 subst = self._PRW_substance.GetValue().strip() 721 else: 722 # normalize, do not simply re-use name from phrasewheel 723 subst = gmMedication.get_substance_by_pk(pk = self._PRW_substance.GetData())['description'] 724 substances = [ 725 [subst['description'], self._PRW_strength.GetValue()] 726 ] 727 else: 728 brand = gmMedication.cBrandedDrug(aPK_obj = pk_brand) 729 prep = brand['preparation'] 730 comps = brand.components 731 if len(comps) == 1: 732 substances = [ 733 [comps[0], self._PRW_strength.GetValue()] 734 ] 735 else: 736 # loop and ask for strengths 737 print "missing" 738 739 emr = gmPerson.gmCurrentPatient().get_emr() 740 epi = self._PRW_episode.GetData(can_create = True) 741 742 # now loop over substances either from brand or from input 743 last_intake = None 744 for subst, strength in substances: 745 intake = emr.add_substance_intake ( 746 substance = subst, 747 episode = epi, 748 preparation = prep 749 ) 750 intake['strength'] = strength 751 intake['started'] = self._DP_started.GetValue(as_pydt = True, invalid_as_none = True) 752 intake['discontinued'] = self._DP_discontinued.GetValue(as_pydt = True, invalid_as_none = True) 753 if intake['discontinued'] is None: 754 intake['discontinue_reason'] = None 755 else: 756 intake['discontinue_reason'] = self._PRW_discontinue_reason().GetValue().strip() 757 intake['schedule'] = self._PRW_schedule.GetValue() 758 intake['aim'] = self._PRW_aim.GetValue() 759 intake['notes'] = self._PRW_notes.GetValue() 760 intake['is_long_term'] = self._CHBOX_long_term.IsChecked() 761 intake['intake_is_approved_of'] = self._CHBOX_approved.IsChecked() 762 if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]: 763 intake['duration'] = None 764 else: 765 intake['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue()) 766 intake['pk_brand'] = pk_brand 767 intake.save() 768 last_intake = intake 769 770 self.data = last_intake 771 772 if self._CHBOX_is_allergy.IsChecked(): 773 if brand is None: 774 allg = self.data.turn_into_allergy(encounter_id = emr.active_encounter['pk_encounter']) 775 else: 776 allg = brand.turn_into_allergy(encounter_id = emr.active_encounter['pk_encounter']) 777 # open for editing 778 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent = self, id = -1) 779 dlg.ShowModal() 780 781 return True
782 783 # # brand pre-selected ? 784 # if pk_brand is None: 785 # # no, so ... 786 # desc = self._PRW_brand.GetValue().strip() 787 # if desc != u'': 788 # # ... create or get it 789 # brand = gmMedication.create_branded_drug ( 790 # brand_name = desc, 791 # preparation = self._PRW_preparation.GetValue().strip(), 792 # return_existing = True 793 # ) 794 # pk_brand = brand['pk'] 795 # else: 796 # # yes, so get it 797 # brand = gmMedication.cBrandedDrug(aPK_obj = pk_brand) 798 # 799 # # brand neither creatable nor pre-selected 800 # if brand is None: 801 # self.data = intake 802 # return True 803 # 804 # # 4) add substance to brand as component (because 805 # # that's effectively what we are saying here) 806 # # FIXME: we may want to ask the user here 807 # # FIXME: or only do it if there are no components yet 808 # if self._PRW_substance.GetData() is None: 809 # brand.add_component(substance = self._PRW_substance.GetValue().strip()) 810 # else: 811 # # normalize substance name 812 # subst = gmMedication.get_substance_by_pk(pk = self._PRW_substance.GetData()) 813 # if subst is not None: 814 # brand.add_component(substance = subst['description']) 815 # 816 #----------------------------------------------------------------
817 - def _save_as_update(self):
818 819 if self._PRW_substance.GetData() is None: 820 self.data['pk_substance'] = gmMedication.create_used_substance ( 821 substance = self._PRW_substance.GetValue().strip() 822 )['pk'] 823 else: 824 self.data['pk_substance'] = self._PRW_substance.GetData() 825 826 self.data['started'] = self._DP_started.GetValue(as_pydt = True, invalid_as_none = True) 827 self.data['discontinued'] = self._DP_discontinued.GetValue(as_pydt = True, invalid_as_none = True) 828 if self.data['discontinued'] is None: 829 self.data['discontinue_reason'] = None 830 else: 831 self.data['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip() 832 self.data['preparation'] = self._PRW_preparation.GetValue() 833 self.data['strength'] = self._PRW_strength.GetValue() 834 self.data['schedule'] = self._PRW_schedule.GetValue() 835 self.data['aim'] = self._PRW_aim.GetValue() 836 self.data['notes'] = self._PRW_notes.GetValue() 837 self.data['is_long_term'] = self._CHBOX_long_term.IsChecked() 838 self.data['intake_is_approved_of'] = self._CHBOX_approved.IsChecked() 839 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True) 840 841 if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]: 842 self.data['duration'] = None 843 else: 844 self.data['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue()) 845 846 if self._PRW_brand.GetData() is None: 847 desc = self._PRW_brand.GetValue().strip() 848 if desc != u'': 849 # create or get brand 850 self.data['pk_brand'] = gmMedication.create_branded_drug ( 851 brand_name = desc, 852 preparation = self._PRW_preparation.GetValue().strip(), 853 return_existing = True 854 )['pk'] 855 else: 856 self.data['pk_brand'] = self._PRW_brand.GetData() 857 858 self.data.save() 859 860 if self._CHBOX_is_allergy.IsChecked(): 861 allg = self.data.turn_into_allergy(encounter_id = emr.active_encounter['pk_encounter']) 862 # open for editing 863 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent = self, id = -1) 864 dlg.ShowModal() 865 866 return True
867 #----------------------------------------------------------------
868 - def _refresh_as_new(self):
869 self._PRW_brand.SetText(u'', None) 870 self._TCTRL_brand_ingredients.SetValue(u'') 871 872 self._PRW_substance.SetText(u'', None) 873 self._PRW_substance.Enable(True) 874 875 self._PRW_strength.SetText(u'', None) 876 self._PRW_strength.Enable(True) 877 878 self._PRW_preparation.SetText(u'', None) 879 self._PRW_preparation.Enable(True) 880 881 self._PRW_schedule.SetText(u'', None) 882 self._PRW_duration.SetText(u'', None) 883 self._PRW_aim.SetText(u'', None) 884 self._PRW_notes.SetText(u'', None) 885 self._PRW_episode.SetText(u'', None) 886 887 self._CHBOX_long_term.SetValue(False) 888 self._CHBOX_approved.SetValue(True) 889 890 self._DP_started.SetValue(gmDateTime.pydt_now_here()) 891 self._DP_discontinued.SetValue(None) 892 self._PRW_discontinue_reason.SetValue(u'') 893 894 #self.__refresh_brand_and_components() 895 self.__refresh_allergies() 896 897 self._PRW_brand.SetFocus()
898 #----------------------------------------------------------------
899 - def _refresh_from_existing(self):
900 901 self._PRW_substance.SetText(self.data['substance'], self.data['pk_substance']) 902 self._PRW_strength.SetText(gmTools.coalesce(self.data['strength'], u''), self.data['strength']) 903 self._PRW_preparation.SetText(self.data['preparation'], self.data['preparation']) 904 905 if self.data['is_long_term']: 906 self._CHBOX_long_term.SetValue(True) 907 self._PRW_duration.Enable(False) 908 self._PRW_duration.SetText(gmTools.u_infinity, None) 909 self._BTN_discontinued_as_planned.Enable(False) 910 else: 911 self._CHBOX_long_term.SetValue(False) 912 self._PRW_duration.Enable(True) 913 self._BTN_discontinued_as_planned.Enable(True) 914 if self.data['duration'] is None: 915 self._PRW_duration.SetText(u'', None) 916 else: 917 self._PRW_duration.SetText(gmDateTime.format_interval(self.data['duration'], gmDateTime.acc_days), self.data['duration']) 918 self._PRW_aim.SetText(gmTools.coalesce(self.data['aim'], u''), self.data['aim']) 919 self._PRW_notes.SetText(gmTools.coalesce(self.data['notes'], u''), self.data['notes']) 920 self._PRW_episode.SetData(self.data['pk_episode']) 921 self._PRW_schedule.SetText(gmTools.coalesce(self.data['schedule'], u''), self.data['schedule']) 922 923 self._CHBOX_approved.SetValue(self.data['intake_is_approved_of']) 924 925 self._DP_started.SetValue(self.data['started']) 926 self._DP_discontinued.SetValue(self.data['discontinued']) 927 self._PRW_discontinue_reason.SetValue(gmTools.coalesce(self.data['discontinue_reason'], u'')) 928 929 self.__refresh_brand_and_components() 930 self.__refresh_allergies() 931 932 self._PRW_substance.SetFocus()
933 #----------------------------------------------------------------
935 self._refresh_as_new()
936 #---------------------------------------------------------------- 937 # event handlers 938 #----------------------------------------------------------------
939 - def _on_leave_brand(self):
940 if self._PRW_brand.GetData() is None: 941 self._PRW_brand.SetText(u'', None) 942 self._LBL_substance.Enable(True) 943 self._PRW_substance.Enable(True) 944 self._BTN_database_substance.Enable(True) 945 self._LBL_strength.Enable(True) 946 self._PRW_strength.Enable(True) 947 self._LBL_preparation.Enable(True) 948 self._PRW_preparation.Enable(True) 949 self._PRW_preparation.SetText(u'', None) 950 self._TCTRL_brand_ingredients.SetValue(u'') 951 else: 952 brand = gmMedication.cBrandedDrug(aPK_obj = self._PRW_brand.GetData()) 953 comps = brand.components 954 955 self._LBL_substance.Enable(False) 956 self._PRW_substance.Enable(False) 957 self._BTN_database_substance.Enable(False) 958 if len(comps) == 1: 959 self._LBL_strength.Enable(True) 960 self._PRW_strength.Enable(True) 961 else: 962 self._LBL_strength.Enable(False) 963 self._PRW_strength.Enable(False) 964 self._LBL_preparation.Enable(False) 965 self._PRW_preparation.Enable(False) 966 self._PRW_preparation.SetText(brand['preparation'], None) 967 self._TCTRL_brand_ingredients.SetValue(u' / '.join ([ 968 u'%s%s' % ( 969 c['description'], 970 gmTools.coalesce(c['atc_code'], u'', u' (%s)') 971 ) for c in comps 972 ]))
973 #----------------------------------------------------------------
974 - def _on_leave_substance(self):
975 if self._PRW_substance.GetValue().strip() == u'': 976 self._PRW_brand.Enable(True) 977 self._BTN_database_brand.Enable(True) 978 979 self._LBL_preparation.Enable(False) 980 self._PRW_preparation.Enable(False) 981 else: 982 self._PRW_brand.SetText(u'', None) 983 self._PRW_brand.Enable(False) 984 self._BTN_database_brand.Enable(False) 985 self._TCTRL_brand_ingredients.SetValue(u'') 986 987 self._LBL_strength.Enable(True) 988 self._PRW_strength.Enable(True) 989 self._LBL_preparation.Enable(True) 990 self._PRW_preparation.Enable(True) 991 self._PRW_preparation.SetText(u'', None)
992 #----------------------------------------------------------------
993 - def _on_discontinued_date_changed(self, event):
994 if self._DP_discontinued.GetValue() is None: 995 self._PRW_discontinue_reason.Enable(False) 996 self._CHBOX_is_allergy.Enable(False) 997 #self._LBL_reason.Enable(False) 998 else: 999 self._PRW_discontinue_reason.Enable(True) 1000 self._CHBOX_is_allergy.Enable(True)
1001 #self._LBL_reason.Enable(True) 1002 #----------------------------------------------------------------
1003 - def _on_get_substance_button_pressed(self, event):
1004 drug_db = get_drug_database() 1005 if drug_db is None: 1006 return 1007 1008 result = drug_db.import_drugs() 1009 if result is None: 1010 return 1011 1012 new_drugs, new_substances = result 1013 if len(new_substances) == 0: 1014 return 1015 1016 # FIXME: could usefully 1017 # FIXME: a) ask which to post-process 1018 # FIXME: b) remember the others for post-processing 1019 first = new_substances[0] 1020 self._PRW_substance.SetText(first['description'], first['pk'])
1021 #----------------------------------------------------------------
1022 - def _on_get_brand_button_pressed(self, event):
1023 drug_db = get_drug_database() 1024 self.__refresh_allergies() 1025 if drug_db is None: 1026 return 1027 1028 result = drug_db.import_drugs() 1029 self.__refresh_allergies() 1030 if result is None: 1031 return 1032 1033 new_drugs, new_substances = result 1034 if len(new_drugs) == 0: 1035 return 1036 # FIXME: could usefully 1037 # FIXME: a) ask which to post-process 1038 # FIXME: b) remember the others for post-processing 1039 first = new_drugs[0] 1040 self._PRW_brand.SetText(first['description'], first['pk']) 1041 1042 self.__refresh_brand_and_components()
1043 #----------------------------------------------------------------
1045 1046 now = gmDateTime.pydt_now_here() 1047 1048 self.__refresh_allergies() 1049 1050 # do we have a (full) plan ? 1051 if None not in [self.data['started'], self.data['duration']]: 1052 planned_end = self.data['started'] + self.data['duration'] 1053 # the plan hasn't ended so [Per plan] can't apply ;-) 1054 if planned_end > now: 1055 return 1056 self._DP_discontinued.SetValue(planned_end) 1057 self._PRW_discontinue_reason.Enable(True) 1058 self._PRW_discontinue_reason.SetValue(u'') 1059 self._CHBOX_is_allergy.Enable(True) 1060 return 1061 1062 # we know started but not duration: apparently the plan is to stop today 1063 if self.data['started'] is not None: 1064 # but we haven't started yet so we can't stop 1065 if self.data['started'] > now: 1066 return 1067 1068 self._DP_discontinued.SetValue(now) 1069 self._PRW_discontinue_reason.Enable(True) 1070 self._PRW_discontinue_reason.SetValue(u'') 1071 self._CHBOX_is_allergy.Enable(True)
1072 #----------------------------------------------------------------
1073 - def _on_chbox_long_term_checked(self, event):
1074 if self._CHBOX_long_term.IsChecked() is True: 1075 self._PRW_duration.Enable(False) 1076 self._BTN_discontinued_as_planned.Enable(False) 1077 self._PRW_discontinue_reason.Enable(False) 1078 self._CHBOX_is_allergy.Enable(False) 1079 else: 1080 self._PRW_duration.Enable(True) 1081 self._BTN_discontinued_as_planned.Enable(True) 1082 self._PRW_discontinue_reason.Enable(True) 1083 self._CHBOX_is_allergy.Enable(True) 1084 1085 self.__refresh_allergies()
1086 #----------------------------------------------------------------
1087 - def _on_chbox_is_allergy_checked(self, event):
1088 if self._CHBOX_is_allergy.IsChecked() is True: 1089 val = self._PRW_discontinue_reason.GetValue().strip() 1090 if not val.startswith(_('not tolerated:')): 1091 self._PRW_discontinue_reason.SetValue(u'%s %s' % (_('not tolerated:'), val)) 1092 1093 self.__refresh_allergies()
1094 #============================================================
1095 -def delete_substance_intake(parent=None, substance=None):
1096 1097 subst = gmMedication.cSubstanceIntakeEntry(aPK_obj = substance) 1098 msg = _( 1099 '\n' 1100 '[%s]\n' 1101 '\n' 1102 'It may be prudent to edit (before deletion) the details\n' 1103 'of this substance intake entry so as to leave behind\n' 1104 'some indication of why it was deleted.\n' 1105 ) % subst.format() 1106 1107 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 1108 parent, 1109 -1, 1110 caption = _('Deleting medication / substance intake'), 1111 question = msg, 1112 button_defs = [ 1113 {'label': _('&Edit'), 'tooltip': _('Allow editing of substance intake entry before deletion.'), 'default': True}, 1114 {'label': _('&Delete'), 'tooltip': _('Delete immediately without editing first.')}, 1115 {'label': _('&Cancel'), 'tooltip': _('Abort. Do not delete or edit substance intake entry.')} 1116 ] 1117 ) 1118 1119 edit_first = dlg.ShowModal() 1120 dlg.Destroy() 1121 1122 if edit_first == wx.ID_CANCEL: 1123 return 1124 1125 if edit_first == wx.ID_YES: 1126 edit_intake_of_substance(parent = parent, substance = subst) 1127 delete_it = gmGuiHelpers.gm_show_question ( 1128 aMessage = _('Now delete substance intake entry ?'), 1129 aTitle = _('Deleting medication / substance intake') 1130 ) 1131 else: 1132 delete_it = True 1133 1134 if not delete_it: 1135 return 1136 1137 gmMedication.delete_substance_intake(substance = substance)
1138 #------------------------------------------------------------
1139 -def edit_intake_of_substance(parent = None, substance=None):
1140 ea = cCurrentMedicationEAPnl(parent = parent, id = -1, substance = substance) 1141 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (substance is not None)) 1142 dlg.SetTitle(gmTools.coalesce(substance, _('Adding substance intake'), _('Editing substance intake'))) 1143 if dlg.ShowModal() == wx.ID_OK: 1144 dlg.Destroy() 1145 return True 1146 dlg.Destroy() 1147 return False
1148 #============================================================ 1149 # current substances grid 1150 #------------------------------------------------------------
1151 -def configure_medication_list_template(parent=None):
1152 1153 if parent is None: 1154 parent = wx.GetApp().GetTopWindow() 1155 1156 template = gmFormWidgets.manage_form_templates ( 1157 parent = parent, 1158 template_types = ['current medication list'] 1159 ) 1160 option = u'form_templates.medication_list' 1161 1162 if template is None: 1163 gmDispatcher.send(signal = 'statustext', msg = _('No medication list template configured.'), beep = True) 1164 return None 1165 1166 if template['engine'] != u'L': 1167 gmDispatcher.send(signal = 'statustext', msg = _('No medication list template configured.'), beep = True) 1168 return None 1169 1170 dbcfg = gmCfg.cCfgSQL() 1171 dbcfg.set ( 1172 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1173 option = option, 1174 value = u'%s - %s' % (template['name_long'], template['external_version']) 1175 ) 1176 1177 return template
1178 #------------------------------------------------------------ 1260 #------------------------------------------------------------
1261 -class cCurrentSubstancesGrid(wx.grid.Grid):
1262 """A grid class for displaying current substance intake. 1263 1264 - does NOT listen to the currently active patient 1265 - thereby it can display any patient at any time 1266 """
1267 - def __init__(self, *args, **kwargs):
1268 1269 wx.grid.Grid.__init__(self, *args, **kwargs) 1270 1271 self.__patient = None 1272 self.__row_data = {} 1273 self.__prev_row = None 1274 self.__prev_tooltip_row = None 1275 self.__prev_cell_0 = None 1276 self.__grouping_mode = u'episode' 1277 self.__filter_show_unapproved = False 1278 self.__filter_show_inactive = False 1279 1280 self.__grouping2col_labels = { 1281 u'episode': [ 1282 _('Episode'), 1283 _('Substance'), 1284 _('Dose'), 1285 _('Schedule'), 1286 _('Started'), 1287 _('Duration'), 1288 _('Brand') 1289 ], 1290 u'brand': [ 1291 _('Brand'), 1292 _('Schedule'), 1293 _('Substance'), 1294 _('Dose'), 1295 _('Started'), 1296 _('Duration'), 1297 _('Episode') 1298 ] 1299 } 1300 1301 self.__grouping2order_by_clauses = { 1302 u'episode': u'pk_health_issue nulls first, episode, substance, started', 1303 u'brand': u'brand nulls last, substance, started' 1304 } 1305 1306 self.__init_ui() 1307 self.__register_events()
1308 #------------------------------------------------------------ 1309 # external API 1310 #------------------------------------------------------------
1311 - def get_selected_cells(self):
1312 1313 sel_block_top_left = self.GetSelectionBlockTopLeft() 1314 sel_block_bottom_right = self.GetSelectionBlockBottomRight() 1315 sel_cols = self.GetSelectedCols() 1316 sel_rows = self.GetSelectedRows() 1317 1318 selected_cells = [] 1319 1320 # individually selected cells (ctrl-click) 1321 selected_cells += self.GetSelectedCells() 1322 1323 # selected rows 1324 selected_cells += list ( 1325 (row, col) 1326 for row in sel_rows 1327 for col in xrange(self.GetNumberCols()) 1328 ) 1329 1330 # selected columns 1331 selected_cells += list ( 1332 (row, col) 1333 for row in xrange(self.GetNumberRows()) 1334 for col in sel_cols 1335 ) 1336 1337 # selection blocks 1338 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()): 1339 selected_cells += [ 1340 (row, col) 1341 for row in xrange(top_left[0], bottom_right[0] + 1) 1342 for col in xrange(top_left[1], bottom_right[1] + 1) 1343 ] 1344 1345 return set(selected_cells)
1346 #------------------------------------------------------------
1347 - def get_selected_rows(self):
1348 rows = {} 1349 1350 for row, col in self.get_selected_cells(): 1351 rows[row] = True 1352 1353 return rows.keys()
1354 #------------------------------------------------------------
1355 - def get_selected_data(self):
1356 return [ self.__row_data[row] for row in self.get_selected_rows() ]
1357 #------------------------------------------------------------
1358 - def repopulate_grid(self):
1359 1360 self.empty_grid() 1361 1362 if self.__patient is None: 1363 return 1364 1365 emr = self.__patient.get_emr() 1366 meds = emr.get_current_substance_intake ( 1367 order_by = self.__grouping2order_by_clauses[self.__grouping_mode], 1368 include_unapproved = self.__filter_show_unapproved, 1369 include_inactive = self.__filter_show_inactive 1370 ) 1371 if not meds: 1372 return 1373 1374 self.BeginBatch() 1375 1376 # columns 1377 labels = self.__grouping2col_labels[self.__grouping_mode] 1378 if self.__filter_show_unapproved: 1379 self.AppendCols(numCols = len(labels) + 1) 1380 else: 1381 self.AppendCols(numCols = len(labels)) 1382 for col_idx in range(len(labels)): 1383 self.SetColLabelValue(col_idx, labels[col_idx]) 1384 if self.__filter_show_unapproved: 1385 self.SetColLabelValue(len(labels), u'OK?') 1386 self.SetColSize(len(labels), 40) 1387 1388 self.AppendRows(numRows = len(meds)) 1389 1390 # loop over data 1391 for row_idx in range(len(meds)): 1392 med = meds[row_idx] 1393 self.__row_data[row_idx] = med 1394 1395 if med['is_currently_active'] is True: 1396 atcs = [] 1397 if med['atc_substance'] is not None: 1398 atcs.append(med['atc_substance']) 1399 # if med['atc_brand'] is not None: 1400 # atcs.append(med['atc_brand']) 1401 # allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],), brand = med['brand']) 1402 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],)) 1403 if allg not in [None, False]: 1404 attr = self.GetOrCreateCellAttr(row_idx, 0) 1405 if allg['type'] == u'allergy': 1406 attr.SetTextColour('red') 1407 else: 1408 attr.SetTextColour('yellow') 1409 self.SetRowAttr(row_idx, attr) 1410 else: 1411 attr = self.GetOrCreateCellAttr(row_idx, 0) 1412 attr.SetTextColour('grey') 1413 self.SetRowAttr(row_idx, attr) 1414 1415 if self.__grouping_mode == u'episode': 1416 if med['pk_episode'] is None: 1417 self.__prev_cell_0 = None 1418 self.SetCellValue(row_idx, 0, gmTools.u_diameter) 1419 else: 1420 if self.__prev_cell_0 != med['episode']: 1421 self.__prev_cell_0 = med['episode'] 1422 self.SetCellValue(row_idx, 0, gmTools.coalesce(med['episode'], u'')) 1423 1424 self.SetCellValue(row_idx, 1, med['substance']) 1425 self.SetCellValue(row_idx, 2, gmTools.coalesce(med['strength'], u'')) 1426 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], u'')) 1427 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d')) 1428 1429 if med['is_long_term']: 1430 self.SetCellValue(row_idx, 5, gmTools.u_infinity) 1431 else: 1432 if med['duration'] is None: 1433 self.SetCellValue(row_idx, 5, u'') 1434 else: 1435 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days)) 1436 1437 if med['pk_brand'] is None: 1438 self.SetCellValue(row_idx, 6, gmTools.coalesce(med['brand'], u'')) 1439 else: 1440 if med['fake_brand']: 1441 self.SetCellValue(row_idx, 6, gmTools.coalesce(med['brand'], u'', _('%s (fake)'))) 1442 else: 1443 self.SetCellValue(row_idx, 6, gmTools.coalesce(med['brand'], u'')) 1444 1445 elif self.__grouping_mode == u'brand': 1446 1447 if med['pk_brand'] is None: 1448 self.__prev_cell_0 = None 1449 self.SetCellValue(row_idx, 0, gmTools.u_diameter) 1450 else: 1451 if self.__prev_cell_0 != med['brand']: 1452 self.__prev_cell_0 = med['brand'] 1453 if med['fake_brand']: 1454 self.SetCellValue(row_idx, 0, gmTools.coalesce(med['brand'], u'', _('%s (fake)'))) 1455 else: 1456 self.SetCellValue(row_idx, 0, gmTools.coalesce(med['brand'], u'')) 1457 1458 self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], u'')) 1459 self.SetCellValue(row_idx, 2, med['substance']) 1460 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['strength'], u'')) 1461 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d')) 1462 1463 if med['is_long_term']: 1464 self.SetCellValue(row_idx, 5, gmTools.u_infinity) 1465 else: 1466 if med['duration'] is None: 1467 self.SetCellValue(row_idx, 5, u'') 1468 else: 1469 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days)) 1470 1471 if med['pk_episode'] is None: 1472 self.SetCellValue(row_idx, 6, u'') 1473 else: 1474 self.SetCellValue(row_idx, 6, gmTools.coalesce(med['episode'], u'')) 1475 1476 else: 1477 raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode) 1478 1479 if self.__filter_show_unapproved: 1480 self.SetCellValue ( 1481 row_idx, 1482 len(labels), 1483 gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, u'', u'?') 1484 ) 1485 1486 #self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE) 1487 1488 self.EndBatch()
1489 #------------------------------------------------------------
1490 - def empty_grid(self):
1491 self.BeginBatch() 1492 self.ClearGrid() 1493 # Windows cannot do "nothing", it rather decides to assert() 1494 # on thinking it is supposed to do nothing 1495 if self.GetNumberRows() > 0: 1496 self.DeleteRows(pos = 0, numRows = self.GetNumberRows()) 1497 if self.GetNumberCols() > 0: 1498 self.DeleteCols(pos = 0, numCols = self.GetNumberCols()) 1499 self.EndBatch() 1500 self.__row_data = {} 1501 self.__prev_cell_0 = None
1502 #------------------------------------------------------------
1503 - def show_info_on_entry(self):
1504 1505 if len(self.__row_data) == 0: 1506 return 1507 1508 sel_rows = self.get_selected_rows() 1509 if len(sel_rows) != 1: 1510 return 1511 1512 drug_db = get_drug_database() 1513 if drug_db is None: 1514 return 1515 1516 drug_db.show_info_on_substance(substance = self.get_selected_data()[0])
1517 #------------------------------------------------------------
1519 1520 if len(self.__row_data) == 0: 1521 return 1522 1523 sel_rows = self.get_selected_rows() 1524 1525 if len(sel_rows) != 1: 1526 return 1527 1528 webbrowser.open ( 1529 url = gmMedication.drug2renal_insufficiency_url(search_term = self.get_selected_data()[0]), 1530 new = False, 1531 autoraise = True 1532 )
1533 #------------------------------------------------------------
1534 - def report_ADR(self):
1535 1536 dbcfg = gmCfg.cCfgSQL() 1537 1538 url = dbcfg.get2 ( 1539 option = u'external.urls.report_ADR', 1540 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1541 bias = u'user', 1542 default = u'https://dcgma.org/uaw/meldung.php' # http://www.akdae.de/Arzneimittelsicherheit/UAW-Meldung/UAW-Meldung-online.html 1543 ) 1544 1545 webbrowser.open(url = url, new = False, autoraise = True)
1546 #------------------------------------------------------------
1547 - def check_interactions(self):
1548 1549 if len(self.__row_data) == 0: 1550 return 1551 1552 drug_db = get_drug_database() 1553 if drug_db is None: 1554 return 1555 1556 if len(self.get_selected_rows()) > 1: 1557 drug_db.check_drug_interactions(substances = self.get_selected_data()) 1558 else: 1559 drug_db.check_drug_interactions(substances = self.__row_data.values())
1560 #------------------------------------------------------------
1561 - def add_substance(self):
1562 edit_intake_of_substance(parent = self, substance = None)
1563 #------------------------------------------------------------
1564 - def edit_substance(self):
1565 1566 rows = self.get_selected_rows() 1567 1568 if len(rows) == 0: 1569 return 1570 1571 if len(rows) > 1: 1572 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit more than one substance at once.'), beep = True) 1573 return 1574 1575 subst = self.get_selected_data()[0] 1576 edit_intake_of_substance(parent = self, substance = subst)
1577 #------------------------------------------------------------
1578 - def delete_substance(self):
1579 1580 rows = self.get_selected_rows() 1581 1582 if len(rows) == 0: 1583 return 1584 1585 if len(rows) > 1: 1586 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete more than one substance at once.'), beep = True) 1587 return 1588 1589 subst = self.get_selected_data()[0] 1590 delete_substance_intake(parent = self, substance = subst['pk_substance_intake'])
1591 #------------------------------------------------------------
1593 rows = self.get_selected_rows() 1594 1595 if len(rows) == 0: 1596 return 1597 1598 if len(rows) > 1: 1599 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create allergy from more than one substance at once.'), beep = True) 1600 return 1601 1602 subst = self.get_selected_data()[0] 1603 if subst['is_currently_active']: 1604 subst['discontinued'] = gmDateTime.pydt_now_here() 1605 if subst['discontinue_reason'] is None: 1606 subst['discontinue_reason'] = _('discontinued due to allergy or intolerance') 1607 subst.save() 1608 1609 emr = self.__patient.get_emr() 1610 allg = subst.turn_into_allergy(encounter_id = emr.active_encounter['pk_encounter']) 1611 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent = self, id = -1) 1612 dlg.ShowModal()
1613 #------------------------------------------------------------
1614 - def print_medication_list(self):
1615 # there could be some filtering/user interaction going on here 1616 _cfg = gmCfg2.gmCfgData() 1617 print_medication_list(parent = self, cleanup = _cfg.get(option = 'debug'))
1618 #------------------------------------------------------------
1619 - def get_row_tooltip(self, row=None):
1620 1621 try: 1622 entry = self.__row_data[row] 1623 except KeyError: 1624 return u' ' 1625 1626 emr = self.__patient.get_emr() 1627 atcs = [] 1628 if entry['atc_substance'] is not None: 1629 atcs.append(entry['atc_substance']) 1630 # if entry['atc_brand'] is not None: 1631 # atcs.append(entry['atc_brand']) 1632 # allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],), brand = entry['brand']) 1633 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (entry['substance'],)) 1634 1635 tt = _('Substance intake entry (%s, %s) [#%s] \n') % ( 1636 gmTools.bool2subst ( 1637 boolean = entry['is_currently_active'], 1638 true_return = gmTools.bool2subst ( 1639 boolean = entry['seems_inactive'], 1640 true_return = _('active, needs check'), 1641 false_return = _('active'), 1642 none_return = _('assumed active') 1643 ), 1644 false_return = _('inactive') 1645 ), 1646 gmTools.bool2subst ( 1647 boolean = entry['intake_is_approved_of'], 1648 true_return = _('approved'), 1649 false_return = _('unapproved') 1650 ), 1651 entry['pk_substance_intake'] 1652 ) 1653 1654 if allg not in [None, False]: 1655 certainty = gmTools.bool2subst(allg['definite'], _('definite'), _('suspected')) 1656 tt += u'\n' 1657 tt += u' !! ---- Cave ---- !!\n' 1658 tt += u' %s (%s): %s (%s)\n' % ( 1659 allg['l10n_type'], 1660 certainty, 1661 allg['descriptor'], 1662 gmTools.coalesce(allg['reaction'], u'')[:40] 1663 ) 1664 tt += u'\n' 1665 1666 tt += u' ' + _('Substance: %s [#%s]\n') % (entry['substance'], entry['pk_substance']) 1667 tt += u' ' + _('Preparation: %s\n') % entry['preparation'] 1668 if entry['strength'] is not None: 1669 tt += u' ' + _('Amount per dose: %s') % entry['strength'] 1670 if entry.ddd is not None: 1671 tt += u' (DDD: %s %s)' % (entry.ddd['ddd'], entry.ddd['unit']) 1672 tt += u'\n' 1673 else: 1674 if entry.ddd is not None: 1675 tt += u' DDD: %s %s' % (entry.ddd['ddd'], entry.ddd['unit']) 1676 tt += u'\n' 1677 tt += gmTools.coalesce(entry['atc_substance'], u'', _(' ATC (substance): %s\n')) 1678 1679 tt += u'\n' 1680 1681 tt += gmTools.coalesce ( 1682 entry['brand'], 1683 u'', 1684 _(' Brand name: %%s [#%s]\n') % entry['pk_brand'] 1685 ) 1686 tt += gmTools.coalesce(entry['atc_brand'], u'', _(' ATC (brand): %s\n')) 1687 1688 tt += u'\n' 1689 1690 tt += gmTools.coalesce(entry['schedule'], u'', _(' Regimen: %s\n')) 1691 1692 if entry['is_long_term']: 1693 duration = u' %s %s' % (gmTools.u_right_arrow, gmTools.u_infinity) 1694 else: 1695 if entry['duration'] is None: 1696 duration = u'' 1697 else: 1698 duration = u' %s %s' % (gmTools.u_right_arrow, gmDateTime.format_interval(entry['duration'], gmDateTime.acc_days)) 1699 1700 tt += _(' Started %s%s%s\n') % ( 1701 entry['started'].strftime('%Y %B %d').decode(gmI18N.get_encoding()), 1702 duration, 1703 gmTools.bool2subst(entry['is_long_term'], _(' (long-term)'), _(' (short-term)'), u'') 1704 ) 1705 1706 if entry['discontinued'] is not None: 1707 tt += _(' Discontinued %s\n') % ( 1708 entry['discontinued'].strftime('%Y %B %d').decode(gmI18N.get_encoding()), 1709 ) 1710 tt += _(' Reason: %s\n') % entry['discontinue_reason'] 1711 1712 tt += u'\n' 1713 1714 tt += gmTools.coalesce(entry['aim'], u'', _(' Aim: %s\n')) 1715 tt += gmTools.coalesce(entry['episode'], u'', _(' Episode: %s\n')) 1716 tt += gmTools.coalesce(entry['notes'], u'', _(' Advice: %s\n')) 1717 1718 tt += u'\n' 1719 1720 tt += _(u'Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % ({ 1721 'row_ver': entry['row_version'], 1722 'mod_when': entry['modified_when'].strftime('%c').decode(gmI18N.get_encoding()), 1723 'mod_by': entry['modified_by'] 1724 }) 1725 1726 return tt
1727 #------------------------------------------------------------ 1728 # internal helpers 1729 #------------------------------------------------------------
1730 - def __init_ui(self):
1731 self.CreateGrid(0, 1) 1732 self.EnableEditing(0) 1733 self.EnableDragGridSize(1) 1734 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows) 1735 1736 self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER) 1737 1738 self.SetRowLabelSize(0) 1739 self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
1740 #------------------------------------------------------------ 1741 # properties 1742 #------------------------------------------------------------
1743 - def _get_patient(self):
1744 return self.__patient
1745
1746 - def _set_patient(self, patient):
1747 self.__patient = patient 1748 self.repopulate_grid()
1749 1750 patient = property(_get_patient, _set_patient) 1751 #------------------------------------------------------------
1752 - def _get_grouping_mode(self):
1753 return self.__grouping_mode
1754
1755 - def _set_grouping_mode(self, mode):
1756 self.__grouping_mode = mode 1757 self.repopulate_grid()
1758 1759 grouping_mode = property(_get_grouping_mode, _set_grouping_mode) 1760 #------------------------------------------------------------
1762 return self.__filter_show_unapproved
1763
1764 - def _set_filter_show_unapproved(self, val):
1765 self.__filter_show_unapproved = val 1766 self.repopulate_grid()
1767 1768 filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved) 1769 #------------------------------------------------------------
1770 - def _get_filter_show_inactive(self):
1771 return self.__filter_show_inactive
1772
1773 - def _set_filter_show_inactive(self, val):
1774 self.__filter_show_inactive = val 1775 self.repopulate_grid()
1776 1777 filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive) 1778 #------------------------------------------------------------ 1779 # event handling 1780 #------------------------------------------------------------
1781 - def __register_events(self):
1782 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow 1783 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells) 1784 #self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels) 1785 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels) 1786 1787 # editing cells 1788 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
1789 #------------------------------------------------------------
1790 - def __on_mouse_over_cells(self, evt):
1791 """Calculate where the mouse is and set the tooltip dynamically.""" 1792 1793 # Use CalcUnscrolledPosition() to get the mouse position within the 1794 # entire grid including what's offscreen 1795 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 1796 1797 # use this logic to prevent tooltips outside the actual cells 1798 # apply to GetRowSize, too 1799 # tot = 0 1800 # for col in xrange(self.NumberCols): 1801 # tot += self.GetColSize(col) 1802 # if xpos <= tot: 1803 # self.tool_tip.Tip = 'Tool tip for Column %s' % ( 1804 # self.GetColLabelValue(col)) 1805 # break 1806 # else: # mouse is in label area beyond the right-most column 1807 # self.tool_tip.Tip = '' 1808 1809 row, col = self.XYToCell(x, y) 1810 1811 if row == self.__prev_tooltip_row: 1812 return 1813 1814 self.__prev_tooltip_row = row 1815 1816 try: 1817 evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row)) 1818 except KeyError: 1819 pass
1820 #------------------------------------------------------------
1821 - def __on_cell_left_dclicked(self, evt):
1822 row = evt.GetRow() 1823 data = self.__row_data[row] 1824 edit_intake_of_substance(parent = self, substance = data)
1825 #============================================================ 1826 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl 1827
1828 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
1829 1830 """Panel holding a grid with current substances. Used as notebook page.""" 1831
1832 - def __init__(self, *args, **kwargs):
1833 1834 wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl.__init__(self, *args, **kwargs) 1835 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1836 1837 self.__register_interests()
1838 #----------------------------------------------------- 1839 # reget-on-paint mixin API 1840 #-----------------------------------------------------
1841 - def _populate_with_data(self):
1842 """Populate cells with data from model.""" 1843 pat = gmPerson.gmCurrentPatient() 1844 if pat.connected: 1845 self._grid_substances.patient = pat 1846 else: 1847 self._grid_substances.patient = None 1848 return True
1849 #-------------------------------------------------------- 1850 # event handling 1851 #--------------------------------------------------------
1852 - def __register_interests(self):
1853 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1854 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_data_reget) 1855 gmDispatcher.connect(signal = u'substance_intake_mod_db', receiver = self._schedule_data_reget)
1856 # active_substance_mod_db 1857 # substance_brand_mod_db 1858 #--------------------------------------------------------
1859 - def _on_pre_patient_selection(self):
1860 wx.CallAfter(self.__on_pre_patient_selection)
1861 #--------------------------------------------------------
1862 - def __on_pre_patient_selection(self):
1863 self._grid_substances.patient = None
1864 #--------------------------------------------------------
1865 - def _on_add_button_pressed(self, event):
1866 self._grid_substances.add_substance()
1867 #--------------------------------------------------------
1868 - def _on_edit_button_pressed(self, event):
1869 self._grid_substances.edit_substance()
1870 #--------------------------------------------------------
1871 - def _on_delete_button_pressed(self, event):
1872 self._grid_substances.delete_substance()
1873 #--------------------------------------------------------
1874 - def _on_info_button_pressed(self, event):
1875 self._grid_substances.show_info_on_entry()
1876 #--------------------------------------------------------
1877 - def _on_interactions_button_pressed(self, event):
1878 self._grid_substances.check_interactions()
1879 #--------------------------------------------------------
1880 - def _on_episode_grouping_selected(self, event):
1881 self._grid_substances.grouping_mode = 'episode'
1882 #--------------------------------------------------------
1883 - def _on_brand_grouping_selected(self, event):
1884 self._grid_substances.grouping_mode = 'brand'
1885 #--------------------------------------------------------
1886 - def _on_show_unapproved_checked(self, event):
1887 self._grid_substances.filter_show_unapproved = self._CHBOX_show_unapproved.GetValue()
1888 #--------------------------------------------------------
1889 - def _on_show_inactive_checked(self, event):
1890 self._grid_substances.filter_show_inactive = self._CHBOX_show_inactive.GetValue()
1891 #--------------------------------------------------------
1892 - def _on_print_button_pressed(self, event):
1893 self._grid_substances.print_medication_list()
1894 #--------------------------------------------------------
1895 - def _on_allergy_button_pressed(self, event):
1896 self._grid_substances.create_allergy_from_substance()
1897 #--------------------------------------------------------
1898 - def _on_button_kidneys_pressed(self, event):
1899 self._grid_substances.show_renal_insufficiency_info()
1900 #--------------------------------------------------------
1901 - def _on_adr_button_pressed(self, event):
1902 self._grid_substances.report_ADR()
1903 #============================================================ 1904 # main 1905 #------------------------------------------------------------ 1906 if __name__ == '__main__': 1907 1908 if len(sys.argv) < 2: 1909 sys.exit() 1910 1911 if sys.argv[1] != 'test': 1912 sys.exit() 1913 1914 from Gnumed.pycommon import gmI18N 1915 1916 gmI18N.activate_locale() 1917 gmI18N.install_domain(domain = 'gnumed') 1918 1919 #---------------------------------------- 1920 app = wx.PyWidgetTester(size = (600, 600)) 1921 app.SetWidget(cATCPhraseWheel, -1) 1922 app.MainLoop() 1923 1924 #============================================================ 1925