Gnumed.business.gmExportArea

Package Gnumed :: Package business :: Module gmExportArea |
[frames] | no frames]

# Source Code for Module Gnumed.business.gmExportArea

      1  """GNUmed export area
      2
      3  Think shopping cart in a web shop.
      4
      5  This is where you want to put documents for further
      6  processing by you or someone else, like your secretary.
      7  """
      8  #============================================================
      9  __license__ = "GPL v2 or later"
     10  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
     11
     12
     13  import sys
     14  import logging
     15  import shutil
     16  import os
     17  import io
     18
     19
     20  if __name__ == '__main__':
     21          sys.path.insert(0, '../../')
     22          from Gnumed.pycommon import gmI18N
     23          gmI18N.activate_locale()
     24          gmI18N.install_domain()
     25  from Gnumed.pycommon import gmTools
     26  from Gnumed.pycommon import gmBusinessDBObject
     27  from Gnumed.pycommon import gmPG2
     28  from Gnumed.pycommon import gmMimeLib
     29  from Gnumed.pycommon import gmDateTime
     30  from Gnumed.pycommon import gmCfg2
     31  from Gnumed.pycommon import gmCrypto
     32
     33  from Gnumed.business import gmDocuments
     34  from Gnumed.business import gmKeywordExpansion
     35
     36
     37  _log = logging.getLogger('gm.exp_area')
     38
     39  PRINT_JOB_DESIGNATION = 'print'
     40
     41  #============================================================
     42  # export area item handling
     43  #------------------------------------------------------------
     44  _SQL_get_export_items = "SELECT * FROM clin.v_export_items WHERE %s"
     45
     46 -class cExportItem(gmBusinessDBObject.cBusinessDBObject):
     47          """Represents an item in the export area table"""
     48
     49          _cmd_fetch_payload = _SQL_get_export_items % "pk_export_item = %s"
     50          _cmds_store_payload = [
     51                  """UPDATE clin.export_item SET
     52                                  fk_identity = %(pk_identity)s,
     53                                  created_by = gm.nullify_empty_string(%(created_by)s),
     54                                  created_when = %(created_when)s,
     55                                  designation = gm.nullify_empty_string(%(designation)s),
     56                                  description = gm.nullify_empty_string(%(description)s),
     57                                  fk_doc_obj = %(pk_doc_obj)s,
     58                                  data = CASE
     59                                          WHEN %(pk_doc_obj)s IS NULL THEN coalesce(data, 'to be replaced by real data')
     60                                          ELSE NULL
     61                                  END,
     62                                  filename = CASE
     63                                          WHEN %(pk_doc_obj)s IS NULL THEN gm.nullify_empty_string(%(filename)s)
     64                                          ELSE NULL
     65                                  END
     66                          WHERE
     67                                  pk = %(pk_export_item)s
     68                                          AND
     69                                  xmin = %(xmin_export_item)s
     70                  """,
     71                  _SQL_get_export_items % 'pk_export_item = %(pk_export_item)s'
     72          ]
     73          _updatable_fields = [
     74                  'pk_identity',
     75                  'created_when',
     76                  'designation',
     77                  'description',
     78                  'pk_doc_obj',
     79                  'filename'
     80          ]
     81          #--------------------------------------------------------
     82 -        def __init__(self, aPK_obj=None, row=None, link_obj=None):
     83                  super(cExportItem, self).__init__(aPK_obj = aPK_obj, row = row, link_obj = link_obj)
     84                  # force auto-healing if need be
     85                  if self._payload[self._idx['pk_identity_raw_needs_update']]:
     86                          _log.warning (
     87                                  'auto-healing export item [%s] from identity [%s] to [%s] because of document part [%s] seems necessary',
     88                                  self._payload[self._idx['pk_export_item']],
     89                                  self._payload[self._idx['pk_identity_raw']],
     90                                  self._payload[self._idx['pk_identity']],
     91                                  self._payload[self._idx['pk_doc_obj']]
     92                          )
     93                          if self._payload[self._idx['pk_doc_obj']] is None:
     94                                  _log.error('however, .fk_doc_obj is NULL, which should not happen, leaving things alone for manual inspection')
     95                                  return
     96                          # only flag ourselves as modified, do not actually
     97                          # modify any values, better safe than sorry
     98                          self._is_modified = True
     99                          self.save()
    100                          self.refetch_payload(ignore_changes = False, link_obj = link_obj)
    101
    102          #--------------------------------------------------------
    103  #       def format(self):
    104  #               return u'%s' % self
    105          #--------------------------------------------------------
    106 -        def update_data_from_file(self, filename=None):
    107                  # sanity check
    108                  if not (os.access(filename, os.R_OK) and os.path.isfile(filename)):
    109                          _log.error('[%s] is not a readable file' % filename)
    110                          return False
    111
    112                  cmd = """
    113                          UPDATE clin.export_item SET
    114                                  data = %(data)s::bytea,
    115                                  fk_doc_obj = NULL,
    116                                  filename = gm.nullify_empty_string(%(fname)s)
    117                          WHERE pk = %(pk)s"""
    118                  args = {'pk': self.pk_obj, 'fname': filename}
    119                  if not gmPG2.file2bytea(query = cmd, filename = filename, args = args):
    120                          return False
    121
    122                  # must update XMIN now ...
    123                  self.refetch_payload()
    124                  return True
    125
    126          #--------------------------------------------------------
    127 -        def save_to_file(self, aChunkSize=0, filename=None, directory=None):
    128
    129                  # data linked from archive
    130                  if self._payload[self._idx['pk_doc_obj']] is not None:
    131                          part = self.document_part
    132                          if filename is None:
    133                                  filename = part.get_useful_filename (
    134                                          make_unique = False,
    135                                          directory = directory,
    136                                          include_gnumed_tag = False,
    137                                          date_before_type = True,
    138                                          name_first = False
    139                                  )
    140                          return part.save_to_file (
    141                                  aChunkSize = aChunkSize,
    142                                  filename = filename,
    143                                  ignore_conversion_problems = True,
    144                                  adjust_extension = True
    145                          )
    146
    147                  # data in export area table
    148                  if filename is None:
    149                          filename = self.get_useful_filename(directory = directory)
    150
    151                  success = gmPG2.bytea2file (
    152                          data_query = {
    153                                  'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM clin.export_item WHERE pk = %(pk)s',
    154                                  'args': {'pk': self.pk_obj}
    155                          },
    156                          filename = filename,
    157                          chunk_size = aChunkSize,
    158                          data_size = self._payload[self._idx['size']]
    159                  )
    160                  if not success:
    161                          return None
    162
    163                  if filename.endswith('.dat'):
    164                          return gmMimeLib.adjust_extension_by_mimetype(filename)
    165
    166                  return filename
    167
    168          #--------------------------------------------------------
    169 -        def display_via_mime(self, chunksize=0, block=None):
    170
    171                  if self._payload[self._idx['pk_doc_obj']] is not None:
    172                          return self.document_part.display_via_mime(chunksize = chunksize, block = block)
    173
    174                  fname = self.save_to_file(aChunkSize = chunksize)
    175                  if fname is None:
    176                          return False, ''
    177
    178                  success, msg = gmMimeLib.call_viewer_on_file(fname, block = block)
    179                  if not success:
    180                          return False, msg
    181
    182                  return True, ''
    183
    184          #--------------------------------------------------------
    185 -        def get_useful_filename(self, patient=None, directory=None):
    186                  patient_part = ''
    187                  if patient is not None:
    188                          patient_part = '-%s' % patient.subdir_name
    189
    190                  # preserve original filename extension if available
    191                  suffix = '.dat'
    192                  if self._payload[self._idx['filename']] is not None:
    193                          tmp, suffix = os.path.splitext (
    194                                  gmTools.fname_sanitize(self._payload[self._idx['filename']]).lower()
    195                          )
    196                          if suffix == '':
    197                                  suffix = '.dat'
    198
    199                  fname = gmTools.get_unique_filename (
    200                          prefix = 'gm-export_item%s-' % patient_part,
    201                          suffix = suffix,
    202                          tmp_dir = directory
    203                  )
    204
    205                  return fname
    206
    207          #--------------------------------------------------------
    208          # properties
    209          #--------------------------------------------------------
    210 -        def _get_doc_part(self):
    211                  if self._payload[self._idx['pk_doc_obj']] is None:
    212                          return None
    213                  return gmDocuments.cDocumentPart(aPK_obj = self._payload[self._idx['pk_doc_obj']])
    214
    215          document_part = property(_get_doc_part, lambda x:x)
    216
    217          #--------------------------------------------------------
    218 -        def _get_is_print_job(self):
    219                  return self._payload[self._idx['designation']] == PRINT_JOB_DESIGNATION
    220
    221 -        def _set_is_print_job(self, is_print_job):
    222                  desig = gmTools.bool2subst(is_print_job, PRINT_JOB_DESIGNATION, None, None)
    223                  if self._payload[self._idx['designation']] == desig:
    224                          return
    225                  self['designation'] = desig
    226                  self.save()
    227
    228          is_print_job = property(_get_is_print_job, _set_is_print_job)
    229
    230  #------------------------------------------------------------
    231 -def get_export_items(order_by=None, pk_identity=None, designation=None):
    232
    233          args = {
    234                  'pat': pk_identity,
    235                  'desig': gmTools.coalesce(designation, PRINT_JOB_DESIGNATION)
    236          }
    237          where_parts = []
    238          if pk_identity is not None:
    239                  where_parts.append('pk_identity = %(pat)s')
    240                  # note that invalidly linked items will be
    241                  # auto-healed when instantiated
    242          if designation is None:
    243                  where_parts.append("designation IS DISTINCT FROM %(desig)s")
    244          else:
    245                  where_parts.append('designation = %(desig)s')
    246
    247          if order_by is None:
    248                  order_by = ''
    249          else:
    250                  order_by = ' ORDER BY %s' % order_by
    251
    252          cmd = (_SQL_get_export_items % ' AND '.join(where_parts)) + order_by
    253          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    254
    255          return [ cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'}) for r in rows ]
    256
    257  #------------------------------------------------------------
    258 -def get_print_jobs(order_by=None, pk_identity=None):
    259          return get_export_items(order_by = order_by, pk_identity = pk_identity, designation = PRINT_JOB_DESIGNATION)
    260
    261  #------------------------------------------------------------
    262 -def create_export_item(description=None, pk_identity=None, pk_doc_obj=None, filename=None):
    263
    264          args = {
    265                  'desc': description,
    266                  'pk_obj': pk_doc_obj,
    267                  'pk_pat': pk_identity,
    268                  'fname': filename
    269          }
    270          cmd = """
    271                  INSERT INTO clin.export_item (
    272                          description,
    273                          fk_doc_obj,
    274                          fk_identity,
    275                          data,
    276                          filename
    277                  ) VALUES (
    278                          gm.nullify_empty_string(%(desc)s),
    279                          %(pk_obj)s,
    280                          %(pk_pat)s,
    281                          (CASE
    282                                  WHEN %(pk_obj)s IS NULL THEN %(fname)s::bytea
    283                                  ELSE NULL::bytea
    284                          END),
    285                          (CASE
    286                                  WHEN %(pk_obj)s IS NULL THEN gm.nullify_empty_string(%(fname)s)
    287                                  ELSE NULL
    288                          END)
    289                  )
    290                  RETURNING pk
    291          """
    292          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
    293
    294          return cExportItem(aPK_obj = rows[0]['pk'])
    295
    296  #------------------------------------------------------------
    297 -def delete_export_item(pk_export_item=None):
    298          args = {'pk': pk_export_item}
    299          cmd = "DELETE FROM clin.export_item WHERE pk = %(pk)s"
    300          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    301          return True
    302
    303  #============================================================
    304  _html_start = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    305         "http://www.w3.org/TR/html4/loose.dtd">
    306  <html>
    307  <head>
    308  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    309  <link rel="icon" type="image/x-icon" href="gnumed.ico">
    310  <title>%(html_title_header)s %(html_title_patient)s</title>
    311  </head>
    312  <body>
    313
    314  <h1>%(title)s</h1>
    315
    316  <h2>Patient</h2>
    317
    318  <p>
    319          %(pat_name)s<br>
    320          %(pat_dob)s
    321  </p>
    322
    323  <p><img src="%(mugshot_url)s" alt="%(mugshot_alt)s" title="%(mugshot_title)s" width="200" border="2"></p>
    324
    325  <h2>%(docs_title)s</h2>
    326
    327  <ul>
    328          <li><a href="./">%(browse_root)s</a></li>
    329          <li><a href="documents/">%(browse_docs)s</a></li>
    330          %(browse_dicomdir)s
    331          %(run_dicom_viewer)s
    332  </ul>
    333
    334  <ul>
    335  """
    336
    337  # <li><a href="documents/filename-1.ext">document 1 description</a></li>
    338
    339  _html_list_item = """   <li><a href="documents/%s">%s</a></li>
    340  """
    341
    342  _html_end = """
    343  </ul>
    344
    345  <p>
    346  %(date)s<br>
    347  </p>
    348
    349  <h2>Praxis</h2>
    350
    351  <p>
    352  %(branch)s @ %(praxis)s
    353  %(adr)s
    354  </p>
    355
    356  <p>(<a href="http://www.gnumed.de">GNUmed</a> version %(gm_ver)s)</p>
    357
    358  </body>
    359  </html>
    360  """
    361
    362
    363  _autorun_inf = (                                                        # needs \r\n for Windows
    364          '[AutoRun.Amd64]\r\n'                                   # 64 bit
    365          'label=%(label)s\r\n'                                   # patient name/DOB
    366          'shellexecute=index.html\r\n'
    367          'action=%(action)s\r\n'                         # % _('Browse patient data')
    368          '%(icon)s\r\n'                                                  # "icon=gnumed.ico" or ""
    369          'UseAutoPlay=1\r\n'
    370          '\r\n'
    371          '[AutoRun]\r\n'                                         # 32 bit
    372          'label=%(label)s\r\n'                                   # patient name/DOB
    373          'shellexecute=index.html\r\n'
    374          'action=%(action)s\r\n'                         # % _('Browse patient data')
    375          '%(icon)s\r\n'                                                  # "icon=gnumed.ico" or ""
    376          'UseAutoPlay=1\r\n'
    377          '\r\n'
    378          '[Content]\r\n'
    379          'PictureFiles=yes\r\n'
    380          'VideoFiles=yes\r\n'
    381          'MusicFiles=no\r\n'
    382          '\r\n'
    383          '[IgnoreContentPaths]\r\n'
    384          '\documents\r\n'
    385          '\r\n'
    386          '[unused]\r\n'
    387          'open=requires explicit executable\r\n'
    388  )
    389
    390
    391  _cd_inf = (
    392  '[Patient Info]\r\n'                                    # needs \r\n for Windows
    393  'PatientName=%s, %s\r\n'
    394  'Gender=%s\r\n'
    395  'BirthDate=%s\r\n'
    396  'CreationDate=%s\r\n'
    397  'PID=%s\r\n'
    398  'EMR=GNUmed\r\n'
    399  'Version=%s\r\n'
    400  '#StudyDate=\r\n'
    401  '#VNRInfo=<body part>\r\n'
    402  '\r\n'
    403  '# name format: lastnames, firstnames\r\n'
    404  '# date format: YYYY-MM-DD (ISO 8601)\r\n'
    405  '# gender format: %s\r\n'
    406  )
    407
    408  _README = """This is a patient data bundle created by the GNUmed Electronic Medical Record.
    409
    410  Patient: %s
    411
    412  Please display <index.html> to browse patient data.
    413
    414  Individual documents are stored under
    415
    416          ./documents/
    417  """
    418
    419  #------------------------------------------------------------
    420 -class cExportArea(object):
    421
    422 -        def __init__(self, pk_identity):
    423                  self.__pk_identity = pk_identity
    424
    425          #--------------------------------------------------------
    426 -        def add_form(self, form=None, designation=None):
    427
    428                  if len(form.final_output_filenames) == 0:
    429                          return True
    430
    431                  items = []
    432                  for fname in form.final_output_filenames:
    433                          item = self.add_file(filename = fname)
    434                          if item is None:
    435                                  for prev_item in items:
    436                                          delete_export_item(pk_export_item = prev_item['pk_export_item'])
    437                                  return False
    438                          items.append(item)
    439                          item['description'] = _('form: %s %s (%s)') % (form.template['name_long'], form.template['external_version'], fname)
    440                          item['designation'] = designation
    441                          item.save()
    442
    443                  return True
    444          #--------------------------------------------------------
    445 -        def add_forms(self, forms=None, designation=None):
    446                  all_ok = True
    447                  for form in forms:
    448                          all_ok = all_ok and self.add_form(form = form, designation = designation)
    449
    450                  return all_ok
    451          #--------------------------------------------------------
    452 -        def add_file(self, filename=None, hint=None):
    453                  try:
    454                          open(filename).close()
    455                  except Exception:
    456                          _log.exception('cannot open file <%s>', filename)
    457                          return None
    458
    459                  file_md5 = gmTools.file2md5(filename = filename, return_hex = True)
    460                  existing_item = self.md5_exists(md5 = file_md5, include_document_parts = False)
    461                  if existing_item is not None:
    462                          _log.debug('md5 match (%s): %s already in export area', file_md5, filename)
    463                          return existing_item
    464
    465                  path, basename = os.path.split(filename)
    466                  item = create_export_item (
    467                          description = '%s: %s (%s/)' % (
    468                                  gmTools.coalesce(hint, _('file'), '%s'),
    469                                  basename,
    470                                  path
    471                          ),
    472                          pk_identity = self.__pk_identity,
    473                          filename = filename
    474                  )
    475
    476                  if item.update_data_from_file(filename = filename):
    477                          return item
    478
    479                  delete_export_item(pk_export_item = item['pk_export_item'])
    480                  return None
    481
    482          #--------------------------------------------------------
    483 -        def add_files(self, filenames=None, hint=None):
    484                  all_ok = True
    485                  for fname in filenames:
    486                          all_ok = all_ok and (self.add_file(filename = fname, hint = hint) is not None)
    487
    488                  return all_ok
    489
    490          #--------------------------------------------------------
    491 -        def add_documents(self, documents=None):
    492                  for doc in documents:
    493                          doc_tag = _('%s (%s)%s') % (
    494                                  doc['l10n_type'],
    495                                  gmDateTime.pydt_strftime(doc['clin_when'], '%Y %b %d'),
    496                                  gmTools.coalesce(doc['comment'], '', ' "%s"')
    497                          )
    498                          for obj in doc.parts:
    499                                  if self.document_part_item_exists(pk_part = obj['pk_obj']):
    500                                          continue
    501                                  f_ext = ''
    502                                  if obj['filename'] is not None:
    503                                          f_ext = os.path.splitext(obj['filename'])[1].strip('.').strip()
    504                                  if f_ext != '':
    505                                          f_ext = ' .' + f_ext.upper()
    506                                  obj_tag = _('part %s (%s%s)%s') % (
    507                                          obj['seq_idx'],
    508                                          gmTools.size2str(obj['size']),
    509                                          f_ext,
    510                                          gmTools.coalesce(obj['obj_comment'], '', ' "%s"')
    511                                  )
    512                                  create_export_item (
    513                                          description = '%s - %s' % (doc_tag, obj_tag),
    514                                          pk_doc_obj = obj['pk_obj']
    515                                  )
    516
    517          #--------------------------------------------------------
    518 -        def document_part_item_exists(self, pk_part=None):
    519                  cmd = "SELECT EXISTS (SELECT 1 FROM clin.export_item WHERE fk_doc_obj = %(pk_obj)s)"
    520                  args = {'pk_obj': pk_part}
    521                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    522                  return rows[0][0]
    523
    524          #--------------------------------------------------------
    525 -        def md5_exists(self, md5=None, include_document_parts=False):
    526                  where_parts = [
    527                          'pk_identity = %(pat)s',
    528                          'md5_sum = %(md5)s'
    529                  ]
    530                  args = {
    531                          'pat': self.__pk_identity,
    532                          'md5': md5
    533                  }
    534
    535                  if not include_document_parts:
    536                          where_parts.append('pk_doc_obj IS NULL')
    537
    538                  cmd = _SQL_get_export_items % ' AND '.join(where_parts)
    539                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    540
    541                  if len(rows) == 0:
    542                          return None
    543
    544                  r = rows[0]
    545                  return cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'})
    546
    547          #--------------------------------------------------------
    548 -        def dump_items_to_disk(self, base_dir=None, items=None):
    549                  if items is None:
    550                          items = self.items
    551                  if len(items) == 0:
    552                          return None
    553                  if base_dir is None:
    554                          from Gnumed.business.gmPerson import cPatient
    555                          pat = cPatient(aPK_obj = self.__pk_identity)
    556                          base_dir = os.path.join(gmTools.mk_sandbox_dir(), pat.subdir_name)
    557                  gmTools.mkdir(base_dir)
    558                  _log.debug('dumping export items to: %s', base_dir)
    559                  for item in items:
    560                          item.save_to_file(directory = base_dir)
    561                  return base_dir
    562
    563          #--------------------------------------------------------
    564 -        def dump_items_to_disk_as_zip(self, base_dir=None, items=None, passphrase=None):
    565                  dump_dir = self.dump_items_to_disk(base_dir = base_dir, items = items)
    566                  if dump_dir is None:
    567                          _log.debug('cannot dump export area items')
    568                          return None
    569                  _cfg = gmCfg2.gmCfgData()
    570                  zip_file = gmTools.create_zip_archive_from_dir (
    571                          dump_dir,
    572                          comment = _('GNUmed Patient Media'),
    573                          overwrite = True,
    574                          passphrase = passphrase,
    575                          verbose = _cfg.get(option = 'debug')
    576                  )
    577                  if zip_file is None:
    578                          gmGuiHelpers.gm_show_error (
    579                                  aMessage = _('Error creating zip file.'),
    580                                  aTitle = _('Creating zip archive')
    581                          )
    582                  return zip_file
    583
    584          #--------------------------------------------------------
    585 -        def export(self, base_dir=None, items=None):
    586
    587                  if items is None:
    588                          items = self.items
    589
    590                  if len(items) == 0:
    591                          return None
    592
    593                  media_base_dir = base_dir
    594
    595                  from Gnumed.business.gmPerson import cPatient
    596                  pat = cPatient(aPK_obj = self.__pk_identity)
    597                  if media_base_dir is None:
    598                          media_base_dir = os.path.join(gmTools.mk_sandbox_dir(), pat.subdir_name)
    599                          gmTools.mkdir(media_base_dir)
    600                  _log.debug('patient media base dir: %s', media_base_dir)
    601
    602                  doc_dir = os.path.join(media_base_dir, r'documents')
    603                  if os.path.isdir(doc_dir):
    604                          index_existing_docs = True
    605                  else:
    606                          index_existing_docs = False
    607                          gmTools.mkdir(doc_dir)
    608
    609                  _html_start_data = {
    610                          'html_title_header': _('Patient data for'),
    611                          'html_title_patient': gmTools.html_escape_string(pat.get_description_gender(with_nickname = False) + ', ' + _('born') + ' ' + pat.get_formatted_dob('%Y %B %d')),
    612                          'title': _('Patient data excerpt'),
    613                          'pat_name': gmTools.html_escape_string(pat.get_description_gender(with_nickname = False)),
    614                          'pat_dob': gmTools.html_escape_string(_('born') + ' ' + pat.get_formatted_dob('%Y %B %d')),
    615                          'mugshot_url': 'documents/no-such-file.png',
    616                          'mugshot_alt': _('no patient photograph available'),
    617                          'mugshot_title': '',
    618                          'docs_title': _('Documents'),
    619                          'browse_root': _('browse storage medium'),
    620                          'browse_docs': _('browse documents area'),
    621                          'browse_dicomdir': '',
    622                          'run_dicom_viewer': ''
    623                  }
    624
    625                  mugshot = pat.document_folder.latest_mugshot
    626                  if mugshot is not None:
    627                          _html_start_data['mugshot_url'] = mugshot.save_to_file(directory = doc_dir, adjust_extension = True)
    628                          _html_start_data['mugshot_alt'] =_('patient photograph from %s') % gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y')
    629                          _html_start_data['mugshot_title'] = gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y')
    630
    631                  if 'DICOMDIR' in os.listdir(media_base_dir):
    632                          _html_start_data['browse_dicomdir'] = '<li><a href="./DICOMDIR">%s</a></li>' % _('show DICOMDIR file')
    633                          # copy DWV into target dir
    634                          dwv_src_dir = os.path.join(gmTools.gmPaths().local_base_dir, 'resources', 'dwv4export')
    635                          if not os.path.isdir(dwv_src_dir):
    636                                  dwv_src_dir = os.path.join(gmTools.gmPaths().system_app_data_dir, 'resources', 'dwv4export')
    637                          if os.path.isdir(dwv_src_dir):
    638                                  dwv_target_dir = os.path.join(media_base_dir, 'dwv')
    639                                  gmTools.rmdir(dwv_target_dir)
    640                                  try:
    641                                          shutil.copytree(dwv_src_dir, dwv_target_dir)
    642                                          _html_start_data['run_dicom_viewer'] = '<li><a href="./dwv/viewers/mobile-local/index.html">%s</a></li>' % _('run Radiology Images (DICOM) Viewer')
    643                                  except (shutil.Error, OSError):
    644                                          _log.exception('cannot include DWV, skipping')
    645
    646                  # index.html
    647                  # - header
    648                  idx_fname = os.path.join(media_base_dir, 'index.html')
    649                  idx_file = io.open(idx_fname, mode = 'wt', encoding = 'utf8')
    650                  idx_file.write(_html_start % _html_start_data)
    651                  # - middle (side effect ! -> exports items into files ...)
    652                  existing_docs = os.listdir(doc_dir)             # get them now, or else we will include the to-be-exported items
    653                  # - export items
    654                  for item in items:
    655                          item_path = item.save_to_file(directory = doc_dir)
    656                          item_fname = os.path.split(item_path)[1]
    657                          idx_file.write(_html_list_item % (
    658                                  item_fname,
    659                                  gmTools.html_escape_string(item['description'])
    660                          ))
    661                  # - preexisting documents
    662                  for doc_fname in existing_docs:
    663                          idx_file.write(_html_list_item % (
    664                                  doc_fname,
    665                                  gmTools.html_escape_string(_('other: %s') % doc_fname)
    666                          ))
    667                  # - footer
    668                  _cfg = gmCfg2.gmCfgData()
    669                  from Gnumed.business.gmPraxis import gmCurrentPraxisBranch
    670                  prax = gmCurrentPraxisBranch()
    671                  lines = []
    672                  adr = prax.branch.org_unit.address
    673                  if adr is not None:
    674                          lines.extend(adr.format())
    675                  for comm in prax.branch.org_unit.comm_channels:
    676                          if comm['is_confidential'] is True:
    677                                  continue
    678                          lines.append('%s: %s' % (
    679                                  comm['l10n_comm_type'],
    680                                  comm['url']
    681                          ))
    682                  adr = ''
    683                  if len(lines) > 0:
    684                          adr = gmTools.html_escape_string('\n'.join(lines), replace_eol = True, keep_visual_eol = True)
    685                  _html_end_data = {
    686                          'branch': gmTools.html_escape_string(prax['branch']),
    687                          'praxis': gmTools.html_escape_string(prax['praxis']),
    688                          'date' : gmTools.html_escape_string(gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y %B %d')),
    689                          'gm_ver': gmTools.html_escape_string(gmTools.coalesce(_cfg.get(option = 'client_version'), 'git HEAD')),
    690                          'adr': adr
    691                  }
    692                  idx_file.write(_html_end % _html_end_data)
    693                  idx_file.close()
    694
    695                  # start.html (just a copy of index.html, really ;-)
    696                  start_fname = os.path.join(media_base_dir, 'start.html')
    697                  try:
    698                          shutil.copy2(idx_fname, start_fname)
    699                  except Exception:
    700                          _log.exception('cannot copy %s to %s', idx_fname, start_fname)
    701
    702                  # autorun.inf
    703                  autorun_dict = {}
    704                  autorun_dict['label'] = self._compute_autorun_inf_label(pat)
    705                  autorun_dict['action'] = _('Browse patient data')
    706                  autorun_dict['icon'] = ''
    707                  media_icon_kwd = '$$gnumed_patient_media_export_icon'
    708                  media_icon_kwd_exp = gmKeywordExpansion.get_expansion (
    709                          keyword = media_icon_kwd,
    710                          textual_only = False,
    711                          binary_only = True
    712                  )
    713                  icon_tmp_file = media_icon_kwd_exp.save_to_file (
    714                          target_mime = 'image/x-icon',
    715                          target_extension = '.ico',
    716                          ignore_conversion_problems = True
    717                  )
    718                  if icon_tmp_file is None:
    719                          _log.debug('cannot retrieve <%s>', media_icon_kwd)
    720                  else:
    721                          media_icon_fname = os.path.join(media_base_dir, 'gnumed.ico')
    722                          try:
    723                                  shutil.move(icon_tmp_file, media_icon_fname)
    724                                  autorun_dict['icon'] = 'icon=gnumed.ico'
    725                          except Exception:
    726                                  _log.exception('cannot move %s to %s', icon_tmp_file, media_icon_fname)
    727                  autorun_fname = os.path.join(media_base_dir, 'AUTORUN.INF')
    728                  autorun_file = io.open(autorun_fname, mode = 'wt', encoding = 'cp1252', errors = 'replace')
    729                  autorun_file.write(_autorun_inf % autorun_dict)
    730                  autorun_file.close()
    731
    732                  # cd.inf
    733                  cd_inf_fname = os.path.join(media_base_dir, 'CD.INF')
    734                  cd_inf_file = io.open(cd_inf_fname, mode = 'wt', encoding = 'utf8')
    735                  cd_inf_file.write(_cd_inf % (
    736                          pat['lastnames'],
    737                          pat['firstnames'],
    738                          gmTools.coalesce(pat['gender'], '?'),
    739                          pat.get_formatted_dob('%Y-%m-%d'),
    740                          gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y-%m-%d'),
    741                          pat.ID,
    742                          _cfg.get(option = 'client_version'),
    743                          ' / '.join([ '%s = %s (%s)' % (g['tag'], g['label'], g['l10n_label']) for g in pat.gender_list ])
    744                  ))
    745                  cd_inf_file.close()
    746
    747                  # README
    748                  readme_fname = os.path.join(media_base_dir, 'README')
    749                  readme_file = io.open(readme_fname, mode = 'wt', encoding = 'utf8')
    750                  readme_file.write(_README % (
    751                          pat.get_description_gender(with_nickname = False) + ', ' + _('born') + ' ' + pat.get_formatted_dob('%Y %B %d')
    752                  ))
    753                  readme_file.close()
    754
    755                  # patient demographics as GDT/XML/VCF/MCF
    756                  pat.export_as_gdt(filename = os.path.join(media_base_dir, 'patient.gdt'))
    757                  pat.export_as_xml_linuxmednews(filename = os.path.join(media_base_dir, 'patient.xml'))
    758                  pat.export_as_vcard(filename = os.path.join(media_base_dir, 'patient.vcf'))
    759                  pat.export_as_mecard(filename = os.path.join(media_base_dir, u'patient.mcf'))
    760
    761                  # praxis VCF/MCF
    762                  shutil.move(prax.vcf, os.path.join(media_base_dir, 'praxis.vcf'))
    763                  prax.export_as_mecard(filename = os.path.join(media_base_dir, u'praxis.mcf'))
    764
    765                  return media_base_dir
    766
    767          #--------------------------------------------------------
    768 -        def export_as_zip(self, base_dir=None, items=None, passphrase=None):
    769                  media_dir = self.export(base_dir = base_dir, items = items)
    770                  if media_dir is None:
    771                          _log.debug('cannot export items')
    772                          return None
    773                  _cfg = gmCfg2.gmCfgData()
    774                  if passphrase is None:
    775                          zip_file = gmCrypto.create_zip_archive_from_dir (
    776                                  media_dir,
    777                                  comment = _('GNUmed Patient Media'),
    778                                  overwrite = True,
    779                                  verbose = _cfg.get(option = 'debug')
    780                          )
    781                  else:
    782                          zip_file = gmCrypto.create_encrypted_zip_archive_from_dir (
    783                                  media_dir,
    784                                  comment = _('GNUmed Patient Media'),
    785                                  overwrite = True,
    786                                  passphrase = passphrase,
    787                                  verbose = _cfg.get(option = 'debug')
    788                          )
    789                  if zip_file is None:
    790                          _log.debug('cannot create zip archive')
    791                          return None
    792                  return zip_file
    793
    794          #--------------------------------------------------------
    795 -        def _compute_autorun_inf_label(self, patient):
    796                  LABEL_MAX_LEN = 32
    797                  dob = patient.get_formatted_dob(format = ' %Y%m%d', none_string = '', honor_estimation = False)
    798                  if dob == '':
    799                          gender_template = ' (%s)'
    800                  else:
    801                          gender_template = ' %s'
    802                  gender = gmTools.coalesce(patient['gender'], '', gender_template)
    803                  name_max_len = LABEL_MAX_LEN - len(gender) - len(dob)                   # they already include appropriate padding
    804                  name = patient.active_name
    805                  last = name['lastnames'].strip()
    806                  first = name['firstnames'].strip()
    807                  len_last = len(last)
    808                  len_first = len(first)
    809                  while (len_last + len_first + 1) > name_max_len:
    810                          if len_first > 6:
    811                                  len_first -= 1
    812                                  if first[len_first - 1] == ' ':
    813                                          len_first -= 1
    814                                  continue
    815                          len_last -= 1
    816                          if last[len_last - 1] == ' ':
    817                                  len_last -= 1
    818                  last = last[:len_last].strip().upper()
    819                  first = first[:len_first].strip()
    820                  # max 32 chars, supposedly ASCII, but CP1252 likely works pretty well
    821                  label = (('%s %s%s%s' % (last, first, dob,      gender)).strip())[:32]
    822                  return label
    823
    824          #--------------------------------------------------------
    825          # properties
    826          #--------------------------------------------------------
    827 -        def get_items(self, designation=None, order_by='designation, description'):
    828                  return get_export_items(order_by = order_by, pk_identity = self.__pk_identity, designation = designation)
    829
    830          items = property(get_items, lambda x:x)
    831          #--------------------------------------------------------
    832 -        def get_printouts(self, order_by='designation, description'):
    833                  return get_print_jobs(order_by = order_by, pk_identity = self.__pk_identity)
    834
    835          printouts = property(get_printouts, lambda x:x)
    836
    837  #============================================================
    838  if __name__ == '__main__':
    839
    840          if len(sys.argv) < 2:
    841                  sys.exit()
    842
    843          if sys.argv[1] != 'test':
    844                  sys.exit()
    845
    846          from Gnumed.pycommon import gmI18N
    847          gmI18N.activate_locale()
    848          gmI18N.install_domain()
    849
    850          from Gnumed.business import gmPraxis
    851
    852          #---------------------------------------
    853 -        def test_export_items():
    854  #               items = get_export_items()
    855  #               for item in items:
    856  #                       print item.format()
    857                  import random
    858                  create_export_item(description = 'description %s' % random.random(), pk_identity = 12, pk_doc_obj = None, filename = 'dummy.dat')
    859                  items = get_export_items()
    860                  for item in items:
    861                          print(item.format())
    862                  item['pk_doc_obj'] = 1
    863                  item.save()
    864                  print(item)
    865
    866          #---------------------------------------
    867 -        def test_export_area():
    868                  exp = cExportArea(12)
    869                  #print exp.export_with_meta_data()
    870                  #print exp.items
    871                  exp.add_file(sys.argv[2])
    872                  prax = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.cPraxisBranch(1))
    873                  print(prax)
    874                  print(prax.branch)
    875                  print(exp.export())
    876
    877          #---------------------------------------
    878 -        def test_label():
    879
    880                  from Gnumed.business.gmPerson import cPatient
    881                  from Gnumed.business.gmPersonSearch import ask_for_patient
    882
    883                  #while ask_for_patient() is not None:
    884                  pat_min = 1
    885                  pat_max = 100
    886                  try:
    887                          pat_min = int(sys.argv[2])
    888                          pat_max = int(sys.argv[3])
    889                  except:
    890                          pass
    891                  cPatient(aPK_obj = pat_min)
    892                  f = io.open('x-auto_inf_labels.txt', mode = 'w', encoding = 'utf8')
    893                  f.write('--------------------------------\n')
    894                  f.write('12345678901234567890123456789012\n')
    895                  f.write('--------------------------------\n')
    896                  for pat_id in range(pat_min, pat_max):
    897                          try:
    898                                  exp_area = cExportArea(pat_id)
    899                                  pat = cPatient(aPK_obj = pat_id)
    900                          except:
    901                                  continue
    902                          f.write(exp_area._compute_autorun_inf_label(pat) + '\n')
    903                  f.close()
    904                  return
    905
    906          #---------------------------------------
    907          #test_export_items()
    908          test_export_area()
    909          #test_label()
    910
    911          sys.exit(0)
    912
    913  #============================================================
    914  # CDROM "run.bat":
    915  #
    916  #@echo off
    917  #
    918  #if defined ProgramFiles(x86) (
    919  #    ::64-bit
    920  #    start /B x64\mdicom.exe /scan .
    921  #) else (
    922  #    ::32-bit
    923  #    start /B win32\mdicom.exe /scan .
    924  #)
    925  #
    926  #--------------------------------------------------
    927