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

Source Code for Module Gnumed.wxpython.gmMacro

   1  """GNUmed macro primitives. 
   2   
   3  This module implements functions a macro can legally use. 
   4  """ 
   5  #===================================================================== 
   6  __version__ = "$Revision: 1.51 $" 
   7  __author__ = "K.Hilbert <karsten.hilbert@gmx.net>" 
   8   
   9  import sys, time, random, types, logging 
  10   
  11   
  12  import wx 
  13   
  14   
  15  if __name__ == '__main__': 
  16          sys.path.insert(0, '../../') 
  17  from Gnumed.pycommon import gmI18N 
  18  if __name__ == '__main__': 
  19          gmI18N.activate_locale() 
  20          gmI18N.install_domain() 
  21  from Gnumed.pycommon import gmGuiBroker 
  22  from Gnumed.pycommon import gmTools 
  23  from Gnumed.pycommon import gmBorg 
  24  from Gnumed.pycommon import gmExceptions 
  25  from Gnumed.pycommon import gmCfg2 
  26  from Gnumed.pycommon import gmDateTime 
  27   
  28  from Gnumed.business import gmPerson, gmDemographicRecord, gmMedication, gmPathLab, gmPersonSearch 
  29  from Gnumed.business import gmVaccination, gmPersonSearch 
  30   
  31  from Gnumed.wxpython import gmGuiHelpers 
  32  from Gnumed.wxpython import gmNarrativeWidgets 
  33  from Gnumed.wxpython import gmPatSearchWidgets 
  34  from Gnumed.wxpython import gmPlugin 
  35  from Gnumed.wxpython import gmEMRStructWidgets 
  36   
  37   
  38  _log = logging.getLogger('gm.scripting') 
  39  _cfg = gmCfg2.gmCfgData() 
  40   
  41  #===================================================================== 
  42  known_placeholders = [ 
  43          'lastname', 
  44          'firstname', 
  45          'title', 
  46          'date_of_birth', 
  47          'progress_notes', 
  48          'soap', 
  49          'soap_s', 
  50          'soap_o', 
  51          'soap_a', 
  52          'soap_p', 
  53          u'client_version', 
  54          u'current_provider', 
  55          u'allergy_state' 
  56  ] 
  57   
  58   
  59  # those must satisfy the pattern "$name::args::optional length$" when used 
  60  known_variant_placeholders = [ 
  61          u'soap', 
  62          u'progress_notes',                      # "data" holds: categories//template 
  63                                                                  #       categories: string with "soap ", " " == None == admin 
  64                                                                  #       template:       u'something %s something'               (do not include // in template !) 
  65          u'emr_journal',                         # "data" format:   <categories>//<template>//<line length>//<time range>//<target format> 
  66                                                                  #       categories:        string with any of "s", "o", "a", "p", " "; 
  67                                                                  #                                  (" " == None == admin category) 
  68                                                                  #       template:          something %s something else 
  69                                                                  #                                  (Do not include // in the template !) 
  70                                                                  #       line length:   the length of individual lines, not the total placeholder length 
  71                                                                  #       time range:        the number of weeks going back in time 
  72                                                                  #       target format: "tex" or anything else, if "tex", data will be tex-escaped 
  73          u'date_of_birth', 
  74          u'adr_street',                          # "data" holds: type of address 
  75          u'adr_number', 
  76          u'adr_location', 
  77          u'adr_postcode', 
  78          u'gender_mapper',                       # "data" holds: value for male // value for female 
  79          u'current_meds',                        # "data" holds: line template 
  80          u'current_meds_table',          # "data" holds: format, options 
  81          u'current_meds_notes',          # "data" holds: format, options 
  82          u'lab_table',                           # "data" holds: format (currently "latex" only) 
  83          u'latest_vaccs_table',          # "data" holds: format, options 
  84          u'today',                                       # "data" holds: strftime format 
  85          u'tex_escape',                          # "data" holds: string to escape 
  86          u'allergies',                           # "data" holds: line template, one allergy per line 
  87          u'allergy_list',                        # "data" holds: template per allergy, allergies on one line 
  88          u'problems',                            # "data" holds: line template, one problem per line 
  89          u'name',                                        # "data" holds: template for name parts arrangement 
  90          u'free_text',                           # show a dialog for entering some free text 
  91          u'soap_for_encounters'          # "data" holds: soap cats // strftime date format 
  92  ] 
  93   
  94  default_placeholder_regex = r'\$<.+?>\$'                                # this one works (except that OOo cannot be non-greedy |-( ) 
  95   
  96  #_regex_parts = [ 
  97  #       r'\$<\w+::.*(?::)\d+>\$', 
  98  #       r'\$<\w+::.+(?!>\$)>\$', 
  99  #       r'\$<\w+?>\$' 
 100  #] 
 101  #default_placeholder_regex = r'|'.join(_regex_parts) 
 102   
 103  default_placeholder_start = u'$<' 
 104  default_placeholder_end = u'>$' 
 105  #===================================================================== 
