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

Source Code for Module Gnumed.wxpython.gmMacro

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