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

Source Code for Module Gnumed.wxpython.gmMacro

   1  #  coding: utf8 
   2  """GNUmed macro primitives. 
   3   
   4  This module implements functions a macro can legally use. 
   5  """ 
   6  #===================================================================== 
   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 
  19  if __name__ == '__main__': 
  20          gmI18N.activate_locale() 
  21          gmI18N.install_domain() 
  22  from Gnumed.pycommon import gmGuiBroker 
  23  from Gnumed.pycommon import gmTools 
  24  from Gnumed.pycommon import gmBorg 
  25  from Gnumed.pycommon import gmExceptions 
  26  from Gnumed.pycommon import gmCfg2 
  27  from Gnumed.pycommon import gmDateTime 
  28  from Gnumed.pycommon import gmMimeLib 
  29   
  30  from Gnumed.business import gmPerson 
  31  from Gnumed.business import gmStaff 
  32  from Gnumed.business import gmDemographicRecord 
  33  from Gnumed.business import gmMedication 
  34  from Gnumed.business import gmPathLab 
  35  from Gnumed.business import gmPersonSearch 
  36  from Gnumed.business import gmVaccination 
  37  from Gnumed.business import gmPersonSearch 
  38  from Gnumed.business import gmKeywordExpansion 
  39   
  40  from Gnumed.wxpython import gmGuiHelpers 
  41  from Gnumed.wxpython import gmNarrativeWidgets 
  42  from Gnumed.wxpython import gmPatSearchWidgets 
  43  from Gnumed.wxpython import gmPersonContactWidgets 
  44  from Gnumed.wxpython import gmPlugin 
  45  from Gnumed.wxpython import gmEMRStructWidgets 
  46  from Gnumed.wxpython import gmListWidgets 
  47  from Gnumed.wxpython import gmDemographicsWidgets 
  48   
  49   
  50  _log = logging.getLogger('gm.scripting') 
  51  _cfg = gmCfg2.gmCfgData() 
  52   
  53  #===================================================================== 
  54  known_placeholders = [ 
  55          'lastname', 
  56          'firstname', 
  57          'title', 
  58          'date_of_birth', 
  59          'progress_notes', 
  60          'soap', 
  61          'soap_s', 
  62          'soap_o', 
  63          'soap_a', 
  64          'soap_p', 
  65          'soap_u', 
  66          u'client_version', 
  67          u'current_provider', 
  68          u'primary_praxis_provider',                     # primary provider for current patient in this praxis 
  69          u'allergy_state' 
  70  ] 
  71   
  72   
  73  # values for the following placeholders must be injected from the outside before 
  74  # using them, in use they must conform to the "placeholder::::max length" syntax, 
  75  # as long as they resolve to None they return themselves 
  76  _injectable_placeholders = { 
  77          u'form_name_long': None, 
  78          u'form_name_short': None, 
  79          u'form_version': None 
  80  } 
  81   
  82   
  83  # the following must satisfy the pattern "$<name::args::(optional) max string length>$" when used 
  84  known_variant_placeholders = [ 
  85          # generic: 
  86          u'free_text',                                                   # show a dialog for entering some free text 
  87          u'text_snippet',                                                # a text snippet, taken from the keyword expansion mechanism 
  88                                                                                          # args: <snippet name>//<template> 
  89          u'data_snippet',                                                # a binary snippet, taken from the keyword expansion mechanism 
  90                                                                                          # args: <snippet name>//<template>//<optional target mime type>//<optional target extension> 
  91                                                                                          # returns full path to an exported copy of the 
  92                                                                                          # data rather than the data itself, 
  93                                                                                          #       template: string template for outputting the path 
  94                                                                                          #       target mime type: a mime type into which to convert the image, no conversion if not given 
  95                                                                                          #       target extension: target file name extension, derived from target mime type if not given 
  96          u'tex_escape',                                                  # "args" holds: string to escape 
  97          u'today',                                                               # "args" holds: strftime format 
  98          u'gender_mapper',                                               # "args" holds: <value when person is male> // <is female> // <is other> 
  99                                                                                          #                               eg. "male//female//other" 
 100                                                                                          #                               or: "Lieber Patient//Liebe Patientin" 
 101   
 102          # patient demographics: 
 103          u'date_of_birth', 
 104   
 105          u'patient_address',                     # "args": <type of address>//<optional formatting template> 
 106          u'adr_street',                          # "args" holds: type of address 
 107          u'adr_number', 
 108          u'adr_subunit', 
 109          u'adr_location', 
 110          u'adr_suburb', 
 111          u'adr_postcode', 
 112          u'adr_region', 
 113          u'adr_country', 
 114   
 115          u'patient_comm',                                                # args: comm channel type as per database 
 116          u'patient_tags',                                                # "args" holds: <%(key)s-template>//<separator> 
 117  #       u'patient_tags_table',                                  # "args" holds: no args 
 118   
 119          u'patient_photo',                                               # args: <template>//<optional target mime type>//<optional target extension> 
 120                                                                                          # returns full path to an exported copy of the 
 121                                                                                          # image rather than the image data itself, 
 122                                                                                          # returns u'' if no mugshot available, 
 123                                                                                          #       template: string template for outputting the path 
 124                                                                                          #       target mime type: a mime type into which to convert the image, no conversion if not given 
 125                                                                                          #       target extension: target file name extension, derived from target mime type if not given 
 126   
 127          u'external_id',                                                 # args: <type of ID>//<issuer of ID> 
 128   
 129   
 130          u'current_meds',                                                # "args" holds: line template 
 131          u'current_meds_table',                                  # "args" holds: format, options 
 132   
 133          # clinical record related: 
 134          u'soap', 
 135          u'progress_notes',                                              # "args" holds: categories//template 
 136                                                                                          #       categories: string with 'soapu '; ' ' == None == admin 
 137                                                                                          #       template:       u'something %s something'               (do not include // in template !) 
 138          u'emr_journal',                                                 # "args" format:   <categories>//<template>//<line length>//<time range>//<target format> 
 139                                                                                          #       categories:        string with any of "s", "o", "a", "p", "u", " "; 
 140                                                                                          #                                  (" " == None == admin category) 
 141                                                                                          #       template:          something %s something else 
 142                                                                                          #                                  (Do not include // in the template !) 
 143                                                                                          #       line length:   the length of individual lines, not the total placeholder length 
 144                                                                                          #       time range:        the number of weeks going back in time 
 145                                                                                          #       target format: "tex" or anything else, if "tex", data will be tex-escaped       (currently only "latex") 
 146          u'current_meds_notes',                                  # "args" holds: format, options 
 147          u'lab_table',                                                   # "args" holds: format (currently "latex" only) 
 148          u'latest_vaccs_table',                                  # "args" holds: format, options 
 149          u'vaccination_history',                                 # "args": <%(key)s-template//date format> to format one vaccination per line 
 150          u'allergies',                                                   # "args" holds: line template, one allergy per line 
 151          u'allergy_list',                                                # "args" holds: template per allergy, allergies on one line 
 152          u'problems',                                                    # "args" holds: line template, one problem per line 
 153          u'PHX',                                                                 # Past medical HiXtory, "args" holds: line template//separator//strftime date format//escape style (latex, currently) 
 154          u'name',                                                                # "args" holds: template for name parts arrangement 
 155          u'soap_for_encounters',                                 # "args" holds: soap cats // strftime date format 
 156          u'encounter_list',                                              # "args" holds: per-encounter template, each ends up on one line 
 157   
 158          # provider related: 
 159          u'current_provider_external_id',                # args: <type of ID>//<issuer of ID> 
 160          u'primary_praxis_provider_external_id', # args: <type of ID>//<issuer of ID> 
 161   
 162          # billing related: 
 163          u'bill',                                                                # args: template for string replacement 
 164          u'bill_item'                                                    # args: template for string replacement 
 165  ] 
 166   
 167  #http://help.libreoffice.org/Common/List_of_Regular_Expressions 
 168  default_placeholder_regex = r'\$<.+?>\$'                                # this one works [except that OOo cannot be non-greedy |-(    ] 
 169  #default_placeholder_regex = r'\$<(?:(?!\$<).)+>\$'             # non-greedy equivalent, uses lookahead (but not supported by LO either |-o  ) 
 170   
 171  default_placeholder_start = u'$<' 
 172  default_placeholder_end = u'>$' 
 173  #===================================================================== 
