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