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