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

Source Code for Module Gnumed.wxpython.gmMacro

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