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