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

# Source Code for Module Gnumed.business.gmDocuments

      1  """This module encapsulates a document stored in a GNUmed database."""
      2  #============================================================
      3  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
      4  __license__ = "GPL v2 or later"
      5
      6  import sys, os, shutil, os.path, types, time, logging
      7
      8
      9  if __name__ == '__main__':
     10          sys.path.insert(0, '../../')
     11  from Gnumed.pycommon import gmExceptions
     12  from Gnumed.pycommon import gmBusinessDBObject
     13  from Gnumed.pycommon import gmPG2
     14  from Gnumed.pycommon import gmTools
     15  from Gnumed.pycommon import gmMimeLib
     16  from Gnumed.pycommon import gmDateTime
     17
     18
     19  _log = logging.getLogger('gm.docs')
     20
     21  MUGSHOT=26
     22  DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE = u'visual progress note'
     23  DOCUMENT_TYPE_PRESCRIPTION = u'prescription'
     24  #============================================================
     25 -class cDocumentFolder:
     26          """Represents a folder with medical documents for a single patient."""
     27
     28 -        def __init__(self, aPKey = None):
     29                  """Fails if
     30
     31                  - patient referenced by aPKey does not exist
     32                  """
     33                  self.pk_patient = aPKey                 # == identity.pk == primary key
     34                  if not self._pkey_exists():
     35                          raise gmExceptions.ConstructorError, "No patient with PK [%s] in database." % aPKey
     36
     37                  # register backend notification interests
     38                  # (keep this last so we won't hang on threads when
     39                  #  failing this constructor for other reasons ...)
     40  #               if not self._register_interests():
     41  #                       raise gmExceptions.ConstructorError, "cannot register signal interests"
     42
     43                  _log.debug('instantiated document folder for patient [%s]' % self.pk_patient)
     44          #--------------------------------------------------------
     45 -        def cleanup(self):
     46                  pass
     47          #--------------------------------------------------------
     48          # internal helper
     49          #--------------------------------------------------------
     50 -        def _pkey_exists(self):
     51                  """Does this primary key exist ?
     52
     53                  - true/false/None
     54                  """
     55                  # patient in demographic database ?
     56                  rows, idx = gmPG2.run_ro_queries(queries = [
     57                          {'cmd': u"select exists(select pk from dem.identity where pk = %s)", 'args': [self.pk_patient]}
     58                  ])
     59                  if not rows[0][0]:
     60                          _log.error("patient [%s] not in demographic database" % self.pk_patient)
     61                          return None
     62                  return True
     63          #--------------------------------------------------------
     64          # API
     65          #--------------------------------------------------------
     66 -        def get_latest_freediams_prescription(self):
     67                  cmd = u"""
     68                          SELECT pk_doc
     69                          FROM blobs.v_doc_med
     70                          WHERE
     71                                  pk_patient = %(pat)s
     72                                          AND
     73                                  type = %(typ)s
     74                                          AND
     75                                  ext_ref = %(ref)s
     76                          ORDER BY
     77                                  clin_when DESC
     78                          LIMIT 1
     79                  """
     80                  args = {
     81                          'pat': self.pk_patient,
     82                          'typ': DOCUMENT_TYPE_PRESCRIPTION,
     83                          'ref': u'FreeDiams'
     84                  }
     85                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     86                  if len(rows) == 0:
     87                          _log.info('no FreeDiams prescription available for patient [%s]' % self.pk_patient)
     88                          return None
     89                  prescription = cDocument(aPK_obj = rows[0][0])
     90                  return prescription
     91          #--------------------------------------------------------
     92 -        def get_latest_mugshot(self):
     93                  cmd = u"SELECT pk_obj FROM blobs.v_latest_mugshot WHERE pk_patient = %s"
     94                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
     95                  if len(rows) == 0:
     96                          _log.info('no mugshots available for patient [%s]' % self.pk_patient)
     97                          return None
     98                  return cDocumentPart(aPK_obj = rows[0][0])
     99
    100          latest_mugshot = property(get_latest_mugshot, lambda x:x)
    101          #--------------------------------------------------------
    102 -        def get_mugshot_list(self, latest_only=True):
    103                  if latest_only:
    104                          cmd = u"select pk_doc, pk_obj from blobs.v_latest_mugshot where pk_patient=%s"
    105                  else:
    106                          cmd = u"""
    107                                  select
    108                                          vdm.pk_doc as pk_doc,
    109                                          dobj.pk as pk_obj
    110                                  from
    111                                          blobs.v_doc_med vdm
    112                                          blobs.doc_obj dobj
    113                                  where
    114                                          vdm.pk_type = (select pk from blobs.doc_type where name = 'patient photograph')
    115                                          and vdm.pk_patient = %s
    116                                          and dobj.fk_doc = vdm.pk_doc
    117                          """
    118                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
    119                  return rows
    120          #--------------------------------------------------------
    121 -        def get_doc_list(self, doc_type=None):
    122                  """return flat list of document IDs"""
    123
    124                  args = {
    125                          'ID': self.pk_patient,
    126                          'TYP': doc_type
    127                  }
    128
    129                  cmd = u"""
    130                          select vdm.pk_doc
    131                          from blobs.v_doc_med vdm
    132                          where
    133                                  vdm.pk_patient = %%(ID)s
    134                                  %s
    135                          order by vdm.clin_when"""
    136
    137                  if doc_type is None:
    138                          cmd = cmd % u''
    139                  else:
    140                          try:
    141                                  int(doc_type)
    142                                  cmd = cmd % u'and vdm.pk_type = %(TYP)s'
    143                          except (TypeError, ValueError):
    144                                  cmd = cmd % u'and vdm.pk_type = (select pk from blobs.doc_type where name = %(TYP)s)'
    145
    146                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    147                  doc_ids = []
    148                  for row in rows:
    149                          doc_ids.append(row[0])
    150                  return doc_ids
    151          #--------------------------------------------------------
    152 -        def get_visual_progress_notes(self, episodes=None, encounter=None):
    153                  return self.get_documents (
    154                          doc_type = DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
    155                          episodes = episodes,
    156                          encounter = encounter
    157                  )
    158          #--------------------------------------------------------
    159 -        def get_unsigned_documents(self):
    160                  args = {'pat': self.pk_patient}
    161                  cmd = _sql_fetch_document_fields % u"""
    162                          pk_doc IN (
    163                                  SELECT DISTINCT ON (b_vo.pk_doc) b_vo.pk_doc
    164                                  FROM blobs.v_obj4doc_no_data b_vo
    165                                  WHERE
    166                                          pk_patient = %(pat)s
    167                                                  AND
    168                                          reviewed IS FALSE
    169                          )
    170                          ORDER BY clin_when DESC"""
    171                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    172                  return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
    173          #--------------------------------------------------------
    174 -        def get_documents(self, doc_type=None, episodes=None, encounter=None, order_by=None, exclude_unsigned=False):
    175                  """Return list of documents."""
    176
    177                  args = {
    178                          'pat': self.pk_patient,
    179                          'type': doc_type,
    180                          'enc': encounter
    181                  }
    182                  where_parts = [u'pk_patient = %(pat)s']
    183
    184                  if doc_type is not None:
    185                          try:
    186                                  int(doc_type)
    187                                  where_parts.append(u'pk_type = %(type)s')
    188                          except (TypeError, ValueError):
    189                                  where_parts.append(u'pk_type = (SELECT pk FROM blobs.doc_type WHERE name = %(type)s)')
    190
    191                  if (episodes is not None) and (len(episodes) > 0):
    192                          where_parts.append(u'pk_episode IN %(epi)s')
    193                          args['epi'] = tuple(episodes)
    194
    195                  if encounter is not None:
    196                          where_parts.append(u'pk_encounter = %(enc)s')
    197
    198                  if exclude_unsigned:
    199                          where_parts.append(u'pk_doc IN (SELECT b_vo.pk_doc FROM blobs.v_obj4doc_no_data b_vo WHERE b_vo.pk_patient = %(pat)s AND b_vo.reviewed IS TRUE)')
    200
    201                  if order_by is None:
    202                          order_by = u'ORDER BY clin_when'
    203
    204                  cmd = u"%s\n%s" % (_sql_fetch_document_fields % u' AND '.join(where_parts), order_by)
    205                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    206
    207                  return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
    208
    209          documents = property(get_documents, lambda x:x)
    210          #--------------------------------------------------------
    211 -        def add_document(self, document_type=None, encounter=None, episode=None):
    212                  return create_document(document_type = document_type, encounter = encounter, episode = episode)
    213  #============================================================
    214  _sql_fetch_document_part_fields = u"select * from blobs.v_obj4doc_no_data where %s"
    215
    216 -class cDocumentPart(gmBusinessDBObject.cBusinessDBObject):
    217          """Represents one part of a medical document."""
    218
    219          _cmd_fetch_payload = _sql_fetch_document_part_fields % u"pk_obj = %s"
    220          _cmds_store_payload = [
    221                  u"""UPDATE blobs.doc_obj SET
    222                                  seq_idx = %(seq_idx)s,
    223                                  comment = gm.nullify_empty_string(%(obj_comment)s),
    224                                  filename = gm.nullify_empty_string(%(filename)s),
    225                                  fk_intended_reviewer = %(pk_intended_reviewer)s,
    226                                  fk_doc = %(pk_doc)s
    227                          WHERE
    228                                  pk = %(pk_obj)s
    229                                          AND
    230                                  xmin = %(xmin_doc_obj)s
    231                          RETURNING
    232                                  xmin AS xmin_doc_obj"""
    233          ]
    234          _updatable_fields = [
    235                  'seq_idx',
    236                  'obj_comment',
    237                  'pk_intended_reviewer',
    238                  'filename',
    239                  'pk_doc'
    240          ]
    241          #--------------------------------------------------------
    242          # retrieve data
    243          #--------------------------------------------------------
    244 -        def export_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False):
    245
    246                  if self._payload[self._idx['size']] == 0:
    247                          return None
    248
    249                  if filename is None:
    250                          suffix = None
    251                          # preserve original filename extension if available
    252                          if self._payload[self._idx['filename']] is not None:
    253                                  name, suffix = os.path.splitext(self._payload[self._idx['filename']])
    254                                  suffix = suffix.strip()
    255                                  if suffix == u'':
    256                                          suffix = None
    257                          # get unique filename
    258                          filename = gmTools.get_unique_filename (
    259                                  prefix = 'gm-doc_obj-page_%s-' % self._payload[self._idx['seq_idx']],
    260                                  suffix = suffix
    261                          )
    262
    263                  success = gmPG2.bytea2file (
    264                          data_query = {
    265                                  'cmd': u'SELECT substring(data from %(start)s for %(size)s) FROM blobs.doc_obj WHERE pk=%(pk)s',
    266                                  'args': {'pk': self.pk_obj}
    267                          },
    268                          filename = filename,
    269                          chunk_size = aChunkSize,
    270                          data_size = self._payload[self._idx['size']]
    271                  )
    272
    273                  if not success:
    274                          return None
    275
    276                  if target_mime is None:
    277                          return filename
    278
    279                  if target_extension is None:
    280                          target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
    281
    282                  target_fname = gmTools.get_unique_filename (
    283                          prefix = 'gm-doc_obj-page_%s-converted-' % self._payload[self._idx['seq_idx']],
    284                          suffix = target_extension
    285                  )
    286                  _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, target_fname)
    287                  if gmMimeLib.convert_file (
    288                          filename = filename,
    289                          target_mime = target_mime,
    290                          target_filename = target_fname
    291                  ):
    292                          return target_fname
    293
    294                  _log.warning('conversion failed')
    295                  if not ignore_conversion_problems:
    296                          return None
    297
    298                  _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename)
    299                  return filename
    300          #--------------------------------------------------------
    301 -        def get_reviews(self):
    302                  cmd = u"""
    303  select
    304          reviewer,
    305          reviewed_when,
    306          is_technically_abnormal,
    307          clinically_relevant,
    308          is_review_by_responsible_reviewer,
    309          is_your_review,
    310          coalesce(comment, '')
    311  from blobs.v_reviewed_doc_objects
    312  where pk_doc_obj = %s
    313  order by
    314          is_your_review desc,
    315          is_review_by_responsible_reviewer desc,
    316          reviewed_when desc
    317  """
    318                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
    319                  return rows
    320          #--------------------------------------------------------
    321 -        def get_containing_document(self):
    322                  return cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
    323          #--------------------------------------------------------
    324          # store data
    325          #--------------------------------------------------------
    326 -        def update_data_from_file(self, fname=None):
    327                  # sanity check
    328                  if not (os.access(fname, os.R_OK) and os.path.isfile(fname)):
    329                          _log.error('[%s] is not a readable file' % fname)
    330                          return False
    331
    332                  if not gmPG2.file2bytea (
    333                          query = u"UPDATE blobs.doc_obj SET data = %(data)s::bytea WHERE pk = %(pk)s RETURNING md5(data) AS md5",
    334                          filename = fname,
    335                          args = {'pk': self.pk_obj},
    336                          file_md5 = gmTools.file2md5(filename = fname, return_hex = True)
    337                  ):
    338                          return False
    339
    340                  # must update XMIN now ...
    341                  self.refetch_payload()
    342                  return True
    343          #--------------------------------------------------------
    344 -        def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
    345                  # row already there ?
    346                  cmd = u"""
    347  select pk
    348  from blobs.reviewed_doc_objs
    349  where
    350          fk_reviewed_row = %s and
    351          fk_reviewer = (select pk from dem.staff where db_user = current_user)"""
    352                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
    353
    354                  # INSERT needed
    355                  if len(rows) == 0:
    356                          cols = [
    357                                  u"fk_reviewer",
    358                                  u"fk_reviewed_row",
    359                                  u"is_technically_abnormal",
    360                                  u"clinically_relevant"
    361                          ]
    362                          vals = [
    363                                  u'%(fk_row)s',
    364                                  u'%(abnormal)s',
    365                                  u'%(relevant)s'
    366                          ]
    367                          args = {
    368                                  'fk_row': self.pk_obj,
    369                                  'abnormal': technically_abnormal,
    370                                  'relevant': clinically_relevant
    371                          }
    372                          cmd = u"""
    373  insert into blobs.reviewed_doc_objs (
    374          %s
    375  ) values (
    376          (select pk from dem.staff where db_user=current_user),
    377          %s
    378  )""" % (', '.join(cols), ', '.join(vals))
    379
    380                  # UPDATE needed
    381                  if len(rows) == 1:
    382                          pk_row = rows[0][0]
    383                          args = {
    384                                  'abnormal': technically_abnormal,
    385                                  'relevant': clinically_relevant,
    386                                  'pk_row': pk_row
    387                          }
    388                          cmd = u"""
    389  update blobs.reviewed_doc_objs set
    390          is_technically_abnormal = %(abnormal)s,
    391          clinically_relevant = %(relevant)s
    392  where
    393          pk=%(pk_row)s"""
    394                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    395
    396                  return True
    397          #--------------------------------------------------------
    398 -        def set_as_active_photograph(self):
    399                  if self._payload[self._idx['type']] != u'patient photograph':
    400                          return False
    401                  # set seq_idx to current max + 1
    402                  rows, idx = gmPG2.run_ro_queries (
    403                          queries = [{
    404                                  'cmd': u'select coalesce(max(seq_idx)+1, 1) from blobs.doc_obj where fk_doc=%(doc_id)s',
    405                                  'args': {'doc_id': self._payload[self._idx['pk_doc']]}
    406                          }]
    407                  )
    408                  self._payload[self._idx['seq_idx']] = rows[0][0]
    409                  self._is_modified = True
    410                  self.save_payload()
    411          #--------------------------------------------------------
    412 -        def display_via_mime(self, chunksize=0, block=None):
    413
    414                  fname = self.export_to_file(aChunkSize = chunksize)
    415                  if fname is None:
    416                          return False, ''
    417
    418                  success, msg = gmMimeLib.call_viewer_on_file(fname, block = block)
    419                  if not success:
    420                          return False, msg
    421
    422                  return True, ''
    423          #--------------------------------------------------------
    424 -        def format_single_line(self):
    425                  f_ext = u''
    426                  if self._payload[self._idx['filename']] is not None:
    427                          f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip()
    428                  if f_ext != u'':
    429                          f_ext = u' .' + f_ext.upper()
    430                  txt = _('part %s, %s%s%s of document %s from %s%s') % (
    431                          self._payload[self._idx['seq_idx']],
    432                          gmTools.size2str(self._payload[self._idx['size']]),
    433                          f_ext,
    434                          gmTools.coalesce(self._payload[self._idx['obj_comment']], u'', u' ("%s")'),
    435                          self._payload[self._idx['l10n_type']],
    436                          gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y %b %d'),
    437                          gmTools.coalesce(self._payload[self._idx['doc_comment']], u'', u' ("%s")')
    438                  )
    439                  return txt
    440          #--------------------------------------------------------
    441 -        def format(self, single_line=False):
    442                  if single_line:
    443                          return self.format_single_line()
    444
    445                  txt = _('%s document part                 [#%s]\n') % (
    446                          gmTools.bool2str (
    447                                  boolean = self._payload[self._idx['reviewed']],
    448                                  true_str = _('Reviewed'),
    449                                  false_str = _('Unreviewed')
    450                          ),
    451                          self._payload[self._idx['pk_obj']]
    452                  )
    453
    454                  f_ext = u''
    455                  if self._payload[self._idx['filename']] is not None:
    456                          f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip()
    457                  if f_ext != u'':
    458                          f_ext = u'.' + f_ext.upper() + u' '
    459                  txt += _(' Part %s: %s %s(%s Bytes)\n') % (
    460                          self._payload[self._idx['seq_idx']],
    461                          gmTools.size2str(self._payload[self._idx['size']]),
    462                          f_ext,
    463                          self._payload[self._idx['size']]
    464                  )
    465
    466                  if self._payload[self._idx['filename']] is not None:
    467                          txt += _(' Filename: %s\n') % self._payload[self._idx['filename']]
    468
    469                  if self._payload[self._idx['obj_comment']] is not None:
    470                          txt += u'\n%s\n' % self._payload[self._idx['obj_comment']]
    471
    472                  return txt
    473  #------------------------------------------------------------
    474 -def delete_document_part(part_pk=None, encounter_pk=None):
    475          cmd = u"select blobs.delete_document_part(%(pk)s, %(enc)s)"
    476          args = {'pk': part_pk, 'enc': encounter_pk}
    477          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    478          return
    479  #============================================================
    480  _sql_fetch_document_fields = u"""
    481                  SELECT
    482                          *,
    483                  COALESCE (
    484                          (SELECT array_agg(seq_idx) FROM blobs.doc_obj b_do WHERE b_do.fk_doc = b_vdm.pk_doc),
    485                          ARRAY[]::integer[]
    486                  )
    487                          AS seq_idx_list
    488                  FROM
    489                          blobs.v_doc_med b_vdm
    490                  WHERE
    491                          %s
    492  """
    493
    494 -class cDocument(gmBusinessDBObject.cBusinessDBObject):
    495          """Represents one medical document."""
    496
    497          _cmd_fetch_payload = _sql_fetch_document_fields % u"pk_doc = %s"
    498          _cmds_store_payload = [
    499                  u"""update blobs.doc_med set
    500                                  fk_type = %(pk_type)s,
    501                                  fk_episode = %(pk_episode)s,
    502                                  fk_encounter = %(pk_encounter)s,
    503                                  clin_when = %(clin_when)s,
    504                                  comment = gm.nullify_empty_string(%(comment)s),
    505                                  ext_ref = gm.nullify_empty_string(%(ext_ref)s)
    506                          where
    507                                  pk = %(pk_doc)s and
    508                                  xmin = %(xmin_doc_med)s""",
    509                  u"""select xmin_doc_med from blobs.v_doc_med where pk_doc = %(pk_doc)s"""
    510                  ]
    511
    512          _updatable_fields = [
    513                  'pk_type',
    514                  'comment',
    515                  'clin_when',
    516                  'ext_ref',
    517                  'pk_episode',
    518                  'pk_encounter'                  # mainly useful when moving visual progress notes to their respective encounters
    519          ]
    520          #--------------------------------------------------------
    521 -        def refetch_payload(self, ignore_changes=False):
    522                  try: del self.__has_unreviewed_parts
    523                  except AttributeError: pass
    524
    525                  return super(cDocument, self).refetch_payload(ignore_changes = ignore_changes)
    526          #--------------------------------------------------------
    527 -        def get_descriptions(self, max_lng=250):
    528                  """Get document descriptions.
    529
    530                  - will return a list of rows
    531                  """
    532                  if max_lng is None:
    533                          cmd = u"SELECT pk, text FROM blobs.doc_desc WHERE fk_doc = %s"
    534                  else:
    535                          cmd = u"SELECT pk, substring(text from 1 for %s) FROM blobs.doc_desc WHERE fk_doc=%%s" % max_lng
    536                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
    537                  return rows
    538          #--------------------------------------------------------
    539 -        def add_description(self, description=None):
    540                  cmd = u"insert into blobs.doc_desc (fk_doc, text) values (%s, %s)"
    541                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj, description]}])
    542                  return True
    543          #--------------------------------------------------------
    544 -        def update_description(self, pk=None, description=None):
    545                  cmd = u"update blobs.doc_desc set text = %(desc)s where fk_doc = %(doc)s and pk = %(pk_desc)s"
    546                  gmPG2.run_rw_queries(queries = [
    547                          {'cmd': cmd, 'args': {'doc': self.pk_obj, 'pk_desc': pk, 'desc': description}}
    548                  ])
    549                  return True
    550          #--------------------------------------------------------
    551 -        def delete_description(self, pk=None):
    552                  cmd = u"delete from blobs.doc_desc where fk_doc = %(doc)s and pk = %(desc)s"
    553                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'doc': self.pk_obj, 'desc': pk}}])
    554                  return True
    555          #--------------------------------------------------------
    556 -        def _get_parts(self):
    557                  cmd = _sql_fetch_document_part_fields % u"pk_doc = %s ORDER BY seq_idx"
    558                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True)
    559                  return [ cDocumentPart(row = {'pk_field': 'pk_obj', 'idx': idx, 'data': r}) for r in rows ]
    560
    561          parts = property(_get_parts, lambda x:x)
    562          #--------------------------------------------------------
    563 -        def add_part(self, file=None):
    564                  """Add a part to the document."""
    565                  # create dummy part
    566                  cmd = u"""
    567                          insert into blobs.doc_obj (
    568                                  fk_doc, data, seq_idx
    569                          ) VALUES (
    570                                  %(doc_id)s,
    571                                  ''::bytea,
    572                                  (select coalesce(max(seq_idx)+1, 1) from blobs.doc_obj where fk_doc=%(doc_id)s)
    573                          )"""
    574                  rows, idx = gmPG2.run_rw_queries (
    575                          queries = [
    576                                  {'cmd': cmd, 'args': {'doc_id': self.pk_obj}},
    577                                  {'cmd': u"select currval('blobs.doc_obj_pk_seq')"}
    578                          ],
    579                          return_data = True
    580                  )
    581                  # init document part instance
    582                  pk_part = rows[0][0]
    583                  new_part = cDocumentPart(aPK_obj = pk_part)
    584                  if not new_part.update_data_from_file(fname=file):
    585                          _log.error('cannot import binary data from [%s] into document part' % file)
    586                          gmPG2.run_rw_queries (
    587                                  queries = [
    588                                          {'cmd': u"delete from blobs.doc_obj where pk = %s", 'args': [pk_part]}
    589                                  ]
    590                          )
    591                          return None
    592                  new_part['filename'] = file
    593                  new_part.save_payload()
    594
    595                  return new_part
    596          #--------------------------------------------------------
    597 -        def add_parts_from_files(self, files=None, reviewer=None):
    598
    599                  new_parts = []
    600
    601                  for filename in files:
    602                          new_part = self.add_part(file = filename)
    603                          if new_part is None:
    604                                  msg = 'cannot instantiate document part object'
    605                                  _log.error(msg)
    606                                  return (False, msg, filename)
    607                          new_parts.append(new_part)
    608
    609                          if reviewer is not None:
    610                                  new_part['pk_intended_reviewer'] = reviewer                     # None == Null
    611                                  success, data = new_part.save_payload()
    612                                  if not success:
    613                                          msg = 'cannot set reviewer to [%s]' % reviewer
    614                                          _log.error(msg)
    615                                          _log.error(str(data))
    616                                          return (False, msg, filename)
    617
    618                  return (True, '', new_parts)
    619          #--------------------------------------------------------
    620 -        def export_parts_to_files(self, export_dir=None, chunksize=0):
    621                  fnames = []
    622                  for part in self.parts:
    623                          fname = part.export_to_file(aChunkSize = chunksize)
    624                          if export_dir is not None:
    625                                  shutil.move(fname, export_dir)
    626                                  fname = os.path.join(export_dir, os.path.split(fname)[1])
    627                          fnames.append(fname)
    628                  return fnames
    629          #--------------------------------------------------------
    630 -        def _get_has_unreviewed_parts(self):
    631                  try:
    632                          return self.__has_unreviewed_parts
    633                  except AttributeError:
    634                          pass
    635
    636                  cmd = u"SELECT EXISTS(SELECT 1 FROM blobs.v_obj4doc_no_data WHERE pk_doc = %(pk)s AND reviewed IS FALSE)"
    637                  args = {'pk': self.pk_obj}
    638                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    639                  self.__has_unreviewed_parts = rows[0][0]
    640
    641                  return self.__has_unreviewed_parts
    642
    643          has_unreviewed_parts = property(_get_has_unreviewed_parts, lambda x:x)
    644          #--------------------------------------------------------
    645 -        def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
    646                  # FIXME: this is probably inefficient
    647                  for part in self.parts:
    648                          if not part.set_reviewed(technically_abnormal, clinically_relevant):
    649                                  return False
    650                  return True
    651          #--------------------------------------------------------
    652 -        def set_primary_reviewer(self, reviewer=None):
    653                  for part in self.parts:
    654                          part['pk_intended_reviewer'] = reviewer
    655                          success, data = part.save_payload()
    656                          if not success:
    657                                  _log.error('cannot set reviewer to [%s]' % reviewer)
    658                                  _log.error(str(data))
    659                                  return False
    660                  return True
    661          #--------------------------------------------------------
    662 -        def format(self):
    663                  part_count = len(self._payload[self._idx['seq_idx_list']])
    664                  if part_count == 0:
    665                          parts = _('no parts')
    666                  elif part_count == 1:
    667                          parts = _('1 part')
    668                  else:
    669                          parts = _('%s parts') % part_count
    670                  txt = _(
    671                          '%s (%s)   #%s\n'
    672                          '\n'
    673                          ' Created: %s\n'
    674                          ' Episode: %s\n'
    675                          '%s'
    676                          '%s'
    677                          '%s'
    678                  ) % (
    679                          self._payload[self._idx['l10n_type']],
    680                          parts,
    681                          self._payload[self._idx['pk_doc']],
    682                          gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], format = '%Y %b %d', accuracy = gmDateTime.acc_days),
    683                          self._payload[self._idx['episode']],
    684                          gmTools.coalesce(self._payload[self._idx['health_issue']], u'', _(' Health issue: %s\n')),
    685                          gmTools.coalesce(self._payload[self._idx['ext_ref']], u'', _(' External reference: %s\n')),
    686                          gmTools.coalesce(self._payload[self._idx['comment']], u'', u' %s')
    687                  )
    688                  return txt
    689  #------------------------------------------------------------
    690 -def create_document(document_type=None, encounter=None, episode=None):
    691          """Returns new document instance or raises an exception.
    692          """
    693          cmd = u"""INSERT INTO blobs.doc_med (fk_type, fk_encounter, fk_episode) VALUES (%(type)s, %(enc)s, %(epi)s) RETURNING pk"""
    694          try:
    695                  int(document_type)
    696          except ValueError:
    697                  cmd = u"""
    698                          INSERT INTO blobs.doc_med (
    699                                  fk_type,
    700                                  fk_encounter,
    701                                  fk_episode
    702                          ) VALUES (
    703                                  coalesce (
    704                                          (SELECT pk from blobs.doc_type bdt where bdt.name = %(type)s),
    705                                          (SELECT pk from blobs.doc_type bdt where _(bdt.name) = %(type)s)
    706                                  ),
    707                                  %(enc)s,
    708                                  %(epi)s
    709                          ) RETURNING pk"""
    710
    711          args = {'type': document_type, 'enc': encounter, 'epi': episode}
    712          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
    713          doc = cDocument(aPK_obj = rows[0][0])
    714          return doc
    715  #------------------------------------------------------------
    716 -def search_for_documents(patient_id=None, type_id=None, external_reference=None):
    717          """Searches for documents with the given patient and type ID."""
    718          if patient_id is None:
    719                  raise ValueError('need patient id to search for document')
    720
    721          args = {'pat_id': patient_id, 'type_id': type_id, 'ref': external_reference}
    722          where_parts = [u'pk_patient = %(pat_id)s']
    723
    724          if type_id is not None:
    725                  where_parts.append(u'pk_type = %(type_id)s')
    726
    727          if external_reference is not None:
    728                  where_parts.append(u'ext_ref = %(ref)s')
    729
    730          cmd = _sql_fetch_document_fields % u' AND '.join(where_parts)
    731          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    732          return [ cDocument(row = {'data': r, 'idx': idx, 'pk_field': 'pk_doc'}) for r in rows ]
    733  #------------------------------------------------------------
    734 -def delete_document(document_id=None, encounter_id=None):
    735          # cascades to doc_obj and doc_desc but not bill.bill
    736          cmd = u"SELECT blobs.delete_document(%(pk)s, %(enc)s)"
    737          args = {'pk': document_id, 'enc': encounter_id}
    738          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
    739          if not rows[0][0]:
    740                  _log.error('cannot delete document [%s]', document_id)
    741                  return False
    742          return True
    743  #------------------------------------------------------------
    744 -def reclassify_documents_by_type(original_type=None, target_type=None):
    745
    746          _log.debug('reclassifying documents by type')
    747          _log.debug('original: %s', original_type)
    748          _log.debug('target: %s', target_type)
    749
    750          if target_type['pk_doc_type'] == original_type['pk_doc_type']:
    751                  return True
    752
    753          cmd = u"""
    754  update blobs.doc_med set
    755          fk_type = %(new_type)s
    756  where
    757          fk_type = %(old_type)s
    758  """
    759          args = {u'new_type': target_type['pk_doc_type'], u'old_type': original_type['pk_doc_type']}
    760
    761          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    762
    763          return True
    764
    765  #============================================================
    766 -class cDocumentType(gmBusinessDBObject.cBusinessDBObject):
    767          """Represents a document type."""
    768          _cmd_fetch_payload = u"""select * from blobs.v_doc_type where pk_doc_type=%s"""
    769          _cmds_store_payload = [
    770                  u"""update blobs.doc_type set
    771                                  name = %(type)s
    772                          where
    773                                  pk=%(pk_obj)s and
    774                                  xmin=%(xmin_doc_type)s""",
    775                  u"""select xmin_doc_type from blobs.v_doc_type where pk_doc_type = %(pk_obj)s"""
    776          ]
    777          _updatable_fields = ['type']
    778          #--------------------------------------------------------
    779 -        def set_translation(self, translation=None):
    780
    781                  if translation.strip() == '':
    782                          return False
    783
    784                  if translation.strip() == self._payload[self._idx['l10n_type']].strip():
    785                          return True
    786
    787                  rows, idx = gmPG2.run_rw_queries (
    788                          queries = [
    789                                  {'cmd': u'select i18n.i18n(%s)', 'args': [self._payload[self._idx['type']]]},
    790                                  {'cmd': u'select i18n.upd_tx((select i18n.get_curr_lang()), %(orig)s, %(tx)s)',
    791                                   'args': {
    792                                          'orig': self._payload[self._idx['type']],
    793                                          'tx': translation
    794                                          }
    795                                  }
    796                          ],
    797                          return_data = True
    798                  )
    799                  if not rows[0][0]:
    800                          _log.error('cannot set translation to [%s]' % translation)
    801                          return False
    802
    803                  return self.refetch_payload()
    804  #------------------------------------------------------------
    805 -def get_document_types():
    806          rows, idx = gmPG2.run_ro_queries (
    807                  queries = [{'cmd': u"SELECT * FROM blobs.v_doc_type"}],
    808                  get_col_idx = True
    809          )
    810          doc_types = []
    811          for row in rows:
    812                  row_def = {'pk_field': 'pk_doc_type', 'idx': idx, 'data': row}
    813                  doc_types.append(cDocumentType(row = row_def))
    814          return doc_types
    815  #------------------------------------------------------------
    816 -def get_document_type_pk(document_type=None):
    817          args = {'typ': document_type.strip()}
    818
    819          cmd = u'SELECT pk FROM blobs.doc_type WHERE name = %(typ)s'
    820          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    821          if len(rows) == 0:
    822                  cmd = u'SELECT pk FROM blobs.doc_type WHERE _(name) = %(typ)s'
    823                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    824
    825          if len(rows) == 0:
    826                  return None
    827
    828          return rows[0]['pk']
    829  #------------------------------------------------------------
    830 -def create_document_type(document_type=None):
    831          # check for potential dupes:
    832          cmd = u'select pk from blobs.doc_type where name = %s'
    833          rows, idx = gmPG2.run_ro_queries (
    834                  queries = [{'cmd': cmd, 'args': [document_type]}]
    835          )
    836          if len(rows) == 0:
    837                  cmd1 = u"INSERT INTO blobs.doc_type (name) VALUES (%s) RETURNING pk"
    838                  rows, idx = gmPG2.run_rw_queries (
    839                          queries = [{'cmd': cmd1, 'args': [document_type]}],
    840                          return_data = True
    841                  )
    842          return cDocumentType(aPK_obj = rows[0][0])
    843  #------------------------------------------------------------
    844 -def delete_document_type(document_type=None):
    845          if document_type['is_in_use']:
    846                  return False
    847          gmPG2.run_rw_queries (
    848                  queries = [{
    849                          'cmd': u'delete from blobs.doc_type where pk=%s',
    850                          'args': [document_type['pk_doc_type']]
    851                  }]
    852          )
    853          return True
    854  #------------------------------------------------------------
    855 -def get_ext_ref():
    856          """This needs *considerably* more smarts."""
    857          dirname = gmTools.get_unique_filename (
    858                  prefix = '',
    859                  suffix = time.strftime(".%Y%m%d-%H%M%S", time.localtime())
    860          )
    861          # extract name for dir
    862          path, doc_ID = os.path.split(dirname)
    863          return doc_ID
    864  #============================================================
    865  # main
    866  #------------------------------------------------------------
    867  if __name__ == '__main__':
    868
    869          if len(sys.argv) < 2:
    870                  sys.exit()
    871
    872          if sys.argv[1] != u'test':
    873                  sys.exit()
    874
    875          #--------------------------------------------------------
    876 -        def test_doc_types():
    877
    878                  print "----------------------"
    879                  print "listing document types"
    880                  print "----------------------"
    881
    882                  for dt in get_document_types():
    883                          print dt
    884
    885                  print "------------------------------"
    886                  print "testing document type handling"
    887                  print "------------------------------"
    888
    889                  dt = create_document_type(document_type = 'dummy doc type for unit test 1')
    890                  print "created:", dt
    891
    892                  dt['type'] = 'dummy doc type for unit test 2'
    893                  dt.save_payload()
    894                  print "changed base name:", dt
    895
    896                  dt.set_translation(translation = 'Dummy-Dokumenten-Typ fuer Unit-Test')
    897                  print "translated:", dt
    898
    899                  print "deleted:", delete_document_type(document_type = dt)
    900
    901                  return
    902          #--------------------------------------------------------
    903 -        def test_adding_doc_part():
    904
    905                  print "-----------------------"
    906                  print "testing document import"
    907                  print "-----------------------"
    908
    909                  docs = search_for_documents(patient_id=12)
    910                  doc = docs[0]
    911                  print "adding to doc:", doc
    912
    913                  fname = sys.argv[1]
    914                  print "adding from file:", fname
    915                  part = doc.add_part(file=fname)
    916                  print "new part:", part
    917
    918                  return
    919          #--------------------------------------------------------
    920 -        def test_get_documents():
    921                  doc_folder = cDocumentFolder(aPKey=12)
    922
    923                  #photo = doc_folder.get_latest_mugshot()
    924                  #print type(photo), photo
    925
    926                  docs = doc_folder.get_documents()
    927                  for doc in docs:
    928                          print type(doc), doc
    929                          print doc.parts
    930                  #pprint(gmBusinessDBObject.jsonclasshintify(docs))
    931          #--------------------------------------------------------
    932          from Gnumed.pycommon import gmI18N
    933          gmI18N.activate_locale()
    934          gmI18N.install_domain()
    935
    936          #test_doc_types()
    937          #test_adding_doc_part()
    938          test_get_documents()
    939
    940  #       print get_ext_ref()
    941
    942  #============================================================
    943