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

Source Code for Module Gnumed.wxpython.gmMacro

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