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