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

Source Code for Module Gnumed.wxpython.gmMacro

   1  # -*- coding: utf-8 -*- 
   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 io 
  16  import datetime 
  17  import urllib.parse 
  18  import codecs 
  19  import re as regex 
  20   
  21   
  22  import wx 
  23   
  24   
  25  if __name__ == '__main__': 
  26          sys.path.insert(0, '../../') 
  27  from Gnumed.pycommon import gmI18N 
  28  if __name__ == '__main__': 
  29          gmI18N.activate_locale() 
  30          gmI18N.install_domain() 
  31  from Gnumed.pycommon import gmGuiBroker 
  32  from Gnumed.pycommon import gmTools 
  33  from Gnumed.pycommon import gmBorg 
  34  from Gnumed.pycommon import gmExceptions 
  35  from Gnumed.pycommon import gmCfg2 
  36  from Gnumed.pycommon import gmDateTime 
  37  from Gnumed.pycommon import gmMimeLib 
  38  from Gnumed.pycommon import gmShellAPI 
  39  from Gnumed.pycommon import gmCrypto 
  40   
  41  from Gnumed.business import gmPerson 
  42  from Gnumed.business import gmStaff 
  43  from Gnumed.business import gmDemographicRecord 
  44  from Gnumed.business import gmMedication 
  45  from Gnumed.business import gmPathLab 
  46  from Gnumed.business import gmPersonSearch 
  47  from Gnumed.business import gmVaccination 
  48  from Gnumed.business import gmKeywordExpansion 
  49  from Gnumed.business import gmPraxis 
  50   
  51  from Gnumed.wxpython import gmGuiHelpers 
  52  from Gnumed.wxpython import gmNarrativeWorkflows 
  53  from Gnumed.wxpython import gmPatSearchWidgets 
  54  from Gnumed.wxpython import gmPersonContactWidgets 
  55  from Gnumed.wxpython import gmPlugin 
  56  from Gnumed.wxpython import gmEMRStructWidgets 
  57  from Gnumed.wxpython import gmEncounterWidgets 
  58  from Gnumed.wxpython import gmListWidgets 
  59  from Gnumed.wxpython import gmDemographicsWidgets 
  60  from Gnumed.wxpython import gmDocumentWidgets 
  61  from Gnumed.wxpython import gmKeywordExpansionWidgets 
  62  from Gnumed.wxpython import gmPraxisWidgets 
  63  from Gnumed.wxpython import gmAddressWidgets 
  64   
  65   
  66  _log = logging.getLogger('gm.scripting') 
  67  _cfg = gmCfg2.gmCfgData() 
  68   
  69  #===================================================================== 
  70  # values for the following placeholders must be injected from the outside before 
  71  # using them, in use they must conform to the "placeholder::::max length" syntax, 
  72  # as long as they resolve to None they return their respective names so the 
  73  # developers can know which placeholder was not set 
  74  known_injectable_placeholders = [ 
  75          'form_name_long', 
  76          'form_name_short', 
  77          'form_version', 
  78          'form_version_internal', 
  79          'form_last_modified' 
  80  ] 
  81   
  82  # the following must satisfy the pattern "$<name::args::(optional) max string length>$" when used 
  83  __known_variant_placeholders = { 
  84          # generic: 
  85          'free_text': u"""show a dialog for entering some free text: 
  86                  args: <message>//<preset> 
  87                          <message>: shown in input dialog, must not contain either 
  88                                  of '::' and whatever the arguments divider is 
  89                                  set to (default '//'), 
  90                          <preset>: whatever to initially show inside the input field, 
  91                  caches input per <message>""", 
  92   
  93          'text_snippet': """a text snippet, taken from the keyword expansion mechanism: 
  94                  args: <snippet name>//<template>""", 
  95   
  96          'data_snippet': """a binary snippet, taken from the keyword expansion mechanism: 
  97                  args: <snippet name>//<template>//<optional target mime type>//<optional target extension> 
  98                  returns full path to an exported copy of the 
  99                  data rather than the data itself, 
 100                  template: string template for outputting the path 
 101                  target mime type: a mime type into which to convert the image, no conversion if not given 
 102                  target extension: target file name extension, derived from target mime type if not given 
 103          """, 
 104          u'qrcode': u"""generate QR code file for a text snippet: 
 105                  returns: path to QR code png file 
 106                  args: <text>//<template> 
 107                          text: utf8 text, will always be encoded in 'binary' mode with utf8 as encoding 
 108                          template: %s-template into which to insert the QR code png file path 
 109          """, 
 110   
 111          # control flow 
 112          'yes_no': """Ask user a yes-no question and return content based on answer. 
 113                  args: msg=<message>//yes=<yes>//no=<no> 
 114                          <message>: shown in the yes/no dialog presented to the user 
 115                          <yes>: returned if the user selects "yes" 
 116                          <no>: returned if the user selects "no" 
 117          """, 
 118   
 119          # text manipulation 
 120          'range_of': """select range of enclosed text (note that this cannot take into account non-length characters such as enclosed LaTeX code 
 121                  args: <enclosed text> 
 122          """, 
 123          'if_not_empty': """format text based on template if not empty 
 124                  args: <possibly-empty-text>//<template-if-not-empty>//<alternative-text-if-empty> 
 125          """, 
 126   
 127          u'tex_escape': u"args: string to escape, mostly obsolete now", 
 128   
 129          u'url_escape': u"""Escapes a string suitable for use as _data_ in an URL 
 130                  args: text to escape 
 131          """, 
 132   
 133          # internal state 
 134          'ph_cfg': u"""Set placeholder handler options. 
 135                  args: option name//option value//macro return string 
 136                  option names: 
 137                          ellipsis: what to use as ellipsis (if anything) when 
 138                                  shortening strings or string regions, setting the 
 139                                  value to NONE will switch off ellipis handling, 
 140                                  default is switched off 
 141                          argumentsdivider: what to use as divider when splitting 
 142                                  an argument string into parts, default is '//', 
 143                                  note that the 'config' placeholder will ALWAYS 
 144                                  use '//' to split its argument string, regardless 
 145                                  of which setting of <argumentsdivider> is in effect, 
 146                                  use DEFAULT to reset this setting back to the 
 147                                  default '//' 
 148                          encoding: the encoding in which data emitted by GNUmed 
 149                                  as placeholder replacement needs to be valid in, 
 150                                  note that GNUmed will still emit unicode to replacement 
 151                                  consumers but it will ensure the data emitted _can_ 
 152                                  be encoded by this target encoding (by roundtripping 
 153                                  unicode-encoding-unicode) 
 154                                  valid from where this placeholder is located at 
 155                                  until further change, 
 156                                  use DEFAULT to reset encoding back to the default 
 157                                  which is to not ensure compatibility, 
 158                                  if the encoding ends in '-strict' then the placeholder 
 159                                  replacement will fail if the roundtrip fails 
 160          """, 
 161          u'if_debugging': u"""set text based on whether debugging is active 
 162                  args: <text-if-debugging>//<text-if-not-debugging> 
 163          """, 
 164   
 165          'today': "args: strftime format", 
 166   
 167          'gender_mapper': """maps gender of patient to a string: 
 168                  args: <value when person is male> // <is female> // <is other> 
 169                  eg. 'male//female//other' 
 170                  or: 'Lieber Patient//Liebe Patientin'""", 
 171          'client_version': "the version of the current client as a string (no 'v' in front)", 
 172   
 173          'gen_adr_street': """part of a generic address, cached, selected from database: 
 174                  args: optional template//optional selection message//optional cache ID 
 175                  template: %s-style formatting template 
 176                  message: text message shown in address selection list 
 177                  cache ID: used to differentiate separate cached invocations of this placeholder 
 178          """, 
 179          'gen_adr_number': """part of a generic address, cached, selected from database: 
 180                  args: optional template//optional selection message//optional cache ID 
 181                  template: %s-style formatting template 
 182                  message: text message shown in address selection list 
 183                  cache ID: used to differentiate separate cached invocations of this placeholder 
 184          """, 
 185          'gen_adr_subunit': """part of a generic address, cached, selected from database: 
 186                  args: optional template//optional selection message//optional cache ID 
 187                  template: %s-style formatting template 
 188                  message: text message shown in address selection list 
 189                  cache ID: used to differentiate separate cached invocations of this placeholder 
 190          """, 
 191          'gen_adr_location': """part of a generic address, cached, selected from database: 
 192                  args: optional template//optional selection message//optional cache ID 
 193                  template: %s-style formatting template 
 194                  message: text message shown in address selection list 
 195                  cache ID: used to differentiate separate cached invocations of this placeholder 
 196          """, 
 197          'gen_adr_suburb': """part of a generic address, cached, selected from database: 
 198                  args: optional template//optional selection message//optional cache ID 
 199                  template: %s-style formatting template 
 200                  message: text message shown in address selection list 
 201                  cache ID: used to differentiate separate cached invocations of this placeholder 
 202          """, 
 203          'gen_adr_postcode': """part of a generic address, cached, selected from database: 
 204                  args: optional template//optional selection message//optional cache ID 
 205                  template: %s-style formatting template 
 206                  message: text message shown in address selection list 
 207                  cache ID: used to differentiate separate cached invocations of this placeholder 
 208          """, 
 209          'gen_adr_region': """part of a generic address, cached, selected from database: 
 210                  args: optional template//optional selection message//optional cache ID 
 211                  template: %s-style formatting template 
 212                  message: text message shown in address selection list 
 213                  cache ID: used to differentiate separate cached invocations of this placeholder 
 214          """, 
 215          'gen_adr_country': """part of a generic address, cached, selected from database: 
 216                  args: optional template//optional selection message//optional cache ID 
 217                  template: %s-style formatting template 
 218                  message: text message shown in address selection list 
 219                  cache ID: used to differentiate separate cached invocations of this placeholder 
 220          """, 
 221   
 222          'receiver_name': """the receiver name, cached, selected from database: 
 223                  receivers are presented for selection from people/addresses related 
 224                  to the patient in some way or other, 
 225                  args: optional template//optional cache ID 
 226                  template: %s-style formatting template 
 227                  cache ID: used to differentiate separate cached invocations of this placeholder 
 228          """, 
 229          'receiver_street': """part of a receiver address, cached, selected from database: 
 230                  receivers are presented for selection from people/addresses related 
 231                  to the patient in some way or other, 
 232                  args: optional template//optional cache ID 
 233                  template: %s-style formatting template 
 234                  cache ID: used to differentiate separate cached invocations of this placeholder 
 235          """, 
 236          'receiver_number': """part of a receiver address, cached, selected from database: 
 237                  receivers are presented for selection from people/addresses related 
 238                  to the patient in some way or other, 
 239                  args: optional template//optional cache ID 
 240                  template: %s-style formatting template 
 241                  cache ID: used to differentiate separate cached invocations of this placeholder 
 242          """, 
 243          'receiver_subunit': """part of a receiver address, cached, selected from database: 
 244                  receivers are presented for selection from people/addresses related 
 245                  to the patient in some way or other, 
 246                  args: optional template//optional cache ID 
 247                  template: %s-style formatting template 
 248                  cache ID: used to differentiate separate cached invocations of this placeholder 
 249          """, 
 250          'receiver_location': """part of a receiver address, cached, selected from database: 
 251                  receivers are presented for selection from people/addresses related 
 252                  to the patient in some way or other, 
 253                  args: optional template//optional cache ID 
 254                  template: %s-style formatting template 
 255                  cache ID: used to differentiate separate cached invocations of this placeholder 
 256          """, 
 257          'receiver_suburb': """part of a receiver address, cached, selected from database: 
 258                  receivers are presented for selection from people/addresses related 
 259                  to the patient in some way or other, 
 260                  args: optional template//optional cache ID 
 261                  template: %s-style formatting template 
 262                  cache ID: used to differentiate separate cached invocations of this placeholder 
 263          """, 
 264          'receiver_postcode': """part of a receiver address, cached, selected from database: 
 265                  receivers are presented for selection from people/addresses related 
 266                  to the patient in some way or other, 
 267                  args: optional template//optional cache ID 
 268                  template: %s-style formatting template 
 269                  cache ID: used to differentiate separate cached invocations of this placeholder 
 270          """, 
 271          'receiver_region': """part of a receiver address, cached, selected from database: 
 272                  receivers are presented for selection from people/addresses related 
 273                  to the patient in some way or other, 
 274                  args: optional template//optional cache ID 
 275                  template: %s-style formatting template 
 276                  cache ID: used to differentiate separate cached invocations of this placeholder 
 277          """, 
 278          'receiver_country': """part of a receiver address, cached, selected from database: 
 279                  receivers are presented for selection from people/addresses related 
 280                  to the patient in some way or other, 
 281                  args: optional template//optional cache ID 
 282                  template: %s-style formatting template 
 283                  cache ID: used to differentiate separate cached invocations of this placeholder 
 284          """, 
 285   
 286          # patient demographics: 
 287          'name': "args: template for name parts arrangement", 
 288          'date_of_birth': "args: strftime date/time format directive", 
 289   
 290          'patient_address': "args: <type of address>//<optional formatting template>", 
 291          'adr_street': "args: <type of address>, cached per type", 
 292          'adr_number': "args: <type of address>, cached per type", 
 293          'adr_subunit': "args: <type of address>, cached per type", 
 294          'adr_location': "args: <type of address>, cached per type", 
 295          'adr_suburb': "args: <type of address>, cached per type", 
 296          'adr_postcode': "args: <type of address>, cached per type", 
 297          'adr_region': "args: <type of address>, cached per type", 
 298          'adr_country': "args: <type of address>, cached per type", 
 299   
 300          'patient_comm': "args: <comm channel type as per database>//<%(field)s-template>", 
 301   
 302          'patient_vcf': """returns path to VCF for current patient 
 303                  args: <template> 
 304                  template: %s-template for path 
 305          """, 
 306          'patient_gdt': """returns path to GDT for current patient 
 307                  args: <template> 
 308                  template: %s-template for path 
 309          """, 
 310          u'patient_mcf': u"""returns MECARD for current patient 
 311                  args: <format>//<template> 
 312                  format: fmt=qr|mcf|txt 
 313                          qr: QR code png file path, 
 314                          mcf: MECARD .mcf file path, 
 315                          txt: MECARD string, 
 316                          default - if omitted - is "txt", 
 317                  template: tmpl=<%s-template string>, "%s" if omitted 
 318          """, 
 319   
 320          'patient_tags': "args: <%(field)s-template>//<separator>", 
 321          #u'patient_tags_table': u"no args", 
 322          'patient_photo': """outputs URL to exported patient photo (cached per mime type and extension): 
 323                  args: <template>//<optional target mime type>//<optional target extension>, 
 324                  returns full path to an exported copy of the 
 325                  image rather than the image data itself, 
 326                  returns u'' if no mugshot available, 
 327                  template: string template for outputting the path 
 328                  target mime type: a mime type into which to convert the image, no conversion if not given 
 329                  target extension: target file name extension, derived from target mime type if not given""", 
 330          'external_id': "args: <type of ID>//<issuer of ID>", 
 331   
 332   
 333          # clinical record related: 
 334          'soap': "get all of SOAPU/ADMIN, no template in args needed", 
 335          'soap_s': "get subset of SOAPU/ADMIN, no template in args needed", 
 336          'soap_o': "get subset of SOAPU/ADMIN, no template in args needed", 
 337          'soap_a': "get subset of SOAPU/ADMIN, no template in args needed", 
 338          'soap_p': "get subset of SOAPU/ADMIN, no template in args needed", 
 339          'soap_u': "get subset of SOAPU/ADMIN, no template in args needed", 
 340          'soap_admin': "get subset of SOAPU/ADMIN, no template in args needed", 
 341   
 342          'progress_notes': """get progress notes: 
 343                  args: categories//template 
 344                  categories: string with 'soapu '; ' ' == None == admin 
 345                  template:       u'something %s something'               (do not include // in template !)""", 
 346   
 347          'soap_for_encounters': """lets the user select a list of encounters for which: 
 348                  LaTeX formatted progress notes are emitted, 
 349                  args: soap categories // strftime date format""", 
 350   
 351          'soap_by_issue': """lets the user select a list of issues and then SOAP entries from those issues: 
 352                  args: soap categories // strftime date format // template""", 
 353   
 354          'soap_by_episode': """lets the user select a list of episodes and then SOAP entries from those episodes: 
 355                  args: soap categories // strftime date format // template""", 
 356   
 357          'emr_journal': """returns EMR journal view entries: 
 358                  args format:   <categories>//<template>//<line length>//<time range>//<target format> 
 359                  categories:        string with any of "s", "o", "a", "p", "u", " "; (" " == None == admin category) 
 360                  template:          something %s something else (Do not include // in the template !) 
 361                  line length:   the maximum length of individual lines, not the total placeholder length 
 362                  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)""", 
 363   
 364          'substance_abuse': """returns substance abuse entries: 
 365                  args: line template 
 366          """, 
 367   
 368          'current_meds': """returns current medications: 
 369                  args: line template//<select> 
 370                  <select>: if this is present the user will be asked which meds to export""", 
 371   
 372          'current_meds_for_rx': """formats substance intakes either by substance (non-product intakes) or by producdt (once per product intake, even if multi-component): 
 373                  args: <line template> 
 374                  <line_template>: template into which to insert each intake, keys from 
 375                  clin.v_substance_intakes, special additional keys: 
 376                          %(contains)s -- list of components 
 377                          %(amount2dispense)s -- how much/many to dispense""", 
 378   
 379          'current_meds_AMTS': """emit LaTeX longtable lines with appropriate page breaks: 
 380                  also creates per-page AMTS QR codes and sets the 
 381                  following internal placeholders: 
 382                          amts_png_file_1 
 383                          amts_png_file_2 
 384                          amts_png_file_3 
 385                          amts_data_file_1 
 386                          amts_data_file_2 
 387                          amts_data_file_3 
 388                          amts_png_file_current_page 
 389                          amts_data_file_utf8 
 390                          amts_png_file_utf8 
 391                  the last of which contains the LaTeX command \\thepage (such that 
 392                  LaTeX can use this in, say, page headers) but omitting the .png 
 393                  (for which LaTeX will look by itself), 
 394                  note that you will have to use the 2nd- or 3rd-pass placeholder 
 395                  format if you plan to insert the above because they will only be 
 396                  available by first (or second) pass processing of the initial 
 397                  placeholder "current_meds_AMTS" 
 398          """, 
 399          'current_meds_AMTS_enhanced': """emit LaTeX longtable lines with appropriate page breaks: 
 400                  this returns the same content as current_meds_AMTS except that 
 401                  it does not truncate output data whenever possible 
 402          """, 
 403   
 404          'current_meds_table': "emits a LaTeX table, no arguments", 
 405          'current_meds_notes': "emits a LaTeX table, no arguments", 
 406          'lab_table': "emits a LaTeX table, no arguments", 
 407          'test_results': "args: <%(field)s-template>//<date format>//<line separator (EOL)>", 
 408          'most_recent_test_results': """most recent test results formatted as defined in <template>: 
 409                  args: <dfmt=...>//<tmpl=...>//<sep=...> 
 410                  <dfmt=...>: strftime format string for test result timestamps, 
 411                  <tmpl=...>: target format specific %s-template, applied to each test result 
 412                  <sep=...>: line separator, can be a Python string escape, such as \n 
 413          """, 
 414          'latest_vaccs_table': "emits a LaTeX table, no arguments", 
 415          'vaccination_history': "args: <%(field)s-template//date format> to format one vaccination per line", 
 416          'allergy_state': "no arguments", 
 417          'allergies': "args: line template, one allergy per line", 
 418          'allergy_list': "args holds: template per allergy, all allergies on one line", 
 419          'problems': "args holds: line template, one problem per line", 
 420          'diagnoses': 'args: line template, one diagnosis per line', 
 421          'PHX': "Past medical HiXtory; args: line template//separator//strftime date format", 
 422          'encounter_list': "args: per-encounter template, each ends up on one line", 
 423   
 424          'documents': """retrieves documents from the archive: 
 425                  args:   <select>//<description>//<template>//<path template>//<path> 
 426                  select: let user select which documents to include, optional, if not given: all documents included 
 427                  description:    whether to include descriptions, optional 
 428                  template:       something %(field)s something else (do not include '//' or '::' itself in the template) 
 429                  path template:  the template for outputting the path to exported 
 430                          copies of the document pages, if not given no pages are exported, 
 431                          this template can contain "%(name)s" and/or "%(fullpath)s" which  
 432                          is replaced by the appropriate value for each exported file 
 433                  path:   into which path to export copies of the document pages, temp dir if not given""", 
 434   
 435          'reminders': """patient reminders: 
 436                  args:   <template>//<date format> 
 437                  template:       something %(field)s something else (do not include '//' or '::' itself in the template)""", 
 438   
 439          'external_care': """External care entries: 
 440                  args:   <template> 
 441                  template:       something %(field)s something else (do not include '//' or '::' itself in the template)""", 
 442   
 443          # provider related: 
 444          'current_provider': "no arguments", 
 445          'current_provider_name': """formatted name of current provider: 
 446                  args: <template>, 
 447                  template:       something %(field)s something else (do not include '//' or '::' itself in the template) 
 448          """, 
 449          'current_provider_title': """formatted name of current provider: 
 450                  args: <optional template>, 
 451                  template:       something %(title)s something else (do not include '//' or '::' itself in the template) 
 452          """, 
 453          'current_provider_firstnames': """formatted name of current provider: 
 454                  args: <optional template>, 
 455                  template:       something %(firstnames)s something else (do not include '//' or '::' itself in the template) 
 456          """, 
 457          'current_provider_lastnames': """formatted name of current provider: 
 458                  args: <optional template>, 
 459                  template:       something %(lastnames)s something else (do not include '//' or '::' itself in the template) 
 460          """, 
 461          'current_provider_external_id': "args: <type of ID>//<issuer of ID>", 
 462          'primary_praxis_provider': "primary provider for current patient in this praxis", 
 463          'primary_praxis_provider_external_id': "args: <type of ID>//<issuer of ID>", 
 464   
 465   
 466          # praxis related: 
 467          'praxis': """retrieve current branch of your praxis: 
 468                  args: <template>//select 
 469                  template:               something %(field)s something else (do not include '//' or '::' itself in the template) 
 470                  select:                 if this is present allow selection of the branch rather than using the current branch""", 
 471   
 472          'praxis_address': "args: <optional formatting template>", 
 473          'praxis_comm': "args: type//<optional formatting template>", 
 474          'praxis_id': "args: <type of ID>//<issuer of ID>//<optional formatting template>", 
 475          'praxis_vcf': """returns path to VCF for current praxis branch 
 476                  args: <template> 
 477                  template: %s-template for path 
 478          """, 
 479          u'praxis_mcf': u"""returns MECARD for current praxis branch 
 480                  args: <format>//<template> 
 481                  format: fmt=qr|mcf|txt 
 482                          qr: QR code png file path, 
 483                          mcf: MECARD .mcf file path, 
 484                          txt: MECARD string, 
 485                          default - if omitted - is "txt", 
 486                  template: tmpl=<%s-template string>, "%s" if omitted 
 487          """, 
 488          u'praxis_scan2pay': u"""return scan2pay data or QR code for current praxis 
 489                  args: <format>, 
 490                          format: fmt=qr|txt 
 491                                  qr: QR code png file path, 
 492                                  txt: scan2pay data string, 
 493                                  default - if omitted: qr 
 494          """, 
 495   
 496          # billing related: 
 497          'bill': """retrieve a bill 
 498                  args: <template>//<date format> 
 499                  template:               something %(field)s something else (do not include '//' or '::' itself in the template) 
 500                  date format:    strftime date format""", 
 501          'bill_scan2pay': u"""return scan2pay data or QR code for a bill 
 502                  args: <format>, 
 503                          format: fmt=qr|txt 
 504                                  qr: QR code png file path, 
 505                                  txt: scan2pay data string, 
 506                                  default - if omitted: qr 
 507          """, 
 508          'bill_item': """retrieve the items of a previously retrieved (and therefore cached until the next retrieval) bill 
 509                  args: <template>//<date format> 
 510                  template:               something %(field)s something else (do not include '//' or '::' itself in the template) 
 511                  date format:    strftime date format""", 
 512          'bill_adr_street': "args: optional template (%s-style formatting template); cached per bill", 
 513          'bill_adr_number': "args: optional template (%s-style formatting template); cached per bill", 
 514          'bill_adr_subunit': "args: optional template (%s-style formatting template); cached per bill", 
 515          'bill_adr_location': "args: optional template (%s-style formatting template); cached per bill", 
 516          'bill_adr_suburb': "args: optional template (%s-style formatting template); cached per bill", 
 517          'bill_adr_postcode': "args: optional template (%s-style formatting template); cached per bill", 
 518          'bill_adr_region': "args: optional template (%s-style formatting template); cached per bill", 
 519          'bill_adr_country': "args: optional template (%s-style formatting template); cached per bill" 
 520   
 521  } 
 522   
 523  known_variant_placeholders = __known_variant_placeholders.keys() 
 524  #known_variant_placeholders.sort() 
 525   
 526   
 527  # http://help.libreoffice.org/Common/List_of_Regular_Expressions 
 528  # except that OOo cannot be non-greedy |-( 
 529  #default_placeholder_regex = r'\$<.+?>\$'                               # previous working placeholder 
 530          # regex logic: 
 531          # starts with "$" 
 532          # followed by "<" 
 533          # followed by > 0 characters but NOT "<" but ONLY up to the NEXT ":" 
 534          # followed by "::" 
 535          # followed by any number of characters  but ONLY up to the NEXT ":" 
 536          # followed by "::" 
 537          # followed by any number of numbers 
 538          # followed by ">" 
 539          # followed by "$" 
 540   
 541  # previous: 
 542  default_placeholder_regex = r'\$<[^<:]+::.*?::\d*?>\$|\$<[^<:]+::.*?::\d+-\d+>\$'         # this one works [except that OOo cannot be non-greedy |-(    ] 
 543  first_pass_placeholder_regex = r'|'.join ([ 
 544          r'\$<[^<:]+::.*?(?=::\d*?>\$)::\d*?>\$', 
 545          r'\$<[^<:]+::.*?(?=::\d+-\d+>\$)::\d+-\d+>\$' 
 546  ]) 
 547  second_pass_placeholder_regex = r'|'.join ([ 
 548          r'\$<<[^<:]+?::.*?(?=::\d*?>>\$)::\d*?>>\$', 
 549          r'\$<<[^<:]+?::.*?(?=::\d+-\d+>>\$)::\d+-\d+>>\$' 
 550  ]) 
 551  third_pass_placeholder_regex = r'|'.join ([ 
 552          r'\$<<<[^<:]+?::.*?(?=::\d*?>>>\$)::\d*?>>>\$', 
 553          r'\$<<<[^<:]+?::.*?(?=::\d+-\d+>>>\$)::\d+-\d+>>>\$' 
 554  ]) 
 555   
 556  default_placeholder_start = '$<' 
 557  default_placeholder_end = '>$' 
 558   
 559  #===================================================================== 
