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