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  from Gnumed.pycommon import gmWorkerThread
      18
      19  from Gnumed.business import gmOrganization
      20
      21
      22  _log = logging.getLogger('gm.docs')
      23
      24  MUGSHOT=26
      25  DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE = 'visual progress note'
      26  DOCUMENT_TYPE_PRESCRIPTION = 'prescription'
      27
      28  #============================================================
      29 -class cDocumentFolder:
      30          """Represents a folder with medical documents for a single patient."""
      31
      32 -        def __init__(self, aPKey = None):
      33                  """Fails if
      34
      35                  - patient referenced by aPKey does not exist
      36                  """
      37                  self.pk_patient = aPKey                 # == identity.pk == primary key
      38                  if not self._pkey_exists():
      39                          raise gmExceptions.ConstructorError("No patient with PK [%s] in database." % aPKey)
      40
      41                  # register backend notification interests
      42                  # (keep this last so we won't hang on threads when
      43                  #  failing this constructor for other reasons ...)
      44  #               if not self._register_interests():
      45  #                       raise gmExceptions.ConstructorError, "cannot register signal interests"
      46
      47                  _log.debug('instantiated document folder for patient [%s]' % self.pk_patient)
      48          #--------------------------------------------------------
      49 -        def cleanup(self):
      50                  pass
      51          #--------------------------------------------------------
      52          # internal helper
      53          #--------------------------------------------------------
      54 -        def _pkey_exists(self):
      55                  """Does this primary key exist ?
      56
      57                  - true/false/None
      58                  """
      59                  # patient in demographic database ?
      60                  rows, idx = gmPG2.run_ro_queries(queries = [
      61                          {'cmd': "select exists(select pk from dem.identity where pk = %s)", 'args': [self.pk_patient]}
      62                  ])
      63                  if not rows[0][0]:
      64                          _log.error("patient [%s] not in demographic database" % self.pk_patient)
      65                          return None
      66                  return True
      67          #--------------------------------------------------------
      68          # API
      69          #--------------------------------------------------------
      70 -        def get_latest_freediams_prescription(self):
      71                  cmd = """
      72                          SELECT pk_doc
      73                          FROM blobs.v_doc_med
      74                          WHERE
      75                                  pk_patient = %(pat)s
      76                                          AND
      77                                  type = %(typ)s
      78                                          AND
      79                                  ext_ref = %(ref)s
      80                          ORDER BY
      81                                  clin_when DESC
      82                          LIMIT 1
      83                  """
      84                  args = {
      85                          'pat': self.pk_patient,
      86                          'typ': DOCUMENT_TYPE_PRESCRIPTION,
      87                          'ref': 'FreeDiams'
      88                  }
      89                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
      90                  if len(rows) == 0:
      91                          _log.info('no FreeDiams prescription available for patient [%s]' % self.pk_patient)
      92                          return None
      93                  prescription = cDocument(aPK_obj = rows[0][0])
      94                  return prescription
      95
      96          #--------------------------------------------------------
      97 -        def get_latest_mugshot(self):
      98                  cmd = "SELECT pk_obj FROM blobs.v_latest_mugshot WHERE pk_patient = %s"
      99                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
     100                  if len(rows) == 0:
     101                          _log.info('no mugshots available for patient [%s]' % self.pk_patient)
     102                          return None
     103                  return cDocumentPart(aPK_obj = rows[0][0])
     104
     105          latest_mugshot = property(get_latest_mugshot, lambda x:x)
     106
     107          #--------------------------------------------------------
     108 -        def get_mugshot_list(self, latest_only=True):
     109                  if latest_only:
     110                          cmd = "select pk_doc, pk_obj from blobs.v_latest_mugshot where pk_patient=%s"
     111                  else:
     112                          cmd = """
     113                                  select
     114                                          vdm.pk_doc as pk_doc,
     115                                          dobj.pk as pk_obj
     116                                  from
     117                                          blobs.v_doc_med vdm
     118                                          blobs.doc_obj dobj
     119                                  where
     120                                          vdm.pk_type = (select pk from blobs.doc_type where name = 'patient photograph')
     121                                          and vdm.pk_patient = %s
     122                                          and dobj.fk_doc = vdm.pk_doc
     123                          """
     124                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
     125                  return rows
     126
     127          #--------------------------------------------------------
     128 -        def get_doc_list(self, doc_type=None):
     129                  """return flat list of document IDs"""
     130
     131                  args = {
     132                          'ID': self.pk_patient,
     133                          'TYP': doc_type
     134                  }
     135
     136                  cmd = """
     137                          select vdm.pk_doc
     138                          from blobs.v_doc_med vdm
     139                          where
     140                                  vdm.pk_patient = %%(ID)s
     141                                  %s
     142                          order by vdm.clin_when"""
     143
     144                  if doc_type is None:
     145                          cmd = cmd % ''
     146                  else:
     147                          try:
     148                                  int(doc_type)
     149                                  cmd = cmd % 'and vdm.pk_type = %(TYP)s'
     150                          except (TypeError, ValueError):
     151                                  cmd = cmd % 'and vdm.pk_type = (select pk from blobs.doc_type where name = %(TYP)s)'
     152
     153                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     154                  doc_ids = []
     155                  for row in rows:
     156                          doc_ids.append(row[0])
     157                  return doc_ids
     158
     159          #--------------------------------------------------------
     160 -        def get_visual_progress_notes(self, episodes=None, encounter=None):
     161                  return self.get_documents (
     162                          doc_type = DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
     163                          pk_episodes = episodes,
     164                          encounter = encounter
     165                  )
     166
     167          #--------------------------------------------------------
     168 -        def get_unsigned_documents(self):
     169                  args = {'pat': self.pk_patient}
     170                  cmd = _SQL_get_document_fields % """
     171                          pk_doc IN (
     172                                  SELECT DISTINCT ON (b_vo.pk_doc) b_vo.pk_doc
     173                                  FROM blobs.v_obj4doc_no_data b_vo
     174                                  WHERE
     175                                          pk_patient = %(pat)s
     176                                                  AND
     177                                          reviewed IS FALSE
     178                          )
     179                          ORDER BY clin_when DESC"""
     180                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     181                  return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
     182
     183          #--------------------------------------------------------
     184 -        def get_documents(self, doc_type=None, pk_episodes=None, encounter=None, order_by=None, exclude_unsigned=False, pk_types=None):
     185                  """Return list of documents."""
     186
     187                  args = {
     188                          'pat': self.pk_patient,
     189                          'type': doc_type,
     190                          'enc': encounter
     191                  }
     192                  where_parts = ['pk_patient = %(pat)s']
     193
     194                  if doc_type is not None:
     195                          try:
     196                                  int(doc_type)
     197                                  where_parts.append('pk_type = %(type)s')
     198                          except (TypeError, ValueError):
     199                                  where_parts.append('pk_type = (SELECT pk FROM blobs.doc_type WHERE name = %(type)s)')
     200
     201                  if pk_types is not None:
     202                          where_parts.append('pk_type IN %(pk_types)s')
     203                          args['pk_types'] = tuple(pk_types)
     204
     205                  if (pk_episodes is not None) and (len(pk_episodes) > 0):
     206                          where_parts.append('pk_episode IN %(epis)s')
     207                          args['epis'] = tuple(pk_episodes)
     208
     209                  if encounter is not None:
     210                          where_parts.append('pk_encounter = %(enc)s')
     211
     212                  if exclude_unsigned:
     213                          where_parts.append('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)')
     214
     215                  if order_by is None:
     216                          order_by = 'ORDER BY clin_when'
     217
     218                  cmd = "%s\n%s" % (_SQL_get_document_fields % ' AND '.join(where_parts), order_by)
     219                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     220
     221                  return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
     222
     223          documents = property(get_documents, lambda x:x)
     224
     225          #--------------------------------------------------------
     226 -        def add_document(self, document_type=None, encounter=None, episode=None, link_obj=None):
     227                  return create_document(link_obj = link_obj, document_type = document_type, encounter = encounter, episode = episode)
     228
     229          #--------------------------------------------------------
     230 -        def add_prescription(self, encounter=None, episode=None, link_obj=None):
     231                  return self.add_document (
     232                          link_obj = link_obj,
     233                          document_type = create_document_type (
     234                                  document_type = DOCUMENT_TYPE_PRESCRIPTION
     235                          )['pk_doc_type'],
     236                          encounter = encounter,
     237                          episode = episode
     238                  )
     239
     240          #--------------------------------------------------------
     241 -        def _get_all_document_org_units(self):
     242                  cmd = gmOrganization._SQL_get_org_unit % (
     243                          'pk_org_unit IN (SELECT DISTINCT ON (pk_org_unit) pk_org_unit FROM blobs.v_doc_med WHERE pk_patient = %(pat)s)'
     244                  )
     245                  args = {'pat': self.pk_patient}
     246                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     247                  return [ gmOrganization.cOrgUnit(row = {'data': r, 'idx': idx, 'pk_field': 'pk_org_unit'}) for r in rows ]
     248
     249          all_document_org_units = property(_get_all_document_org_units, lambda x:x)
     250
     251  #============================================================
     252  _SQL_get_document_part_fields = "select * from blobs.v_obj4doc_no_data where %s"
     253
     254 -class cDocumentPart(gmBusinessDBObject.cBusinessDBObject):
     255          """Represents one part of a medical document."""
     256
     257          _cmd_fetch_payload = _SQL_get_document_part_fields % "pk_obj = %s"
     258          _cmds_store_payload = [
     259                  """UPDATE blobs.doc_obj SET
     260                                  seq_idx = %(seq_idx)s,
     261                                  comment = gm.nullify_empty_string(%(obj_comment)s),
     262                                  filename = gm.nullify_empty_string(%(filename)s),
     263                                  fk_intended_reviewer = %(pk_intended_reviewer)s
     264                          WHERE
     265                                  pk = %(pk_obj)s
     266                                          AND
     267                                  xmin = %(xmin_doc_obj)s
     268                          RETURNING
     269                                  xmin AS xmin_doc_obj"""
     270          ]
     271          _updatable_fields = [
     272                  'seq_idx',
     273                  'obj_comment',
     274                  'pk_intended_reviewer',
     275                  'filename'
     276          ]
     277          #--------------------------------------------------------
     278          # retrieve data
     279          #--------------------------------------------------------
     280 -        def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, adjust_extension=False, conn=None):
     281
     282                  if filename is None:
     283                          filename = self.get_useful_filename(make_unique = True, directory = directory)
     284
     285                  filename = self.__download_to_file(filename = filename)
     286                  if filename is None:
     287                          return None
     288
     289                  if target_mime is None:
     290                          if filename.endswith('.dat'):
     291                                  if adjust_extension:
     292                                          return gmMimeLib.adjust_extension_by_mimetype(filename)
     293                          return filename
     294
     295                  if target_extension is None:
     296                          target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
     297
     298                  target_path, name = os.path.split(filename)
     299                  name, tmp = os.path.splitext(name)
     300                  target_fname = gmTools.get_unique_filename (
     301                          prefix = '%s-conv-' % name,
     302                          suffix = target_extension
     303                  )
     304                  _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, target_fname)
     305                  if gmMimeLib.convert_file (
     306                          filename = filename,
     307                          target_mime = target_mime,
     308                          target_filename = target_fname
     309                  ):
     310                          return target_fname
     311
     312                  _log.warning('conversion failed')
     313                  if not ignore_conversion_problems:
     314                          return None
     315
     316                  if filename.endswith('.dat'):
     317                          if adjust_extension:
     318                                  filename = gmMimeLib.adjust_extension_by_mimetype(filename)
     319                  _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename)
     320                  return filename
     321
     322          #--------------------------------------------------------
     323 -        def get_reviews(self):
     324                  cmd = """
     325  SELECT
     326          reviewer,
     327          reviewed_when,
     328          is_technically_abnormal,
     329          clinically_relevant,
     330          is_review_by_responsible_reviewer,
     331          is_your_review,
     332          coalesce(comment, '')
     333  FROM blobs.v_reviewed_doc_objects
     334  WHERE pk_doc_obj = %s
     335  ORDER BY
     336          is_your_review desc,
     337          is_review_by_responsible_reviewer desc,
     338          reviewed_when desc
     339  """
     340                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
     341                  return rows
     342
     343          #--------------------------------------------------------
     344 -        def __get_containing_document(self):
     345                  return cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
     346
     347          containing_document = property(__get_containing_document)
     348
     349          #--------------------------------------------------------
     350          # store data
     351          #--------------------------------------------------------
     352 -        def update_data_from_file(self, fname=None, link_obj=None):
     353                  # sanity check
     354                  if not (os.access(fname, os.R_OK) and os.path.isfile(fname)):
     355                          _log.error('[%s] is not a readable file' % fname)
     356                          return False
     357
     358                  if not gmPG2.file2bytea (
     359                          conn = link_obj,
     360                          query = "UPDATE blobs.doc_obj SET data = %(data)s::bytea WHERE pk = %(pk)s RETURNING md5(data) AS md5",
     361                          filename = fname,
     362                          args = {'pk': self.pk_obj},
     363                          file_md5 = gmTools.file2md5(filename = fname, return_hex = True)
     364                  ):
     365                          return False
     366
     367                  # must update XMIN now ...
     368                  self.refetch_payload(link_obj = link_obj)
     369                  return True
     370
     371          #--------------------------------------------------------
     372 -        def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
     373                  # row already there ?
     374                  cmd = """
     375  select pk
     376  from blobs.reviewed_doc_objs
     377  where
     378          fk_reviewed_row = %s and
     379          fk_reviewer = (select pk from dem.staff where db_user = current_user)"""
     380                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
     381
     382                  # INSERT needed
     383                  if len(rows) == 0:
     384                          cols = [
     385                                  "fk_reviewer",
     386                                  "fk_reviewed_row",
     387                                  "is_technically_abnormal",
     388                                  "clinically_relevant"
     389                          ]
     390                          vals = [
     391                                  '%(fk_row)s',
     392                                  '%(abnormal)s',
     393                                  '%(relevant)s'
     394                          ]
     395                          args = {
     396                                  'fk_row': self.pk_obj,
     397                                  'abnormal': technically_abnormal,
     398                                  'relevant': clinically_relevant
     399                          }
     400                          cmd = """
     401  insert into blobs.reviewed_doc_objs (
     402          %s
     403  ) values (
     404          (select pk from dem.staff where db_user=current_user),
     405          %s
     406  )""" % (', '.join(cols), ', '.join(vals))
     407
     408                  # UPDATE needed
     409                  if len(rows) == 1:
     410                          pk_review = rows[0][0]
     411                          args = {
     412                                  'abnormal': technically_abnormal,
     413                                  'relevant': clinically_relevant,
     414                                  'pk_review': pk_review
     415                          }
     416                          cmd = """
     417                                  UPDATE blobs.reviewed_doc_objs SET
     418                                          is_technically_abnormal = %(abnormal)s,
     419                                          clinically_relevant = %(relevant)s
     420                                  WHERE
     421                                          pk = %(pk_review)s
     422                          """
     423                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     424
     425                  return True
     426
     427          #--------------------------------------------------------
     428 -        def set_as_active_photograph(self):
     429                  if self._payload[self._idx['type']] != 'patient photograph':
     430                          return False
     431                  # set seq_idx to current max + 1
     432                  cmd = 'SELECT coalesce(max(seq_idx)+1, 1) FROM blobs.doc_obj WHERE fk_doc = %(doc_id)s'
     433                  rows, idx = gmPG2.run_ro_queries (
     434                          queries = [{
     435                                  'cmd': cmd,
     436                                  'args': {'doc_id': self._payload[self._idx['pk_doc']]}
     437                          }]
     438                  )
     439                  self._payload[self._idx['seq_idx']] = rows[0][0]
     440                  self._is_modified = True
     441                  self.save_payload()
     442
     443          #--------------------------------------------------------
     444 -        def reattach(self, pk_doc=None):
     445                  if pk_doc == self._payload[self._idx['pk_doc']]:
     446                          return True
     447
     448                  cmd = """
     449                          UPDATE blobs.doc_obj SET
     450                                  fk_doc = %(pk_doc_target)s,
     451                                  -- coalesce needed for no-parts target docs
     452                                  seq_idx = (SELECT coalesce(max(seq_idx) + 1, 1) FROM blobs.doc_obj WHERE fk_doc = %(pk_doc_target)s)
     453                          WHERE
     454                                  EXISTS(SELECT 1 FROM blobs.doc_med WHERE pk = %(pk_doc_target)s)
     455                                          AND
     456                                  pk = %(pk_obj)s
     457                                          AND
     458                                  xmin = %(xmin_doc_obj)s
     459                          RETURNING fk_doc
     460                  """
     461                  args = {
     462                          'pk_doc_target': pk_doc,
     463                          'pk_obj': self.pk_obj,
     464                          'xmin_doc_obj': self._payload[self._idx['xmin_doc_obj']]
     465                  }
     466                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
     467                  if len(rows) == 0:
     468                          return False
     469                  # The following should never hold true because the target
     470                  # fk_doc is returned from the query and it is checked for
     471                  # equality before the UPDATE already. Assuming the update
     472                  # failed to update a row because the target fk_doc did
     473                  # not exist we would not get *any* rows in return - for
     474                  # which condition we also already checked
     475                  if rows[0]['fk_doc'] == self._payload[self._idx['pk_doc']]:
     476                          return False
     477
     478                  self.refetch_payload()
     479                  return True
     480
     481          #--------------------------------------------------------
     482 -        def display_via_mime(self, chunksize=0, block=None):
     483
     484                  fname = self.save_to_file(aChunkSize = chunksize)
     485                  if fname is None:
     486                          return False, ''
     487
     488                  success, msg = gmMimeLib.call_viewer_on_file(fname, block = block)
     489                  if not success:
     490                          return False, msg
     491
     492                  return True, ''
     493
     494          #--------------------------------------------------------
     495 -        def format_single_line(self):
     496                  f_ext = ''
     497                  if self._payload[self._idx['filename']] is not None:
     498                          f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip()
     499                  if f_ext != '':
     500                          f_ext = ' .' + f_ext.upper()
     501                  txt = _('part %s, %s%s%s of document %s from %s%s') % (
     502                          self._payload[self._idx['seq_idx']],
     503                          gmTools.size2str(self._payload[self._idx['size']]),
     504                          f_ext,
     505                          gmTools.coalesce(self._payload[self._idx['obj_comment']], '', ' ("%s")'),
     506                          self._payload[self._idx['l10n_type']],
     507                          gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y %b %d'),
     508                          gmTools.coalesce(self._payload[self._idx['doc_comment']], '', ' ("%s")')
     509                  )
     510                  return txt
     511
     512          #--------------------------------------------------------
     513 -        def format(self, single_line=False):
     514                  if single_line:
     515                          return self.format_single_line()
     516
     517                  txt = _('%s document part                 [#%s]\n') % (
     518                          gmTools.bool2str (
     519                                  boolean = self._payload[self._idx['reviewed']],
     520                                  true_str = _('Reviewed'),
     521                                  false_str = _('Unreviewed')
     522                          ),
     523                          self._payload[self._idx['pk_obj']]
     524                  )
     525                  f_ext = ''
     526                  if self._payload[self._idx['filename']] is not None:
     527                          f_ext = os.path.splitext(self._payload[self._idx['filename']])[1].strip('.').strip()
     528                  if f_ext != '':
     529                          f_ext = '.' + f_ext.upper() + ' '
     530                  txt += _(' Part %s: %s %s(%s Bytes)\n') % (
     531                          self._payload[self._idx['seq_idx']],
     532                          gmTools.size2str(self._payload[self._idx['size']]),
     533                          f_ext,
     534                          self._payload[self._idx['size']]
     535                  )
     536                  if self._payload[self._idx['filename']] is not None:
     537                          path, fname = os.path.split(self._payload[self._idx['filename']])
     538                          if not path.endswith(os.path.sep):
     539                                  if path != '':
     540                                          path += os.path.sep
     541                          if path != '':
     542                                  path = ' (%s)' % path
     543                          txt += _(' Filename: %s%s\n') % (fname, path)
     544                  if self._payload[self._idx['obj_comment']] is not None:
     545                          txt += '\n%s\n' % self._payload[self._idx['obj_comment']]
     546                  return txt
     547
     548          #--------------------------------------------------------
     549 -        def format_metainfo(self, callback=None):
     550                  """If <callback> is not None it will receive a tuple (status, description, pk_obj)."""
     551                  if callback is None:
     552                          return self.__run_metainfo_formatter()
     553
     554                  gmWorkerThread.execute_in_worker_thread (
     555                          payload_function = self.__run_metainfo_formatter,
     556                          completion_callback = callback,
     557                          worker_name = 'doc_part-metainfo_formatter-'
     558                  )
     559
     560          #--------------------------------------------------------
     561 -        def get_useful_filename(self, patient=None, make_unique=False, directory=None, include_gnumed_tag=True, date_before_type=False, name_first=True):
     562                  patient_part = ''
     563                  if patient is not None:
     564                          if name_first:
     565                                  patient_part = '%s-' % patient.subdir_name
     566                          else:
     567                                  patient_part = '-%s' % patient.subdir_name
     568
     569                  # preserve original filename extension if available
     570                  suffix = '.dat'
     571                  if self._payload[self._idx['filename']] is not None:
     572                          tmp, suffix = os.path.splitext (
     573                                  gmTools.fname_sanitize(self._payload[self._idx['filename']]).lower()
     574                          )
     575                          if suffix == '':
     576                                  suffix = '.dat'
     577
     578                  if include_gnumed_tag:
     579                          fname_template = 'gm_doc-part_%s-%%s' % self._payload[self._idx['seq_idx']]
     580                  else:
     581                          fname_template = '%%s-part_%s' % self._payload[self._idx['seq_idx']]
     582
     583                  if date_before_type:
     584                          date_type_part = '%s-%s' % (
     585                                  gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y-%m-%d', 'utf-8', gmDateTime.acc_days),
     586                                  self._payload[self._idx['l10n_type']].replace(' ', '_').replace('-', '_'),
     587                          )
     588                  else:
     589                          date_type_part = '%s-%s' % (
     590                                  self._payload[self._idx['l10n_type']].replace(' ', '_').replace('-', '_'),
     591                                  gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y-%m-%d', 'utf-8', gmDateTime.acc_days)
     592                          )
     593
     594                  if name_first:
     595                          date_type_name_part = patient_part + date_type_part
     596                  else:
     597                          date_type_name_part = date_type_part + patient_part
     598
     599                  fname = fname_template % date_type_name_part
     600
     601                  if make_unique:
     602                          fname = gmTools.get_unique_filename (
     603                                  prefix = '%s-' % gmTools.fname_sanitize(fname),
     604                                  suffix = suffix,
     605                                  tmp_dir = directory
     606                          )
     607                  else:
     608                          fname = gmTools.fname_sanitize(os.path.join(gmTools.coalesce(directory, gmTools.gmPaths().tmp_dir), fname + suffix))
     609
     610                  return fname
     611
     612          useful_filename = property(get_useful_filename)
     613
     614          #--------------------------------------------------------
     615          # internal helpers
     616          #--------------------------------------------------------
     617 -        def __download_to_file(self, filename=None, aChunkSize=0, conn=None):
     618                  if self._payload[self._idx['size']] == 0:
     619                          _log.debug('part size 0, nothing to download')
     620                          return None
     621
     622                  if filename is None:
     623                          filename = gmTools.get_unique_filename()
     624                  success = gmPG2.bytea2file (
     625                          data_query = {
     626                                  'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM blobs.doc_obj WHERE pk=%(pk)s',
     627                                  'args': {'pk': self.pk_obj}
     628                          },
     629                          filename = filename,
     630                          chunk_size = aChunkSize,
     631                          data_size = self._payload[self._idx['size']],
     632                          conn = conn
     633                  )
     634                  if not success:
     635                          return None
     636
     637                  return filename
     638
     639          #--------------------------------------------------------
     640 -        def __run_metainfo_formatter(self):
     641                  filename = self.__download_to_file()
     642                  if filename is None:
     643                          _log.error('problem downloading part')
     644                          return (False, _('problem downloading document part'))
     645
     646                  status, desc = gmMimeLib.describe_file(filename)
     647                  return (status, desc, self.pk_obj)
     648
     649  #------------------------------------------------------------
     650 -def delete_document_part(part_pk=None, encounter_pk=None):
     651          cmd = """
     652                  SELECT blobs.delete_document_part(%(pk)s, %(enc)s)
     653                  WHERE NOT EXISTS
     654                          (SELECT 1 FROM clin.export_item where fk_doc_obj = %(pk)s)
     655          """
     656          args = {'pk': part_pk, 'enc': encounter_pk}
     657          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     658          return
     659
     660  #============================================================
     661  _SQL_get_document_fields = "SELECT * FROM blobs.v_doc_med b_vdm WHERE %s"
     662
     663 -class cDocument(gmBusinessDBObject.cBusinessDBObject):
     664          """Represents one medical document."""
     665
     666          _cmd_fetch_payload = _SQL_get_document_fields % "pk_doc = %s"
     667          _cmds_store_payload = [
     668                  """UPDATE blobs.doc_med SET
     669                                  fk_type = %(pk_type)s,
     670                                  fk_episode = %(pk_episode)s,
     671                                  fk_encounter = %(pk_encounter)s,
     672                                  fk_org_unit = %(pk_org_unit)s,
     673                                  unit_is_receiver = %(unit_is_receiver)s,
     674                                  clin_when = %(clin_when)s,
     675                                  comment = gm.nullify_empty_string(%(comment)s),
     676                                  ext_ref = gm.nullify_empty_string(%(ext_ref)s),
     677                                  fk_hospital_stay = %(pk_hospital_stay)s
     678                          WHERE
     679                                  pk = %(pk_doc)s and
     680                                  xmin = %(xmin_doc_med)s
     681                          RETURNING
     682                                  xmin AS xmin_doc_med"""
     683          ]
     684          _updatable_fields = [
     685                  'pk_type',
     686                  'comment',
     687                  'clin_when',
     688                  'ext_ref',
     689                  'pk_episode',
     690                  'pk_encounter',                 # mainly useful when moving visual progress notes to their respective encounters
     691                  'pk_org_unit',
     692                  'unit_is_receiver',
     693                  'pk_hospital_stay'
     694          ]
     695
     696          #--------------------------------------------------------
     697 -        def refetch_payload(self, ignore_changes=False, link_obj=None):
     698                  try: del self.__has_unreviewed_parts
     699                  except AttributeError: pass
     700
     701                  return super(cDocument, self).refetch_payload(ignore_changes = ignore_changes, link_obj = link_obj)
     702
     703          #--------------------------------------------------------
     704 -        def get_descriptions(self, max_lng=250):
     705                  """Get document descriptions.
     706
     707                  - will return a list of rows
     708                  """
     709                  if max_lng is None:
     710                          cmd = "SELECT pk, text FROM blobs.doc_desc WHERE fk_doc = %s"
     711                  else:
     712                          cmd = "SELECT pk, substring(text from 1 for %s) FROM blobs.doc_desc WHERE fk_doc=%%s" % max_lng
     713                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
     714                  return rows
     715
     716          #--------------------------------------------------------
     717 -        def add_description(self, description=None):
     718                  cmd = "insert into blobs.doc_desc (fk_doc, text) values (%s, %s)"
     719                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj, description]}])
     720                  return True
     721
     722          #--------------------------------------------------------
     723 -        def update_description(self, pk=None, description=None):
     724                  cmd = "update blobs.doc_desc set text = %(desc)s where fk_doc = %(doc)s and pk = %(pk_desc)s"
     725                  gmPG2.run_rw_queries(queries = [
     726                          {'cmd': cmd, 'args': {'doc': self.pk_obj, 'pk_desc': pk, 'desc': description}}
     727                  ])
     728                  return True
     729
     730          #--------------------------------------------------------
     731 -        def delete_description(self, pk=None):
     732                  cmd = "delete from blobs.doc_desc where fk_doc = %(doc)s and pk = %(desc)s"
     733                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'doc': self.pk_obj, 'desc': pk}}])
     734                  return True
     735
     736          #--------------------------------------------------------
     737 -        def _get_parts(self):
     738                  cmd = _SQL_get_document_part_fields % "pk_doc = %s ORDER BY seq_idx"
     739                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True)
     740                  return [ cDocumentPart(row = {'pk_field': 'pk_obj', 'idx': idx, 'data': r}) for r in rows ]
     741
     742          parts = property(_get_parts, lambda x:x)
     743
     744          #--------------------------------------------------------
     745 -        def add_part(self, file=None, link_obj=None):
     746                  """Add a part to the document."""
     747                  # create dummy part
     748                  cmd = """
     749                          INSERT INTO blobs.doc_obj (
     750                                  fk_doc, data, seq_idx
     751                          ) VALUES (
     752                                  %(doc_id)s,
     753                                  ''::bytea,
     754                                  (SELECT coalesce(max(seq_idx)+1, 1) FROM blobs.doc_obj WHERE fk_doc = %(doc_id)s)
     755                          ) RETURNING pk"""
     756                  rows, idx = gmPG2.run_rw_queries (
     757                          link_obj = link_obj,
     758                          queries = [{'cmd': cmd, 'args': {'doc_id': self.pk_obj}}],
     759                          return_data = True
     760                  )
     761                  # init document part instance
     762                  pk_part = rows[0][0]
     763                  new_part = cDocumentPart(aPK_obj = pk_part, link_obj = link_obj)
     764                  if not new_part.update_data_from_file(link_obj = link_obj, fname = file):
     765                          _log.error('cannot import binary data from [%s] into document part' % file)
     766                          gmPG2.run_rw_queries (
     767                                  link_obj = link_obj,
     768                                  queries = [{'cmd': "DELETE FROM blobs.doc_obj WHERE pk = %s", 'args': [pk_part]}]
     769                          )
     770                          return None
     771                  new_part['filename'] = file
     772                  new_part.save_payload(conn = link_obj)
     773
     774                  return new_part
     775
     776          #--------------------------------------------------------
     777 -        def add_parts_from_files(self, files=None, reviewer=None):
     778
     779                  new_parts = []
     780
     781                  for filename in files:
     782                          new_part = self.add_part(file = filename)
     783                          if new_part is None:
     784                                  msg = 'cannot instantiate document part object from [%s]' % filename
     785                                  _log.error(msg)
     786                                  return (False, msg, filename)
     787                          new_parts.append(new_part)
     788
     789                          if reviewer is not None:
     790                                  new_part['pk_intended_reviewer'] = reviewer                     # None == Null
     791                                  success, data = new_part.save_payload()
     792                                  if not success:
     793                                          msg = 'cannot set reviewer to [%s] on [%s]' % (reviewer, filename)
     794                                          _log.error(msg)
     795                                          _log.error(str(data))
     796                                          return (False, msg, filename)
     797
     798                  return (True, '', new_parts)
     799
     800          #--------------------------------------------------------
     801 -        def save_parts_to_files(self, export_dir=None, chunksize=0, conn=None):
     802                  fnames = []
     803                  for part in self.parts:
     804                          fname = part.save_to_file(aChunkSize = chunksize, directory = export_dir, conn = conn)
     805                          if fname is None:
     806                                  _log.error('cannot export document part [%s]', part)
     807                                  continue
     808                          fnames.append(fname)
     809                  return fnames
     810
     811          #--------------------------------------------------------
     812 -        def _get_has_unreviewed_parts(self):
     813                  try:
     814                          return self.__has_unreviewed_parts
     815                  except AttributeError:
     816                          pass
     817
     818                  cmd = "SELECT EXISTS(SELECT 1 FROM blobs.v_obj4doc_no_data WHERE pk_doc = %(pk)s AND reviewed IS FALSE)"
     819                  args = {'pk': self.pk_obj}
     820                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     821                  self.__has_unreviewed_parts = rows[0][0]
     822
     823                  return self.__has_unreviewed_parts
     824
     825          has_unreviewed_parts = property(_get_has_unreviewed_parts, lambda x:x)
     826
     827          #--------------------------------------------------------
     828 -        def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
     829                  # FIXME: this is probably inefficient
     830                  for part in self.parts:
     831                          if not part.set_reviewed(technically_abnormal, clinically_relevant):
     832                                  return False
     833                  return True
     834
     835          #--------------------------------------------------------
     836 -        def set_primary_reviewer(self, reviewer=None):
     837                  for part in self.parts:
     838                          part['pk_intended_reviewer'] = reviewer
     839                          success, data = part.save_payload()
     840                          if not success:
     841                                  _log.error('cannot set reviewer to [%s]' % reviewer)
     842                                  _log.error(str(data))
     843                                  return False
     844                  return True
     845
     846          #--------------------------------------------------------
     847 -        def format_single_line(self):
     848
     849                  part_count = len(self._payload[self._idx['seq_idx_list']])
     850                  if part_count == 0:
     851                          parts = _('no parts')
     852                  elif part_count == 1:
     853                          parts = _('1 part')
     854                  else:
     855                          parts = _('%s parts') % part_count
     856
     857                  detail = ''
     858                  if self._payload[self._idx['ext_ref']] is not None:
     859                          detail = self._payload[self._idx['ext_ref']]
     860                  if self._payload[self._idx['unit']] is not None:
     861                          template = _('%s of %s')
     862                          if detail == '':
     863                                  detail = _('%s of %s') % (
     864                                          self._payload[self._idx['unit']],
     865                                          self._payload[self._idx['organization']]
     866                                  )
     867                          else:
     868                                  detail += (' @ ' + template % (
     869                                          self._payload[self._idx['unit']],
     870                                          self._payload[self._idx['organization']]
     871                                  ))
     872                  if detail != '':
     873                          detail = ' (%s)' % detail
     874
     875                  return '%s %s (%s):%s%s' % (
     876                          gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], '%Y %b %d', accuracy = gmDateTime.acc_days),
     877                          self._payload[self._idx['l10n_type']],
     878                          parts,
     879                          gmTools.coalesce(self._payload[self._idx['comment']], '', ' "%s"'),
     880                          detail
     881                  )
     882
     883          #--------------------------------------------------------
     884 -        def format(self, single_line=False):
     885                  if single_line:
     886                          return self.format_single_line()
     887
     888                  part_count = len(self._payload[self._idx['seq_idx_list']])
     889                  if part_count == 0:
     890                          parts = _('no parts')
     891                  elif part_count == 1:
     892                          parts = _('1 part')
     893                  else:
     894                          parts = _('%s parts') % part_count
     895                  org = ''
     896                  if self._payload[self._idx['unit']] is not None:
     897                          if self._payload[self._idx['unit_is_receiver']]:
     898                                  org = _(' Receiver: %s @ %s\n') % (
     899                                          self._payload[self._idx['unit']],
     900                                          self._payload[self._idx['organization']]
     901                                  )
     902                          else:
     903                                  org = _(' Sender: %s @ %s\n') % (
     904                                          self._payload[self._idx['unit']],
     905                                          self._payload[self._idx['organization']]
     906                                  )
     907                  stay = ''
     908                  if self._payload[self._idx['pk_hospital_stay']] is not None:
     909                          stay = _('Hospital stay') + ': %s\n' % self.hospital_stay.format (
     910                                  left_margin = 0,
     911                                  include_procedures = False,
     912                                  include_docs = False,
     913                                  include_episode = False
     914                          )
     915
     916                  txt = _(
     917                          '%s (%s)   #%s\n'
     918                          ' Created: %s\n'
     919                          ' Episode: %s\n'
     920                          '%s'
     921                          '%s'
     922                          '%s'
     923                          '%s'
     924                          '%s'
     925                  ) % (
     926                          self._payload[self._idx['l10n_type']],
     927                          parts,
     928                          self._payload[self._idx['pk_doc']],
     929                          gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], format = '%Y %b %d', accuracy = gmDateTime.acc_days),
     930                          self._payload[self._idx['episode']],
     931                          gmTools.coalesce(self._payload[self._idx['health_issue']], '', _(' Health issue: %s\n')),
     932                          gmTools.coalesce(self._payload[self._idx['ext_ref']], '', _(' External reference: %s\n')),
     933                          org,
     934                          stay,
     935                          gmTools.coalesce(self._payload[self._idx['comment']], '', ' %s')
     936                  )
     937
     938                  return txt
     939
     940          #--------------------------------------------------------
     941 -        def _get_hospital_stay(self):
     942                  if self._payload[self._idx['pk_hospital_stay']] is None:
     943                          return None
     944                  from Gnumed.business import gmEMRStructItems
     945                  return gmEMRStructItems.cHospitalStay(self._payload[self._idx['pk_hospital_stay']])
     946
     947          hospital_stay = property(_get_hospital_stay, lambda x:x)
     948
     949          #--------------------------------------------------------
     950 -        def _get_org_unit(self):
     951                  if self._payload[self._idx['pk_org_unit']] is None:
     952                          return None
     953                  return gmOrganization.cOrgUnit(self._payload[self._idx['pk_org_unit']])
     954
     955          org_unit = property(_get_org_unit, lambda x:x)
     956
     957          #--------------------------------------------------------
     958 -        def _get_procedures(self):
     959                  from Gnumed.business.gmEMRStructItems import get_procedures4document
     960                  return get_procedures4document(pk_document = self.pk_obj)
     961
     962          procedures = property(_get_procedures, lambda x:x)
     963
     964          #--------------------------------------------------------
     965 -        def _get_bills(self):
     966                  from Gnumed.business.gmBilling import get_bills4document
     967                  return get_bills4document(pk_document = self.pk_obj)
     968
     969          bills = property(_get_bills, lambda x:x)
     970
     971  #------------------------------------------------------------
     972 -def create_document(document_type=None, encounter=None, episode=None, link_obj=None):
     973          """Returns new document instance or raises an exception."""
     974          try:
     975                  int(document_type)
     976                  cmd = """INSERT INTO blobs.doc_med (fk_type, fk_encounter, fk_episode) VALUES (%(type)s, %(enc)s, %(epi)s) RETURNING pk"""
     977          except ValueError:
     978                  create_document_type(document_type = document_type)
     979                  cmd = """
     980                          INSERT INTO blobs.doc_med (
     981                                  fk_type,
     982                                  fk_encounter,
     983                                  fk_episode
     984                          ) VALUES (
     985                                  coalesce (
     986                                          (SELECT pk from blobs.doc_type bdt where bdt.name = %(type)s),
     987                                          (SELECT pk from blobs.doc_type bdt where _(bdt.name) = %(type)s)
     988                                  ),
     989                                  %(enc)s,
     990                                  %(epi)s
     991                          ) RETURNING pk"""
     992          args = {'type': document_type, 'enc': encounter, 'epi': episode}
     993          rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True)
     994          doc = cDocument(aPK_obj = rows[0][0], link_obj = link_obj)
     995          return doc
     996
     997  #------------------------------------------------------------
     998 -def search_for_documents(patient_id=None, type_id=None, external_reference=None, pk_episode=None, pk_types=None):
     999          """Searches for documents with the given patient and type ID."""
    1000
    1001          if (patient_id is None) and (pk_episode is None):
    1002                  raise ValueError('need patient_id or pk_episode to search for document')
    1003
    1004          where_parts = []
    1005          args = {
    1006                  'pat_id': patient_id,
    1007                  'type_id': type_id,
    1008                  'ref': external_reference,
    1009                  'pk_epi': pk_episode
    1010          }
    1011
    1012          if patient_id is not None:
    1013                  where_parts.append('pk_patient = %(pat_id)s')
    1014
    1015          if type_id is not None:
    1016                  where_parts.append('pk_type = %(type_id)s')
    1017
    1018          if external_reference is not None:
    1019                  where_parts.append('ext_ref = %(ref)s')
    1020
    1021          if pk_episode is not None:
    1022                  where_parts.append('pk_episode = %(pk_epi)s')
    1023
    1024          if pk_types is not None:
    1025                  where_parts.append('pk_type IN %(pk_types)s')
    1026                  args['pk_types'] = tuple(pk_types)
    1027
    1028          cmd = _SQL_get_document_fields % ' AND '.join(where_parts)
    1029          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1030          return [ cDocument(row = {'data': r, 'idx': idx, 'pk_field': 'pk_doc'}) for r in rows ]
    1031
    1032  #------------------------------------------------------------
    1033 -def delete_document(document_id=None, encounter_id=None):
    1034          # cascades to doc_obj and doc_desc but not bill.bill
    1035          cmd = "SELECT blobs.delete_document(%(pk)s, %(enc)s)"
    1036          args = {'pk': document_id, 'enc': encounter_id}
    1037          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
    1038          if not rows[0][0]:
    1039                  _log.error('cannot delete document [%s]', document_id)
    1040                  return False
    1041          return True
    1042
    1043  #------------------------------------------------------------
    1044 -def reclassify_documents_by_type(original_type=None, target_type=None):
    1045
    1046          _log.debug('reclassifying documents by type')
    1047          _log.debug('original: %s', original_type)
    1048          _log.debug('target: %s', target_type)
    1049
    1050          if target_type['pk_doc_type'] == original_type['pk_doc_type']:
    1051                  return True
    1052
    1053          cmd = """
    1054  update blobs.doc_med set
    1055          fk_type = %(new_type)s
    1056  where
    1057          fk_type = %(old_type)s
    1058  """
    1059          args = {'new_type': target_type['pk_doc_type'], 'old_type': original_type['pk_doc_type']}
    1060
    1061          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    1062
    1063          return True
    1064
    1065  #============================================================
    1066 -class cDocumentType(gmBusinessDBObject.cBusinessDBObject):
    1067          """Represents a document type."""
    1068          _cmd_fetch_payload = """select * from blobs.v_doc_type where pk_doc_type=%s"""
    1069          _cmds_store_payload = [
    1070                  """update blobs.doc_type set
    1071                                  name = %(type)s
    1072                          where
    1073                                  pk=%(pk_obj)s and
    1074                                  xmin=%(xmin_doc_type)s""",
    1075                  """select xmin_doc_type from blobs.v_doc_type where pk_doc_type = %(pk_obj)s"""
    1076          ]
    1077          _updatable_fields = ['type']
    1078          #--------------------------------------------------------
    1079 -        def set_translation(self, translation=None):
    1080
    1081                  if translation.strip() == '':
    1082                          return False
    1083
    1084                  if translation.strip() == self._payload[self._idx['l10n_type']].strip():
    1085                          return True
    1086
    1087                  rows, idx = gmPG2.run_rw_queries (
    1088                          queries = [
    1089                                  {'cmd': 'select i18n.i18n(%s)', 'args': [self._payload[self._idx['type']]]},
    1090                                  {'cmd': 'select i18n.upd_tx((select i18n.get_curr_lang()), %(orig)s, %(tx)s)',
    1091                                   'args': {
    1092                                          'orig': self._payload[self._idx['type']],
    1093                                          'tx': translation
    1094                                          }
    1095                                  }
    1096                          ],
    1097                          return_data = True
    1098                  )
    1099                  if not rows[0][0]:
    1100                          _log.error('cannot set translation to [%s]' % translation)
    1101                          return False
    1102
    1103                  return self.refetch_payload()
    1104
    1105  #------------------------------------------------------------
    1106 -def get_document_types():
    1107          rows, idx = gmPG2.run_ro_queries (
    1108                  queries = [{'cmd': "SELECT * FROM blobs.v_doc_type"}],
    1109                  get_col_idx = True
    1110          )
    1111          doc_types = []
    1112          for row in rows:
    1113                  row_def = {'pk_field': 'pk_doc_type', 'idx': idx, 'data': row}
    1114                  doc_types.append(cDocumentType(row = row_def))
    1115          return doc_types
    1116
    1117  #------------------------------------------------------------
    1118 -def get_document_type_pk(document_type=None):
    1119          args = {'typ': document_type.strip()}
    1120
    1121          cmd = 'SELECT pk FROM blobs.doc_type WHERE name = %(typ)s'
    1122          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1123          if len(rows) == 0:
    1124                  cmd = 'SELECT pk FROM blobs.doc_type WHERE _(name) = %(typ)s'
    1125                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1126
    1127          if len(rows) == 0:
    1128                  return None
    1129
    1130          return rows[0]['pk']
    1131
    1132  #------------------------------------------------------------
    1133 -def map_types2pk(document_types=None):
    1134          args = {'types': tuple(document_types)}
    1135          cmd = 'SELECT pk_doc_type, coalesce(l10n_type, type) as desc FROM blobs.v_doc_type WHERE l10n_type IN %(types)s OR type IN %(types)s'
    1136          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1137          return rows
    1138
    1139  #------------------------------------------------------------
    1140 -def create_document_type(document_type=None):
    1141          # check for potential dupes:
    1142          cmd = 'SELECT pk FROM blobs.doc_type WHERE name = %s'
    1143          rows, idx = gmPG2.run_ro_queries (
    1144                  queries = [{'cmd': cmd, 'args': [document_type]}]
    1145          )
    1146          if len(rows) == 0:
    1147                  _log.debug('creating document type [%s]', document_type)
    1148                  cmd1 = "INSERT INTO blobs.doc_type (name) VALUES (%s) RETURNING pk"
    1149                  rows, idx = gmPG2.run_rw_queries (
    1150                          queries = [{'cmd': cmd1, 'args': [document_type]}],
    1151                          return_data = True
    1152                  )
    1153          return cDocumentType(aPK_obj = rows[0][0])
    1154
    1155  #------------------------------------------------------------
    1156 -def delete_document_type(document_type=None):
    1157          if document_type['is_in_use']:
    1158                  return False
    1159          gmPG2.run_rw_queries (
    1160                  queries = [{
    1161                          'cmd': 'delete from blobs.doc_type where pk=%s',
    1162                          'args': [document_type['pk_doc_type']]
    1163                  }]
    1164          )
    1165          return True
    1166
    1167  #------------------------------------------------------------
    1168 -def get_ext_ref():
    1169          """This needs *considerably* more smarts."""
    1170          dirname = gmTools.get_unique_filename (
    1171                  prefix = '',
    1172                  suffix = time.strftime(".%Y%m%d-%H%M%S", time.localtime())
    1173          )
    1174          # extract name for dir
    1175          path, doc_ID = os.path.split(dirname)
    1176          return doc_ID
    1177
    1178  #============================================================
    1179  # main
    1180  #------------------------------------------------------------
    1181  if __name__ == '__main__':
    1182
    1183          if len(sys.argv) < 2:
    1184                  sys.exit()
    1185
    1186          if sys.argv[1] != 'test':
    1187                  sys.exit()
    1188
    1189          #--------------------------------------------------------
    1190 -        def test_doc_types():
    1191
    1192                  print("----------------------")
    1193                  print("listing document types")
    1194                  print("----------------------")
    1195
    1196                  for dt in get_document_types():
    1197                          print(dt)
    1198
    1199                  print("------------------------------")
    1200                  print("testing document type handling")
    1201                  print("------------------------------")
    1202
    1203                  dt = create_document_type(document_type = 'dummy doc type for unit test 1')
    1204                  print("created:", dt)
    1205
    1206                  dt['type'] = 'dummy doc type for unit test 2'
    1207                  dt.save_payload()
    1208                  print("changed base name:", dt)
    1209
    1210                  dt.set_translation(translation = 'Dummy-Dokumenten-Typ fuer Unit-Test')
    1211                  print("translated:", dt)
    1212
    1213                  print("deleted:", delete_document_type(document_type = dt))
    1214
    1215                  return
    1216          #--------------------------------------------------------
    1217 -        def test_adding_doc_part():
    1218
    1219                  print("-----------------------")
    1220                  print("testing document import")
    1221                  print("-----------------------")
    1222
    1223                  docs = search_for_documents(patient_id=12)
    1224                  doc = docs[0]
    1225                  print("adding to doc:", doc)
    1226
    1227                  fname = sys.argv[1]
    1228                  print("adding from file:", fname)
    1229                  part = doc.add_part(file=fname)
    1230                  print("new part:", part)
    1231
    1232                  return
    1233          #--------------------------------------------------------
    1234 -        def test_get_documents():
    1235
    1236                  doc_folder = cDocumentFolder(aPKey=12)
    1237
    1238                  #photo = doc_folder.get_latest_mugshot()
    1239                  #print type(photo), photo
    1240
    1241                  docs = doc_folder.get_documents()
    1242                  for doc in docs:
    1243                          #print type(doc), doc
    1244                          #print doc.parts
    1245                          #print doc.format_single_line()
    1246                          print('--------------------------')
    1247                          print(doc.format(single_line = True))
    1248                          print(doc.format())
    1249
    1250          #--------------------------------------------------------
    1251 -        def test_get_useful_filename():
    1252                  pk = 12
    1253                  from Gnumed.business.gmPerson import cPatient
    1254                  pat = cPatient(pk)
    1255                  doc_folder = cDocumentFolder(aPKey = pk)
    1256                  for doc in doc_folder.documents:
    1257                          for part in doc.parts:
    1258                                  print(part.get_useful_filename (
    1259                                          patient = pat,
    1260                                          make_unique = True,
    1261                                          directory = None,
    1262                                          include_gnumed_tag = False,
    1263                                          date_before_type = True,
    1264                                          name_first = False
    1265                                  ))
    1266
    1267          #--------------------------------------------------------
    1268 -        def test_part_metainfo_formatter():
    1269
    1270                  #--------------------------------
    1271                  def desc_printer(worker_result):
    1272                          status, desc = worker_result
    1273                          print('printer callback:')
    1274                          print(status)
    1275                          print(desc)
    1276                          print('<hit key> for next')
    1277                          return
    1278                  #--------------------------------
    1279
    1280                  pk = 12
    1281                  from Gnumed.business.gmPerson import cPatient
    1282                  pat = cPatient(pk)
    1283                  doc_folder = cDocumentFolder(aPKey = pk)
    1284                  for doc in doc_folder.documents:
    1285                          for part in doc.parts:
    1286                                  part.format_metainfo(callback = desc_printer)
    1287                                  input('waiting ...')
    1288  #                               success, desc = part.format_metainfo()
    1289  #                               print(success)
    1290  #                               print(desc)
    1291  #                               input('next')
    1292
    1293  #                               print(part.get_useful_filename (
    1294  #                                       patient = pat,
    1295  #                                       make_unique = True,
    1296  #                                       directory = None,
    1297  #                                       include_gnumed_tag = False,
    1298  #                                       date_before_type = True,
    1299  #                                       name_first = False
    1300  #                               ))
    1301
    1302          #--------------------------------------------------------
    1303          from Gnumed.pycommon import gmI18N
    1304          gmI18N.activate_locale()
    1305          gmI18N.install_domain()
    1306
    1307          #test_doc_types()
    1308          #test_adding_doc_part()
    1309          #test_get_documents()
    1310          #test_get_useful_filename()
    1311          test_part_metainfo_formatter()
    1312
    1313  #       print get_ext_ref()
    1314