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