174 -class gmPlaceholderHandler(gmBorg.cBorg):
175 """Returns values for placeholders. 176 177 - patient related placeholders operate on the currently active patient 178 - is passed to the forms handling code, for example 179 180 Return values when .debug is False: 181 - errors with placeholders return None 182 - placeholders failing to resolve to a value return an empty string 183 184 Return values when .debug is True: 185 - errors with placeholders return an error string 186 - placeholders failing to resolve to a value return a warning string 187 188 There are several types of placeholders: 189 190 simple static placeholders 191 - those are listed in known_placeholders 192 - they are used as-is 193 194 extended static placeholders 195 - those are, effectively, static placeholders 196 with a maximum length attached (after "::::") 197 198 injectable placeholders 199 - they must be set up before use by set_placeholder() 200 - they should be removed after use by unset_placeholder() 201 - the syntax is like extended static placeholders 202 - they are listed in _injectable_placeholders 203 204 variant placeholders 205 - those are listed in known_variant_placeholders 206 - they are parsed into placeholder, data, and maximum length 207 - the length is optional 208 - data is passed to the handler 209 210 Note that this cannot be called from a non-gui thread unless 211 wrapped in wx.CallAfter(). 212 """
213 - def __init__(self, *args, **kwargs):
214 215 self.pat = gmPerson.gmCurrentPatient() 216 self.debug = False 217 218 self.invalid_placeholder_template = _('invalid placeholder [%s]') 219 220 self.__cache = {}
221 #-------------------------------------------------------- 222 # external API 223 #--------------------------------------------------------
224 - def set_placeholder(self, key=None, value=None):
227 #--------------------------------------------------------
228 - def unset_placeholder(self, key=None):
231 #--------------------------------------------------------
232 - def set_cache_value(self, key=None, value=None):
233 self.__cache[key] = value
234 #--------------------------------------------------------
235 - def unset_cache_value(self, key=None):
236 del self.__cache[key]
237 #-------------------------------------------------------- 238 # __getitem__ API 239 #--------------------------------------------------------
240 - def __getitem__(self, placeholder):
241 """Map self['placeholder'] to self.placeholder. 242 243 This is useful for replacing placeholders parsed out 244 of documents as strings. 245 246 Unknown/invalid placeholders still deliver a result but 247 it will be glaringly obvious if debugging is enabled. 248 """ 249 _log.debug('replacing [%s]', placeholder) 250 251 original_placeholder = placeholder 252 253 if placeholder.startswith(default_placeholder_start): 254 placeholder = placeholder[len(default_placeholder_start):] 255 if placeholder.endswith(default_placeholder_end): 256 placeholder = placeholder[:-len(default_placeholder_end)] 257 else: 258 _log.debug('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end) 259 if self.debug: 260 return self.invalid_placeholder_template % original_placeholder 261 return None 262 263 # simple static placeholder ? 264 if placeholder in known_placeholders: 265 return getattr(self, placeholder) 266 267 # injectable placeholder ? 268 parts = placeholder.split('::::', 1) 269 if len(parts) == 2: 270 name, lng = parts 271 unknown_injectable = False 272 try: 273 val = _injectable_placeholders[name] 274 except KeyError: 275 unknown_injectable = True 276 except: 277 _log.exception('placeholder handling error: %s', original_placeholder) 278 if self.debug: 279 return self.invalid_placeholder_template % original_placeholder 280 return None 281 if not unknown_injectable: 282 if val is None: 283 if self.debug: 284 return u'injectable placeholder [%s]: no value available' % name 285 return placeholder 286 return val[:int(lng)] 287 288 # extended static placeholder ? 289 parts = placeholder.split('::::', 1) 290 if len(parts) == 2: 291 name, lng = parts 292 try: 293 return getattr(self, name)[:int(lng)] 294 except: 295 _log.exception('placeholder handling error: %s', original_placeholder) 296 if self.debug: 297 return self.invalid_placeholder_template % original_placeholder 298 return None 299 300 # variable placeholders 301 parts = placeholder.split('::') 302 303 if len(parts) == 1: 304 _log.warning('invalid placeholder layout: %s', original_placeholder) 305 if self.debug: 306 return self.invalid_placeholder_template % original_placeholder 307 return None 308 309 if len(parts) == 2: 310 name, data = parts 311 lng = None 312 313 if len(parts) == 3: 314 name, data, lng = parts 315 try: 316 lng = int(lng) 317 except (TypeError, ValueError): 318 _log.error('placeholder length definition error: %s, discarding length: >%s<', original_placeholder, lng) 319 lng = None 320 321 if len(parts) > 3: 322 _log.warning('invalid placeholder layout: %s', original_placeholder) 323 if self.debug: 324 return self.invalid_placeholder_template % original_placeholder 325 return None 326 327 handler = getattr(self, '_get_variant_%s' % name, None) 328 if handler is None: 329 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder) 330 if self.debug: 331 return self.invalid_placeholder_template % original_placeholder 332 return None 333 334 try: 335 if lng is None: 336 return handler(data = data) 337 return handler(data = data)[:lng] 338 except: 339 _log.exception('placeholder handling error: %s', original_placeholder) 340 if self.debug: 341 return self.invalid_placeholder_template % original_placeholder 342 return None 343 344 _log.error('something went wrong, should never get here') 345 return None
346 #-------------------------------------------------------- 347 # properties actually handling placeholders 348 #-------------------------------------------------------- 349 # property helpers 350 #--------------------------------------------------------
351 - def _setter_noop(self, val):
352 """This does nothing, used as a NOOP properties setter.""" 353 pass
354 #--------------------------------------------------------
355 - def _get_lastname(self):
356 return self.pat.get_active_name()['lastnames']
357 #--------------------------------------------------------
358 - def _get_firstname(self):
359 return self.pat.get_active_name()['firstnames']
360 #--------------------------------------------------------
361 - def _get_title(self):
362 return gmTools.coalesce(self.pat.get_active_name()['title'], u'')
363 #--------------------------------------------------------
364 - def _get_dob(self):
365 return self._get_variant_date_of_birth(data='%x')
366 #--------------------------------------------------------
367 - def _get_progress_notes(self):
368 return self._get_variant_soap()
369 #--------------------------------------------------------
370 - def _get_soap_s(self):
371 return self._get_variant_soap(data = u's')
372 #--------------------------------------------------------
373 - def _get_soap_o(self):
374 return self._get_variant_soap(data = u'o')
375 #--------------------------------------------------------
376 - def _get_soap_a(self):
377 return self._get_variant_soap(data = u'a')
378 #--------------------------------------------------------
379 - def _get_soap_p(self):
380 return self._get_variant_soap(data = u'p')
381 #--------------------------------------------------------
382 - def _get_soap_u(self):
383 return self._get_variant_soap(data = u'u')
384 #--------------------------------------------------------
385 - def _get_soap_admin(self):
386 return self._get_variant_soap(soap_cats = None)
387 #--------------------------------------------------------
388 - def _get_client_version(self):
389 return gmTools.coalesce ( 390 _cfg.get(option = u'client_version'), 391 u'%s' % self.__class__.__name__ 392 )
393 #--------------------------------------------------------
395 prov = self.pat.primary_provider 396 if prov is None: 397 return self._get_current_provider() 398 399 title = gmTools.coalesce ( 400 prov['title'], 401 gmPerson.map_gender2salutation(prov['gender']) 402 ) 403 404 tmp = u'%s %s. %s' % ( 405 title, 406 prov['firstnames'][:1], 407 prov['lastnames'] 408 ) 409 410 return tmp
411 #--------------------------------------------------------
412 - def _get_current_provider(self):
413 prov = gmStaff.gmCurrentProvider() 414 415 title = gmTools.coalesce ( 416 prov['title'], 417 gmPerson.map_gender2salutation(prov['gender']) 418 ) 419 420 tmp = u'%s %s. %s' % ( 421 title, 422 prov['firstnames'][:1], 423 prov['lastnames'] 424 ) 425 426 return tmp
427 #--------------------------------------------------------
428 - def _get_allergy_state(self):
429 allg_state = self.pat.get_emr().allergy_state 430 431 if allg_state['last_confirmed'] is None: 432 date_confirmed = u'' 433 else: 434 date_confirmed = u' (%s)' % allg_state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding()) 435 436 tmp = u'%s%s' % ( 437 allg_state.state_string, 438 date_confirmed 439 ) 440 return tmp
441 #-------------------------------------------------------- 442 # property definitions for static placeholders 443 #-------------------------------------------------------- 444 placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop) 445 446 #-------------------------------------------------------- 447 448 # placeholders 449 lastname = property(_get_lastname, _setter_noop) 450 firstname = property(_get_firstname, _setter_noop) 451 title = property(_get_title, _setter_noop) 452 date_of_birth = property(_get_dob, _setter_noop) 453 454 progress_notes = property(_get_progress_notes, _setter_noop) 455 soap = property(_get_progress_notes, _setter_noop) 456 soap_s = property(_get_soap_s, _setter_noop) 457 soap_o = property(_get_soap_o, _setter_noop) 458 soap_a = property(_get_soap_a, _setter_noop) 459 soap_p = property(_get_soap_p, _setter_noop) 460 soap_u = property(_get_soap_u, _setter_noop) 461 soap_admin = property(_get_soap_admin, _setter_noop) 462 463 allergy_state = property(_get_allergy_state, _setter_noop) 464 465 client_version = property(_get_client_version, _setter_noop) 466 467 current_provider = property(_get_current_provider, _setter_noop) 468 primary_praxis_provider = property(_get_primary_praxis_provider, _setter_noop) 469 #-------------------------------------------------------- 470 # variant handlers 471 #--------------------------------------------------------
472 - def _get_variant_encounter_list(self, data=None):
473 474 encounters = gmEMRStructWidgets.select_encounters(single_selection = False) 475 if not encounters: 476 return u'' 477 478 template = data 479 480 lines = [] 481 for enc in encounters: 482 try: 483 lines.append(template % enc) 484 except: 485 lines.append(u'error formatting encounter') 486 _log.exception('problem formatting encounter list') 487 _log.error('template: %s', template) 488 _log.error('encounter: %s', encounter) 489 490 return u'\n'.join(lines)
491 #--------------------------------------------------------
492 - def _get_variant_soap_for_encounters(self, data=None):
493 """Select encounters from list and format SOAP thereof. 494 495 data: soap_cats (' ' -> None -> admin) // date format 496 """ 497 # defaults 498 cats = None 499 date_format = None 500 501 if data is not None: 502 data_parts = data.split('//') 503 504 # part[0]: categories 505 if len(data_parts[0]) > 0: 506 cats = [] 507 if u' ' in data_parts[0]: 508 cats.append(None) 509 data_parts[0] = data_parts[0].replace(u' ', u'') 510 cats.extend(list(data_parts[0])) 511 512 # part[1]: date format 513 if len(data_parts) > 1: 514 if len(data_parts[1]) > 0: 515 date_format = data_parts[1] 516 517 encounters = gmEMRStructWidgets.select_encounters(single_selection = False) 518 if not encounters: 519 return u'' 520 521 chunks = [] 522 for enc in encounters: 523 chunks.append(enc.format_latex ( 524 date_format = date_format, 525 soap_cats = cats, 526 soap_order = u'soap_rank, date' 527 )) 528 529 return u''.join(chunks)
530 #--------------------------------------------------------
531 - def _get_variant_emr_journal(self, data=None):
532 # default: all categories, neutral template 533 cats = list(u'soapu') 534 cats.append(None) 535 template = u'%s' 536 interactive = True 537 line_length = 9999 538 target_format = None 539 time_range = None 540 541 if data is not None: 542 data_parts = data.split('//') 543 544 # part[0]: categories 545 cats = [] 546 # ' ' -> None == admin 547 for c in list(data_parts[0]): 548 if c == u' ': 549 c = None 550 cats.append(c) 551 # '' -> SOAP + None 552 if cats == u'': 553 cats = list(u'soapu').append(None) 554 555 # part[1]: template 556 if len(data_parts) > 1: 557 template = data_parts[1] 558 559 # part[2]: line length 560 if len(data_parts) > 2: 561 try: 562 line_length = int(data_parts[2]) 563 except: 564 line_length = 9999 565 566 # part[3]: weeks going back in time 567 if len(data_parts) > 3: 568 try: 569 time_range = 7 * int(data_parts[3]) 570 except: 571 time_range = None 572 573 # part[4]: output format 574 if len(data_parts) > 4: 575 target_format = data_parts[4] 576 577 # FIXME: will need to be a generator later on 578 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range) 579 580 if len(narr) == 0: 581 return u'' 582 583 if target_format == u'tex': 584 keys = narr[0].keys() 585 lines = [] 586 line_dict = {} 587 for n in narr: 588 for key in keys: 589 if isinstance(n[key], basestring): 590 line_dict[key] = gmTools.tex_escape_string(text = n[key]) 591 continue 592 line_dict[key] = n[key] 593 try: 594 lines.append((template % line_dict)[:line_length]) 595 except KeyError: 596 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys)) 597 else: 598 try: 599 lines = [ (template % n)[:line_length] for n in narr ] 600 except KeyError: 601 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys())) 602 603 return u'\n'.join(lines)
604 #--------------------------------------------------------
605 - def _get_variant_progress_notes(self, data=None):
606 return self._get_variant_soap(data=data)
607 #--------------------------------------------------------
608 - def _get_variant_soap(self, data=None):
609 610 # default: all categories, neutral template 611 cats = list(u'soapu') 612 cats.append(None) 613 template = u'%s' 614 615 if data is not None: 616 data_parts = data.split('//') 617 618 # part[0]: categories 619 cats = [] 620 # ' ' -> None == admin 621 for cat in list(data_parts[0]): 622 if cat == u' ': 623 cat = None 624 cats.append(cat) 625 # '' -> SOAP + None 626 if cats == u'': 627 cats = list(u'soapu') 628 cats.append(None) 629 630 # part[1]: template 631 if len(data_parts) > 1: 632 template = data_parts[1] 633 634 #narr = gmNarrativeWidgets.select_narrative_from_episodes_new(soap_cats = cats) 635 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats) 636 637 if narr is None: 638 return u'' 639 640 if len(narr) == 0: 641 return u'' 642 643 try: 644 narr = [ template % n['narrative'] for n in narr ] 645 except KeyError: 646 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys())) 647 648 return u'\n'.join(narr)
649 #--------------------------------------------------------
650 - def _get_variant_name(self, data=None):
651 if data is None: 652 return [_('template is missing')] 653 654 name = self.pat.get_active_name() 655 656 parts = { 657 'title': gmTools.coalesce(name['title'], u''), 658 'firstnames': name['firstnames'], 659 'lastnames': name['lastnames'], 660 'preferred': gmTools.coalesce ( 661 initial = name['preferred'], 662 instead = u' ', 663 template_initial = u' "%s" ' 664 ) 665 } 666 667 return data % parts
668 #--------------------------------------------------------
669 - def _get_variant_date_of_birth(self, data='%x'):
670 return self.pat.get_formatted_dob(format = str(data), encoding = gmI18N.get_encoding())
671 #-------------------------------------------------------- 672 # FIXME: extend to all supported genders
673 - def _get_variant_gender_mapper(self, data='male//female//other'):
674 values = data.split('//', 2) 675 676 if len(values) == 2: 677 male_value, female_value = values 678 other_value = u'<unkown gender>' 679 elif len(values) == 3: 680 male_value, female_value, other_value = values 681 else: 682 return _('invalid gender mapping layout: [%s]') % data 683 684 if self.pat['gender'] == u'm': 685 return male_value 686 687 if self.pat['gender'] == u'f': 688 return female_value 689 690 return other_value
691 #-------------------------------------------------------- 692 # address related placeholders 693 #--------------------------------------------------------
694 - def _get_variant_patient_address(self, data=u''):
695 696 data_parts = data.split(u'//') 697 698 # address type 699 adr_type = data_parts[0].strip() 700 orig_type = adr_type 701 if adr_type != u'': 702 adrs = self.pat.get_addresses(address_type = adr_type) 703 if len(adrs) == 0: 704 _log.warning('no address for type [%s]', adr_type) 705 adr_type = u'' 706 if adr_type == u'': 707 _log.debug('asking user for address type') 708 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat) 709 if adr is None: 710 if self.debug: 711 return _('no address type replacement selected') 712 return u'' 713 adr_type = adr['address_type'] 714 adr = self.pat.get_addresses(address_type = adr_type)[0] 715 716 # formatting template 717 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s') 718 if len(data_parts) > 1: 719 if data_parts[1].strip() != u'': 720 template = data_parts[1] 721 722 try: 723 return template % adr.fields_as_dict() 724 except StandardError: 725 _log.exception('error formatting address') 726 _log.error('template: %s', template) 727 728 return None
729 #--------------------------------------------------------
730 - def __get_variant_adr_part(self, data=u'?', part=None):
731 requested_type = data.strip() 732 cache_key = 'adr-type-%s' % requested_type 733 try: 734 type2use = self.__cache[cache_key] 735 _log.debug('cache hit (%s): [%s] -> [%s]', cache_key, requested_type, type2use) 736 except KeyError: 737 type2use = requested_type 738 if type2use != u'': 739 adrs = self.pat.get_addresses(address_type = type2use) 740 if len(adrs) == 0: 741 _log.warning('no address of type [%s] for <%s> field extraction', requested_type, part) 742 type2use = u'' 743 if type2use == u'': 744 _log.debug('asking user for replacement address type') 745 adr = gmPersonContactWidgets.select_address(missing = requested_type, person = self.pat) 746 if adr is None: 747 _log.debug('no replacement selected') 748 if self.debug: 749 return _('no address type replacement selected') 750 return u'' 751 type2use = adr['address_type'] 752 self.__cache[cache_key] = type2use 753 _log.debug('caching (%s): [%s] -> [%s]', cache_key, requested_type, type2use) 754 755 return self.pat.get_addresses(address_type = type2use)[0][part]
756 #--------------------------------------------------------
757 - def _get_variant_adr_street(self, data=u'?'):
758 return self.__get_variant_adr_part(data = data, part = 'street')
759 #--------------------------------------------------------
760 - def _get_variant_adr_number(self, data=u'?'):
761 return self.__get_variant_adr_part(data = data, part = 'number')
762 #--------------------------------------------------------
763 - def _get_variant_adr_subunit(self, data=u'?'):
764 return self.__get_variant_adr_part(data = data, part = 'subunit')
765 #--------------------------------------------------------
766 - def _get_variant_adr_location(self, data=u'?'):
767 return self.__get_variant_adr_part(data = data, part = 'urb')
768 #--------------------------------------------------------
769 - def _get_variant_adr_suburb(self, data=u'?'):
770 return self.__get_variant_adr_part(data = data, part = 'suburb')
771 #--------------------------------------------------------
772 - def _get_variant_adr_postcode(self, data=u'?'):
773 return self.__get_variant_adr_part(data = data, part = 'postcode')
774 #--------------------------------------------------------
775 - def _get_variant_adr_region(self, data=u'?'):
776 return self.__get_variant_adr_part(data = data, part = 'l10n_state')
777 #--------------------------------------------------------
778 - def _get_variant_adr_country(self, data=u'?'):
779 return self.__get_variant_adr_part(data = data, part = 'l10n_country')
780 #--------------------------------------------------------
781 - def _get_variant_patient_comm(self, data=u'?'):
782 comms = self.pat.get_comm_channels(comm_medium = data) 783 if len(comms) == 0: 784 if self.debug: 785 return _('no URL for comm channel [%s]') % data 786 return u'' 787 return comms[0]['url']
788 #--------------------------------------------------------
789 - def _get_variant_patient_photo(self, data=None):
790 791 template = u'%s' 792 target_mime = None 793 target_ext = None 794 if data is not None: 795 parts = data.split(u'//') 796 template = parts[0] 797 if len(parts) > 1: 798 target_mime = parts[1].strip() 799 if len(parts) > 2: 800 target_ext = parts[2].strip() 801 if target_ext is None: 802 if target_mime is not None: 803 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime) 804 805 mugshot = self.pat.document_folder.latest_mugshot 806 if mugshot is None: 807 if self.debug: 808 return _('no mugshot available') 809 return u'' 810 811 fname = mugshot.export_to_file ( 812 target_mime = target_mime, 813 target_extension = target_ext, 814 ignore_conversion_problems = True 815 ) 816 if fname is None: 817 if self.debug: 818 return _('cannot export or convert latest mugshot') 819 return u'' 820 821 return template % fname
822 #--------------------------------------------------------
823 - def _get_variant_patient_tags(self, data=u'%s//\\n'):
824 if len(self.pat.tags) == 0: 825 if self.debug: 826 return _('no tags for this patient') 827 return u'' 828 829 tags = gmDemographicsWidgets.select_patient_tags(patient = self.pat) 830 831 if tags is None: 832 if self.debug: 833 return _('no patient tags selected for inclusion') % data 834 return u'' 835 836 template, separator = data.split('//', 2) 837 838 return separator.join([ template % t.fields_as_dict() for t in tags ])
839 # #-------------------------------------------------------- 840 # def _get_variant_patient_tags_table(self, data=u'?'): 841 # pass 842 #--------------------------------------------------------
843 - def _get_variant_current_provider_external_id(self, data=u''):
844 data_parts = data.split(u'//') 845 if len(data_parts) < 2: 846 return u'current provider external ID: template is missing' 847 848 id_type = data_parts[0].strip() 849 if id_type == u'': 850 return u'current provider external ID: type is missing' 851 852 issuer = data_parts[1].strip() 853 if issuer == u'': 854 return u'current provider external ID: issuer is missing' 855 856 prov = gmStaff.gmCurrentProvider() 857 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer) 858 859 if len(ids) == 0: 860 if self.debug: 861 return _('no external ID [%s] by [%s]') % (id_type, issuer) 862 return u'' 863 864 return ids[0]['value']
865 #--------------------------------------------------------
867 data_parts = data.split(u'//') 868 if len(data_parts) < 2: 869 return u'primary in-praxis provider external ID: template is missing' 870 871 id_type = data_parts[0].strip() 872 if id_type == u'': 873 return u'primary in-praxis provider external ID: type is missing' 874 875 issuer = data_parts[1].strip() 876 if issuer == u'': 877 return u'primary in-praxis provider external ID: issuer is missing' 878 879 prov = self.pat.primary_provider 880 if prov is None: 881 if self.debug: 882 return _('no primary in-praxis provider') 883 return u'' 884 885 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer) 886 887 if len(ids) == 0: 888 if self.debug: 889 return _('no external ID [%s] by [%s]') % (id_type, issuer) 890 return u'' 891 892 return ids[0]['value']
893 #--------------------------------------------------------
894 - def _get_variant_external_id(self, data=u''):
895 data_parts = data.split(u'//') 896 if len(data_parts) < 2: 897 return u'patient external ID: template is missing' 898 899 id_type = data_parts[0].strip() 900 if id_type == u'': 901 return u'patient external ID: type is missing' 902 903 issuer = data_parts[1].strip() 904 if issuer == u'': 905 return u'patient external ID: issuer is missing' 906 907 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer) 908 909 if len(ids) == 0: 910 if self.debug: 911 return _('no external ID [%s] by [%s]') % (id_type, issuer) 912 return u'' 913 914 return ids[0]['value']
915 #--------------------------------------------------------
916 - def _get_variant_allergy_list(self, data=None):
917 if data is None: 918 return [_('template is missing')] 919 920 template, separator = data.split('//', 2) 921 922 emr = self.pat.get_emr() 923 return separator.join([ template % a for a in emr.get_allergies() ])
924 #--------------------------------------------------------
925 - def _get_variant_allergies(self, data=None):
926 927 if data is None: 928 return [_('template is missing')] 929 930 emr = self.pat.get_emr() 931 return u'\n'.join([ data % a for a in emr.get_allergies() ])
932 #--------------------------------------------------------
933 - def _get_variant_current_meds(self, data=None):
934 935 if data is None: 936 return [_('template is missing')] 937 938 emr = self.pat.get_emr() 939 current_meds = emr.get_current_substance_intake ( 940 include_inactive = False, 941 include_unapproved = False, 942 order_by = u'brand, substance' 943 ) 944 945 return u'\n'.join([ data % m.fields_as_dict(date_format = '%Y %B %d') for m in current_meds ])
946 #--------------------------------------------------------
947 - def _get_variant_current_meds_table(self, data=None):
948 949 options = data.split('//') 950 951 if u'latex' in options: 952 return gmMedication.format_substance_intake ( 953 emr = self.pat.get_emr(), 954 output_format = u'latex', 955 table_type = u'by-brand' 956 ) 957 958 _log.error('no known current medications table formatting style in [%s]', data) 959 return _('unknown current medication table formatting style')
960 #--------------------------------------------------------
961 - def _get_variant_current_meds_notes(self, data=None):
962 963 options = data.split('//') 964 965 if u'latex' in options: 966 return gmMedication.format_substance_intake_notes ( 967 emr = self.pat.get_emr(), 968 output_format = u'latex', 969 table_type = u'by-brand' 970 ) 971 972 _log.error('no known current medications notes formatting style in [%s]', data) 973 return _('unknown current medication notes formatting style')
974 #--------------------------------------------------------
975 - def _get_variant_lab_table(self, data=None):
976 977 options = data.split('//') 978 979 emr = self.pat.get_emr() 980 981 if u'latex' in options: 982 return gmPathLab.format_test_results ( 983 results = emr.get_test_results_by_date(), 984 output_format = u'latex' 985 ) 986 987 _log.error('no known test results table formatting style in [%s]', data) 988 return _('unknown test results table formatting style [%s]') % data
989 #--------------------------------------------------------
990 - def _get_variant_latest_vaccs_table(self, data=None):
991 992 options = data.split('//') 993 994 emr = self.pat.get_emr() 995 996 if u'latex' in options: 997 return gmVaccination.format_latest_vaccinations(output_format = u'latex', emr = emr) 998 999 _log.error('no known vaccinations table formatting style in [%s]', data) 1000 return _('unknown vaccinations table formatting style [%s]') % data
1001 #--------------------------------------------------------
1002 - def _get_variant_vaccination_history(self, data=None):
1003 options = data.split('//') 1004 template = options[0] 1005 if len(options) > 1: 1006 date_format = options[1] 1007 else: 1008 date_format = u'%Y %B %d' 1009 1010 emr = self.pat.get_emr() 1011 vaccs = emr.get_vaccinations(order_by = u'date_given DESC, vaccine') 1012 1013 return u'\n'.join([ template % v.fields_as_dict(date_format = date_format) for v in vaccs ])
1014 #--------------------------------------------------------
1015 - def _get_variant_PHX(self, data=None):
1016 1017 if data is None: 1018 if self.debug: 1019 _log.error('PHX: missing placeholder arguments') 1020 return _('PHX: Invalid placeholder options.') 1021 return u'' 1022 1023 _log.debug('arguments: %s', data) 1024 1025 data_parts = data.split(u'//') 1026 template = u'%s' 1027 separator = u'\n' 1028 date_format = '%Y %B %d' 1029 esc_style = None 1030 try: 1031 template = data_parts[0] 1032 separator = data_parts[1] 1033 date_format = data_parts[2] 1034 esc_style = data_parts[3] 1035 except IndexError: 1036 pass 1037 1038 phxs = gmEMRStructWidgets.select_health_issues(emr = self.pat.emr) 1039 if phxs is None: 1040 if self.debug: 1041 return _('no PHX for this patient (available or selected)') 1042 return u'' 1043 1044 return separator.join ([ 1045 template % phx.fields_as_dict ( 1046 date_format = date_format, 1047 escape_style = esc_style, 1048 bool_strings = (_('yes'), _('no')) 1049 ) for phx in phxs 1050 ])
1051 #--------------------------------------------------------
1052 - def _get_variant_problems(self, data=None):
1053 1054 if data is None: 1055 return [_('template is missing')] 1056 1057 probs = self.pat.get_emr().get_problems() 1058 1059 return u'\n'.join([ data % p for p in probs ])
1060 #--------------------------------------------------------
1061 - def _get_variant_today(self, data='%x'):
1062 return gmDateTime.pydt_now_here().strftime(str(data)).decode(gmI18N.get_encoding())
1063 #--------------------------------------------------------
1064 - def _get_variant_tex_escape(self, data=None):
1065 return gmTools.tex_escape_string(text = data)
1066 #--------------------------------------------------------
1067 - def _get_variant_text_snippet(self, data=None):
1068 data_parts = data.split(u'//') 1069 keyword = data_parts[0] 1070 template = u'%s' 1071 if len(data_parts) > 1: 1072 template = data_parts[1] 1073 1074 expansion = gmKeywordExpansion.get_expansion ( 1075 keyword = keyword, 1076 textual_only = True, 1077 binary_only = False 1078 ) 1079 if expansion is None: 1080 if self.debug: 1081 return _('no textual expansion found for keyword <%s>' % keyword) 1082 return u'' 1083 1084 # FIXME: support decryption 1085 return template % expansion['expansion']
1086 1087 #--------------------------------------------------------
1088 - def _get_variant_data_snippet(self, data=None):
1089 parts = data.split(u'//') 1090 keyword = parts[0] 1091 template = u'%s' 1092 target_mime = None 1093 target_ext = None 1094 if len(parts) > 1: 1095 template = parts[1] 1096 if len(parts) > 2: 1097 target_mime = parts[2].strip() 1098 if len(parts) > 3: 1099 target_ext = parts[3].strip() 1100 if target_ext is None: 1101 if target_mime is not None: 1102 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime) 1103 1104 expansion = gmKeywordExpansion.get_expansion ( 1105 keyword = keyword, 1106 textual_only = False, 1107 binary_only = True 1108 ) 1109 if expansion is None: 1110 if self.debug: 1111 return _('no binary expansion found for keyword <%s>' % keyword) 1112 return u'' 1113 1114 filename = expansion.export_to_file ( 1115 target_mime = target_mime, 1116 target_extension = target_ext, 1117 ignore_conversion_problems = True 1118 ) 1119 if filename is None: 1120 if self.debug: 1121 return _('cannot export or convert data for binary expansion keyword <%s>' % keyword) 1122 return u'' 1123 1124 # FIXME: support decryption 1125 return template % filename
1126 1127 #--------------------------------------------------------
1128 - def _get_variant_free_text(self, data=u'tex//'):
1129 # <data>: 1130 # format: tex (only, currently) 1131 # message: shown in input dialog, must not contain "//" or "::" 1132 1133 data_parts = data.split('//') 1134 format = data_parts[0] 1135 if len(data_parts) > 1: 1136 msg = data_parts[1] 1137 else: 1138 msg = _('generic text') 1139 1140 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 1141 None, 1142 -1, 1143 title = _('Replacing <free_text> placeholder'), 1144 msg = _('Below you can enter free text.\n\n [%s]') % msg 1145 ) 1146 dlg.enable_user_formatting = True 1147 decision = dlg.ShowModal() 1148 1149 if decision != wx.ID_SAVE: 1150 dlg.Destroy() 1151 if self.debug: 1152 return _('Text input cancelled by user.') 1153 return u'' 1154 1155 text = dlg.value.strip() 1156 if dlg.is_user_formatted: 1157 dlg.Destroy() 1158 return text 1159 1160 dlg.Destroy() 1161 1162 if format == u'tex': 1163 return gmTools.tex_escape_string(text = text) 1164 1165 return text
1166 #--------------------------------------------------------
1167 - def _get_variant_bill(self, data=None):
1168 try: 1169 bill = self.__cache['bill'] 1170 except KeyError: 1171 from Gnumed.wxpython import gmBillingWidgets 1172 bill = gmBillingWidgets.manage_bills(patient = self.pat) 1173 if bill is None: 1174 if self.debug: 1175 return _('no bill selected') 1176 return u'' 1177 self.__cache['bill'] = bill 1178 1179 return data % bill.fields_as_dict(date_format = '%Y %B %d')
1180 #--------------------------------------------------------
1181 - def _get_variant_bill_item(self, data=None):
1182 try: 1183 bill = self.__cache['bill'] 1184 except KeyError: 1185 from Gnumed.wxpython import gmBillingWidgets 1186 bill = gmBillingWidgets.manage_bills(patient = self.pat) 1187 if bill is None: 1188 if self.debug: 1189 return _('no bill selected') 1190 return u'' 1191 self.__cache['bill'] = bill 1192 1193 return u'\n'.join([ data % i.fields_as_dict(date_format = '%Y %B %d') for i in bill.bill_items ])
1194 #-------------------------------------------------------- 1195 # internal helpers 1196 #--------------------------------------------------------
1197 - def __let_user_select_comm_type(self, missing=None):
1198 pass
1199 #=====================================================================
1200 -class cMacroPrimitives:
1201 """Functions a macro can legally use. 1202 1203 An instance of this class is passed to the GNUmed scripting 1204 listener. Hence, all actions a macro can legally take must 1205 be defined in this class. Thus we achieve some screening for 1206 security and also thread safety handling. 1207 """ 1208 #-----------------------------------------------------------------
1209 - def __init__(self, personality = None):
1210 if personality is None: 1211 raise gmExceptions.ConstructorError, 'must specify personality' 1212 self.__personality = personality 1213 self.__attached = 0 1214 self._get_source_personality = None 1215 self.__user_done = False 1216 self.__user_answer = 'no answer yet' 1217 self.__pat = gmPerson.gmCurrentPatient() 1218 1219 self.__auth_cookie = str(random.random()) 1220 self.__pat_lock_cookie = str(random.random()) 1221 self.__lock_after_load_cookie = str(random.random()) 1222 1223 _log.info('slave mode personality is [%s]', personality)
1224 #----------------------------------------------------------------- 1225 # public API 1226 #-----------------------------------------------------------------
1227 - def attach(self, personality = None):
1228 if self.__attached: 1229 _log.error('attach with [%s] rejected, already serving a client', personality) 1230 return (0, _('attach rejected, already serving a client')) 1231 if personality != self.__personality: 1232 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality)) 1233 return (0, _('attach to personality [%s] rejected') % personality) 1234 self.__attached = 1 1235 self.__auth_cookie = str(random.random()) 1236 return (1, self.__auth_cookie)
1237 #-----------------------------------------------------------------
1238 - def detach(self, auth_cookie=None):
1239 if not self.__attached: 1240 return 1 1241 if auth_cookie != self.__auth_cookie: 1242 _log.error('rejecting detach() with cookie [%s]' % auth_cookie) 1243 return 0 1244 self.__attached = 0 1245 return 1
1246 #-----------------------------------------------------------------
1247 - def force_detach(self):
1248 if not self.__attached: 1249 return 1 1250 self.__user_done = False 1251 # FIXME: use self.__sync_cookie for syncing with user interaction 1252 wx.CallAfter(self._force_detach) 1253 return 1
1254 #-----------------------------------------------------------------
1255 - def version(self):
1256 ver = _cfg.get(option = u'client_version') 1257 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
1258 #-----------------------------------------------------------------
1259 - def shutdown_gnumed(self, auth_cookie=None, forced=False):
1260 """Shuts down this client instance.""" 1261 if not self.__attached: 1262 return 0 1263 if auth_cookie != self.__auth_cookie: 1264 _log.error('non-authenticated shutdown_gnumed()') 1265 return 0 1266 wx.CallAfter(self._shutdown_gnumed, forced) 1267 return 1
1268 #-----------------------------------------------------------------
1269 - def raise_gnumed(self, auth_cookie = None):
1270 """Raise ourselves to the top of the desktop.""" 1271 if not self.__attached: 1272 return 0 1273 if auth_cookie != self.__auth_cookie: 1274 _log.error('non-authenticated raise_gnumed()') 1275 return 0 1276 return "cMacroPrimitives.raise_gnumed() not implemented"
1277 #-----------------------------------------------------------------
1278 - def get_loaded_plugins(self, auth_cookie = None):
1279 if not self.__attached: 1280 return 0 1281 if auth_cookie != self.__auth_cookie: 1282 _log.error('non-authenticated get_loaded_plugins()') 1283 return 0 1284 gb = gmGuiBroker.GuiBroker() 1285 return gb['horstspace.notebook.gui'].keys()
1286 #-----------------------------------------------------------------
1287 - def raise_notebook_plugin(self, auth_cookie = None, a_plugin = None):
1288 """Raise a notebook plugin within GNUmed.""" 1289 if not self.__attached: 1290 return 0 1291 if auth_cookie != self.__auth_cookie: 1292 _log.error('non-authenticated raise_notebook_plugin()') 1293 return 0 1294 # FIXME: use semaphore 1295 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin) 1296 return 1
1297 #-----------------------------------------------------------------
1298 - def load_patient_from_external_source(self, auth_cookie = None):
1299 """Load external patient, perhaps create it. 1300 1301 Callers must use get_user_answer() to get status information. 1302 It is unsafe to proceed without knowing the completion state as 1303 the controlled client may be waiting for user input from a 1304 patient selection list. 1305 """ 1306 if not self.__attached: 1307 return (0, _('request rejected, you are not attach()ed')) 1308 if auth_cookie != self.__auth_cookie: 1309 _log.error('non-authenticated load_patient_from_external_source()') 1310 return (0, _('rejected load_patient_from_external_source(), not authenticated')) 1311 if self.__pat.locked: 1312 _log.error('patient is locked, cannot load from external source') 1313 return (0, _('current patient is locked')) 1314 self.__user_done = False 1315 wx.CallAfter(self._load_patient_from_external_source) 1316 self.__lock_after_load_cookie = str(random.random()) 1317 return (1, self.__lock_after_load_cookie)
1318 #-----------------------------------------------------------------
1319 - def lock_loaded_patient(self, auth_cookie = None, lock_after_load_cookie = None):
1320 if not self.__attached: 1321 return (0, _('request rejected, you are not attach()ed')) 1322 if auth_cookie != self.__auth_cookie: 1323 _log.error('non-authenticated lock_load_patient()') 1324 return (0, _('rejected lock_load_patient(), not authenticated')) 1325 # FIXME: ask user what to do about wrong cookie 1326 if lock_after_load_cookie != self.__lock_after_load_cookie: 1327 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie) 1328 return (0, 'patient lock-after-load request rejected, wrong cookie provided') 1329 self.__pat.locked = True 1330 self.__pat_lock_cookie = str(random.random()) 1331 return (1, self.__pat_lock_cookie)
1332 #-----------------------------------------------------------------
1333 - def lock_into_patient(self, auth_cookie = None, search_params = None):
1334 if not self.__attached: 1335 return (0, _('request rejected, you are not attach()ed')) 1336 if auth_cookie != self.__auth_cookie: 1337 _log.error('non-authenticated lock_into_patient()') 1338 return (0, _('rejected lock_into_patient(), not authenticated')) 1339 if self.__pat.locked: 1340 _log.error('patient is already locked') 1341 return (0, _('already locked into a patient')) 1342 searcher = gmPersonSearch.cPatientSearcher_SQL() 1343 if type(search_params) == types.DictType: 1344 idents = searcher.get_identities(search_dict=search_params) 1345 raise StandardError("must use dto, not search_dict") 1346 else: 1347 idents = searcher.get_identities(search_term=search_params) 1348 if idents is None: 1349 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict)) 1350 if len(idents) == 0: 1351 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict)) 1352 # FIXME: let user select patient 1353 if len(idents) > 1: 1354 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict)) 1355 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]): 1356 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict)) 1357 self.__pat.locked = True 1358 self.__pat_lock_cookie = str(random.random()) 1359 return (1, self.__pat_lock_cookie)
1360 #-----------------------------------------------------------------
1361 - def unlock_patient(self, auth_cookie = None, unlock_cookie = None):
1362 if not self.__attached: 1363 return (0, _('request rejected, you are not attach()ed')) 1364 if auth_cookie != self.__auth_cookie: 1365 _log.error('non-authenticated unlock_patient()') 1366 return (0, _('rejected unlock_patient, not authenticated')) 1367 # we ain't locked anyways, so succeed 1368 if not self.__pat.locked: 1369 return (1, '') 1370 # FIXME: ask user what to do about wrong cookie 1371 if unlock_cookie != self.__pat_lock_cookie: 1372 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie) 1373 return (0, 'patient unlock request rejected, wrong cookie provided') 1374 self.__pat.locked = False 1375 return (1, '')
1376 #-----------------------------------------------------------------
1377 - def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
1378 if not self.__attached: 1379 return 0 1380 if auth_cookie != self.__auth_cookie: 1381 _log.error('non-authenticated select_identity()') 1382 return 0 1383 return "cMacroPrimitives.assume_staff_identity() not implemented"
1384 #-----------------------------------------------------------------
1385 - def get_user_answer(self):
1386 if not self.__user_done: 1387 return (0, 'still waiting') 1388 self.__user_done = False 1389 return (1, self.__user_answer)
1390 #----------------------------------------------------------------- 1391 # internal API 1392 #-----------------------------------------------------------------
1393 - def _force_detach(self):
1394 msg = _( 1395 'Someone tries to forcibly break the existing\n' 1396 'controlling connection. This may or may not\n' 1397 'have legitimate reasons.\n\n' 1398 'Do you want to allow breaking the connection ?' 1399 ) 1400 can_break_conn = gmGuiHelpers.gm_show_question ( 1401 aMessage = msg, 1402 aTitle = _('forced detach attempt') 1403 ) 1404 if can_break_conn: 1405 self.__user_answer = 1 1406 else: 1407 self.__user_answer = 0 1408 self.__user_done = True 1409 if can_break_conn: 1410 self.__pat.locked = False 1411 self.__attached = 0 1412 return 1
1413 #-----------------------------------------------------------------
1414 - def _shutdown_gnumed(self, forced=False):
1415 top_win = wx.GetApp().GetTopWindow() 1416 if forced: 1417 top_win.Destroy() 1418 else: 1419 top_win.Close()
1420 #-----------------------------------------------------------------
1422 patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True) 1423 if patient is not None: 1424 self.__user_answer = 1 1425 else: 1426 self.__user_answer = 0 1427 self.__user_done = True 1428 return 1
1429 #===================================================================== 1430 # main 1431 #===================================================================== 1432 if __name__ == '__main__': 1433 1434 if len(sys.argv) < 2: 1435 sys.exit() 1436 1437 if sys.argv[1] != 'test': 1438 sys.exit() 1439 1440 gmI18N.activate_locale() 1441 gmI18N.install_domain() 1442 1443 #--------------------------------------------------------
1444 - def test_placeholders():
1445 handler = gmPlaceholderHandler() 1446 handler.debug = True 1447 1448 for placeholder in ['a', 'b']: 1449 print handler[placeholder] 1450 1451 pat = gmPersonSearch.ask_for_patient() 1452 if pat is None: 1453 return 1454 1455 gmPatSearchWidgets.set_active_patient(patient = pat) 1456 1457 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d'] 1458 1459 app = wx.PyWidgetTester(size = (200, 50)) 1460 for placeholder in known_placeholders: 1461 print placeholder, "=", handler[placeholder] 1462 1463 ph = 'progress_notes::ap' 1464 print '%s: %s' % (ph, handler[ph])
1465 #--------------------------------------------------------
1466 - def test_new_variant_placeholders():
1467 1468 tests = [ 1469 # should work: 1470 '$<lastname>$', 1471 '$<lastname::::3>$', 1472 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$', 1473 1474 # should fail: 1475 'lastname', 1476 '$<lastname', 1477 '$<lastname::', 1478 '$<lastname::>$', 1479 '$<lastname::abc>$', 1480 '$<lastname::abc::>$', 1481 '$<lastname::abc::3>$', 1482 '$<lastname::abc::xyz>$', 1483 '$<lastname::::>$', 1484 '$<lastname::::xyz>$', 1485 1486 '$<date_of_birth::%Y-%m-%d>$', 1487 '$<date_of_birth::%Y-%m-%d::3>$', 1488 '$<date_of_birth::%Y-%m-%d::>$', 1489 1490 # should work: 1491 '$<adr_location::home::35>$', 1492 '$<gender_mapper::male//female//other::5>$', 1493 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$', 1494 '$<allergy_list::%(descriptor)s, >$', 1495 '$<current_meds_table::latex//by-brand>$' 1496 1497 # 'firstname', 1498 # 'title', 1499 # 'date_of_birth', 1500 # 'progress_notes', 1501 # 'soap', 1502 # 'soap_s', 1503 # 'soap_o', 1504 # 'soap_a', 1505 # 'soap_p', 1506 1507 # 'soap', 1508 # 'progress_notes', 1509 # 'date_of_birth' 1510 ] 1511 1512 # tests = [ 1513 # '$<latest_vaccs_table::latex>$' 1514 # ] 1515 1516 pat = gmPersonSearch.ask_for_patient() 1517 if pat is None: 1518 return 1519 1520 gmPatSearchWidgets.set_active_patient(patient = pat) 1521 1522 handler = gmPlaceholderHandler() 1523 handler.debug = True 1524 1525 for placeholder in tests: 1526 print placeholder, "=>", handler[placeholder] 1527 print "--------------" 1528 raw_input()
1529 1530 # print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d'] 1531 1532 # app = wx.PyWidgetTester(size = (200, 50)) 1533 # for placeholder in known_placeholders: 1534 # print placeholder, "=", handler[placeholder] 1535 1536 # ph = 'progress_notes::ap' 1537 # print '%s: %s' % (ph, handler[ph]) 1538 1539 #--------------------------------------------------------
1540 - def test_scripting():
1541 from Gnumed.pycommon import gmScriptingListener 1542 import xmlrpclib 1543 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999) 1544 1545 s = xmlrpclib.ServerProxy('http://localhost:9999') 1546 print "should fail:", s.attach() 1547 print "should fail:", s.attach('wrong cookie') 1548 print "should work:", s.version() 1549 print "should fail:", s.raise_gnumed() 1550 print "should fail:", s.raise_notebook_plugin('test plugin') 1551 print "should fail:", s.lock_into_patient('kirk, james') 1552 print "should fail:", s.unlock_patient() 1553 status, conn_auth = s.attach('unit test') 1554 print "should work:", status, conn_auth 1555 print "should work:", s.version() 1556 print "should work:", s.raise_gnumed(conn_auth) 1557 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james') 1558 print "should work:", status, pat_auth 1559 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie') 1560 print "should work", s.unlock_patient(conn_auth, pat_auth) 1561 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'} 1562 status, pat_auth = s.lock_into_patient(conn_auth, data) 1563 print "should work:", status, pat_auth 1564 print "should work", s.unlock_patient(conn_auth, pat_auth) 1565 print s.detach('bogus detach cookie') 1566 print s.detach(conn_auth) 1567 del s 1568 1569 listener.shutdown()
1570 #--------------------------------------------------------
1571 - def test_placeholder_regex():
1572 1573 import re as regex 1574 1575 tests = [ 1576 ' $<lastname>$ ', 1577 ' $<lastname::::3>$ ', 1578 1579 # should fail: 1580 '$<date_of_birth::%Y-%m-%d>$', 1581 '$<date_of_birth::%Y-%m-%d::3>$', 1582 '$<date_of_birth::%Y-%m-%d::>$', 1583 1584 '$<adr_location::home::35>$', 1585 '$<gender_mapper::male//female//other::5>$', 1586 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$', 1587 '$<allergy_list::%(descriptor)s, >$', 1588 1589 '\\noindent Patient: $<lastname>$, $<firstname>$', 1590 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$', 1591 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$' 1592 ] 1593 1594 tests = [ 1595 1596 'junk $<lastname::::3>$ junk', 1597 'junk $<lastname::abc::3>$ junk', 1598 'junk $<lastname::abc>$ junk', 1599 'junk $<lastname>$ junk', 1600 1601 'junk $<lastname>$ junk $<firstname>$ junk', 1602 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk', 1603 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk', 1604 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk' 1605 1606 ] 1607 1608 print "testing placeholder regex:", default_placeholder_regex 1609 print "" 1610 1611 for t in tests: 1612 print 'line: "%s"' % t 1613 print "placeholders:" 1614 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE): 1615 print ' => "%s"' % p 1616 print " "
1617 #--------------------------------------------------------
1618 - def test_placeholder():
1619 1620 phs = [ 1621 #u'emr_journal::soapu //%(clin_when)s %(modified_by)s %(soap_cat)s %(narrative)s//110::', 1622 #u'free_text::tex//placeholder test::9999', 1623 #u'soap_for_encounters:://::9999', 1624 #u'soap_a',, 1625 #u'encounter_list::%(started)s: %(assessment_of_encounter)s::30', 1626 #u'patient_comm::homephone::1234', 1627 #u'$<patient_address::work::1234>$', 1628 #u'adr_region::home::1234', 1629 #u'adr_country::fehlt::1234', 1630 #u'adr_subunit::fehlt::1234', 1631 #u'adr_suburb::fehlt-auch::1234', 1632 #u'external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234', 1633 #u'primary_praxis_provider', 1634 #u'current_provider', 1635 #u'current_provider_external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234', 1636 #u'current_provider_external_id::LANR//LÄK::1234' 1637 #u'primary_praxis_provider_external_id::LANR//LÄK::1234' 1638 #u'form_name_long::::1234', 1639 #u'form_name_long::::5', 1640 #u'form_name_long::::', 1641 #u'form_version::::5', 1642 #u'$<current_meds::\item %(brand)s %(preparation)s (%(substance)s) from %(started)s for %(duration)s as %(schedule)s until %(discontinued)s\\n::250>$', 1643 #u'$<vaccination_history::%(date_given)s: %(vaccine)s [%(batch_no)s] %(l10n_indications)s::250>$', 1644 #u'$<date_of_birth::%Y %B %d::20>$', 1645 #u'$<patient_tags::Tag "%(l10n_description)s": %(comment)s//\\n- ::250>$', 1646 #u'$<PHX::%(description)s\n side: %(laterality)s, active: %(is_active)s, relevant: %(clinically_relevant)s, caused death: %(is_cause_of_death)s//\n//%Y %B %d//latex::250>$', 1647 #u'$<patient_photo::\includegraphics[width=60mm]{%s}//image/png//.png::250>$', 1648 u'$<data_snippet::binary_test_snippet//path=<%s>//image/png//.png::250>$', 1649 u'$<data_snippet::autograph-LMcC//path=<%s>//image/jpg//.jpg::250>$' 1650 ] 1651 1652 handler = gmPlaceholderHandler() 1653 handler.debug = True 1654 1655 gmStaff.set_current_provider_to_logged_on_user() 1656 pat = gmPersonSearch.ask_for_patient() 1657 if pat is None: 1658 return 1659 1660 gmPatSearchWidgets.set_active_patient(patient = pat) 1661 1662 app = wx.PyWidgetTester(size = (200, 50)) 1663 #handler.set_placeholder('form_name_long', 'ein Testformular') 1664 for ph in phs: 1665 print ph 1666 print "result:" 1667 print '%s' % handler[ph]
1668 #handler.unset_placeholder('form_name_long') 1669 #-------------------------------------------------------- 1670 1671 #test_placeholders() 1672 #test_new_variant_placeholders() 1673 #test_scripting() 1674 #test_placeholder_regex() 1675 test_placeholder() 1676 1677 #===================================================================== 1678