560 -def show_placeholders():
561 562 fname = gmTools.get_unique_filename(prefix = 'gm-placeholders-', suffix = '.txt') 563 ph_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace') 564 565 ph_file.write('Here you can find some more documentation on placeholder use:\n') 566 ph_file.write('\n http://wiki.gnumed.de/bin/view/Gnumed/GmManualLettersForms\n\n\n') 567 568 ph_file.write('Variable placeholders:\n') 569 ph_file.write('Usage: $<PLACEHOLDER_NAME::ARGUMENTS::REGION_DEFINITION>$)\n') 570 ph_file.write(' REGION_DEFINITION:\n') 571 ph_file.write('* a single number specifying the maximum output length or\n') 572 ph_file.write('* a number, a "-", followed by a second number specifying the region of the string to return\n') 573 ph_file.write('ARGUMENTS:\n') 574 ph_file.write('* depend on the actual placeholder (see there)\n') 575 ph_file.write('* if a template is supported it will be used to %-format the output\n') 576 ph_file.write('* templates may be either %s-style or %(name)s-style\n') 577 ph_file.write('* templates cannot contain "::"\n') 578 ph_file.write('* templates cannot contain whatever the arguments divider is set to (default "//")\n') 579 for ph in known_variant_placeholders: 580 txt = __known_variant_placeholders[ph] 581 ph_file.write('\n') 582 ph_file.write(' ---=== %s ===---\n' % ph) 583 ph_file.write('\n') 584 ph_file.write(txt) 585 ph_file.write('\n\n') 586 ph_file.write('\n') 587 588 ph_file.write('Known injectable placeholders (use like: $<PLACEHOLDER_NAME::ARGUMENTS::MAX OUTPUT LENGTH>$):\n') 589 for ph in known_injectable_placeholders: 590 ph_file.write(' %s\n' % ph) 591 ph_file.write('\n') 592 593 ph_file.close() 594 gmMimeLib.call_viewer_on_file(aFile = fname, block = False)
595 596 #=====================================================================
597 -class gmPlaceholderHandler(gmBorg.cBorg):
598 """Returns values for placeholders. 599 600 - patient related placeholders operate on the currently active patient 601 - is passed to the forms handling code, for example 602 603 Return values when .debug is False: 604 - errors with placeholders return None 605 - placeholders failing to resolve to a value return an empty string 606 607 Return values when .debug is True: 608 - errors with placeholders return an error string 609 - placeholders failing to resolve to a value return a warning string 610 611 There are several types of placeholders: 612 613 injectable placeholders 614 - they must be set up before use by set_placeholder() 615 - they should be removed after use by unset_placeholder() 616 - the syntax is like extended static placeholders 617 - known ones are listed in known_injectable_placeholders 618 - per-form ones can be used but must exist before 619 the form is processed 620 621 variant placeholders 622 - those are listed in known_variant_placeholders 623 - they are parsed into placeholder, data, and maximum length 624 - the length is optional 625 - data is passed to the handler 626 627 Note that this cannot be called from a non-gui thread unless 628 wrapped in wx.CallAfter(). 629 """
630 - def __init__(self, *args, **kwargs):
631 632 self.pat = gmPerson.gmCurrentPatient() 633 self.debug = False 634 635 self.invalid_placeholder_template = _('invalid placeholder >>>>>%s<<<<<') 636 637 self.__injected_placeholders = {} 638 self.__cache = {} 639 640 self.__esc_style = None 641 self.__esc_func = lambda x:x 642 643 self.__ellipsis = None 644 self.__args_divider = '//' 645 self.__data_encoding = None 646 self.__data_encoding_strict = False
647 648 #-------------------------------------------------------- 649 # external API 650 #--------------------------------------------------------
651 - def set_placeholder(self, key=None, value=None, known_only=True):
652 _log.debug('setting [%s]', key) 653 if key not in known_injectable_placeholders: 654 if known_only: 655 raise ValueError('un-injectable placeholder [%s]' % key) 656 657 _log.debug('placeholder [%s] not known as injectable', key) 658 659 self.__injected_placeholders[key] = value
660 661 #--------------------------------------------------------
662 - def unset_placeholder(self, key=None):
663 _log.debug('unsetting [%s]', key) 664 try: 665 del self.__injected_placeholders[key] 666 except KeyError: 667 _log.debug('injectable placeholder [%s] unknown', key)
668 669 #--------------------------------------------------------
670 - def set_cache_value(self, key=None, value=None):
671 self.__cache[key] = value
672 #--------------------------------------------------------
673 - def unset_cache_value(self, key=None):
674 del self.__cache[key]
675 676 #--------------------------------------------------------
677 - def _set_escape_style(self, escape_style=None):
678 self.__esc_style = escape_style 679 return
680 681 escape_style = property(lambda x:x, _set_escape_style) 682 683 #--------------------------------------------------------
684 - def _set_escape_function(self, escape_function=None):
685 if escape_function is None: 686 self.__esc_func = lambda x:x 687 return 688 if not callable(escape_function): 689 raise ValueError('[%s._set_escape_function]: <%s> not callable' % (self.__class__.__name__, escape_function)) 690 self.__esc_func = escape_function 691 return
692 693 escape_function = property(lambda x:x, _set_escape_function) 694 695 #--------------------------------------------------------
696 - def _set_ellipsis(self, ellipsis):
697 if ellipsis == 'NONE': 698 ellipsis = None 699 self.__ellipsis = ellipsis
700 701 ellipsis = property(lambda x: self.__ellipsis, _set_ellipsis) 702 703 #--------------------------------------------------------
704 - def _set_arguments_divider(self, divider):
705 if divider == 'DEFAULT': 706 divider = '//' 707 self.__args_divider = divider
708 709 arguments_divider = property(lambda x: self.__args_divider, _set_arguments_divider) 710 711 #--------------------------------------------------------
712 - def _set_data_encoding(self, encoding):
713 if encoding == 'NONE': 714 self.__data_encoding = None 715 self.__data_encoding_strict = False 716 717 self.__data_encoding_strict = False 718 if encoding.endswith('-strict'): 719 self.__data_encoding_strict = True 720 encoding = encoding[:-7] 721 try: 722 codecs.lookup(encoding) 723 self.__data_encoding = encoding 724 except LookupError: 725 _log.error('<codecs> module can NOT handle encoding [%s]' % enc)
726 727 data_encoding = property(lambda x: self.__data_encoding, _set_data_encoding) 728 729 #-------------------------------------------------------- 730 placeholder_regex = property(lambda x: default_placeholder_regex, lambda x:x) 731 732 first_pass_placeholder_regex = property(lambda x: first_pass_placeholder_regex, lambda x:x) 733 second_pass_placeholder_regex = property(lambda x: second_pass_placeholder_regex, lambda x:x) 734 third_pass_placeholder_regex = property(lambda x: third_pass_placeholder_regex, lambda x:x) 735 736 #--------------------------------------------------------
737 - def __parse_region_definition(self, region_str):
738 region_str = region_str.strip() 739 740 if region_str == '': 741 return None, None 742 743 try: 744 pos_last_char = int(region_str) 745 return 0, pos_last_char 746 except (TypeError, ValueError): 747 _log.debug('region definition not a simple length') 748 749 # note that we only check for "legality", not for reasonable bounds 750 first_last = region_str.split('-') 751 if len(first_last) != 2: 752 _log.error('invalid placeholder region definition: %s', region_str) 753 raise ValueError 754 755 try: 756 pos_first_char = int(first_last[0].strip()) 757 pos_last_char = int(first_last[1].strip()) 758 except (TypeError, ValueError): 759 _log.error('invalid placeholder region definition: %s', region_str) 760 raise ValueError 761 762 # user says 1,2,... (= character position in string), Python needs 0,1,... (indexes 0-based) 763 if pos_first_char > 0: 764 pos_first_char -= 1 765 766 return pos_first_char, pos_last_char
767 768 #--------------------------------------------------------
769 - def __make_compatible_with_encoding(self, data_str):
770 if self.__data_encoding is None: 771 return data_str 772 773 try: 774 codecs.encode(data_str, self.__data_encoding, 'strict') 775 return data_str 776 except UnicodeEncodeError: 777 _log.error('cannot strict-encode string into [%s]: %s', self.__data_encoding, data_str) 778 779 if self.__data_encoding_strict: 780 return 'not compatible with encoding [%s]: %s' % (self.__data_encoding, data_str) 781 782 try: 783 import unidecode 784 except ImportError: 785 _log.debug('cannot transliterate, <unidecode> module not installed') 786 return codecs.encode(data_str, self.__data_encoding, 'replace').decode(self.__data_encoding) 787 788 return unidecode.unidecode(data_str).decode('utf8')
789 790 #-------------------------------------------------------- 791 # __getitem__ API 792 #--------------------------------------------------------
793 - def __getitem__(self, placeholder):
794 """Map self['placeholder'] to self.placeholder. 795 796 This is useful for replacing placeholders parsed out 797 of documents as strings. 798 799 Unknown/invalid placeholders still deliver a result but 800 it will be glaringly obvious if debugging is enabled. 801 """ 802 _log.debug('replacing [%s]', placeholder) 803 804 original_placeholder_def = placeholder 805 806 # remove leading/trailing '$<(<<)' and '(>>)>$' 807 if placeholder.startswith(default_placeholder_start): 808 placeholder = placeholder.lstrip('$').lstrip('<') 809 if placeholder.endswith(default_placeholder_end): 810 placeholder = placeholder.rstrip('$').rstrip('>') 811 else: 812 _log.error('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end) 813 if self.debug: 814 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 815 return None 816 817 # injectable placeholder ? 818 parts = placeholder.split('::::', 1) 819 if len(parts) == 2: 820 ph_name, region_str = parts 821 is_an_injectable = True 822 try: 823 val = self.__injected_placeholders[ph_name] 824 except KeyError: 825 is_an_injectable = False 826 except Exception: 827 _log.exception('injectable placeholder handling error: %s', original_placeholder_def) 828 if self.debug: 829 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 830 831 return None 832 833 if is_an_injectable: 834 if val is None: 835 if self.debug: 836 return self._escape('injectable placeholder [%s]: no value available' % ph_name) 837 return placeholder 838 try: 839 pos_first_char, pos_last_char = self.__parse_region_definition(region_str) 840 except ValueError: 841 if self.debug: 842 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 843 return None 844 if pos_last_char is None: 845 return self.__make_compatible_with_encoding(val) 846 # ellipsis needed ? 847 if len(val) > (pos_last_char - pos_first_char): 848 # ellipsis wanted ? 849 if self.__ellipsis is not None: 850 return self.__make_compatible_with_encoding(val[pos_first_char:(pos_last_char-len(self.__ellipsis))] + self.__ellipsis) 851 return self.__make_compatible_with_encoding(val[pos_first_char:pos_last_char]) 852 853 # variable placeholders 854 if len(placeholder.split('::', 2)) < 3: 855 _log.error('invalid placeholder structure: %s', original_placeholder_def) 856 if self.debug: 857 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 858 return None 859 860 ph_name, data_and_lng = placeholder.split('::', 1) # note: split _is_ lsplit 861 options, region_str = data_and_lng.rsplit('::', 1) 862 _log.debug('placeholder parts: name=[%s]; region_def=[%s]; options=>>>%s<<<', ph_name, region_str, options) 863 try: 864 pos_first_char, pos_last_char = self.__parse_region_definition(region_str) 865 except ValueError: 866 if self.debug: 867 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 868 return None 869 870 handler = getattr(self, '_get_variant_%s' % ph_name, None) 871 if handler is None: 872 _log.warning('no handler <_get_variant_%s> for placeholder %s', ph_name, original_placeholder_def) 873 if self.debug: 874 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 875 return None 876 877 try: 878 val = handler(data = options) 879 if pos_last_char is None: 880 return self.__make_compatible_with_encoding(val) 881 # ellipsis needed ? 882 if len(val) > (pos_last_char - pos_first_char): 883 # ellipsis wanted ? 884 if self.__ellipsis is not None: 885 return self.__make_compatible_with_encoding(val[pos_first_char:(pos_last_char-len(self.__ellipsis))] + self.__ellipsis) 886 return self.__make_compatible_with_encoding(val[pos_first_char:pos_last_char]) 887 888 except Exception: 889 _log.exception('placeholder handling error: %s', original_placeholder_def) 890 if self.debug: 891 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 892 return None 893 894 _log.error('something went wrong, should never get here') 895 return None
896 897 #-------------------------------------------------------- 898 # placeholder handlers 899 #--------------------------------------------------------
900 - def _get_variant_ph_cfg(self, data=None):
901 options = data.split('//') # ALWAYS use '//' for splitting, regardless of self.__args_divider 902 name = options[0] 903 val = options[1] 904 if name == 'ellipsis': 905 self.ellipsis = val 906 elif name == 'argumentsdivider': 907 self.arguments_divider = val 908 elif name == 'encoding': 909 self.data_encoding = val 910 if len(options) > 2: 911 return options[2] % {'name': name, 'value': val} 912 return ''
913 #--------------------------------------------------------
914 - def _get_variant_client_version(self, data=None):
915 return self._escape ( 916 gmTools.coalesce ( 917 _cfg.get(option = 'client_version'), 918 '%s' % self.__class__.__name__ 919 ) 920 )
921 #--------------------------------------------------------
922 - def _get_variant_reminders(self, data=None):
923 924 from Gnumed.wxpython import gmProviderInboxWidgets 925 926 template = _('due %(due_date)s: %(comment)s (%(interval_due)s)') 927 date_format = '%Y %b %d' 928 929 data_parts = data.split(self.__args_divider) 930 931 if len(data_parts) > 0: 932 if data_parts[0].strip() != '': 933 template = data_parts[0] 934 935 if len(data_parts) > 1: 936 if data_parts[1].strip() != '': 937 date_format = data_parts[1] 938 939 reminders = gmProviderInboxWidgets.manage_reminders(patient = self.pat.ID) 940 941 if reminders is None: 942 return '' 943 944 if len(reminders) == 0: 945 return '' 946 947 lines = [ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in reminders ] 948 949 return '\n'.join(lines)
950 #--------------------------------------------------------
951 - def _get_variant_external_care(self, data=None):
952 953 from Gnumed.wxpython import gmExternalCareWidgets 954 external_cares = gmExternalCareWidgets.manage_external_care() 955 956 if external_cares is None: 957 return '' 958 959 if len(external_cares) == 0: 960 return '' 961 962 template = data 963 lines = [ template % ext.fields_as_dict(escape_style = self.__esc_style) for ext in external_cares ] 964 965 return '\n'.join(lines)
966 #--------------------------------------------------------
967 - def _get_variant_documents(self, data=None):
968 969 select = False 970 include_descriptions = False 971 template = '%s' 972 path_template = None 973 export_path = None 974 975 data_parts = data.split(self.__args_divider) 976 977 if 'select' in data_parts: 978 select = True 979 data_parts.remove('select') 980 981 if 'description' in data_parts: 982 include_descriptions = True 983 data_parts.remove('description') 984 985 template = data_parts[0] 986 987 if len(data_parts) > 1: 988 path_template = data_parts[1] 989 990 if len(data_parts) > 2: 991 export_path = data_parts[2] 992 993 # create path 994 if export_path is not None: 995 export_path = os.path.normcase(os.path.expanduser(export_path)) 996 gmTools.mkdir(export_path) 997 998 # select docs 999 if select: 1000 docs = gmDocumentWidgets.manage_documents(msg = _('Select the patient documents to reference from the new document.'), single_selection = False) 1001 else: 1002 docs = self.pat.document_folder.documents 1003 1004 if docs is None: 1005 return '' 1006 1007 lines = [] 1008 for doc in docs: 1009 lines.append(template % doc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style)) 1010 if include_descriptions: 1011 for desc in doc.get_descriptions(max_lng = None): 1012 lines.append(self._escape(desc['text'] + '\n')) 1013 if path_template is not None: 1014 for part_name in doc.save_parts_to_files(export_dir = export_path): 1015 path, name = os.path.split(part_name) 1016 lines.append(path_template % {'fullpath': part_name, 'name': name}) 1017 1018 return '\n'.join(lines)
1019 #--------------------------------------------------------
1020 - def _get_variant_encounter_list(self, data=None):
1021 1022 encounters = gmEncounterWidgets.select_encounters(single_selection = False) 1023 if not encounters: 1024 return '' 1025 1026 template = data 1027 1028 lines = [] 1029 for enc in encounters: 1030 try: 1031 lines.append(template % enc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style)) 1032 except Exception: 1033 lines.append('error formatting encounter') 1034 _log.exception('problem formatting encounter list') 1035 _log.error('template: %s', template) 1036 _log.error('encounter: %s', encounter) 1037 1038 return '\n'.join(lines)
1039 #--------------------------------------------------------
1040 - def _get_variant_soap_for_encounters(self, data=None):
1041 """Select encounters from list and format SOAP thereof. 1042 1043 data: soap_cats (' ' -> None -> admin) // date format 1044 """ 1045 # defaults 1046 cats = None 1047 date_format = None 1048 1049 if data is not None: 1050 data_parts = data.split(self.__args_divider) 1051 1052 # part[0]: categories 1053 if len(data_parts[0]) > 0: 1054 cats = [] 1055 if ' ' in data_parts[0]: 1056 cats.append(None) 1057 data_parts[0] = data_parts[0].replace(' ', '') 1058 cats.extend(list(data_parts[0])) 1059 1060 # part[1]: date format 1061 if len(data_parts) > 1: 1062 if len(data_parts[1]) > 0: 1063 date_format = data_parts[1] 1064 1065 encounters = gmEncounterWidgets.select_encounters(single_selection = False) 1066 if not encounters: 1067 return '' 1068 1069 chunks = [] 1070 for enc in encounters: 1071 chunks.append(enc.format_latex ( 1072 date_format = date_format, 1073 soap_cats = cats, 1074 soap_order = 'soap_rank, date' 1075 )) 1076 1077 return ''.join(chunks)
1078 #--------------------------------------------------------
1079 - def _get_variant_emr_journal(self, data=None):
1080 # default: all categories, neutral template 1081 cats = list('soapu') 1082 cats.append(None) 1083 template = '%s' 1084 interactive = True 1085 line_length = 9999 1086 time_range = None 1087 1088 if data is not None: 1089 data_parts = data.split(self.__args_divider) 1090 1091 # part[0]: categories 1092 cats = [] 1093 # ' ' -> None == admin 1094 for c in list(data_parts[0]): 1095 if c == ' ': 1096 c = None 1097 cats.append(c) 1098 # '' -> SOAP + None 1099 if cats == '': 1100 cats = list('soapu').append(None) 1101 1102 # part[1]: template 1103 if len(data_parts) > 1: 1104 template = data_parts[1] 1105 1106 # part[2]: line length 1107 if len(data_parts) > 2: 1108 try: 1109 line_length = int(data_parts[2]) 1110 except Exception: 1111 line_length = 9999 1112 1113 # part[3]: weeks going back in time 1114 if len(data_parts) > 3: 1115 try: 1116 time_range = 7 * int(data_parts[3]) 1117 except Exception: 1118 #time_range = None # infinite 1119 # pass on literally, meaning it must be a valid PG interval string 1120 time_range = data_parts[3] 1121 1122 # FIXME: will need to be a generator later on 1123 narr = self.pat.emr.get_as_journal(soap_cats = cats, time_range = time_range) 1124 1125 if len(narr) == 0: 1126 return '' 1127 1128 keys = narr[0].keys() 1129 lines = [] 1130 line_dict = {} 1131 for n in narr: 1132 for key in keys: 1133 if isinstance(n[key], str): 1134 line_dict[key] = self._escape(text = n[key]) 1135 continue 1136 line_dict[key] = n[key] 1137 try: 1138 lines.append((template % line_dict)[:line_length]) 1139 except KeyError: 1140 return 'invalid key in template [%s], valid keys: %s]' % (template, str(keys)) 1141 1142 return '\n'.join(lines)
1143 #--------------------------------------------------------
1144 - def _get_variant_soap_by_issue(self, data=None):
1145 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = 'issue')
1146 #--------------------------------------------------------
1147 - def _get_variant_soap_by_episode(self, data=None):
1148 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = 'episode')
1149 #--------------------------------------------------------
1150 - def __get_variant_soap_by_issue_or_episode(self, data=None, mode=None):
1151 1152 # default: all categories, neutral template 1153 cats = list('soapu') 1154 cats.append(None) 1155 1156 date_format = None 1157 template = '%s' 1158 1159 if data is not None: 1160 data_parts = data.split(self.__args_divider) 1161 1162 # part[0]: categories 1163 if len(data_parts[0]) > 0: 1164 cats = [] 1165 if ' ' in data_parts[0]: 1166 cats.append(None) 1167 cats.extend(list(data_parts[0].replace(' ', ''))) 1168 1169 # part[1]: date format 1170 if len(data_parts) > 1: 1171 if len(data_parts[1]) > 0: 1172 date_format = data_parts[1] 1173 1174 # part[2]: template 1175 if len(data_parts) > 2: 1176 if len(data_parts[2]) > 0: 1177 template = data_parts[2] 1178 1179 if mode == 'issue': 1180 narr = gmNarrativeWorkflows.select_narrative_by_issue(soap_cats = cats) 1181 else: 1182 narr = gmNarrativeWorkflows.select_narrative_by_episode(soap_cats = cats) 1183 1184 if narr is None: 1185 return '' 1186 1187 if len(narr) == 0: 1188 return '' 1189 1190 try: 1191 narr = [ template % n.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for n in narr ] 1192 except KeyError: 1193 return 'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys())) 1194 1195 return '\n'.join(narr)
1196 #--------------------------------------------------------
1197 - def _get_variant_progress_notes(self, data=None):
1198 return self._get_variant_soap(data = data)
1199 #--------------------------------------------------------
1200 - def _get_variant_soap_s(self, data=None):
1201 return self._get_variant_soap(data = 's')
1202 #--------------------------------------------------------
1203 - def _get_variant_soap_o(self, data=None):
1204 return self._get_variant_soap(data = 'o')
1205 #--------------------------------------------------------
1206 - def _get_variant_soap_a(self, data=None):
1207 return self._get_variant_soap(data = 'a')
1208 #--------------------------------------------------------
1209 - def _get_variant_soap_p(self, data=None):
1210 return self._get_variant_soap(data = 'p')
1211 #--------------------------------------------------------
1212 - def _get_variant_soap_u(self, data=None):
1213 return self._get_variant_soap(data = 'u')
1214 #--------------------------------------------------------
1215 - def _get_variant_soap_admin(self, data=None):
1216 return self._get_variant_soap(data = ' ')
1217 #--------------------------------------------------------
1218 - def _get_variant_soap(self, data=None):
1219 1220 # default: all categories, neutral template 1221 cats = list('soapu') 1222 cats.append(None) 1223 template = '%(narrative)s' 1224 1225 if data is not None: 1226 data_parts = data.split(self.__args_divider) 1227 1228 # part[0]: categories 1229 cats = [] 1230 # ' ' -> None == admin 1231 for cat in list(data_parts[0]): 1232 if cat == ' ': 1233 cat = None 1234 cats.append(cat) 1235 # '' -> SOAP + None 1236 if cats == '': 1237 cats = list('soapu') 1238 cats.append(None) 1239 1240 # part[1]: template 1241 if len(data_parts) > 1: 1242 template = data_parts[1] 1243 1244 #narr = gmNarrativeWorkflows.select_narrative_from_episodes(soap_cats = cats) 1245 narr = gmNarrativeWorkflows.select_narrative(soap_cats = cats) 1246 1247 if narr is None: 1248 return '' 1249 1250 if len(narr) == 0: 1251 return '' 1252 1253 # if any "%s" is in the template there cannot be any %(field)s 1254 # and we also restrict the fields to .narrative (this is the 1255 # old placeholder behaviour 1256 if '%s' in template: 1257 narr = [ self._escape(n['narrative']) for n in narr ] 1258 else: 1259 narr = [ n.fields_as_dict(escape_style = self.__esc_style) for n in narr ] 1260 1261 try: 1262 narr = [ template % n for n in narr ] 1263 except KeyError: 1264 return 'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys())) 1265 except TypeError: 1266 return 'cannot mix "%%s" and "%%(field)s" in template [%s]' % template 1267 1268 return '\n'.join(narr)
1269 1270 #--------------------------------------------------------
1271 - def _get_variant_title(self, data=None):
1272 return self._get_variant_name(data = '%(title)s')
1273 #--------------------------------------------------------
1274 - def _get_variant_firstname(self, data=None):
1275 return self._get_variant_name(data = '%(firstnames)s')
1276 #--------------------------------------------------------
1277 - def _get_variant_lastname(self, data=None):
1278 return self._get_variant_name(data = '%(lastnames)s')
1279 #--------------------------------------------------------
1280 - def _get_variant_name(self, data=None):
1281 if data is None: 1282 return [_('template is missing')] 1283 1284 name = self.pat.get_active_name() 1285 1286 parts = { 1287 'title': self._escape(gmTools.coalesce(name['title'], '')), 1288 'firstnames': self._escape(name['firstnames']), 1289 'lastnames': self._escape(name['lastnames']), 1290 'preferred': self._escape(gmTools.coalesce ( 1291 value2test = name['preferred'], 1292 return_instead = ' ', 1293 template4value = ' "%s" ' 1294 )) 1295 } 1296 1297 return data % parts
1298 1299 #--------------------------------------------------------
1300 - def _get_variant_date_of_birth(self, data='%Y %b %d'):
1301 return self.pat.get_formatted_dob(format = data)
1302 1303 #-------------------------------------------------------- 1304 # FIXME: extend to all supported genders
1305 - def _get_variant_gender_mapper(self, data='male//female//other'):
1306 1307 values = data.split('//', 2) 1308 1309 if len(values) == 2: 1310 male_value, female_value = values 1311 other_value = '<unkown gender>' 1312 elif len(values) == 3: 1313 male_value, female_value, other_value = values 1314 else: 1315 return _('invalid gender mapping layout: [%s]') % data 1316 1317 if self.pat['gender'] == 'm': 1318 return self._escape(male_value) 1319 1320 if self.pat['gender'] == 'f': 1321 return self._escape(female_value) 1322 1323 return self._escape(other_value)
1324 #-------------------------------------------------------- 1325 # address related placeholders 1326 #--------------------------------------------------------
1327 - def __get_variant_gen_adr_part(self, data='?', part=None):
1328 1329 template = '%s' 1330 msg = _('Select the address you want to use !') 1331 cache_id = '' 1332 options = data.split('//', 4) 1333 if len(options) > 0: 1334 template = options[0] 1335 if template.strip() == '': 1336 template = '%s' 1337 if len(options) > 1: 1338 msg = options[1] 1339 if len(options) > 2: 1340 cache_id = options[2] 1341 1342 cache_key = 'generic_address::' + cache_id 1343 try: 1344 adr2use = self.__cache[cache_key] 1345 _log.debug('cache hit (%s): [%s]', cache_key, adr2use) 1346 except KeyError: 1347 adr2use = None 1348 1349 if adr2use is None: 1350 dlg = gmAddressWidgets.cAddressSelectionDlg(None, -1) 1351 dlg.message = msg 1352 choice = dlg.ShowModal() 1353 adr2use = dlg.address 1354 dlg.DestroyLater() 1355 if choice == wx.ID_CANCEL: 1356 return '' 1357 self.__cache[cache_key] = adr2use 1358 1359 return template % self._escape(adr2use[part])
1360 #--------------------------------------------------------
1361 - def _get_variant_gen_adr_street(self, data='?'):
1362 return self.__get_variant_gen_adr_part(data = data, part = 'street')
1363 #--------------------------------------------------------
1364 - def _get_variant_gen_adr_number(self, data='?'):
1365 return self.__get_variant_gen_adr_part(data = data, part = 'number')
1366 #--------------------------------------------------------
1367 - def _get_variant_gen_adr_subunit(self, data='?'):
1368 return self.__get_variant_gen_adr_part(data = data, part = 'subunit')
1369 #--------------------------------------------------------
1370 - def _get_variant_gen_adr_location(self, data='?'):
1371 return self.__get_variant_gen_adr_part(data = data, part = 'urb')
1372 #--------------------------------------------------------
1373 - def _get_variant_gen_adr_suburb(self, data='?'):
1374 return self.__get_variant_gen_adr_part(data = data, part = 'suburb')
1375 #--------------------------------------------------------
1376 - def _get_variant_gen_adr_postcode(self, data='?'):
1377 return self.__get_variant_gen_adr_part(data = data, part = 'postcode')
1378 #--------------------------------------------------------
1379 - def _get_variant_gen_adr_region(self, data='?'):
1380 return self.__get_variant_gen_adr_part(data = data, part = 'l10n_region')
1381 #--------------------------------------------------------
1382 - def _get_variant_gen_adr_country(self, data='?'):
1383 return self.__get_variant_gen_adr_part(data = data, part = 'l10n_country')
1384 #--------------------------------------------------------
1385 - def __get_variant_receiver_part(self, data='%s', part=None):
1386 1387 template = '%s' 1388 cache_id = '' 1389 options = data.split('//', 3) 1390 if len(options) > 0: 1391 template = options[0] 1392 if template.strip() == '': 1393 template = '%s' 1394 if len(options) > 1: 1395 cache_id = options[1] 1396 1397 cache_key = 'receiver::' + cache_id 1398 try: 1399 name, adr = self.__cache[cache_key] 1400 _log.debug('cache hit (%s): [%s:%s]', cache_key, name, adr) 1401 except KeyError: 1402 name = None 1403 adr = None 1404 1405 if name is None: 1406 from Gnumed.wxpython import gmFormWidgets 1407 dlg = gmFormWidgets.cReceiverSelectionDlg(None, -1) 1408 dlg.patient = self.pat 1409 choice = dlg.ShowModal() 1410 name = dlg.name 1411 adr = dlg.address 1412 dlg.DestroyLater() 1413 if choice == wx.ID_CANCEL: 1414 return '' 1415 self.__cache[cache_key] = (name, adr) 1416 1417 if part == 'name': 1418 return template % self._escape(name) 1419 1420 return template % self._escape(gmTools.coalesce(adr[part], ''))
1421 #--------------------------------------------------------
1422 - def _get_variant_receiver_name(self, data='%s'):
1423 return self.__get_variant_receiver_part(data = data, part = 'name')
1424 #--------------------------------------------------------
1425 - def _get_variant_receiver_street(self, data='%s'):
1426 return self.__get_variant_receiver_part(data = data, part = 'street')
1427 #--------------------------------------------------------
1428 - def _get_variant_receiver_number(self, data='%s'):
1429 return self.__get_variant_receiver_part(data = data, part = 'number')
1430 #--------------------------------------------------------
1431 - def _get_variant_receiver_subunit(self, data='%s'):
1432 return self.__get_variant_receiver_part(data = data, part = 'subunit')
1433 #--------------------------------------------------------
1434 - def _get_variant_receiver_location(self, data='%s'):
1435 return self.__get_variant_receiver_part(data = data, part = 'urb')
1436 #--------------------------------------------------------
1437 - def _get_variant_receiver_suburb(self, data='%s'):
1438 return self.__get_variant_receiver_part(data = data, part = 'suburb')
1439 #--------------------------------------------------------
1440 - def _get_variant_receiver_postcode(self, data='%s'):
1441 return self.__get_variant_receiver_part(data = data, part = 'postcode')
1442 #--------------------------------------------------------
1443 - def _get_variant_receiver_region(self, data='%s'):
1444 return self.__get_variant_receiver_part(data = data, part = 'l10n_region')
1445 #--------------------------------------------------------
1446 - def _get_variant_receiver_country(self, data='%s'):
1447 return self.__get_variant_receiver_part(data = data, part = 'l10n_country')
1448 #--------------------------------------------------------
1449 - def _get_variant_patient_address(self, data=''):
1450 1451 data_parts = data.split(self.__args_divider) 1452 1453 # address type 1454 adr_type = data_parts[0].strip() 1455 orig_type = adr_type 1456 if adr_type != '': 1457 adrs = self.pat.get_addresses(address_type = adr_type) 1458 if len(adrs) == 0: 1459 _log.warning('no address for type [%s]', adr_type) 1460 adr_type = '' 1461 if adr_type == '': 1462 _log.debug('asking user for address type') 1463 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat) 1464 if adr is None: 1465 if self.debug: 1466 return _('no address type replacement selected') 1467 return '' 1468 adr_type = adr['address_type'] 1469 adr = self.pat.get_addresses(address_type = adr_type)[0] 1470 1471 # formatting template 1472 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_region)s, %(l10n_country)s') 1473 if len(data_parts) > 1: 1474 if data_parts[1].strip() != '': 1475 template = data_parts[1] 1476 1477 try: 1478 return template % adr.fields_as_dict(escape_style = self.__esc_style) 1479 except Exception: 1480 _log.exception('error formatting address') 1481 _log.error('template: %s', template) 1482 1483 return None
1484 #--------------------------------------------------------
1485 - def __get_variant_adr_part(self, data='?', part=None):
1486 requested_type = data.strip() 1487 cache_key = 'adr-type-%s' % requested_type 1488 try: 1489 type2use = self.__cache[cache_key] 1490 _log.debug('cache hit (%s): [%s] -> [%s]', cache_key, requested_type, type2use) 1491 except KeyError: 1492 type2use = requested_type 1493 if type2use != '': 1494 adrs = self.pat.get_addresses(address_type = type2use) 1495 if len(adrs) == 0: 1496 _log.warning('no address of type [%s] for <%s> field extraction', requested_type, part) 1497 type2use = '' 1498 if type2use == '': 1499 _log.debug('asking user for replacement address type') 1500 adr = gmPersonContactWidgets.select_address(missing = requested_type, person = self.pat) 1501 if adr is None: 1502 _log.debug('no replacement selected') 1503 if self.debug: 1504 return self._escape(_('no address type replacement selected')) 1505 return '' 1506 type2use = adr['address_type'] 1507 self.__cache[cache_key] = type2use 1508 _log.debug('caching (%s): [%s] -> [%s]', cache_key, requested_type, type2use) 1509 1510 part_data = self.pat.get_addresses(address_type = type2use)[0][part] 1511 if part_data is None: 1512 part_data = '' # do escape empty string since we never know what target formats need 1513 return self._escape(part_data)
1514 1515 #--------------------------------------------------------
1516 - def _get_variant_adr_street(self, data='?'):
1517 return self.__get_variant_adr_part(data = data, part = 'street')
1518 #--------------------------------------------------------
1519 - def _get_variant_adr_number(self, data='?'):
1520 return self.__get_variant_adr_part(data = data, part = 'number')
1521 #--------------------------------------------------------
1522 - def _get_variant_adr_subunit(self, data='?'):
1523 return self.__get_variant_adr_part(data = data, part = 'subunit')
1524 #--------------------------------------------------------
1525 - def _get_variant_adr_location(self, data='?'):
1526 return self.__get_variant_adr_part(data = data, part = 'urb')
1527 #--------------------------------------------------------
1528 - def _get_variant_adr_suburb(self, data='?'):
1529 return self.__get_variant_adr_part(data = data, part = 'suburb')
1530 #--------------------------------------------------------
1531 - def _get_variant_adr_postcode(self, data='?'):
1532 return self.__get_variant_adr_part(data = data, part = 'postcode')
1533 #--------------------------------------------------------
1534 - def _get_variant_adr_region(self, data='?'):
1535 return self.__get_variant_adr_part(data = data, part = 'l10n_region')
1536 #--------------------------------------------------------
1537 - def _get_variant_adr_country(self, data='?'):
1538 return self.__get_variant_adr_part(data = data, part = 'l10n_country')
1539 #--------------------------------------------------------
1540 - def _get_variant_patient_comm(self, data=None):
1541 comm_type = None 1542 template = '%(url)s' 1543 if data is not None: 1544 data_parts = data.split(self.__args_divider) 1545 if len(data_parts) > 0: 1546 comm_type = data_parts[0] 1547 if len(data_parts) > 1: 1548 template = data_parts[1] 1549 1550 comms = self.pat.get_comm_channels(comm_medium = comm_type) 1551 if len(comms) == 0: 1552 if self.debug: 1553 return self._escape(_('no URL for comm channel [%s]') % data) 1554 return '' 1555 1556 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
1557 #--------------------------------------------------------
1558 - def _get_variant_patient_photo(self, data=None):
1559 1560 template = '%s' 1561 target_mime = None 1562 target_ext = None 1563 if data is not None: 1564 parts = data.split(self.__args_divider) 1565 template = parts[0] 1566 if len(parts) > 1: 1567 target_mime = parts[1].strip() 1568 if len(parts) > 2: 1569 target_ext = parts[2].strip() 1570 if target_ext is None: 1571 if target_mime is not None: 1572 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime) 1573 1574 cache_key = 'patient_photo_path::%s::%s' % (target_mime, target_ext) 1575 try: 1576 fname = self.__cache[cache_key] 1577 _log.debug('cache hit on [%s]: %s', cache_key, fname) 1578 except KeyError: 1579 mugshot = self.pat.document_folder.latest_mugshot 1580 if mugshot is None: 1581 if self.debug: 1582 return self._escape(_('no mugshot available')) 1583 return '' 1584 fname = mugshot.save_to_file ( 1585 target_mime = target_mime, 1586 target_extension = target_ext, 1587 ignore_conversion_problems = True 1588 ) 1589 if fname is None: 1590 if self.debug: 1591 return self._escape(_('cannot export or convert latest mugshot')) 1592 return '' 1593 self.__cache[cache_key] = fname 1594 1595 return template % fname
1596 1597 #--------------------------------------------------------
1598 - def _get_variant_patient_vcf(self, data):
1599 options = data.split(self.__args_divider) 1600 template = options[0].strip() 1601 if template == '': 1602 template = '%s' 1603 1604 return template % self.pat.export_as_vcard()
1605 1606 #--------------------------------------------------------
1607 - def _get_variant_patient_mcf(self, data):
1608 template, format = self.__parse_ph_options ( 1609 option_defs = {'tmpl': '%s', 'fmt': 'txt'}, 1610 options_string = data 1611 ) 1612 if format not in ['qr', 'mcf', 'txt']: 1613 if self.debug: 1614 return self._escape(_('patient_mcf: invalid format (qr/mcf/txt)')) 1615 return '' 1616 1617 if format == 'txt': 1618 return template % self._escape(self.pat.MECARD) 1619 1620 if format == 'mcf': 1621 return template % self.pat.export_as_mecard() 1622 1623 if format == 'qr': 1624 qr_filename = gmTools.create_qrcode(text = self.pat.MECARD) 1625 if qr_filename is None: 1626 return self._escape('patient_mcf-cannot_create_QR_code') 1627 return template % qr_filename 1628 1629 return None
1630 1631 #--------------------------------------------------------
1632 - def _get_variant_patient_gdt(self, data):
1633 options = data.split(self.__args_divider) 1634 template = options[0].strip() 1635 if template == '': 1636 template = '%s' 1637 1638 return template % self.pat.export_as_gdt()
1639 1640 #--------------------------------------------------------
1641 - def _get_variant_patient_tags(self, data='%s//\\n'):
1642 if len(self.pat.tags) == 0: 1643 if self.debug: 1644 return self._escape(_('no tags for this patient')) 1645 return '' 1646 1647 tags = gmDemographicsWidgets.select_patient_tags(patient = self.pat) 1648 1649 if tags is None: 1650 if self.debug: 1651 return self._escape(_('no patient tags selected for inclusion') % data) 1652 return '' 1653 1654 template, separator = data.split('//', 2) 1655 1656 return separator.join([ template % t.fields_as_dict(escape_style = self.__esc_style) for t in tags ])
1657 # #-------------------------------------------------------- 1658 # def _get_variant_patient_tags_table(self, data=u'?'): 1659 # pass 1660 #-------------------------------------------------------- 1661 # praxis related placeholders 1662 #--------------------------------------------------------
1663 - def _get_variant_praxis(self, data=None):
1664 options = data.split(self.__args_divider) 1665 1666 if 'select' in options: 1667 options.remove('select') 1668 branch = 'select branch' 1669 else: 1670 branch = gmPraxis.cPraxisBranch(aPK_obj = gmPraxis.gmCurrentPraxisBranch()['pk_praxis_branch']) 1671 1672 template = '%s' 1673 if len(options) > 0: 1674 template = options[0] 1675 if template.strip() == '': 1676 template = '%s' 1677 1678 return template % branch.fields_as_dict(escape_style = self.__esc_style)
1679 1680 #--------------------------------------------------------
1681 - def _get_variant_praxis_vcf(self, data=None):
1682 1683 cache_key = 'current_branch_vcf_path' 1684 try: 1685 vcf_name = self.__cache[cache_key] 1686 _log.debug('cache hit (%s): [%s]', cache_key, vcf_name) 1687 except KeyError: 1688 vcf_name = gmPraxis.gmCurrentPraxisBranch().vcf 1689 self.__cache[cache_key] = vcf_name 1690 1691 template = '%s' 1692 if data.strip() != '': 1693 template = data 1694 1695 return template % vcf_name
1696 1697 #--------------------------------------------------------
1698 - def _get_variant_praxis_mcf(self, data):
1699 template, format = self.__parse_ph_options ( 1700 option_defs = {'tmpl': '%s', 'fmt': 'txt'}, 1701 options_string = data 1702 ) 1703 if format not in ['qr', 'mcf', 'txt']: 1704 if self.debug: 1705 return self._escape(_('praxis_mcf: invalid format (qr/mcf/txt)')) 1706 return '' 1707 1708 if format == 'txt': 1709 return template % self._escape(gmPraxis.gmCurrentPraxisBranch().MECARD) 1710 1711 if format == 'mcf': 1712 return template % gmPraxis.gmCurrentPraxisBranch().export_as_mecard() 1713 1714 if format == 'qr': 1715 qr_filename = gmTools.create_qrcode(text = gmPraxis.gmCurrentPraxisBranch().MECARD) 1716 if qr_filename is None: 1717 return self._escape('praxis_mcf-cannot_create_QR_code') 1718 return template % qr_filename 1719 1720 return None
1721 1722 #--------------------------------------------------------
1723 - def _get_variant_praxis_scan2pay(self, data):
1724 format = self.__parse_ph_options(option_defs = {'fmt': 'qr'}, options_string = data) 1725 if format not in ['qr', 'txt']: 1726 if self.debug: 1727 return self._escape(_('praxis_scan2pay: invalid format (qr/txt)')) 1728 return u'' 1729 1730 data_str = gmPraxis.gmCurrentPraxisBranch().scan2pay_data 1731 if data_str is None: 1732 if self.debug: 1733 return self._escape('praxis_scan2pay-cannot_create_data_file') 1734 return '' 1735 1736 if format == 'txt': 1737 return self._escape(data_str) 1738 #return template % self._escape(gmPraxis.gmCurrentPraxisBranch().MECARD) 1739 1740 if format == 'qr': 1741 qr_filename = gmTools.create_qrcode(text = data_str) 1742 if qr_filename is None: 1743 if self.debug: 1744 return self._escape('praxis_scan2pay-cannot_create_QR_code') 1745 return '' 1746 #return template % qr_filename 1747 return qr_filename 1748 1749 return None
1750 1751 #--------------------------------------------------------
1752 - def _get_variant_praxis_address(self, data=''):
1753 options = data.split(self.__args_divider) 1754 1755 # formatting template 1756 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_region)s, %(l10n_country)s') 1757 if len(options) > 0: 1758 if options[0].strip() != '': 1759 template = options[0] 1760 1761 adr = gmPraxis.gmCurrentPraxisBranch().address 1762 if adr is None: 1763 if self.debug: 1764 return _('no address recorded') 1765 return '' 1766 try: 1767 return template % adr.fields_as_dict(escape_style = self.__esc_style) 1768 except Exception: 1769 _log.exception('error formatting address') 1770 _log.error('template: %s', template) 1771 1772 return None
1773 1774 #--------------------------------------------------------
1775 - def _get_variant_praxis_comm(self, data=None):
1776 options = data.split(self.__args_divider) 1777 comm_type = options[0] 1778 template = '%(url)s' 1779 if len(options) > 1: 1780 template = options[1] 1781 1782 comms = gmPraxis.gmCurrentPraxisBranch().get_comm_channels(comm_medium = comm_type) 1783 if len(comms) == 0: 1784 if self.debug: 1785 return self._escape(_('no URL for comm channel [%s]') % data) 1786 return '' 1787 1788 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
1789 1790 #--------------------------------------------------------
1791 - def _get_variant_praxis_id(self, data=None):
1792 options = data.split(self.__args_divider) 1793 id_type = options[0].strip() 1794 if id_type == '': 1795 return self._escape('praxis external ID: type is missing') 1796 1797 if len(options) > 1: 1798 issuer = options[1].strip() 1799 if issuer == '': 1800 issue = None 1801 else: 1802 issuer = None 1803 1804 if len(options) > 2: 1805 template = options[2] 1806 else: 1807 template = '%(name)s: %(value)s (%(issuer)s)' 1808 1809 ids = gmPraxis.gmCurrentPraxisBranch().get_external_ids(id_type = id_type, issuer = issuer) 1810 if len(ids) == 0: 1811 if self.debug: 1812 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1813 return '' 1814 1815 return template % self._escape_dict(the_dict = ids[0], none_string = '')
1816 1817 #-------------------------------------------------------- 1818 # provider related placeholders 1819 #--------------------------------------------------------
1820 - def _get_variant_current_provider(self, data=None):
1821 prov = gmStaff.gmCurrentProvider() 1822 1823 tmp = '%s%s. %s' % ( 1824 gmTools.coalesce(prov['title'], '', '%s '), 1825 prov['firstnames'][:1], 1826 prov['lastnames'] 1827 ) 1828 return self._escape(tmp)
1829 1830 #--------------------------------------------------------
1831 - def _get_variant_current_provider_title(self, data=None):
1832 if data is None: 1833 template = u'%(title)s' 1834 elif data.strip() == u'': 1835 data = u'%(title)s' 1836 return self._get_variant_current_provider_name(data = data)
1837 1838 #--------------------------------------------------------
1839 - def _get_variant_current_provider_firstnames(self, data=None):
1840 if data is None: 1841 data = u'%(firstnames)s' 1842 elif data.strip() == u'': 1843 data = u'%(firstnames)s' 1844 return self._get_variant_current_provider_name(data = data)
1845 1846 #--------------------------------------------------------
1847 - def _get_variant_current_provider_lastnames(self, data=None):
1848 if data is None: 1849 data = u'%(lastnames)s' 1850 elif data.strip() == u'': 1851 data = u'%(lastnames)s' 1852 return self._get_variant_current_provider_name(data = data)
1853 1854 #--------------------------------------------------------
1855 - def _get_variant_current_provider_name(self, data=None):
1856 if data is None: 1857 return [_('template is missing')] 1858 if data.strip() == '': 1859 return [_('template is empty')] 1860 prov = gmStaff.gmCurrentProvider() 1861 name = prov.identity.get_active_name() 1862 parts = { 1863 'title': self._escape(gmTools.coalesce(name['title'], '')), 1864 'firstnames': self._escape(name['firstnames']), 1865 'lastnames': self._escape(name['lastnames']), 1866 'preferred': self._escape(gmTools.coalesce(name['preferred'], '')), 1867 'alias': self._escape(prov['short_alias']) 1868 } 1869 return data % parts
1870 1871 #--------------------------------------------------------
1873 data_parts = data.split(self.__args_divider) 1874 if len(data_parts) < 2: 1875 return self._escape('current provider external ID: template is missing') 1876 1877 id_type = data_parts[0].strip() 1878 if id_type == '': 1879 return self._escape('current provider external ID: type is missing') 1880 1881 issuer = data_parts[1].strip() 1882 if issuer == '': 1883 return self._escape('current provider external ID: issuer is missing') 1884 1885 prov = gmStaff.gmCurrentProvider() 1886 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer) 1887 1888 if len(ids) == 0: 1889 if self.debug: 1890 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1891 return '' 1892 1893 return self._escape(ids[0]['value'])
1894 1895 #--------------------------------------------------------
1896 - def _get_variant_primary_praxis_provider(self, data=None):
1897 prov = self.pat.primary_provider 1898 if prov is None: 1899 return self._get_variant_current_provider() 1900 1901 title = gmTools.coalesce ( 1902 prov['title'], 1903 gmPerson.map_gender2salutation(prov['gender']) 1904 ) 1905 1906 tmp = '%s %s. %s' % ( 1907 title, 1908 prov['firstnames'][:1], 1909 prov['lastnames'] 1910 ) 1911 return self._escape(tmp)
1912 1913 #--------------------------------------------------------
1915 data_parts = data.split(self.__args_divider) 1916 if len(data_parts) < 2: 1917 return self._escape('primary in-praxis provider external ID: template is missing') 1918 1919 id_type = data_parts[0].strip() 1920 if id_type == '': 1921 return self._escape('primary in-praxis provider external ID: type is missing') 1922 1923 issuer = data_parts[1].strip() 1924 if issuer == '': 1925 return self._escape('primary in-praxis provider external ID: issuer is missing') 1926 1927 prov = self.pat.primary_provider 1928 if prov is None: 1929 if self.debug: 1930 return self._escape(_('no primary in-praxis provider')) 1931 return '' 1932 1933 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer) 1934 1935 if len(ids) == 0: 1936 if self.debug: 1937 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1938 return '' 1939 1940 return self._escape(ids[0]['value'])
1941 1942 #--------------------------------------------------------
1943 - def _get_variant_external_id(self, data=''):
1944 data_parts = data.split(self.__args_divider) 1945 if len(data_parts) < 2: 1946 return self._escape('patient external ID: template is missing') 1947 1948 id_type = data_parts[0].strip() 1949 if id_type == '': 1950 return self._escape('patient external ID: type is missing') 1951 1952 issuer = data_parts[1].strip() 1953 if issuer == '': 1954 return self._escape('patient external ID: issuer is missing') 1955 1956 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer) 1957 1958 if len(ids) == 0: 1959 if self.debug: 1960 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1961 return '' 1962 1963 return self._escape(ids[0]['value'])
1964 1965 #--------------------------------------------------------
1966 - def _get_variant_allergy_state(self, data=None):
1967 allg_state = self.pat.emr.allergy_state 1968 1969 if allg_state['last_confirmed'] is None: 1970 date_confirmed = '' 1971 else: 1972 date_confirmed = ' (%s)' % gmDateTime.pydt_strftime ( 1973 allg_state['last_confirmed'], 1974 format = '%Y %B %d' 1975 ) 1976 1977 tmp = '%s%s' % ( 1978 allg_state.state_string, 1979 date_confirmed 1980 ) 1981 return self._escape(tmp)
1982 1983 #--------------------------------------------------------
1984 - def _get_variant_allergy_list(self, data=None):
1985 if data is None: 1986 return self._escape(_('template is missing')) 1987 1988 template, separator = data.split('//', 2) 1989 1990 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() ])
1991 1992 #--------------------------------------------------------
1993 - def _get_variant_allergies(self, data=None):
1994 1995 if data is None: 1996 return self._escape(_('template is missing')) 1997 1998 return '\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() ])
1999 2000 #--------------------------------------------------------
2001 - def _get_variant_current_meds_AMTS_enhanced(self, data=None):
2002 return self._get_variant_current_meds_AMTS(data=data, strict=False)
2003 2004 #--------------------------------------------------------
2005 - def _get_variant_current_meds_AMTS(self, data=None, strict=True):
2006 2007 # select intakes 2008 emr = self.pat.emr 2009 from Gnumed.wxpython import gmMedicationWidgets 2010 intakes2export = gmMedicationWidgets.manage_substance_intakes(emr = emr) 2011 if intakes2export is None: 2012 return '' 2013 if len(intakes2export) == 0: 2014 return '' 2015 2016 # make them unique: 2017 unique_intakes = {} 2018 for intake in intakes2export: 2019 if intake['pk_drug_product'] is None: 2020 unique_intakes[intake['pk_substance']] = intake 2021 else: 2022 unique_intakes[intake['product']] = intake 2023 del intakes2export 2024 unique_intakes = unique_intakes.values() 2025 2026 # create data files / datamatrix code files 2027 self.__create_amts_datamatrix_files(intakes = unique_intakes) 2028 2029 # create AMTS-LaTeX per intake 2030 intake_as_latex_rows = [] 2031 for intake in unique_intakes: 2032 intake_as_latex_rows.append(intake._get_as_amts_latex(strict = strict)) 2033 del unique_intakes 2034 2035 # append allergy information 2036 # - state 2037 intake_as_latex_rows.extend(emr.allergy_state._get_as_amts_latex(strict = strict)) 2038 # - allergies 2039 for allg in emr.get_allergies(): 2040 intake_as_latex_rows.append(allg._get_as_amts_latex(strict = strict)) 2041 2042 # insert \newpage after each group of 15 rows 2043 table_rows = intake_as_latex_rows[:15] 2044 if len(intake_as_latex_rows) > 15: 2045 table_rows.append('\\newpage') 2046 table_rows.extend(intake_as_latex_rows[15:30]) 2047 if len(intake_as_latex_rows) > 30: 2048 table_rows.append('\\newpage') 2049 table_rows.extend(intake_as_latex_rows[30:45]) 2050 2051 if strict: 2052 return '\n'.join(table_rows) 2053 2054 # allow two more pages in enhanced mode 2055 if len(intake_as_latex_rows) > 45: 2056 table_rows.append('\\newpage') 2057 table_rows.extend(intake_as_latex_rows[30:45]) 2058 2059 if len(intake_as_latex_rows) > 60: 2060 table_rows.append('\\newpage') 2061 table_rows.extend(intake_as_latex_rows[30:45]) 2062 2063 return '\n'.join(table_rows)
2064 2065 #--------------------------------------------------------
2066 - def __create_amts_datamatrix_files(self, intakes=None):
2067 2068 # setup dummy files 2069 for idx in [1,2,3]: 2070 self.set_placeholder(key = 'amts_data_file_%s' % idx, value = './missing-file.txt', known_only = False) 2071 self.set_placeholder(key = 'amts_png_file_%s' % idx, value = './missing-file.png', known_only = False) 2072 self.set_placeholder(key = 'amts_png_file_current_page', value = './missing-file-current-page.png', known_only = False) 2073 self.set_placeholder(key = 'amts_png_file_utf8', value = './missing-file-utf8.png', known_only = False) 2074 self.set_placeholder(key = 'amts_data_file_utf8', value = './missing-file-utf8.txt', known_only = False) 2075 2076 # find processor 2077 found, dmtx_creator = gmShellAPI.detect_external_binary(binary = 'gm-create_datamatrix') 2078 _log.debug(dmtx_creator) 2079 if not found: 2080 _log.error('gm-create_datamatrix(.bat/.exe) not found') 2081 return 2082 2083 png_dir = gmTools.mk_sandbox_dir() 2084 _log.debug('sandboxing AMTS datamatrix PNGs in: %s', png_dir) 2085 2086 from Gnumed.business import gmForms 2087 2088 # generate GNUmed-enhanced non-conformant data file and datamatrix 2089 # for embedding (utf8, unabridged data fields) 2090 amts_data_template_def_file = gmMedication.generate_amts_data_template_definition_file(strict = False) 2091 _log.debug('amts data template definition file: %s', amts_data_template_def_file) 2092 form = gmForms.cTextForm(template_file = amts_data_template_def_file) 2093 # <S>ection with intakes</S> 2094 amts_sections = '<S>%s</S>' % ''.join ([ 2095 i._get_as_amts_data(strict = False) for i in intakes 2096 ]) 2097 # <S>ection with allergy data</S> 2098 emr = self.pat.emr 2099 amts_sections += emr.allergy_state._get_as_amts_data(strict = False) % ''.join ([ 2100 a._get_as_amts_data(strict = False) for a in emr.get_allergies() 2101 ]) 2102 self.set_placeholder(key = 'amts_intakes_as_data_enhanced', value = amts_sections, known_only = False) 2103 # self.set_placeholder(key = u'amts_check_symbol', value = gmMedication.calculate_amts_data_check_symbol(intakes = intakes), known_only = False) 2104 self.set_placeholder(key = 'amts_total_pages', value = '1', known_only = False) 2105 success = form.substitute_placeholders(data_source = self) 2106 self.unset_placeholder(key = 'amts_intakes_as_data_enhanced') 2107 # self.unset_placeholder(key = u'amts_check_symbol') 2108 self.unset_placeholder(key = 'amts_total_pages') 2109 if not success: 2110 _log.error('cannot substitute into amts data file form template') 2111 return 2112 data_file = form.re_editable_filenames[0] 2113 png_file = os.path.join(png_dir, 'gm4amts-datamatrix-utf8.png') 2114 cmd = '%s %s %s' % (dmtx_creator, data_file, png_file) 2115 success = gmShellAPI.run_command_in_shell(command = cmd, blocking = True) 2116 if not success: 2117 _log.error('error running [%s]' % cmd) 2118 return 2119 self.set_placeholder(key = 'amts_data_file_utf8', value = data_file, known_only = False) 2120 self.set_placeholder(key = 'amts_png_file_utf8', value = png_file, known_only = False) 2121 2122 # generate conformant per-page files: 2123 total_pages = (len(intakes) / 15.0) 2124 if total_pages > int(total_pages): 2125 total_pages += 1 2126 total_pages = int(total_pages) 2127 _log.debug('total pages: %s', total_pages) 2128 2129 png_file_base = os.path.join(png_dir, 'gm4amts-datamatrix-page-') 2130 for this_page in range(1,total_pages+1): 2131 intakes_this_page = intakes[(this_page-1)*15:this_page*15] 2132 amts_data_template_def_file = gmMedication.generate_amts_data_template_definition_file(strict = True) 2133 _log.debug('amts data template definition file: %s', amts_data_template_def_file) 2134 form = gmForms.cTextForm(template_file = amts_data_template_def_file) 2135 # <S>ection with intakes</S> 2136 amts_sections = '<S>%s</S>' % ''.join ([ 2137 i._get_as_amts_data(strict = False) for i in intakes_this_page 2138 ]) 2139 if this_page == total_pages: 2140 # <S>ection with allergy data</S> 2141 emr = self.pat.emr 2142 amts_sections += emr.allergy_state._get_as_amts_data(strict = False) % ''.join ([ 2143 a._get_as_amts_data(strict = False) for a in emr.get_allergies() 2144 ]) 2145 self.set_placeholder(key = 'amts_intakes_as_data', value = amts_sections, known_only = False) 2146 # self.set_placeholder(key = u'amts_check_symbol', value = gmMedication.calculate_amts_data_check_symbol(intakes = intakes_this_page), known_only = False) 2147 if total_pages == 1: 2148 pg_idx = '' 2149 else: 2150 pg_idx = '%s' % this_page 2151 self.set_placeholder(key = 'amts_page_idx', value = pg_idx, known_only = False) 2152 self.set_placeholder(key = 'amts_total_pages', value = '%s' % total_pages, known_only = False) 2153 success = form.substitute_placeholders(data_source = self) 2154 self.unset_placeholder(key = 'amts_intakes_as_data') 2155 # self.unset_placeholder(key = u'amts_check_symbol') 2156 self.unset_placeholder(key = 'amts_page_idx') 2157 self.unset_placeholder(key = 'amts_total_pages') 2158 if not success: 2159 _log.error('cannot substitute into amts data file form template') 2160 return 2161 2162 data_file = form.re_editable_filenames[0] 2163 png_file = '%s%s.png' % (png_file_base, this_page) 2164 latin1_data_file = gmTools.recode_file ( 2165 source_file = data_file, 2166 source_encoding = 'utf8', 2167 target_encoding = 'latin1', 2168 base_dir = os.path.split(data_file)[0] 2169 ) 2170 cmd = '%s %s %s' % (dmtx_creator, latin1_data_file, png_file) 2171 success = gmShellAPI.run_command_in_shell(command = cmd, blocking = True) 2172 if not success: 2173 _log.error('error running [%s]' % cmd) 2174 return 2175 2176 # cache file names for later use in \embedfile 2177 self.set_placeholder(key = 'amts_data_file_%s' % this_page, value = latin1_data_file, known_only = False) 2178 self.set_placeholder(key = 'amts_png_file_%s' % this_page, value = png_file, known_only = False) 2179 2180 self.set_placeholder(key = 'amts_png_file_current_page', value = png_file_base + '\\thepage', known_only = False)
2181 2182 #--------------------------------------------------------
2183 - def _get_variant_current_meds_for_rx(self, data=None):
2184 if data is None: 2185 return self._escape(_('current_meds_for_rx: template is missing')) 2186 2187 emr = self.pat.emr 2188 from Gnumed.wxpython import gmMedicationWidgets 2189 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr) 2190 if current_meds is None: 2191 return '' 2192 2193 intakes2show = {} 2194 for intake in current_meds: 2195 fields_dict = intake.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) 2196 fields_dict['medically_formatted_start'] = self._escape(intake.medically_formatted_start) 2197 if intake['pk_drug_product'] is None: 2198 fields_dict['product'] = self._escape(_('generic %s') % fields_dict['substance']) 2199 fields_dict['contains'] = self._escape('%s %s%s' % (fields_dict['substance'], fields_dict['amount'], fields_dict['unit'])) 2200 intakes2show[fields_dict['product']] = fields_dict 2201 else: 2202 comps = [ c.split('::') for c in intake.containing_drug['components'] ] 2203 fields_dict['contains'] = self._escape('; '.join([ '%s %s%s' % (c[0], c[1], c[2]) for c in comps ])) 2204 intakes2show[intake['product']] = fields_dict # this will make multi-component drugs unique 2205 2206 intakes2dispense = {} 2207 for product, intake in intakes2show.items(): 2208 msg = _('Dispense how much/many of "%(product)s (%(contains)s)" ?') % intake 2209 amount2dispense = wx.GetTextFromUser(msg, _('Amount to dispense ?')) 2210 if amount2dispense == '': 2211 continue 2212 intake['amount2dispense'] = amount2dispense 2213 intakes2dispense[product] = intake 2214 2215 return '\n'.join([ data % intake for intake in intakes2dispense.values() ])
2216 2217 #--------------------------------------------------------
2218 - def _get_variant_substance_abuse(self, data=None):
2219 if data is None: 2220 return self._escape(_('template is missing')) 2221 template = data 2222 from Gnumed.wxpython import gmHabitWidgets 2223 abuses = gmHabitWidgets.manage_substance_abuse(patient = self.pat) 2224 if abuses is None: 2225 return '' 2226 lines = [] 2227 for a in abuses: 2228 fields = a.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) 2229 fields['harmful_use_type'] = a.harmful_use_type_string 2230 lines.append(template % fields) 2231 return '\n'.join(lines)
2232 2233 #--------------------------------------------------------
2234 - def _get_variant_current_meds(self, data=None):
2235 2236 if data is None: 2237 return self._escape(_('template is missing')) 2238 2239 parts = data.split(self.__args_divider) 2240 template = parts[0] 2241 ask_user = False 2242 if len(parts) > 1: 2243 ask_user = (parts[1] == 'select') 2244 2245 emr = self.pat.emr 2246 if ask_user: 2247 from Gnumed.wxpython import gmMedicationWidgets 2248 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr) 2249 if current_meds is None: 2250 return '' 2251 else: 2252 current_meds = emr.get_current_medications ( 2253 include_inactive = False, 2254 include_unapproved = True, 2255 order_by = 'product, substance' 2256 ) 2257 if len(current_meds) == 0: 2258 return '' 2259 2260 lines = [] 2261 for m in current_meds: 2262 data = m.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) 2263 data['medically_formatted_start'] = self._escape(m.medically_formatted_start) 2264 lines.append(template % data) 2265 2266 return '\n'.join(lines)
2267 #--------------------------------------------------------
2268 - def _get_variant_current_meds_table(self, data=None):
2269 return gmMedication.format_substance_intake ( 2270 emr = self.pat.emr, 2271 output_format = self.__esc_style, 2272 table_type = 'by-product' 2273 )
2274 #--------------------------------------------------------
2275 - def _get_variant_current_meds_notes(self, data=None):
2276 return gmMedication.format_substance_intake_notes ( 2277 emr = self.pat.emr, 2278 output_format = self.__esc_style, 2279 table_type = 'by-product' 2280 )
2281 #--------------------------------------------------------
2282 - def _get_variant_lab_table(self, data=None):
2283 return gmPathLab.format_test_results ( 2284 results = self.pat.emr.get_test_results_by_date(), 2285 output_format = self.__esc_style 2286 )
2287 2288 #--------------------------------------------------------
2289 - def _get_variant_test_results(self, data=None):
2290 2291 template = '' 2292 date_format = '%Y %b %d %H:%M' 2293 separator = '\n' 2294 options = data.split(self.__args_divider) 2295 try: 2296 template = options[0].strip() 2297 date_format = options[1] 2298 separator = options[2] 2299 except IndexError: 2300 pass 2301 2302 if date_format.strip() == '': 2303 date_format = '%Y %b %d %H:%M' 2304 if separator.strip() == '': 2305 separator = '\n' 2306 2307 from Gnumed.wxpython.gmMeasurementWidgets import manage_measurements 2308 results = manage_measurements(single_selection = False, emr = self.pat.emr) 2309 if results is None: 2310 if self.debug: 2311 return self._escape(_('no results for this patient (available or selected)')) 2312 return '' 2313 2314 if template == '': 2315 return (separator + separator).join([ self._escape(r.format(date_format = date_format)) for r in results ]) 2316 2317 return separator.join([ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in results ])
2318 2319 #--------------------------------------------------------
2320 - def _get_variant_most_recent_test_results(self, data=None):
2321 most_recent = gmPathLab.get_most_recent_result_for_test_types ( 2322 pk_test_types = None, # we want most recent results for *all* tests 2323 pk_patient = self.pat.ID, 2324 consider_meta_type = True, 2325 order_by = 'unified_name' 2326 ) 2327 if len(most_recent) == 0: 2328 if self.debug: 2329 return self._escape(_('no results for this patient available')) 2330 return '' 2331 2332 from Gnumed.wxpython.gmMeasurementWidgets import manage_measurements 2333 results2show = manage_measurements ( 2334 single_selection = False, 2335 measurements2manage = most_recent, 2336 message = _('Most recent results: select the ones to include') 2337 ) 2338 if results2show is None: 2339 if self.debug: 2340 return self._escape(_('no results for this patient selected')) 2341 return '' 2342 2343 template, date_format, separator = self.__parse_ph_options ( 2344 option_defs = {'tmpl': '', 'dfmt': '%Y %b %d', 'sep': '\n'}, 2345 options_string = data 2346 ) 2347 if template == '': 2348 return (separator + separator).join([ self._escape(r.format(date_format = date_format)) for r in results2show ]) 2349 2350 return separator.join([ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in results2show ])
2351 2352 #--------------------------------------------------------
2353 - def _get_variant_latest_vaccs_table(self, data=None):
2354 return gmVaccination.format_latest_vaccinations ( 2355 output_format = self.__esc_style, 2356 emr = self.pat.emr 2357 )
2358 #--------------------------------------------------------
2359 - def _get_variant_vaccination_history(self, data=None):
2360 options = data.split(self.__args_divider) 2361 template = options[0] 2362 if len(options) > 1: 2363 date_format = options[1] 2364 else: 2365 date_format = '%Y %b %d' 2366 vaccinations_as_dict = [] 2367 for v in self.pat.emr.get_vaccinations(order_by = 'date_given DESC, vaccine'): 2368 v_as_dict = v.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) 2369 v_as_dict['l10n_indications'] = [ ind['l10n_indication'] for ind in v['indications'] ] 2370 vaccinations_as_dict.append(v_as_dict) 2371 2372 return u'\n'.join([ template % v for v in vaccinations_as_dict ])
2373 2374 #--------------------------------------------------------
2375 - def _get_variant_PHX(self, data=None):
2376 2377 if data is None: 2378 if self.debug: 2379 _log.error('PHX: missing placeholder arguments') 2380 return self._escape(_('PHX: Invalid placeholder options.')) 2381 return '' 2382 2383 _log.debug('arguments: %s', data) 2384 2385 data_parts = data.split(self.__args_divider) 2386 template = '%s' 2387 separator = '\n' 2388 date_format = '%Y %b %d' 2389 try: 2390 template = data_parts[0] 2391 separator = data_parts[1] 2392 date_format = data_parts[2] 2393 except IndexError: 2394 pass 2395 2396 phxs = gmEMRStructWidgets.select_health_issues(emr = self.pat.emr) 2397 if phxs is None: 2398 if self.debug: 2399 return self._escape(_('no PHX for this patient (available or selected)')) 2400 return '' 2401 2402 return separator.join ([ 2403 template % phx.fields_as_dict ( 2404 date_format = date_format, 2405 escape_style = self.__esc_style, 2406 bool_strings = (self._escape(_('yes')), self._escape(_('no'))) 2407 ) for phx in phxs 2408 ])
2409 2410 #--------------------------------------------------------
2411 - def _get_variant_problems(self, data=None):
2412 2413 if data is None: 2414 return self._escape(_('template is missing')) 2415 probs = self.pat.emr.get_problems() 2416 return '\n'.join([ data % p.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for p in probs ])
2417 2418 #--------------------------------------------------------
2419 - def _get_variant_diagnoses(self, data=None):
2420 2421 if data is None: 2422 return self._escape(_('template is missing')) 2423 template = data 2424 dxs = self.pat.emr.candidate_diagnoses 2425 if len(dxs) == 0: 2426 _log.debug('no diagnoses available') 2427 return '' 2428 selected = gmListWidgets.get_choices_from_list ( 2429 msg = _('Select the relevant diagnoses:'), 2430 caption = _('Diagnosis selection'), 2431 columns = [ _('Diagnosis'), _('Marked confidential'), _('Certainty'), _('Source') ], 2432 choices = [[ 2433 dx['diagnosis'], 2434 gmTools.bool2subst(dx['explicitely_confidential'], _('yes'), _('no'), _('unknown')), 2435 gmTools.coalesce(dx['diagnostic_certainty_classification'], ''), 2436 dx['source'] 2437 ] for dx in dxs 2438 ], 2439 data = dxs, 2440 single_selection = False, 2441 can_return_empty = True 2442 ) 2443 if selected is None: 2444 _log.debug('user did not select any diagnoses') 2445 return '' 2446 if len(selected) == 0: 2447 _log.debug('user did not select any diagnoses') 2448 return '' 2449 #return template % {'diagnosis': u'', 'diagnostic_certainty_classification': u''} 2450 return '\n'.join(template % self._escape_dict(dx, none_string = '?', bool_strings = [_('yes'), _('no')]) for dx in selected)
2451 2452 #--------------------------------------------------------
2453 - def _get_variant_today(self, data='%Y %b %d'):
2454 return self._escape(gmDateTime.pydt_now_here().strftime(data))
2455 2456 #--------------------------------------------------------
2457 - def _get_variant_tex_escape(self, data=None):
2459 2460 #--------------------------------------------------------
2461 - def _get_variant_url_escape(self, data=None):
2462 return self._escape(urllib.parse.quote(data.encode('utf8')))
2463 2464 #--------------------------------------------------------
2465 - def _get_variant_text_snippet(self, data=None):
2466 data_parts = data.split(self.__args_divider) 2467 keyword = data_parts[0] 2468 template = '%s' 2469 if len(data_parts) > 1: 2470 template = data_parts[1] 2471 expansion = gmKeywordExpansionWidgets.expand_keyword(keyword = keyword, show_list_if_needed = True) 2472 if expansion is None: 2473 if self.debug: 2474 return self._escape(_('no textual expansion found for keyword <%s>') % keyword) 2475 return '' 2476 2477 #return template % self._escape(expansion) 2478 return template % expansion
2479 2480 #--------------------------------------------------------
2481 - def _get_variant_data_snippet(self, data=None):
2482 parts = data.split(self.__args_divider) 2483 keyword = parts[0] 2484 template = '%s' 2485 target_mime = None 2486 target_ext = None 2487 if len(parts) > 1: 2488 template = parts[1] 2489 if len(parts) > 2: 2490 if parts[2].strip() != '': 2491 target_mime = parts[2].strip() 2492 if len(parts) > 3: 2493 if parts[3].strip() != '': 2494 target_ext = parts[3].strip() 2495 2496 expansion = gmKeywordExpansion.get_expansion ( 2497 keyword = keyword, 2498 textual_only = False, 2499 binary_only = True 2500 ) 2501 if expansion is None: 2502 if self.debug: 2503 return self._escape(_('no binary expansion found for keyword <%s>') % keyword) 2504 return '' 2505 2506 saved_fname = expansion.save_to_file() 2507 if saved_fname is None: 2508 if self.debug: 2509 return self._escape(_('cannot export data of binary expansion keyword <%s>') % keyword) 2510 return '' 2511 2512 if expansion['is_encrypted']: 2513 saved_fname = gmCrypto.gpg_decrypt_file(filename = saved_fname) 2514 if saved_fname is None: 2515 if self.debug: 2516 return self._escape(_('cannot decrypt data of binary expansion keyword <%s>') % keyword) 2517 return '' 2518 2519 if target_mime is None: 2520 return template % saved_fname 2521 2522 converted_fname = gmMimeLib.convert_file(filename = saved_fname, target_mime = target_mime, target_extension = target_ext) 2523 if converted_fname is None: 2524 if self.debug: 2525 return self._escape(_('cannot convert data of binary expansion keyword <%s>') % keyword) 2526 # hoping that the target can cope: 2527 return template % saved_fname 2528 2529 return template % converted_fname
2530 2531 #--------------------------------------------------------
2532 - def _get_variant_qrcode(self, data=None):
2533 options = data.split(self.__args_divider) 2534 if len(options) == 0: 2535 return None 2536 text4qr = options[0] 2537 if len(options) > 1: 2538 template = options[1] 2539 else: 2540 template = u'%s' 2541 qr_filename = gmTools.create_qrcode(text = text4qr) 2542 if qr_filename is None: 2543 return self._escape('cannot_create_QR_code') 2544 2545 return template % qr_filename
2546 2547 #--------------------------------------------------------
2548 - def _get_variant_range_of(self, data=None):
2549 if data is None: 2550 return None 2551 # wrapper code already takes care of actually 2552 # selecting the range so all we need to do here 2553 # is to return the data itself 2554 return data
2555 2556 #--------------------------------------------------------
2557 - def _get_variant_yes_no(self, data=None):
2558 if data is None: 2559 return None 2560 2561 defaults = {'msg': None, 'yes': None, 'no': ''} 2562 msg, yes_txt, no_txt = self.__parse_ph_options(option_defs = defaults, options_string = data) 2563 if None in [msg, yes_txt]: 2564 return self._escape(u'YES_NO lacks proper definition') 2565 2566 yes = gmGuiHelpers.gm_show_question(question = msg, cancel_button = False, title = 'Placeholder question') 2567 if yes: 2568 return self._escape(yes_txt) 2569 2570 return self._escape(no_txt)
2571 2572 #--------------------------------------------------------
2573 - def _get_variant_if_not_empty(self, data=None):
2574 if data is None: 2575 return None 2576 2577 parts = data.split(self.__args_divider) 2578 if len(parts) < 3: 2579 return 'IF_NOT_EMPTY lacks <instead> definition' 2580 txt = parts[0] 2581 template = parts[1] 2582 instead = parts[2] 2583 2584 if txt.strip() == '': 2585 return instead 2586 if '%s' in template: 2587 return template % txt 2588 return template
2589 2590 #--------------------------------------------------------
2591 - def _get_variant_if_debugging(self, data=None):
2592 2593 if data is None: 2594 return None 2595 parts = data.split(self.__args_divider) 2596 if len(parts) < 2: 2597 return self._escape(u'IF_DEBUGGING lacks proper definition') 2598 debug_str = parts[0] 2599 non_debug_str = parts[1] 2600 if self.debug: 2601 return debug_str 2602 return non_debug_str
2603 2604 #--------------------------------------------------------
2605 - def _get_variant_free_text(self, data=None):
2606 2607 if data is None: 2608 parts = [] 2609 msg = _('generic text') 2610 cache_key = 'free_text::%s' % datetime.datetime.now() 2611 else: 2612 parts = data.split(self.__args_divider) 2613 msg = parts[0] 2614 cache_key = 'free_text::%s' % msg 2615 2616 try: 2617 return self.__cache[cache_key] 2618 except KeyError: 2619 pass 2620 2621 if len(parts) > 1: 2622 preset = parts[1] 2623 else: 2624 preset = '' 2625 2626 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 2627 None, 2628 -1, 2629 title = _('Replacing <free_text> placeholder'), 2630 msg = _('Below you can enter free text.\n\n [%s]') % msg, 2631 text = preset 2632 ) 2633 dlg.enable_user_formatting = True 2634 decision = dlg.ShowModal() 2635 text = dlg.value.strip() 2636 is_user_formatted = dlg.is_user_formatted 2637 dlg.DestroyLater() 2638 2639 if decision != wx.ID_SAVE: 2640 if self.debug: 2641 return self._escape(_('Text input cancelled by user.')) 2642 return self._escape('') 2643 2644 # user knows "best" 2645 if is_user_formatted: 2646 self.__cache[cache_key] = text 2647 return text 2648 2649 text = self._escape(text) 2650 self.__cache[cache_key] = text 2651 return text
2652 2653 #--------------------------------------------------------
2654 - def _get_variant_bill(self, data=None):
2655 try: 2656 bill = self.__cache['bill'] 2657 except KeyError: 2658 from Gnumed.wxpython import gmBillingWidgets 2659 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2660 if bill is None: 2661 if self.debug: 2662 return self._escape(_('no bill selected')) 2663 return '' 2664 self.__cache['bill'] = bill 2665 2666 parts = data.split(self.__args_divider) 2667 template = parts[0] 2668 if len(parts) > 1: 2669 date_format = parts[1] 2670 else: 2671 date_format = '%Y %B %d' 2672 2673 return template % bill.fields_as_dict(date_format = date_format, escape_style = self.__esc_style)
2674 2675 #--------------------------------------------------------
2676 - def _get_variant_bill_scan2pay(self, data=None):
2677 try: 2678 bill = self.__cache['bill'] 2679 except KeyError: 2680 from Gnumed.wxpython import gmBillingWidgets 2681 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2682 if bill is None: 2683 if self.debug: 2684 return self._escape(_('no bill selected')) 2685 return '' 2686 self.__cache['bill'] = bill 2687 2688 format = self.__parse_ph_options(option_defs = {'fmt': 'qr'}, options_string = data) 2689 if format not in ['qr', 'txt']: 2690 if self.debug: 2691 return self._escape(_('praxis_scan2pay: invalid format (qr/txt)')) 2692 return '' 2693 2694 from Gnumed.business import gmBilling 2695 data_str = gmBilling.get_scan2pay_data ( 2696 gmPraxis.gmCurrentPraxisBranch(), 2697 bill, 2698 provider = gmStaff.gmCurrentProvider() 2699 ) 2700 if data_str is None: 2701 if self.debug: 2702 return self._escape('bill_scan2pay-cannot_create_data_file') 2703 return '' 2704 2705 if format == 'txt': 2706 return self._escape(data_str) 2707 2708 if format == 'qr': 2709 qr_filename = gmTools.create_qrcode(text = data_str) 2710 if qr_filename is not None: 2711 return qr_filename 2712 if self.debug: 2713 return self._escape('bill_scan2pay-cannot_create_QR_code') 2714 return '' 2715 2716 return None
2717 2718 #--------------------------------------------------------
2719 - def _get_variant_bill_item(self, data=None):
2720 try: 2721 bill = self.__cache['bill'] 2722 except KeyError: 2723 from Gnumed.wxpython import gmBillingWidgets 2724 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2725 if bill is None: 2726 if self.debug: 2727 return self._escape(_('no bill selected')) 2728 return '' 2729 self.__cache['bill'] = bill 2730 2731 parts = data.split(self.__args_divider) 2732 template = parts[0] 2733 if len(parts) > 1: 2734 date_format = parts[1] 2735 else: 2736 date_format = '%Y %B %d' 2737 2738 return '\n'.join([ template % i.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for i in bill.bill_items ])
2739 2740 #--------------------------------------------------------
2741 - def __get_variant_bill_adr_part(self, data=None, part=None):
2742 try: 2743 bill = self.__cache['bill'] 2744 except KeyError: 2745 from Gnumed.wxpython import gmBillingWidgets 2746 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2747 if bill is None: 2748 if self.debug: 2749 return self._escape(_('no bill selected')) 2750 return '' 2751 self.__cache['bill'] = bill 2752 self.__cache['bill-adr'] = bill.address 2753 2754 try: 2755 bill_adr = self.__cache['bill-adr'] 2756 except KeyError: 2757 bill_adr = bill.address 2758 self.__cache['bill-adr'] = bill_adr 2759 2760 if bill_adr is None: 2761 if self.debug: 2762 return self._escape(_('[%s] bill has no address') % part) 2763 return '' 2764 2765 if bill_adr[part] is None: 2766 return self._escape('') 2767 2768 if data is None: 2769 return self._escape(bill_adr[part]) 2770 2771 if data == '': 2772 return self._escape(bill_adr[part]) 2773 2774 return data % self._escape(bill_adr[part])
2775 2776 #--------------------------------------------------------
2777 - def _get_variant_bill_adr_street(self, data='?'):
2778 return self.__get_variant_bill_adr_part(data = data, part = 'street')
2779 2780 #--------------------------------------------------------
2781 - def _get_variant_bill_adr_number(self, data='?'):
2782 return self.__get_variant_bill_adr_part(data = data, part = 'number')
2783 2784 #--------------------------------------------------------
2785 - def _get_variant_bill_adr_subunit(self, data='?'):
2786 return self.__get_variant_bill_adr_part(data = data, part = 'subunit')
2787 #--------------------------------------------------------
2788 - def _get_variant_bill_adr_location(self, data='?'):
2789 return self.__get_variant_bill_adr_part(data = data, part = 'urb')
2790 2791 #--------------------------------------------------------
2792 - def _get_variant_bill_adr_suburb(self, data='?'):
2793 return self.__get_variant_bill_adr_part(data = data, part = 'suburb')
2794 2795 #--------------------------------------------------------
2796 - def _get_variant_bill_adr_postcode(self, data='?'):
2797 return self.__get_variant_bill_adr_part(data = data, part = 'postcode')
2798 2799 #--------------------------------------------------------
2800 - def _get_variant_bill_adr_region(self, data='?'):
2801 return self.__get_variant_bill_adr_part(data = data, part = 'l10n_region')
2802 2803 #--------------------------------------------------------
2804 - def _get_variant_bill_adr_country(self, data='?'):
2805 return self.__get_variant_bill_adr_part(data = data, part = 'l10n_country')
2806 2807 #-------------------------------------------------------- 2808 # internal helpers 2809 #--------------------------------------------------------
2810 - def __parse_ph_options(self, option_defs=None, options_string=None):
2811 """Returns a tuple of values in the order of option_defs.keys().""" 2812 assert (option_defs is not None), '<option_defs> must not be None' 2813 assert (options_string is not None), '<options_string> must not be None' 2814 2815 _log.debug('parsing ::%s:: for %s', options_string, option_defs) 2816 options2return = option_defs.copy() 2817 if options_string.strip() == '': 2818 return tuple([ options2return[o_name] for o_name in options2return.keys() ]) 2819 2820 if '=' not in options_string: 2821 return tuple([ 'invalid param fmt' for o_name in options2return.keys() ]) 2822 2823 options = options_string.split(self.__args_divider) 2824 for opt in options: 2825 opt_name, opt_val = opt.split('=', 1) 2826 if opt_name.strip() not in option_defs.keys(): 2827 continue 2828 options2return[opt_name] = opt_val 2829 2830 _log.debug('found: %s', options2return) 2831 return tuple([ options2return[o_name] for o_name in options2return.keys() ])
2832 2833 #--------------------------------------------------------
2834 - def _escape(self, text=None):
2835 if self.__esc_func is None: 2836 return text 2837 assert (text is not None), 'text=None passed to _escape()' 2838 return self.__esc_func(text)
2839 2840 #--------------------------------------------------------
2841 - def _escape_dict(self, the_dict=None, date_format='%Y %b %d %H:%M', none_string='', bool_strings=None):
2842 if bool_strings is None: 2843 bools = {True: _('true'), False: _('false')} 2844 else: 2845 bools = {True: bool_strings[0], False: bool_strings[1]} 2846 data = {} 2847 for field in the_dict.keys(): 2848 # FIXME: harden against BYTEA fields 2849 #if type(self._payload[self._idx[field]]) == ... 2850 # data[field] = _('<%s bytes of binary data>') % len(self._payload[self._idx[field]]) 2851 # continue 2852 val = the_dict[field] 2853 if val is None: 2854 data[field] = none_string 2855 continue 2856 if isinstance(val, bool): 2857 data[field] = bools[val] 2858 continue 2859 if isinstance(val, datetime.datetime): 2860 data[field] = gmDateTime.pydt_strftime(val, format = date_format) 2861 if self.__esc_style in ['latex', 'tex']: 2862 data[field] = gmTools.tex_escape_string(data[field]) 2863 elif self.__esc_style in ['xetex', 'xelatex']: 2864 data[field] = gmTools.xetex_escape_string(data[field]) 2865 continue 2866 try: 2867 data[field] = str(val, encoding = 'utf8', errors = 'replace') 2868 except TypeError: 2869 try: 2870 data[field] = str(val) 2871 except (UnicodeDecodeError, TypeError): 2872 val = '%s' % str(val) 2873 data[field] = val.decode('utf8', 'replace') 2874 if self.__esc_style in ['latex', 'tex']: 2875 data[field] = gmTools.tex_escape_string(data[field]) 2876 elif self.__esc_style in ['xetex', 'xelatex']: 2877 data[field] = gmTools.xetex_escape_string(data[field]) 2878 return data
2879 2880 #---------------------------------------------------------------------
2881 -def test_placeholders():
2882 2883 _log.debug('testing for placeholders with pattern: %s', first_pass_placeholder_regex) 2884 2885 data_source = gmPlaceholderHandler() 2886 original_line = '' 2887 2888 while True: 2889 # get input from user 2890 line = wx.GetTextFromUser ( 2891 _('Enter some text containing a placeholder:'), 2892 _('Testing placeholders'), 2893 centre = True, 2894 default_value = original_line 2895 ) 2896 if line.strip() == '': 2897 break 2898 original_line = line 2899 # replace 2900 placeholders_in_line = regex.findall(first_pass_placeholder_regex, line, regex.IGNORECASE) 2901 if len(placeholders_in_line) == 0: 2902 continue 2903 for placeholder in placeholders_in_line: 2904 try: 2905 val = data_source[placeholder] 2906 except Exception: 2907 val = _('error with placeholder [%s]') % placeholder 2908 if val is None: 2909 val = _('error with placeholder [%s]') % placeholder 2910 line = line.replace(placeholder, val) 2911 # show 2912 msg = _( 2913 'Input: %s\n' 2914 '\n' 2915 'Output:\n' 2916 '%s' 2917 ) % ( 2918 original_line, 2919 line 2920 ) 2921 gmGuiHelpers.gm_show_info ( 2922 title = _('Testing placeholders'), 2923 info = msg 2924 )
2925 2926 #=====================================================================
2927 -class cMacroPrimitives:
2928 """Functions a macro can legally use. 2929 2930 An instance of this class is passed to the GNUmed scripting 2931 listener. Hence, all actions a macro can legally take must 2932 be defined in this class. Thus we achieve some screening for 2933 security and also thread safety handling. 2934 """ 2935 #-----------------------------------------------------------------
2936 - def __init__(self, personality = None):
2937 if personality is None: 2938 raise gmExceptions.ConstructorError('must specify personality') 2939 self.__personality = personality 2940 self.__attached = 0 2941 self._get_source_personality = None 2942 self.__user_done = False 2943 self.__user_answer = 'no answer yet' 2944 self.__pat = gmPerson.gmCurrentPatient() 2945 2946 self.__auth_cookie = str(random.random()) 2947 self.__pat_lock_cookie = str(random.random()) 2948 self.__lock_after_load_cookie = str(random.random()) 2949 2950 _log.info('slave mode personality is [%s]', personality)
2951 #----------------------------------------------------------------- 2952 # public API 2953 #-----------------------------------------------------------------
2954 - def attach(self, personality = None):
2955 if self.__attached: 2956 _log.error('attach with [%s] rejected, already serving a client', personality) 2957 return (0, _('attach rejected, already serving a client')) 2958 if personality != self.__personality: 2959 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality)) 2960 return (0, _('attach to personality [%s] rejected') % personality) 2961 self.__attached = 1 2962 self.__auth_cookie = str(random.random()) 2963 return (1, self.__auth_cookie)
2964 #-----------------------------------------------------------------
2965 - def detach(self, auth_cookie=None):
2966 if not self.__attached: 2967 return 1 2968 if auth_cookie != self.__auth_cookie: 2969 _log.error('rejecting detach() with cookie [%s]' % auth_cookie) 2970 return 0 2971 self.__attached = 0 2972 return 1
2973 #-----------------------------------------------------------------
2974 - def force_detach(self):
2975 if not self.__attached: 2976 return 1 2977 self.__user_done = False 2978 # FIXME: use self.__sync_cookie for syncing with user interaction 2979 wx.CallAfter(self._force_detach) 2980 return 1
2981 #-----------------------------------------------------------------
2982 - def version(self):
2983 ver = _cfg.get(option = 'client_version') 2984 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
2985 #-----------------------------------------------------------------
2986 - def shutdown_gnumed(self, auth_cookie=None, forced=False):
2987 """Shuts down this client instance.""" 2988 if not self.__attached: 2989 return 0 2990 if auth_cookie != self.__auth_cookie: 2991 _log.error('non-authenticated shutdown_gnumed()') 2992 return 0 2993 wx.CallAfter(self._shutdown_gnumed, forced) 2994 return 1
2995 #-----------------------------------------------------------------
2996 - def raise_gnumed(self, auth_cookie = None):
2997 """Raise ourselves to the top of the desktop.""" 2998 if not self.__attached: 2999 return 0 3000 if auth_cookie != self.__auth_cookie: 3001 _log.error('non-authenticated raise_gnumed()') 3002 return 0 3003 return "cMacroPrimitives.raise_gnumed() not implemented"
3004 #-----------------------------------------------------------------
3005 - def get_loaded_plugins(self, auth_cookie = None):
3006 if not self.__attached: 3007 return 0 3008 if auth_cookie != self.__auth_cookie: 3009 _log.error('non-authenticated get_loaded_plugins()') 3010 return 0 3011 gb = gmGuiBroker.GuiBroker() 3012 return gb['horstspace.notebook.gui'].keys()
3013 #-----------------------------------------------------------------
3014 - def raise_notebook_plugin(self, auth_cookie = None, a_plugin = None):
3015 """Raise a notebook plugin within GNUmed.""" 3016 if not self.__attached: 3017 return 0 3018 if auth_cookie != self.__auth_cookie: 3019 _log.error('non-authenticated raise_notebook_plugin()') 3020 return 0 3021 # FIXME: use semaphore 3022 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin) 3023 return 1
3024 #-----------------------------------------------------------------
3025 - def load_patient_from_external_source(self, auth_cookie = None):
3026 """Load external patient, perhaps create it. 3027 3028 Callers must use get_user_answer() to get status information. 3029 It is unsafe to proceed without knowing the completion state as 3030 the controlled client may be waiting for user input from a 3031 patient selection list. 3032 """ 3033 if not self.__attached: 3034 return (0, _('request rejected, you are not attach()ed')) 3035 if auth_cookie != self.__auth_cookie: 3036 _log.error('non-authenticated load_patient_from_external_source()') 3037 return (0, _('rejected load_patient_from_external_source(), not authenticated')) 3038 if self.__pat.locked: 3039 _log.error('patient is locked, cannot load from external source') 3040 return (0, _('current patient is locked')) 3041 self.__user_done = False 3042 wx.CallAfter(self._load_patient_from_external_source) 3043 self.__lock_after_load_cookie = str(random.random()) 3044 return (1, self.__lock_after_load_cookie)
3045 #-----------------------------------------------------------------
3046 - def lock_loaded_patient(self, auth_cookie = None, lock_after_load_cookie = None):
3047 if not self.__attached: 3048 return (0, _('request rejected, you are not attach()ed')) 3049 if auth_cookie != self.__auth_cookie: 3050 _log.error('non-authenticated lock_load_patient()') 3051 return (0, _('rejected lock_load_patient(), not authenticated')) 3052 # FIXME: ask user what to do about wrong cookie 3053 if lock_after_load_cookie != self.__lock_after_load_cookie: 3054 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie) 3055 return (0, 'patient lock-after-load request rejected, wrong cookie provided') 3056 self.__pat.locked = True 3057 self.__pat_lock_cookie = str(random.random()) 3058 return (1, self.__pat_lock_cookie)
3059 #-----------------------------------------------------------------
3060 - def lock_into_patient(self, auth_cookie = None, search_params = None):
3061 if not self.__attached: 3062 return (0, _('request rejected, you are not attach()ed')) 3063 if auth_cookie != self.__auth_cookie: 3064 _log.error('non-authenticated lock_into_patient()') 3065 return (0, _('rejected lock_into_patient(), not authenticated')) 3066 if self.__pat.locked: 3067 _log.error('patient is already locked') 3068 return (0, _('already locked into a patient')) 3069 searcher = gmPersonSearch.cPatientSearcher_SQL() 3070 if type(search_params) == dict: 3071 idents = searcher.get_identities(search_dict=search_params) 3072 raise Exception("must use dto, not search_dict") 3073 else: 3074 idents = searcher.get_identities(search_term=search_params) 3075 if idents is None: 3076 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict)) 3077 if len(idents) == 0: 3078 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict)) 3079 # FIXME: let user select patient 3080 if len(idents) > 1: 3081 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict)) 3082 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]): 3083 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict)) 3084 self.__pat.locked = True 3085 self.__pat_lock_cookie = str(random.random()) 3086 return (1, self.__pat_lock_cookie)
3087 #-----------------------------------------------------------------
3088 - def unlock_patient(self, auth_cookie = None, unlock_cookie = None):
3089 if not self.__attached: 3090 return (0, _('request rejected, you are not attach()ed')) 3091 if auth_cookie != self.__auth_cookie: 3092 _log.error('non-authenticated unlock_patient()') 3093 return (0, _('rejected unlock_patient, not authenticated')) 3094 # we ain't locked anyways, so succeed 3095 if not self.__pat.locked: 3096 return (1, '') 3097 # FIXME: ask user what to do about wrong cookie 3098 if unlock_cookie != self.__pat_lock_cookie: 3099 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie) 3100 return (0, 'patient unlock request rejected, wrong cookie provided') 3101 self.__pat.locked = False 3102 return (1, '')
3103 #-----------------------------------------------------------------
3104 - def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
3105 if not self.__attached: 3106 return 0 3107 if auth_cookie != self.__auth_cookie: 3108 _log.error('non-authenticated select_identity()') 3109 return 0 3110 return "cMacroPrimitives.assume_staff_identity() not implemented"
3111 #-----------------------------------------------------------------
3112 - def get_user_answer(self):
3113 if not self.__user_done: 3114 return (0, 'still waiting') 3115 self.__user_done = False 3116 return (1, self.__user_answer)
3117 #----------------------------------------------------------------- 3118 # internal API 3119 #-----------------------------------------------------------------
3120 - def _force_detach(self):
3121 msg = _( 3122 'Someone tries to forcibly break the existing\n' 3123 'controlling connection. This may or may not\n' 3124 'have legitimate reasons.\n\n' 3125 'Do you want to allow breaking the connection ?' 3126 ) 3127 can_break_conn = gmGuiHelpers.gm_show_question ( 3128 aMessage = msg, 3129 aTitle = _('forced detach attempt') 3130 ) 3131 if can_break_conn: 3132 self.__user_answer = 1 3133 else: 3134 self.__user_answer = 0 3135 self.__user_done = True 3136 if can_break_conn: 3137 self.__pat.locked = False 3138 self.__attached = 0 3139 return 1
3140 #-----------------------------------------------------------------
3141 - def _shutdown_gnumed(self, forced=False):
3142 top_win = wx.GetApp().GetTopWindow() 3143 if forced: 3144 top_win.DestroyLater() 3145 else: 3146 top_win.Close()
3147 #-----------------------------------------------------------------
3149 patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True) 3150 if patient is not None: 3151 self.__user_answer = 1 3152 else: 3153 self.__user_answer = 0 3154 self.__user_done = True 3155 return 1
3156 #===================================================================== 3157 # main 3158 #===================================================================== 3159 if __name__ == '__main__': 3160 3161 if len(sys.argv) < 2: 3162 sys.exit() 3163 3164 if sys.argv[1] != 'test': 3165 sys.exit() 3166 3167 gmI18N.activate_locale() 3168 gmI18N.install_domain() 3169 3170 #--------------------------------------------------------
3171 - def test_placeholders():
3172 handler = gmPlaceholderHandler() 3173 handler.debug = True 3174 3175 for placeholder in ['a', 'b']: 3176 print(handler[placeholder]) 3177 3178 pat = gmPersonSearch.ask_for_patient() 3179 if pat is None: 3180 return 3181 3182 gmPatSearchWidgets.set_active_patient(patient = pat) 3183 3184 print('DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']) 3185 3186 app = wx.PyWidgetTester(size = (200, 50)) 3187 3188 ph = 'progress_notes::ap' 3189 print('%s: %s' % (ph, handler[ph]))
3190 #--------------------------------------------------------
3191 - def test_new_variant_placeholders():
3192 3193 tests = [ 3194 # should work: 3195 '$<lastname>$', 3196 '$<lastname::::3>$', 3197 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$', 3198 3199 # should fail: 3200 'lastname', 3201 '$<lastname', 3202 '$<lastname::', 3203 '$<lastname::>$', 3204 '$<lastname::abc>$', 3205 '$<lastname::abc::>$', 3206 '$<lastname::abc::3>$', 3207 '$<lastname::abc::xyz>$', 3208 '$<lastname::::>$', 3209 '$<lastname::::xyz>$', 3210 3211 '$<date_of_birth::%Y-%m-%d>$', 3212 '$<date_of_birth::%Y-%m-%d::3>$', 3213 '$<date_of_birth::%Y-%m-%d::>$', 3214 3215 # should work: 3216 '$<adr_location::home::35>$', 3217 '$<gender_mapper::male//female//other::5>$', 3218 '$<current_meds::==> %(product)s %(preparation)s (%(substance)s) <==\n::50>$', 3219 '$<allergy_list::%(descriptor)s, >$', 3220 '$<current_meds_table::latex//>$' 3221 3222 # 'firstname', 3223 # 'title', 3224 # 'date_of_birth', 3225 # 'progress_notes', 3226 # 'soap', 3227 # 'soap_s', 3228 # 'soap_o', 3229 # 'soap_a', 3230 # 'soap_p', 3231 3232 # 'soap', 3233 # 'progress_notes', 3234 # 'date_of_birth' 3235 ] 3236 3237 # tests = [ 3238 # '$<latest_vaccs_table::latex>$' 3239 # ] 3240 3241 pat = gmPersonSearch.ask_for_patient() 3242 if pat is None: 3243 return 3244 3245 gmPatSearchWidgets.set_active_patient(patient = pat) 3246 3247 handler = gmPlaceholderHandler() 3248 handler.debug = True 3249 3250 for placeholder in tests: 3251 print(placeholder, "=>", handler[placeholder]) 3252 print("--------------") 3253 input()
3254 3255 # print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d'] 3256 3257 # app = wx.PyWidgetTester(size = (200, 50)) 3258 3259 # ph = 'progress_notes::ap' 3260 # print '%s: %s' % (ph, handler[ph]) 3261 3262 #--------------------------------------------------------
3263 - def test_scripting():
3264 from Gnumed.pycommon import gmScriptingListener 3265 import xmlrpc.client 3266 3267 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999) 3268 3269 s = xmlrpc.client.ServerProxy('http://localhost:9999') 3270 print("should fail:", s.attach()) 3271 print("should fail:", s.attach('wrong cookie')) 3272 print("should work:", s.version()) 3273 print("should fail:", s.raise_gnumed()) 3274 print("should fail:", s.raise_notebook_plugin('test plugin')) 3275 print("should fail:", s.lock_into_patient('kirk, james')) 3276 print("should fail:", s.unlock_patient()) 3277 status, conn_auth = s.attach('unit test') 3278 print("should work:", status, conn_auth) 3279 print("should work:", s.version()) 3280 print("should work:", s.raise_gnumed(conn_auth)) 3281 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james') 3282 print("should work:", status, pat_auth) 3283 print("should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')) 3284 print("should work", s.unlock_patient(conn_auth, pat_auth)) 3285 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'} 3286 status, pat_auth = s.lock_into_patient(conn_auth, data) 3287 print("should work:", status, pat_auth) 3288 print("should work", s.unlock_patient(conn_auth, pat_auth)) 3289 print(s.detach('bogus detach cookie')) 3290 print(s.detach(conn_auth)) 3291 del s 3292 3293 listener.shutdown()
3294 #--------------------------------------------------------
3295 - def test_placeholder_regex():
3296 3297 import re as regex 3298 3299 tests = [ 3300 ' $<lastname>$ ', 3301 ' $<lastname::::3>$ ', 3302 3303 # should fail: 3304 '$<date_of_birth::%Y-%m-%d>$', 3305 '$<date_of_birth::%Y-%m-%d::3>$', 3306 '$<date_of_birth::%Y-%m-%d::>$', 3307 3308 '$<adr_location::home::35>$', 3309 '$<gender_mapper::male//female//other::5>$', 3310 '$<current_meds::==> %(product)s %(preparation)s (%(substance)s) <==\\n::50>$', 3311 '$<allergy_list::%(descriptor)s, >$', 3312 3313 '\\noindent Patient: $<lastname>$, $<firstname>$', 3314 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$', 3315 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(product)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$' 3316 ] 3317 3318 tests = [ 3319 3320 'junk $<lastname::::3>$ junk', 3321 'junk $<lastname::abc::3>$ junk', 3322 'junk $<lastname::abc>$ junk', 3323 'junk $<lastname>$ junk', 3324 3325 'junk $<lastname>$ junk $<firstname>$ junk', 3326 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk', 3327 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk', 3328 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk' 3329 3330 ] 3331 3332 tests = [ 3333 # u'junk $<<<date_of_birth::%Y %B %d $<inner placeholder::%Y %B %d::20>$::20>>>$ junk', 3334 # u'junk $<date_of_birth::%Y %B %d::20>$ $<date_of_birth::%Y %B %d::20>$', 3335 # u'junk $<date_of_birth::%Y %B %d::>$ $<date_of_birth::%Y %B %d::20>$ $<<date_of_birth::%Y %B %d::20>>$', 3336 # u'junk $<date_of_birth::::20>$', 3337 # u'junk $<date_of_birth::::>$', 3338 'junk $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::>>>$ junk', 3339 'junk $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::250>>>$ junk', 3340 'junk $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::3-4>>>$ junk', 3341 3342 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::->>>$ junk', 3343 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::3->>>$ junk', 3344 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::-4>>>$ should fail', 3345 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail>>>$ junk', 3346 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail->>>$ junk', 3347 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::-should_fail>>>$ junk', 3348 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail-4>>>$ junk', 3349 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::3-should_fail>>>$ junk', 3350 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail-should_fail>>>$ junk', 3351 ] 3352 3353 tests = [ 3354 'junk $<<<should pass::template::>>>$ junk', 3355 'junk $<<<should pass::template::10>>>$ junk', 3356 'junk $<<<should pass::template::10-20>>>$ junk', 3357 'junk $<<<should pass::template $<<dummy::template 2::10>>$::>>>$ junk', 3358 'junk $<<<should pass::template $<dummy::template 2::10>$::>>>$ junk', 3359 3360 'junk $<<<should pass::template::>>>$ junk $<<<should pass 2::template 2::>>>$ junk', 3361 'junk $<<<should pass::template::>>>$ junk $<<should pass 2::template 2::>>$ junk', 3362 'junk $<<<should pass::template::>>>$ junk $<should pass 2::template 2::>$ junk', 3363 3364 'junk $<<<should fail::template $<<<dummy::template 2::10>>>$::>>>$ junk', 3365 3366 'junk $<<<should fail::template::10->>>$ junk', 3367 'junk $<<<should fail::template::10->>>$ junk', 3368 'junk $<<<should fail::template::10->>>$ junk', 3369 'junk $<<<should fail::template::10->>>$ junk', 3370 'junk $<first_pass::junk $<<<3rd_pass::template::20>>>$ junk::8-10>$ junk' 3371 ] 3372 3373 #print "testing placeholder regex:", first_pass_placeholder_regex 3374 ##print "testing placeholder regex:", second_pass_placeholder_regex 3375 ##print "testing placeholder regex:", third_pass_placeholder_regex 3376 #print "" 3377 #for t in tests: 3378 # print 'line: "%s"' % t 3379 # phs = regex.findall(first_pass_placeholder_regex, t, regex.IGNORECASE) 3380 # #phs = regex.findall(second_pass_placeholder_regex, t, regex.IGNORECASE) 3381 # #phs = regex.findall(third_pass_placeholder_regex, t, regex.IGNORECASE) 3382 # print " %s placeholders:" % len(phs) 3383 # for p in phs: 3384 # print ' => ', p 3385 # print " " 3386 3387 all_tests = { 3388 first_pass_placeholder_regex: [ 3389 # different lengths/regions 3390 ('junk $<first_level::template::>$ junk', ['$<first_level::template::>$']), 3391 ('junk $<first_level::template::10>$ junk', ['$<first_level::template::10>$']), 3392 ('junk $<first_level::template::10-12>$ junk', ['$<first_level::template::10-12>$']), 3393 3394 # inside is other-level: 3395 ('junk $<first_level::$<<insert::insert_template::0>>$::10-12>$ junk', ['$<first_level::$<<insert::insert_template::0>>$::10-12>$']), 3396 ('junk $<first_level::$<<<insert::insert_template::0>>>$::10-12>$ junk', ['$<first_level::$<<<insert::insert_template::0>>>$::10-12>$']), 3397 3398 # outside is other-level: 3399 ('junk $<<second_level::$<insert::insert_template::0>$::10-12>>$ junk', ['$<insert::insert_template::0>$']), 3400 ('junk $<<<third_level::$<insert::insert_template::0>$::10-12>>>$ junk', ['$<insert::insert_template::0>$']), 3401 3402 # other level on same line 3403 ('junk $<first_level 1::template 1::>$ junk $<<second_level 2::template 2::>>$ junk', ['$<first_level 1::template 1::>$']), 3404 ('junk $<first_level 1::template 1::>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<first_level 1::template 1::>$']), 3405 3406 # this should produce 2 matches 3407 ('junk $<first_level 1::template 1::>$ junk $<first_level 2::template 2::>$ junk', ['$<first_level 1::template 1::>$', '$<first_level 2::template 2::>$']), 3408 3409 # this will produce a mismatch, due to illegal nesting of same-level placeholders 3410 ('returns illegal match: junk $<first_level::$<insert::insert_template::0>$::10-12>$ junk', ['$<first_level::$<insert::insert_template::0>$::10-12>$']), 3411 ], 3412 second_pass_placeholder_regex: [ 3413 # different lengths/regions 3414 ('junk $<<second_level::template::>>$ junk', ['$<<second_level::template::>>$']), 3415 ('junk $<<second_level::template::10>>$ junk', ['$<<second_level::template::10>>$']), 3416 ('junk $<<second_level::template::10-12>>$ junk', ['$<<second_level::template::10-12>>$']), 3417 3418 # inside is other-level: 3419 ('junk $<<second_level::$<insert::insert_template::0>$::10-12>>$ junk', ['$<<second_level::$<insert::insert_template::0>$::10-12>>$']), 3420 ('junk $<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$ junk', ['$<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$']), 3421 3422 # outside is other-level: 3423 ('junk $<first_level::$<<insert::insert_template::0>>$::10-12>$ junk', ['$<<insert::insert_template::0>>$']), 3424 ('junk $<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$ junk', ['$<<insert::insert_template::0>>$']), 3425 3426 # other level on same line 3427 ('junk $<first_level 1::template 1::>$ junk $<<second_level 2::template 2::>>$ junk', ['$<<second_level 2::template 2::>>$']), 3428 ('junk $<<second_level 1::template 1::>>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<second_level 1::template 1::>>$']), 3429 3430 # this should produce 2 matches 3431 ('junk $<<second_level 1::template 1::>>$ junk $<<second_level 2::template 2::>>$ junk', ['$<<second_level 1::template 1::>>$', '$<<second_level 2::template 2::>>$']), 3432 3433 # this will produce a mismatch, due to illegal nesting of same-level placeholders 3434 ('returns illegal match: junk $<<second_level::$<<insert::insert_template::0>>$::10-12>>$ junk', ['$<<second_level::$<<insert::insert_template::0>>$::10-12>>$']), 3435 3436 ], 3437 third_pass_placeholder_regex: [ 3438 # different lengths/regions 3439 ('junk $<<<third_level::template::>>>$ junk', ['$<<<third_level::template::>>>$']), 3440 ('junk $<<<third_level::template::10>>>$ junk', ['$<<<third_level::template::10>>>$']), 3441 ('junk $<<<third_level::template::10-12>>>$ junk', ['$<<<third_level::template::10-12>>>$']), 3442 3443 # inside is other-level: 3444 ('junk $<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$ junk', ['$<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$']), 3445 ('junk $<<<third_level::$<insert::insert_template::0>$::10-12>>>$ junk', ['$<<<third_level::$<insert::insert_template::0>$::10-12>>>$']), 3446 3447 # outside is other-level: 3448 ('junk $<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$ junk', ['$<<<insert::insert_template::0>>>$']), 3449 ('junk $<first_level::$<<<insert::insert_template::0>>>$::10-12>$ junk', ['$<<<insert::insert_template::0>>>$']), 3450 3451 # other level on same line 3452 ('junk $<first_level 1::template 1::>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<<third_level 2::template 2::>>>$']), 3453 ('junk $<<second_level 1::template 1::>>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<<third_level 2::template 2::>>>$']), 3454 3455 # this will produce a mismatch, due to illegal nesting of same-level placeholders 3456 ('returns illegal match: junk $<<<third_level::$<<<insert::insert_template::0>>>$::10-12>>>$ junk', ['$<<<third_level::$<<<insert::insert_template::0>>>$::10-12>>>$']), 3457 ] 3458 } 3459 3460 for pattern in [first_pass_placeholder_regex, second_pass_placeholder_regex, third_pass_placeholder_regex]: 3461 print("") 3462 print("-----------------------------") 3463 print("regex:", pattern) 3464 tests = all_tests[pattern] 3465 for t in tests: 3466 line, expected_results = t 3467 phs = regex.findall(pattern, line, regex.IGNORECASE) 3468 if len(phs) > 0: 3469 if phs == expected_results: 3470 continue 3471 3472 print("") 3473 print("failed") 3474 print("line:", line) 3475 3476 if len(phs) == 0: 3477 print("no match") 3478 continue 3479 3480 if len(phs) > 1: 3481 print("several matches") 3482 for r in expected_results: 3483 print("expected:", r) 3484 for p in phs: 3485 print("found:", p) 3486 continue 3487 3488 print("unexpected match") 3489 print("expected:", expected_results) 3490 print("found: ", phs)
3491 3492 #--------------------------------------------------------
3493 - def test_placeholder():
3494 3495 phs = [ 3496 #u'emr_journal::soapu //%(clin_when)s %(modified_by)s %(soap_cat)s %(narrative)s//1000 days::', 3497 #u'free_text::placeholder test//preset::9999', 3498 #u'soap_for_encounters:://::9999', 3499 #u'soap_p', 3500 #u'encounter_list::%(started)s: %(assessment_of_encounter)s::30', 3501 #u'patient_comm::homephone::1234', 3502 #u'$<patient_address::work::1234>$', 3503 #u'adr_region::home::1234', 3504 #u'adr_country::fehlt::1234', 3505 #u'adr_subunit::fehlt::1234', 3506 #u'adr_suburb::fehlt-auch::1234', 3507 #u'external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234', 3508 #u'primary_praxis_provider', 3509 #u'current_provider::::3-5', 3510 #u'current_provider_external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234', 3511 #u'current_provider_external_id::LANR//LÄK::1234' 3512 #u'$<current_provider_external_id::KV-LANR//KV::1234>$' 3513 #u'primary_praxis_provider_external_id::LANR//LÄK::1234' 3514 #u'form_name_long::::1234', 3515 #u'form_name_long::::5', 3516 #u'form_name_long::::', 3517 #u'form_version::::5', 3518 #u'$<current_meds::\item %(product)s %(preparation)s (%(substance)s) from %(started)s for %(duration)s as %(schedule)s until %(discontinued)s\\n::250>$', 3519 #u'$<vaccination_history::%(date_given)s: %(vaccine)s [%(batch_no)s] %(l10n_indications)s::250>$', 3520 #u'$<date_of_birth::%Y %B %d::20>$', 3521 #u'$<date_of_birth::%Y %B %d::>$', 3522 #u'$<date_of_birth::::20>$', 3523 #u'$<date_of_birth::::>$', 3524 #u'$<patient_tags::Tag "%(l10n_description)s": %(comment)s//\\n- ::250>$', 3525 #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>$', 3526 #u'$<patient_photo::\includegraphics[width=60mm]{%s}//image/png//.png::250>$', 3527 #u'$<data_snippet::binary_test_snippet//path=<%s>//image/png//.png::250>$', 3528 #u'$<data_snippet::autograph-LMcC//path=<%s>//image/jpg//.jpg::250>$', 3529 #u'$<current_meds::%s ($<lastname::::50>$)//select::>$', 3530 #u'$<current_meds::%s//select::>$', 3531 #u'$<soap_by_issue::soapu //%Y %b %d//%(narrative)s::1000>$', 3532 #u'$<soap_by_episode::soapu //%Y %b %d//%(narrative)s::1000>$', 3533 #u'$<documents::select//description//document %(clin_when)s: %(l10n_type)s// file: %(fullpath)s (<some path>/%(name)s)//~/gnumed/export/::>$', 3534 #u'$<soap::soapu //%s::9999>$', 3535 #u'$<soap::soapu //%(soap_cat)s: %(date)s | %(provider)s | %(narrative)s::9999>$' 3536 #u'$<test_results:://%c::>$' 3537 #u'$<test_results::%(unified_abbrev)s: %(unified_val)s %(val_unit)s//%c::>$' 3538 #'$<most_recent_test_results::tmpl=%(unified_name)s & %(unified_val)s%(val_unit)s & [%(unified_target_min)s--%(unified_target_max)s] %(unified_target_range)s & %(clin_when)s \\tabularnewline::>$' #<dfmt=...>//<tmpl=...>//<sep=...> 3539 #u'$<reminders:://::>$' 3540 #u'$<current_meds_for_rx::%(product)s (%(contains)s): dispense %(amount2dispense)s ::>$' 3541 #u'$<praxis::%(branch)s (%(praxis)s)::>$' 3542 #u'$<praxis_address::::120>$' 3543 #u'$<praxis_id::::120>$' 3544 #u'$<gen_adr_street::Street = %s//Wählen Sie die Empfängeradresse !::120>$', u'$<gen_adr_location::Ort = %s::120>$', u'$<gen_adr_country::::120>$' 3545 3546 #u'$<receiver_name::%s::120>$', 3547 #u'$<receiver_street::%s//a::120>$', 3548 #u'$<receiver_number:: %s//a::120>$', 3549 #u'$<receiver_subunit:: %s::120>$', 3550 #u'$<receiver_postcode::%s//b::120>$', 3551 #u'$<receiver_location:: %s::120>$', 3552 #u'$<receiver_country::, %s::120>$', 3553 #u'$<external_care::%(issue)s: %(provider)s of %(unit)s@%(organization)s (%(comment)s)::1024>$', 3554 #u'$<url_escape::hello world ü::>$', 3555 #u'$<substance_abuse::%(substance)s (%(harmful_use_type)s) last=%(last_checked_when)s stop=%(discontinued)s // %(notes)s::>$', 3556 #u'bill_adr_region::region %s::1234', 3557 #u'bill_adr_country::%s::1234', 3558 #u'bill_adr_subunit::subunit: %s::1234', 3559 #u'bill_adr_suburb::-> %s::1234', 3560 #u'bill_adr_street::::1234', 3561 #u'bill_adr_number::%s::1234', 3562 #u'$<diagnoses::\listitem %s::>$' 3563 #u'$<patient_mcf::fmt=txt//card=%s::>$', 3564 #u'$<patient_mcf::fmt=mcf//mcf=%s::>$', 3565 #u'$<patient_mcf::fmt=qr//png=%s::>$' 3566 #u'$<praxis_scan2pay::fmt=txt::>$', 3567 #u'$<praxis_scan2pay::fmt=qr::>$' 3568 #u'$<bill_scan2pay::fmt=txt::>$', 3569 #u'$<bill_scan2pay::fmt=qr::>$', 3570 #'$<yes_no::msg=do you want to select yes or no or something else ? Look at the title//yes=it was yes//no=oh no!::>$' 3571 '$<data_snippet::autograph-ncq//path=<%s>//image/jpg//.jpg::250>$', 3572 ] 3573 3574 from Gnumed.pycommon import gmPG2 3575 from Gnumed.pycommon import gmConnectionPool 3576 l, creds = gmPG2.request_login_params() 3577 pool = gmConnectionPool.gmConnectionPool() 3578 pool.credentials = creds 3579 3580 handler = gmPlaceholderHandler() 3581 handler.debug = True 3582 3583 gmStaff.set_current_provider_to_logged_on_user() 3584 gmPraxisWidgets.set_active_praxis_branch(no_parent = True) 3585 pat = gmPersonSearch.ask_for_patient() 3586 if pat is None: 3587 return 3588 gmPatSearchWidgets.set_active_patient(patient = pat) 3589 3590 #app = wx.PyWidgetTester(size = (200, 50)) 3591 #handler.set_placeholder('form_name_long', 'ein Testformular') 3592 for ph in phs: 3593 print(ph) 3594 print(" result:") 3595 print(' %s' % handler[ph])
3596 #handler.unset_placeholder('form_name_long') 3597 3598 #--------------------------------------------------------
3599 - def test():
3600 pat = gmPersonSearch.ask_for_patient() 3601 if pat is None: 3602 sys.exit() 3603 gmPerson.set_active_patient(patient = pat) 3604 from Gnumed.wxpython import gmMedicationWidgets 3605 gmMedicationWidgets.manage_substance_intakes()
3606 3607 #--------------------------------------------------------
3608 - def test_show_phs():
3609 show_placeholders()
3610 3611 #--------------------------------------------------------
3612 - def test_parse_ph_options(option_defs=None, options_string=None):
3613 """Returns a tuple of values in the order of option_defs.keys().""" 3614 assert (option_defs is not None), '<option_defs> must not be None' 3615 assert (options_string is not None), '<options_string> must not be None' 3616 3617 _log.debug('parsing ::%s:: for %s', options_string, option_defs) 3618 options2return = option_defs.copy() 3619 options = options_string.split('//') 3620 for opt in options: 3621 opt_name, opt_val = opt.split('=', 1) 3622 if opt_name.strip() not in option_defs.keys(): 3623 continue 3624 options2return[opt_name] = opt_val 3625 3626 return tuple([ options2return[o_name] for o_name in options2return.keys() ])
3627 3628 #-------------------------------------------------------- 3629 #a,b,c = test_parse_ph_options(option_defs = {'opt1': 1, 'opt2': 'two', 'opt4': 'vier'}, options_string = 'opt1=one//opt2=2//opt3=drei') 3630 #sys.exit() 3631 3632 app = wx.App() 3633 3634 #test_placeholders() 3635 #test_new_variant_placeholders() 3636 #test_scripting() 3637 #test_placeholder_regex() 3638 #test() 3639 test_placeholder() 3640 #test_show_phs() 3641