106 -class gmPlaceholderHandler(gmBorg.cBorg):
107 """Replaces placeholders in forms, fields, etc. 108 109 - patient related placeholders operate on the currently active patient 110 - is passed to the forms handling code, for example 111 112 Note that this cannot be called from a non-gui thread unless 113 wrapped in wx.CallAfter. 114 115 There are currently three types of placeholders: 116 117 simple static placeholders 118 - those are listed in known_placeholders 119 - they are used as-is 120 121 extended static placeholders 122 - those are like the static ones but have "::::<NUMBER>" appended 123 where <NUMBER> is the maximum length 124 125 variant placeholders 126 - those are listed in known_variant_placeholders 127 - they are parsed into placeholder, data, and maximum length 128 - the length is optional 129 - data is passed to the handler 130 """
131 - def __init__(self, *args, **kwargs):
132 133 self.pat = gmPerson.gmCurrentPatient() 134 self.debug = False 135 136 self.invalid_placeholder_template = _('invalid placeholder [%s]')
137 #-------------------------------------------------------- 138 # __getitem__ API 139 #--------------------------------------------------------
140 - def __getitem__(self, placeholder):
141 """Map self['placeholder'] to self.placeholder. 142 143 This is useful for replacing placeholders parsed out 144 of documents as strings. 145 146 Unknown/invalid placeholders still deliver a result but 147 it will be glaringly obvious if debugging is enabled. 148 """ 149 _log.debug('replacing [%s]', placeholder) 150 151 original_placeholder = placeholder 152 153 if placeholder.startswith(default_placeholder_start): 154 placeholder = placeholder[len(default_placeholder_start):] 155 if placeholder.endswith(default_placeholder_end): 156 placeholder = placeholder[:-len(default_placeholder_end)] 157 else: 158 _log.debug('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end) 159 if self.debug: 160 return self.invalid_placeholder_template % original_placeholder 161 return None 162 163 # simple static placeholder ? 164 if placeholder in known_placeholders: 165 return getattr(self, placeholder) 166 167 # extended static placeholder ? 168 parts = placeholder.split('::::', 1) 169 if len(parts) == 2: 170 name, lng = parts 171 try: 172 return getattr(self, name)[:int(lng)] 173 except: 174 _log.exception('placeholder handling error: %s', original_placeholder) 175 if self.debug: 176 return self.invalid_placeholder_template % original_placeholder 177 return None 178 179 # variable placeholders 180 parts = placeholder.split('::') 181 if len(parts) == 2: 182 name, data = parts 183 lng = None 184 if len(parts) == 3: 185 name, data, lng = parts 186 try: 187 lng = int(lng) 188 except: 189 _log.exception('placeholder length definition error: %s, discarding length', original_placeholder) 190 lng = None 191 if len(parts) > 3: 192 _log.warning('invalid placeholder layout: %s', original_placeholder) 193 if self.debug: 194 return self.invalid_placeholder_template % original_placeholder 195 return None 196 197 handler = getattr(self, '_get_variant_%s' % name, None) 198 if handler is None: 199 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder) 200 if self.debug: 201 return self.invalid_placeholder_template % original_placeholder 202 return None 203 204 try: 205 if lng is None: 206 return handler(data = data) 207 return handler(data = data)[:lng] 208 except: 209 _log.exception('placeholder handling error: %s', original_placeholder) 210 if self.debug: 211 return self.invalid_placeholder_template % original_placeholder 212 return None 213 214 _log.error('something went wrong, should never get here') 215 return None
216 #-------------------------------------------------------- 217 # properties actually handling placeholders 218 #-------------------------------------------------------- 219 # property helpers 220 #--------------------------------------------------------
221 - def _setter_noop(self, val):
222 """This does nothing, used as a NOOP properties setter.""" 223 pass
224 #--------------------------------------------------------
225 - def _get_lastname(self):
226 return self.pat.get_active_name()['lastnames']
227 #--------------------------------------------------------
228 - def _get_firstname(self):
229 return self.pat.get_active_name()['firstnames']
230 #--------------------------------------------------------
231 - def _get_title(self):
232 return gmTools.coalesce(self.pat.get_active_name()['title'], u'')
233 #--------------------------------------------------------
234 - def _get_dob(self):
235 return self._get_variant_date_of_birth(data='%x')
236 #--------------------------------------------------------
237 - def _get_progress_notes(self):
238 return self._get_variant_soap()
239 #--------------------------------------------------------
240 - def _get_soap_s(self):
241 return self._get_variant_soap(data = u's')
242 #--------------------------------------------------------
243 - def _get_soap_o(self):
244 return self._get_variant_soap(data = u'o')
245 #--------------------------------------------------------
246 - def _get_soap_a(self):
247 return self._get_variant_soap(data = u'a')
248 #--------------------------------------------------------
249 - def _get_soap_p(self):
250 return self._get_variant_soap(data = u'p')
251 #--------------------------------------------------------
252 - def _get_soap_admin(self):
253 return self._get_variant_soap(soap_cats = None)
254 #--------------------------------------------------------
255 - def _get_client_version(self):
256 return gmTools.coalesce ( 257 _cfg.get(option = u'client_version'), 258 u'%s' % self.__class__.__name__ 259 )
260 #--------------------------------------------------------
261 - def _get_current_provider(self):
262 prov = gmPerson.gmCurrentProvider() 263 264 title = gmTools.coalesce ( 265 prov['title'], 266 gmPerson.map_gender2salutation(prov['gender']) 267 ) 268 269 tmp = u'%s %s. %s' % ( 270 title, 271 prov['firstnames'][:1], 272 prov['lastnames'] 273 ) 274 275 return tmp
276 #--------------------------------------------------------
277 - def _get_allergy_state(self):
278 allg_state = self.pat.get_emr().allergy_state 279 280 if allg_state['last_confirmed'] is None: 281 date_confirmed = u'' 282 else: 283 date_confirmed = u' (%s)' % allg_state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding()) 284 285 tmp = u'%s%s' % ( 286 allg_state.state_string, 287 date_confirmed 288 ) 289 return tmp
290 #-------------------------------------------------------- 291 # property definitions for static placeholders 292 #-------------------------------------------------------- 293 placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop) 294 295 # placeholders 296 lastname = property(_get_lastname, _setter_noop) 297 firstname = property(_get_firstname, _setter_noop) 298 title = property(_get_title, _setter_noop) 299 date_of_birth = property(_get_dob, _setter_noop) 300 301 progress_notes = property(_get_progress_notes, _setter_noop) 302 soap = property(_get_progress_notes, _setter_noop) 303 soap_s = property(_get_soap_s, _setter_noop) 304 soap_o = property(_get_soap_o, _setter_noop) 305 soap_a = property(_get_soap_a, _setter_noop) 306 soap_p = property(_get_soap_p, _setter_noop) 307 soap_admin = property(_get_soap_admin, _setter_noop) 308 309 allergy_state = property(_get_allergy_state, _setter_noop) 310 311 client_version = property(_get_client_version, _setter_noop) 312 313 current_provider = property(_get_current_provider, _setter_noop) 314 #-------------------------------------------------------- 315 # variant handlers 316 #--------------------------------------------------------
317 - def _get_variant_soap_for_encounters(self, data=None):
318 """Select encounters from list and format SOAP thereof. 319 320 data: soap_cats (' ' -> None -> admin) // date format 321 """ 322 # defaults 323 cats = None 324 date_format = None 325 326 if data is not None: 327 data_parts = data.split('//') 328 329 # part[0]: categories 330 if len(data_parts[0]) > 0: 331 cats = [] 332 if u' ' in data_parts[0]: 333 cats.append(None) 334 data_parts[0] = data_parts[0].replace(u' ', u'') 335 cats.extend(list(data_parts[0])) 336 337 # part[1]: date format 338 if len(data_parts) > 0: 339 if len(data_parts[1]) > 0: 340 date_format = data_parts[1] 341 342 encounters = gmEMRStructWidgets.select_encounters(single_selection = False) 343 344 chunks = [] 345 for enc in encounters: 346 chunks.append(enc.format_latex(date_format = date_format, soap_cats = cats)) 347 348 return u''.join(chunks)
349 #--------------------------------------------------------
350 - def _get_variant_emr_journal(self, data=None):
351 # default: all categories, neutral template 352 cats = list(u'soap') 353 cats.append(None) 354 template = u'%s' 355 interactive = True 356 line_length = 9999 357 target_format = None 358 time_range = None 359 360 if data is not None: 361 data_parts = data.split('//') 362 363 # part[0]: categories 364 cats = [] 365 # ' ' -> None == admin 366 for c in list(data_parts[0]): 367 if c == u' ': 368 c = None 369 cats.append(c) 370 # '' -> SOAP + None 371 if cats == u'': 372 cats = list(u'soap').append(None) 373 374 # part[1]: template 375 if len(data_parts) > 0: 376 template = data_parts[1] 377 378 # part[2]: line length 379 if len(data_parts) > 1: 380 try: 381 line_length = int(data_parts[2]) 382 except: 383 line_length = 9999 384 385 # part[3]: weeks going back in time 386 if len(data_parts) > 2: 387 try: 388 time_range = 7 * int(data_parts[3]) 389 except: 390 time_range = None 391 392 # part[4]: output format 393 if len(data_parts) > 3: 394 target_format = data_parts[4] 395 396 # FIXME: will need to be a generator later on 397 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range) 398 399 if len(narr) == 0: 400 return u'' 401 402 if target_format == u'tex': 403 keys = narr[0].keys() 404 lines = [] 405 line_dict = {} 406 for n in narr: 407 for key in keys: 408 if isinstance(n[key], basestring): 409 line_dict[key] = gmTools.tex_escape_string(text = n[key]) 410 continue 411 line_dict[key] = n[key] 412 try: 413 lines.append((template % line_dict)[:line_length]) 414 except KeyError: 415 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys)) 416 else: 417 try: 418 lines = [ (template % n)[:line_length] for n in narr ] 419 except KeyError: 420 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys())) 421 422 return u'\n'.join(lines)
423 #--------------------------------------------------------
424 - def _get_variant_progress_notes(self, data=None):
425 return self._get_variant_soap(data=data)
426 #--------------------------------------------------------
427 - def _get_variant_soap(self, data=None):
428 429 # default: all categories, neutral template 430 cats = list(u'soap') 431 cats.append(None) 432 template = u'%s' 433 434 if data is not None: 435 data_parts = data.split('//') 436 437 # part[0]: categories 438 cats = [] 439 # ' ' -> None == admin 440 for cat in list(data_parts[0]): 441 if cat == u' ': 442 cat = None 443 cats.append(cat) 444 # '' -> SOAP + None 445 if cats == u'': 446 cats = list(u'soap') 447 cats.append(None) 448 449 # part[1]: template 450 if len(data_parts) > 0: 451 template = data_parts[1] 452 453 #narr = gmNarrativeWidgets.select_narrative_from_episodes_new(soap_cats = cats) 454 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats) 455 456 if narr is None: 457 return u'' 458 459 if len(narr) == 0: 460 return u'' 461 462 try: 463 narr = [ template % n['narrative'] for n in narr ] 464 except KeyError: 465 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys())) 466 467 return u'\n'.join(narr)
468 #--------------------------------------------------------
469 - def _get_variant_name(self, data=None):
470 if data is None: 471 return [_('template is missing')] 472 473 name = self.pat.get_active_name() 474 475 parts = { 476 'title': gmTools.coalesce(name['title'], u''), 477 'firstnames': name['firstnames'], 478 'lastnames': name['lastnames'], 479 'preferred': gmTools.coalesce ( 480 initial = name['preferred'], 481 instead = u' ', 482 template_initial = u' "%s" ' 483 ) 484 } 485 486 return data % parts
487 #--------------------------------------------------------
488 - def _get_variant_date_of_birth(self, data='%x'):
489 return self.pat.get_formatted_dob(format = str(data), encoding = gmI18N.get_encoding())
490 #-------------------------------------------------------- 491 # FIXME: extend to all supported genders
492 - def _get_variant_gender_mapper(self, data='male//female//other'):
493 values = data.split('//', 2) 494 495 if len(values) == 2: 496 male_value, female_value = values 497 other_value = u'<unkown gender>' 498 elif len(values) == 3: 499 male_value, female_value, other_value = values 500 else: 501 return _('invalid gender mapping layout: [%s]') % data 502 503 if self.pat['gender'] == u'm': 504 return male_value 505 506 if self.pat['gender'] == u'f': 507 return female_value 508 509 return other_value
510 #--------------------------------------------------------
511 - def _get_variant_adr_street(self, data=u'?'):
512 # if data == u'?': 513 # types = xxxxxxxxxxx 514 adrs = self.pat.get_addresses(address_type=data) 515 if len(adrs) == 0: 516 return _('no street for address type [%s]') % data 517 return adrs[0]['street']
518 #--------------------------------------------------------
519 - def _get_variant_adr_number(self, data=u'?'):
520 adrs = self.pat.get_addresses(address_type=data) 521 if len(adrs) == 0: 522 return _('no number for address type [%s]') % data 523 return adrs[0]['number']
524 #--------------------------------------------------------
525 - def _get_variant_adr_location(self, data=u'?'):
526 adrs = self.pat.get_addresses(address_type=data) 527 if len(adrs) == 0: 528 return _('no location for address type [%s]') % data 529 return adrs[0]['urb']
530 #--------------------------------------------------------
531 - def _get_variant_adr_postcode(self, data=u'?'):
532 adrs = self.pat.get_addresses(address_type=data) 533 if len(adrs) == 0: 534 return _('no postcode for address type [%s]') % data 535 return adrs[0]['postcode']
536 #--------------------------------------------------------
537 - def _get_variant_allergy_list(self, data=None):
538 if data is None: 539 return [_('template is missing')] 540 541 template, separator = data.split('//', 2) 542 543 emr = self.pat.get_emr() 544 return separator.join([ template % a for a in emr.get_allergies() ])
545 #--------------------------------------------------------
546 - def _get_variant_allergies(self, data=None):
547 548 if data is None: 549 return [_('template is missing')] 550 551 emr = self.pat.get_emr() 552 return u'\n'.join([ data % a for a in emr.get_allergies() ])
553 #--------------------------------------------------------
554 - def _get_variant_current_meds(self, data=None):
555 556 if data is None: 557 return [_('template is missing')] 558 559 emr = self.pat.get_emr() 560 current_meds = emr.get_current_substance_intake ( 561 include_inactive = False, 562 include_unapproved = False, 563 order_by = u'brand, substance' 564 ) 565 566 # FIXME: we should be dealing with translating None to u'' here 567 568 return u'\n'.join([ data % m for m in current_meds ])
569 #--------------------------------------------------------
570 - def _get_variant_current_meds_table(self, data=None):
571 572 options = data.split('//') 573 574 if u'latex' in options: 575 return gmMedication.format_substance_intake ( 576 emr = self.pat.get_emr(), 577 output_format = u'latex', 578 table_type = u'by-brand' 579 ) 580 581 _log.error('no known current medications table formatting style in [%]', data) 582 return _('unknown current medication table formatting style')
583 #--------------------------------------------------------
584 - def _get_variant_current_meds_notes(self, data=None):
585 586 options = data.split('//') 587 588 if u'latex' in options: 589 return gmMedication.format_substance_intake_notes ( 590 emr = self.pat.get_emr(), 591 output_format = u'latex', 592 table_type = u'by-brand' 593 ) 594 595 _log.error('no known current medications notes formatting style in [%]', data) 596 return _('unknown current medication notes formatting style')
597 #--------------------------------------------------------
598 - def _get_variant_lab_table(self, data=None):
599 600 options = data.split('//') 601 602 emr = self.pat.get_emr() 603 604 if u'latex' in options: 605 return gmPathLab.format_test_results ( 606 results = emr.get_test_results_by_date(), 607 output_format = u'latex' 608 ) 609 610 _log.error('no known test results table formatting style in [%s]', data) 611 return _('unknown test results table formatting style [%s]') % data
612 #--------------------------------------------------------
613 - def _get_variant_latest_vaccs_table(self, data=None):
614 615 options = data.split('//') 616 617 emr = self.pat.get_emr() 618 619 if u'latex' in options: 620 return gmVaccination.format_latest_vaccinations(output_format = u'latex', emr = emr) 621 622 _log.error('no known vaccinations table formatting style in [%s]', data) 623 return _('unknown vaccinations table formatting style [%s]') % data
624 #--------------------------------------------------------
625 - def _get_variant_problems(self, data=None):
626 627 if data is None: 628 return [_('template is missing')] 629 630 probs = self.pat.get_emr().get_problems() 631 632 return u'\n'.join([ data % p for p in probs ])
633 #--------------------------------------------------------
634 - def _get_variant_today(self, data='%x'):
635 return gmDateTime.pydt_now_here().strftime(str(data)).decode(gmI18N.get_encoding())
636 #--------------------------------------------------------
637 - def _get_variant_tex_escape(self, data=None):
638 return gmTools.tex_escape_string(text = data)
639 #--------------------------------------------------------
640 - def _get_variant_free_text(self, data=u'tex//'):
641 # <data>: 642 # format: tex (only, currently) 643 # message: shown in input dialog, must not contain "//" or "::" 644 645 format, msg = data.split('//') 646 647 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 648 None, 649 -1, 650 title = _('Replacing <free_text> placeholder'), 651 msg = _('Below you can enter free text.\n\n [%s]') % msg 652 ) 653 dlg.enable_user_formatting = True 654 decision = dlg.ShowModal() 655 656 if decision != wx.ID_SAVE: 657 dlg.Destroy() 658 return _('Text input cancelled by user.') 659 660 text = dlg.value.strip() 661 if dlg.is_user_formatted: 662 dlg.Destroy() 663 return text 664 665 dlg.Destroy() 666 667 if format == u'tex': 668 return gmTools.tex_escape_string(text = text) 669 670 return text
671 #-------------------------------------------------------- 672 # internal helpers 673 #-------------------------------------------------------- 674 675 #=====================================================================
676 -class cMacroPrimitives:
677 """Functions a macro can legally use. 678 679 An instance of this class is passed to the GNUmed scripting 680 listener. Hence, all actions a macro can legally take must 681 be defined in this class. Thus we achieve some screening for 682 security and also thread safety handling. 683 """ 684 #-----------------------------------------------------------------
685 - def __init__(self, personality = None):
686 if personality is None: 687 raise gmExceptions.ConstructorError, 'must specify personality' 688 self.__personality = personality 689 self.__attached = 0 690 self._get_source_personality = None 691 self.__user_done = False 692 self.__user_answer = 'no answer yet' 693 self.__pat = gmPerson.gmCurrentPatient() 694 695 self.__auth_cookie = str(random.random()) 696 self.__pat_lock_cookie = str(random.random()) 697 self.__lock_after_load_cookie = str(random.random()) 698 699 _log.info('slave mode personality is [%s]', personality)
700 #----------------------------------------------------------------- 701 # public API 702 #-----------------------------------------------------------------
703 - def attach(self, personality = None):
704 if self.__attached: 705 _log.error('attach with [%s] rejected, already serving a client', personality) 706 return (0, _('attach rejected, already serving a client')) 707 if personality != self.__personality: 708 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality)) 709 return (0, _('attach to personality [%s] rejected') % personality) 710 self.__attached = 1 711 self.__auth_cookie = str(random.random()) 712 return (1, self.__auth_cookie)
713 #-----------------------------------------------------------------
714 - def detach(self, auth_cookie=None):
715 if not self.__attached: 716 return 1 717 if auth_cookie != self.__auth_cookie: 718 _log.error('rejecting detach() with cookie [%s]' % auth_cookie) 719 return 0 720 self.__attached = 0 721 return 1
722 #-----------------------------------------------------------------
723 - def force_detach(self):
724 if not self.__attached: 725 return 1 726 self.__user_done = False 727 # FIXME: use self.__sync_cookie for syncing with user interaction 728 wx.CallAfter(self._force_detach) 729 return 1
730 #-----------------------------------------------------------------
731 - def version(self):
732 ver = _cfg.get(option = u'client_version') 733 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
734 #-----------------------------------------------------------------
735 - def shutdown_gnumed(self, auth_cookie=None, forced=False):
736 """Shuts down this client instance.""" 737 if not self.__attached: 738 return 0 739 if auth_cookie != self.__auth_cookie: 740 _log.error('non-authenticated shutdown_gnumed()') 741 return 0 742 wx.CallAfter(self._shutdown_gnumed, forced) 743 return 1
744 #-----------------------------------------------------------------
745 - def raise_gnumed(self, auth_cookie = None):
746 """Raise ourselves to the top of the desktop.""" 747 if not self.__attached: 748 return 0 749 if auth_cookie != self.__auth_cookie: 750 _log.error('non-authenticated raise_gnumed()') 751 return 0 752 return "cMacroPrimitives.raise_gnumed() not implemented"
753 #-----------------------------------------------------------------
754 - def get_loaded_plugins(self, auth_cookie = None):
755 if not self.__attached: 756 return 0 757 if auth_cookie != self.__auth_cookie: 758 _log.error('non-authenticated get_loaded_plugins()') 759 return 0 760 gb = gmGuiBroker.GuiBroker() 761 return gb['horstspace.notebook.gui'].keys()
762 #-----------------------------------------------------------------
763 - def raise_notebook_plugin(self, auth_cookie = None, a_plugin = None):
764 """Raise a notebook plugin within GNUmed.""" 765 if not self.__attached: 766 return 0 767 if auth_cookie != self.__auth_cookie: 768 _log.error('non-authenticated raise_notebook_plugin()') 769 return 0 770 # FIXME: use semaphore 771 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin) 772 return 1
773 #-----------------------------------------------------------------
774 - def load_patient_from_external_source(self, auth_cookie = None):
775 """Load external patient, perhaps create it. 776 777 Callers must use get_user_answer() to get status information. 778 It is unsafe to proceed without knowing the completion state as 779 the controlled client may be waiting for user input from a 780 patient selection list. 781 """ 782 if not self.__attached: 783 return (0, _('request rejected, you are not attach()ed')) 784 if auth_cookie != self.__auth_cookie: 785 _log.error('non-authenticated load_patient_from_external_source()') 786 return (0, _('rejected load_patient_from_external_source(), not authenticated')) 787 if self.__pat.locked: 788 _log.error('patient is locked, cannot load from external source') 789 return (0, _('current patient is locked')) 790 self.__user_done = False 791 wx.CallAfter(self._load_patient_from_external_source) 792 self.__lock_after_load_cookie = str(random.random()) 793 return (1, self.__lock_after_load_cookie)
794 #-----------------------------------------------------------------
795 - def lock_loaded_patient(self, auth_cookie = None, lock_after_load_cookie = None):
796 if not self.__attached: 797 return (0, _('request rejected, you are not attach()ed')) 798 if auth_cookie != self.__auth_cookie: 799 _log.error('non-authenticated lock_load_patient()') 800 return (0, _('rejected lock_load_patient(), not authenticated')) 801 # FIXME: ask user what to do about wrong cookie 802 if lock_after_load_cookie != self.__lock_after_load_cookie: 803 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie) 804 return (0, 'patient lock-after-load request rejected, wrong cookie provided') 805 self.__pat.locked = True 806 self.__pat_lock_cookie = str(random.random()) 807 return (1, self.__pat_lock_cookie)
808 #-----------------------------------------------------------------
809 - def lock_into_patient(self, auth_cookie = None, search_params = None):
810 if not self.__attached: 811 return (0, _('request rejected, you are not attach()ed')) 812 if auth_cookie != self.__auth_cookie: 813 _log.error('non-authenticated lock_into_patient()') 814 return (0, _('rejected lock_into_patient(), not authenticated')) 815 if self.__pat.locked: 816 _log.error('patient is already locked') 817 return (0, _('already locked into a patient')) 818 searcher = gmPersonSearch.cPatientSearcher_SQL() 819 if type(search_params) == types.DictType: 820 idents = searcher.get_identities(search_dict=search_params) 821 print "must use dto, not search_dict" 822 print xxxxxxxxxxxxxxxxx 823 else: 824 idents = searcher.get_identities(search_term=search_params) 825 if idents is None: 826 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict)) 827 if len(idents) == 0: 828 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict)) 829 # FIXME: let user select patient 830 if len(idents) > 1: 831 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict)) 832 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]): 833 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict)) 834 self.__pat.locked = True 835 self.__pat_lock_cookie = str(random.random()) 836 return (1, self.__pat_lock_cookie)
837 #-----------------------------------------------------------------
838 - def unlock_patient(self, auth_cookie = None, unlock_cookie = None):
839 if not self.__attached: 840 return (0, _('request rejected, you are not attach()ed')) 841 if auth_cookie != self.__auth_cookie: 842 _log.error('non-authenticated unlock_patient()') 843 return (0, _('rejected unlock_patient, not authenticated')) 844 # we ain't locked anyways, so succeed 845 if not self.__pat.locked: 846 return (1, '') 847 # FIXME: ask user what to do about wrong cookie 848 if unlock_cookie != self.__pat_lock_cookie: 849 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie) 850 return (0, 'patient unlock request rejected, wrong cookie provided') 851 self.__pat.locked = False 852 return (1, '')
853 #-----------------------------------------------------------------
854 - def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
855 if not self.__attached: 856 return 0 857 if auth_cookie != self.__auth_cookie: 858 _log.error('non-authenticated select_identity()') 859 return 0 860 return "cMacroPrimitives.assume_staff_identity() not implemented"
861 #-----------------------------------------------------------------
862 - def get_user_answer(self):
863 if not self.__user_done: 864 return (0, 'still waiting') 865 self.__user_done = False 866 return (1, self.__user_answer)
867 #----------------------------------------------------------------- 868 # internal API 869 #-----------------------------------------------------------------
870 - def _force_detach(self):
871 msg = _( 872 'Someone tries to forcibly break the existing\n' 873 'controlling connection. This may or may not\n' 874 'have legitimate reasons.\n\n' 875 'Do you want to allow breaking the connection ?' 876 ) 877 can_break_conn = gmGuiHelpers.gm_show_question ( 878 aMessage = msg, 879 aTitle = _('forced detach attempt') 880 ) 881 if can_break_conn: 882 self.__user_answer = 1 883 else: 884 self.__user_answer = 0 885 self.__user_done = True 886 if can_break_conn: 887 self.__pat.locked = False 888 self.__attached = 0 889 return 1
890 #-----------------------------------------------------------------
891 - def _shutdown_gnumed(self, forced=False):
892 top_win = wx.GetApp().GetTopWindow() 893 if forced: 894 top_win.Destroy() 895 else: 896 top_win.Close()
897 #-----------------------------------------------------------------
899 patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True) 900 if patient is not None: 901 self.__user_answer = 1 902 else: 903 self.__user_answer = 0 904 self.__user_done = True 905 return 1
906 #===================================================================== 907 # main 908 #===================================================================== 909 if __name__ == '__main__': 910 911 if len(sys.argv) < 2: 912 sys.exit() 913 914 if sys.argv[1] != 'test': 915 sys.exit() 916 917 gmI18N.activate_locale() 918 gmI18N.install_domain() 919 920 #--------------------------------------------------------
921 - def test_placeholders():
922 handler = gmPlaceholderHandler() 923 handler.debug = True 924 925 for placeholder in ['a', 'b']: 926 print handler[placeholder] 927 928 pat = gmPersonSearch.ask_for_patient() 929 if pat is None: 930 return 931 932 gmPatSearchWidgets.set_active_patient(patient = pat) 933 934 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d'] 935 936 app = wx.PyWidgetTester(size = (200, 50)) 937 for placeholder in known_placeholders: 938 print placeholder, "=", handler[placeholder] 939 940 ph = 'progress_notes::ap' 941 print '%s: %s' % (ph, handler[ph])
942 #--------------------------------------------------------
943 - def test_new_variant_placeholders():
944 945 tests = [ 946 # should work: 947 '$<lastname>$', 948 '$<lastname::::3>$', 949 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$', 950 951 # should fail: 952 'lastname', 953 '$<lastname', 954 '$<lastname::', 955 '$<lastname::>$', 956 '$<lastname::abc>$', 957 '$<lastname::abc::>$', 958 '$<lastname::abc::3>$', 959 '$<lastname::abc::xyz>$', 960 '$<lastname::::>$', 961 '$<lastname::::xyz>$', 962 963 '$<date_of_birth::%Y-%m-%d>$', 964 '$<date_of_birth::%Y-%m-%d::3>$', 965 '$<date_of_birth::%Y-%m-%d::>$', 966 967 # should work: 968 '$<adr_location::home::35>$', 969 '$<gender_mapper::male//female//other::5>$', 970 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$', 971 '$<allergy_list::%(descriptor)s, >$', 972 '$<current_meds_table::latex//by-brand>$' 973 974 # 'firstname', 975 # 'title', 976 # 'date_of_birth', 977 # 'progress_notes', 978 # 'soap', 979 # 'soap_s', 980 # 'soap_o', 981 # 'soap_a', 982 # 'soap_p', 983 984 # 'soap', 985 # 'progress_notes', 986 # 'date_of_birth' 987 ] 988 989 tests = [ 990 '$<latest_vaccs_table::latex>$' 991 ] 992 993 pat = gmPersonSearch.ask_for_patient() 994 if pat is None: 995 return 996 997 gmPatSearchWidgets.set_active_patient(patient = pat) 998 999 handler = gmPlaceholderHandler() 1000 handler.debug = True 1001 1002 for placeholder in tests: 1003 print placeholder, "=>", handler[placeholder] 1004 print "--------------" 1005 raw_input()
1006 1007 # print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d'] 1008 1009 # app = wx.PyWidgetTester(size = (200, 50)) 1010 # for placeholder in known_placeholders: 1011 # print placeholder, "=", handler[placeholder] 1012 1013 # ph = 'progress_notes::ap' 1014 # print '%s: %s' % (ph, handler[ph]) 1015 1016 #--------------------------------------------------------
1017 - def test_scripting():
1018 from Gnumed.pycommon import gmScriptingListener 1019 import xmlrpclib 1020 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999) 1021 1022 s = xmlrpclib.ServerProxy('http://localhost:9999') 1023 print "should fail:", s.attach() 1024 print "should fail:", s.attach('wrong cookie') 1025 print "should work:", s.version() 1026 print "should fail:", s.raise_gnumed() 1027 print "should fail:", s.raise_notebook_plugin('test plugin') 1028 print "should fail:", s.lock_into_patient('kirk, james') 1029 print "should fail:", s.unlock_patient() 1030 status, conn_auth = s.attach('unit test') 1031 print "should work:", status, conn_auth 1032 print "should work:", s.version() 1033 print "should work:", s.raise_gnumed(conn_auth) 1034 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james') 1035 print "should work:", status, pat_auth 1036 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie') 1037 print "should work", s.unlock_patient(conn_auth, pat_auth) 1038 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'} 1039 status, pat_auth = s.lock_into_patient(conn_auth, data) 1040 print "should work:", status, pat_auth 1041 print "should work", s.unlock_patient(conn_auth, pat_auth) 1042 print s.detach('bogus detach cookie') 1043 print s.detach(conn_auth) 1044 del s 1045 1046 listener.shutdown()
1047 #--------------------------------------------------------
1048 - def test_placeholder_regex():
1049 1050 import re as regex 1051 1052 tests = [ 1053 ' $<lastname>$ ', 1054 ' $<lastname::::3>$ ', 1055 1056 # should fail: 1057 '$<date_of_birth::%Y-%m-%d>$', 1058 '$<date_of_birth::%Y-%m-%d::3>$', 1059 '$<date_of_birth::%Y-%m-%d::>$', 1060 1061 '$<adr_location::home::35>$', 1062 '$<gender_mapper::male//female//other::5>$', 1063 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$', 1064 '$<allergy_list::%(descriptor)s, >$', 1065 1066 '\\noindent Patient: $<lastname>$, $<firstname>$', 1067 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$', 1068 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$' 1069 ] 1070 1071 tests = [ 1072 1073 'junk $<lastname::::3>$ junk', 1074 'junk $<lastname::abc::3>$ junk', 1075 'junk $<lastname::abc>$ junk', 1076 'junk $<lastname>$ junk', 1077 1078 'junk $<lastname>$ junk $<firstname>$ junk', 1079 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk', 1080 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk', 1081 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk' 1082 1083 ] 1084 1085 print "testing placeholder regex:", default_placeholder_regex 1086 print "" 1087 1088 for t in tests: 1089 print 'line: "%s"' % t 1090 print "placeholders:" 1091 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE): 1092 print ' => "%s"' % p 1093 print " "
1094 #--------------------------------------------------------
1095 - def test_placeholder():
1096 1097 #ph = u'emr_journal::soap //%(date)s %(modified_by)s %(soap_cat)s %(narrative)s//30::' 1098 #ph = u'free_text::latex//placeholder test::9999' 1099 ph = u'soap_for_encounters:://::9999' 1100 1101 handler = gmPlaceholderHandler() 1102 handler.debug = True 1103 1104 pat = gmPersonSearch.ask_for_patient() 1105 if pat is None: 1106 return 1107 1108 gmPatSearchWidgets.set_active_patient(patient = pat) 1109 1110 app = wx.PyWidgetTester(size = (200, 50)) 1111 print u'%s => %s' % (ph, handler[ph])
1112 #-------------------------------------------------------- 1113 1114 #test_placeholders() 1115 #test_new_variant_placeholders() 1116 #test_scripting() 1117 #test_placeholder_regex() 1118 test_placeholder() 1119 1120 #===================================================================== 1121