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