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