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

Source Code for Module Gnumed.business.gmExportArea

   1  """GNUmed export area 
   2   
   3  Think shopping cart in a web shop. 
   4   
   5  This is where you want to put documents for further 
   6  processing by you or someone else, like your secretary. 
   7  """ 
   8  #============================================================ 
   9  __license__ = "GPL v2 or later" 
  10  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  11   
  12   
  13  import sys 
  14  import logging 
  15  import shutil 
  16  import os 
  17  import io 
  18  import platform 
  19   
  20   
  21  if __name__ == '__main__': 
  22          sys.path.insert(0, '../../') 
  23          from Gnumed.pycommon import gmI18N 
  24          gmI18N.activate_locale() 
  25          gmI18N.install_domain() 
  26  from Gnumed.pycommon import gmTools 
  27  from Gnumed.pycommon import gmBusinessDBObject 
  28  from Gnumed.pycommon import gmPG2 
  29  from Gnumed.pycommon import gmMimeLib 
  30  from Gnumed.pycommon import gmDateTime 
  31  from Gnumed.pycommon import gmCfg2 
  32  from Gnumed.pycommon import gmCrypto 
  33   
  34  from Gnumed.business import gmDocuments 
  35  from Gnumed.business import gmKeywordExpansion 
  36   
  37   
  38  _log = logging.getLogger('gm.exp_area') 
  39  _cfg = gmCfg2.gmCfgData() 
  40   
  41  PRINT_JOB_DESIGNATION = 'print' 
  42  DOCUMENTS_SUBDIR = 'documents' 
  43  DIRENTRY_README_NAME = '.README.GNUmed-DIRENTRY' 
  44   
  45  #============================================================ 
  46  # export area item handling 
  47  #------------------------------------------------------------ 
  48  _SQL_get_export_items = "SELECT * FROM clin.v_export_items WHERE %s" 
  49   
50 -class cExportItem(gmBusinessDBObject.cBusinessDBObject):
51 """Represents an item in the export area table""" 52 53 _cmd_fetch_payload = _SQL_get_export_items % "pk_export_item = %s" 54 _cmds_store_payload = [ 55 """UPDATE clin.export_item SET 56 fk_identity = %(pk_identity)s, 57 created_by = gm.nullify_empty_string(%(created_by)s), 58 created_when = %(created_when)s, 59 designation = gm.nullify_empty_string(%(designation)s), 60 description = gm.nullify_empty_string(%(description)s), 61 fk_doc_obj = %(pk_doc_obj)s, 62 data = CASE 63 WHEN %(pk_doc_obj)s IS NULL THEN coalesce(data, 'to be replaced by real data') 64 ELSE NULL 65 END, 66 filename = CASE 67 WHEN %(pk_doc_obj)s IS NULL THEN gm.nullify_empty_string(%(filename)s) 68 ELSE NULL 69 END 70 WHERE 71 pk = %(pk_export_item)s 72 AND 73 xmin = %(xmin_export_item)s 74 """, 75 _SQL_get_export_items % 'pk_export_item = %(pk_export_item)s' 76 ] 77 _updatable_fields = [ 78 'pk_identity', 79 'created_when', 80 'designation', 81 'description', 82 'pk_doc_obj', 83 'filename' 84 ] 85 #--------------------------------------------------------
86 - def __init__(self, aPK_obj=None, row=None, link_obj=None):
87 super(cExportItem, self).__init__(aPK_obj = aPK_obj, row = row, link_obj = link_obj) 88 # force auto-healing if need be 89 if self._payload[self._idx['pk_identity_raw_needs_update']]: 90 _log.warning ( 91 'auto-healing export item [%s] from identity [%s] to [%s] because of document part [%s] seems necessary', 92 self._payload[self._idx['pk_export_item']], 93 self._payload[self._idx['pk_identity_raw']], 94 self._payload[self._idx['pk_identity']], 95 self._payload[self._idx['pk_doc_obj']] 96 ) 97 if self._payload[self._idx['pk_doc_obj']] is None: 98 _log.error('however, .fk_doc_obj is NULL, which should not happen, leaving things alone for manual inspection') 99 return 100 # only flag ourselves as modified, do not actually 101 # modify any values, better safe than sorry 102 self._is_modified = True 103 self.save() 104 self.refetch_payload(ignore_changes = False, link_obj = link_obj)
105 106 #-------------------------------------------------------- 107 # def format(self): 108 # return u'%s' % self 109 #--------------------------------------------------------
110 - def update_data(self, data=None):
111 assert (data is not None), '<data> must not be <None>' 112 113 SQL = """ 114 UPDATE clin.export_item SET 115 data = %(data)s::bytea, 116 fk_doc_obj = NULL 117 WHERE pk = %(pk)s""" 118 args = {'pk': self.pk_obj, 'data': data} 119 gmPG2.run_rw_queries(queries = [{'cmd': SQL, 'args': args}], return_data = False, get_col_idx = False) 120 # must update XMIN now ... 121 self.refetch_payload() 122 return True
123 124 #--------------------------------------------------------
125 - def update_data_from_file(self, filename=None):
126 # sanity check 127 if not (os.access(filename, os.R_OK) and os.path.isfile(filename)): 128 _log.error('[%s] is not a readable file' % filename) 129 return False 130 131 cmd = """ 132 UPDATE clin.export_item SET 133 data = %(data)s::bytea, 134 fk_doc_obj = NULL, 135 filename = gm.nullify_empty_string(%(fname)s) 136 WHERE pk = %(pk)s""" 137 args = {'pk': self.pk_obj, 'fname': filename} 138 if not gmPG2.file2bytea(query = cmd, filename = filename, args = args): 139 return False 140 141 # must update XMIN now ... 142 self.refetch_payload() 143 return True
144 145 #--------------------------------------------------------
146 - def save_to_file(self, aChunkSize=0, filename=None, directory=None, passphrase=None, convert2pdf=False):
147 # data linked from archive ? 148 part_fname = self.__save_doc_obj(filename = filename, directory = directory) 149 if part_fname is False: 150 return None 151 152 if part_fname is not None: 153 if passphrase is None: 154 return part_fname 155 156 enc_filename = gmCrypto.encrypt_file ( 157 filename = part_fname, 158 passphrase = passphrase, 159 verbose = _cfg.get(option = 'debug'), 160 remove_unencrypted = True, 161 convert2pdf = convert2pdf 162 ) 163 removed = gmTools.remove_file(part_fname) 164 if enc_filename is None: 165 _log.error('cannot encrypt') 166 return None 167 168 if removed: 169 return enc_filename 170 171 _log.error('cannot remove unencrypted file') 172 gmTools.remove(enc_filename) 173 return None 174 175 # valid DIRENTRY ? 176 if self.is_valid_DIRENTRY: 177 target_dir = self.__save_direntry(directory, passphrase = passphrase) 178 if target_dir is False: 179 return None 180 181 if target_dir is not None: 182 return target_dir 183 184 # but still a DIRENTRY ? 185 if self.is_DIRENTRY: 186 # yes, but not valid (other machine, local dir not found) 187 return None 188 189 # normal item with data in export area table 190 if filename is None: 191 filename = self.get_useful_filename(directory = directory) 192 success = gmPG2.bytea2file ( 193 data_query = { 194 'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM clin.export_item WHERE pk = %(pk)s', 195 'args': {'pk': self.pk_obj} 196 }, 197 filename = filename, 198 chunk_size = aChunkSize, 199 data_size = self._payload[self._idx['size']] 200 ) 201 if not success: 202 return None 203 204 if filename.endswith('.dat'): 205 filename = gmMimeLib.adjust_extension_by_mimetype(filename) 206 if passphrase is None: 207 return filename 208 209 enc_filename = gmCrypto.encrypt_file ( 210 filename = filename, 211 passphrase = passphrase, 212 verbose = _cfg.get(option = 'debug'), 213 remove_unencrypted = True, 214 convert2pdf = convert2pdf 215 ) 216 removed = gmTools.remove_file(filename) 217 if enc_filename is None: 218 _log.error('cannot encrypt') 219 return None 220 221 if removed: 222 return enc_filename 223 224 _log.error('cannot remove unencrypted file') 225 gmTools.remove(enc_filename) 226 return None
227 228 #--------------------------------------------------------
229 - def display_via_mime(self, chunksize=0, block=None):
230 231 # document part 232 if self._payload[self._idx['pk_doc_obj']] is not None: 233 return self.document_part.display_via_mime(chunksize = chunksize, block = block) 234 235 # DIR entry 236 if self._payload[self._idx['filename']].startswith('DIR::'): 237 # FIXME: error handling with malformed entries 238 tag, node, path = self._payload[self._idx['filename']].split('::', 2) 239 if node != platform.node(): 240 msg = _( 241 'This item points to a directory on the computer named:\n' 242 ' %s\n' 243 'You are, however, currently using another computer:\n' 244 ' %s\n' 245 'Directory items can only be viewed/saved/exported\n' 246 'on the computer they are pointing to.' 247 ) % (node, platform.node()) 248 return False, msg 249 success, msg = gmMimeLib.call_viewer_on_file(path, block = block) 250 return success, msg 251 252 # data -> save 253 fname = self.save_to_file(aChunkSize = chunksize) 254 if fname is None: 255 return False, '' 256 257 success, msg = gmMimeLib.call_viewer_on_file(fname, block = block) 258 if not success: 259 return False, msg 260 261 return True, ''
262 263 #--------------------------------------------------------
264 - def get_useful_filename(self, patient=None, directory=None):
265 patient_part = '' 266 if patient is not None: 267 patient_part = '-%s' % patient.subdir_name 268 269 # preserve original filename extension if available 270 suffix = '.dat' 271 if self._payload[self._idx['filename']] is not None: 272 tmp, suffix = os.path.splitext ( 273 gmTools.fname_sanitize(self._payload[self._idx['filename']]).lower() 274 ) 275 if suffix == '': 276 suffix = '.dat' 277 fname = gmTools.get_unique_filename ( 278 prefix = 'gm-export_item%s-' % patient_part, 279 suffix = suffix, 280 tmp_dir = directory 281 ) 282 return fname
283 284 #-------------------------------------------------------- 285 # helpers 286 #--------------------------------------------------------
287 - def __save_doc_obj(self, filename=None, directory=None, passphrase=None):
288 """Save doc object part into target. 289 290 None: not a doc obj 291 True: success 292 False: failure 293 """ 294 if self._payload[self._idx['pk_doc_obj']] is None: 295 return None 296 297 part = self.document_part 298 if filename is None: 299 filename = part.get_useful_filename ( 300 make_unique = False, 301 directory = directory, 302 include_gnumed_tag = False, 303 date_before_type = True, 304 name_first = False 305 ) 306 part_fname = part.save_to_file ( 307 filename = filename, 308 ignore_conversion_problems = True, 309 adjust_extension = True 310 ) 311 if part_fname is None: 312 _log.error('cannot save document part to file') 313 return False 314 315 if passphrase is None: 316 return part_fname 317 318 enc_filename = gmCrypto.encrypt_file ( 319 filename = part_fname, 320 passphrase = passphrase, 321 verbose = _cfg.get(option = 'debug'), 322 remove_unencrypted = True 323 ) 324 removed = gmTools.remove_file(filename) 325 if enc_filename is None: 326 _log.error('cannot encrypt') 327 return False 328 329 if removed: 330 return enc_filename 331 332 _log.error('cannot remove unencrypted file') 333 gmTools.remove(enc_filename) 334 return False
335 336 #--------------------------------------------------------
337 - def __save_direntry(self, directory=None, passphrase=None):
338 """Move DIRENTRY source into target. 339 340 None: not a DIRENTRY 341 True: success 342 False: failure 343 """ 344 # do not process malformed entries 345 try: 346 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 347 except ValueError: 348 _log.exception('malformed DIRENTRY: [%s]', self._payload[self._idx['filename']]) 349 return False 350 # source and target paths must not overlap 351 if directory is None: 352 directory = gmTools.mk_sandbox_dir(prefix = 'exp-') 353 if directory.startswith(local_fs_path): 354 _log.error('cannot dump DIRENTRY item [%s]: must not be subdirectory of target dir [%s]', self._payload[self._idx['filename']], directory) 355 return False 356 if local_fs_path.startswith(directory): 357 _log.error('cannot dump DIRENTRY item [%s]: target dir [%s] must not be subdirectory of DIRENTRY', self._payload[self._idx['filename']], directory) 358 return False 359 360 _log.debug('dumping DIRENTRY item [%s] into [%s]', self._payload[self._idx['filename']], directory) 361 sandbox_dir = gmTools.mk_sandbox_dir() 362 _log.debug('sandbox: %s', sandbox_dir) 363 tmp = gmTools.copy_tree_content(local_fs_path, sandbox_dir) 364 if tmp is None: 365 _log.error('cannot dump DIRENTRY item [%s] into [%s]: copy error', self._payload[self._idx['filename']], sandbox_dir) 366 return False 367 368 gmTools.remove_file(os.path.join(tmp, DIRENTRY_README_NAME)) 369 370 if passphrase is not None: 371 _log.debug('encrypting sandbox: %s', sandbox_dir) 372 encrypted = gmCrypto.encrypt_directory_content ( 373 directory = sandbox_dir, 374 passphrase = passphrase, 375 verbose = _cfg.get(option = 'debug'), 376 remove_unencrypted = True 377 ) 378 if not encrypted: 379 _log.error('cannot dump DIRENTRY item [%s]: encryption problem in [%s]', self._payload[self._idx['filename']], sandbox_dir) 380 return False 381 382 tmp = gmTools.copy_tree_content(sandbox_dir, directory) 383 if tmp is None: 384 _log.debug('cannot dump DIRENTRY item [%s] into [%s]: copy error', self._payload[self._idx['filename']], directory) 385 return False 386 387 return directory
388 389 #-------------------------------------------------------- 390 # properties 391 #--------------------------------------------------------
392 - def _get_doc_part(self):
393 if self._payload[self._idx['pk_doc_obj']] is None: 394 return None 395 return gmDocuments.cDocumentPart(aPK_obj = self._payload[self._idx['pk_doc_obj']])
396 397 document_part = property(_get_doc_part, lambda x:x) 398 399 #--------------------------------------------------------
400 - def _get_is_print_job(self):
401 return self._payload[self._idx['designation']] == PRINT_JOB_DESIGNATION
402
403 - def _set_is_print_job(self, is_print_job):
404 desig = gmTools.bool2subst(is_print_job, PRINT_JOB_DESIGNATION, None, None) 405 if self._payload[self._idx['designation']] == desig: 406 return 407 self['designation'] = desig 408 self.save()
409 410 is_print_job = property(_get_is_print_job, _set_is_print_job) 411 412 #--------------------------------------------------------
413 - def _is_DIRENTRY(self):
414 """Check whether this item looks like a DIRENTRY.""" 415 if self._payload[self._idx['filename']] is None: 416 return False 417 if not self._payload[self._idx['filename']].startswith('DIR::'): 418 return False 419 if len(self._payload[self._idx['filename']].split('::', 2)) != 3: 420 return False 421 return True
422 423 is_DIRENTRY = property(_is_DIRENTRY) 424 425 #--------------------------------------------------------
426 - def _is_valid_DIRENTRY(self):
427 """Check whether this item is a _valid_ DIRENTRY.""" 428 if not self.is_DIRENTRY: 429 return False 430 # malformed ? 431 try: 432 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 433 except ValueError: 434 # should not happen because structure already checked in .is_DIRENTRY, 435 # better safe than sorry 436 _log.exception('DIRENTRY [%s]: malformed', self._payload[self._idx['filename']]) 437 return False 438 # this machine ? 439 if node != platform.node(): 440 _log.warning('DIRENTRY [%s]: not on this machine (%s)', self._payload[self._idx['filename']], platform.node()) 441 return False 442 # valid path ? 443 if not os.path.isdir(local_fs_path): 444 _log.warning('DIRENTRY [%s]: directory not found (old DIRENTRY ?)', self._payload[self._idx['filename']]) 445 return False 446 return True
447 448 is_valid_DIRENTRY = property(_is_valid_DIRENTRY) 449 450 #--------------------------------------------------------
451 - def _is_DICOM_directory(self):
452 """Check whether this item points to a DICOMDIR.""" 453 if not self.is_valid_DIRENTRY: 454 return False 455 456 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 457 found_DICOMDIR = False 458 for fs_entry in os.listdir(local_fs_path): 459 # found a subdir 460 if os.path.isdir(os.path.join(local_fs_path, fs_entry)): 461 # allow for any number of subdirs 462 continue 463 # found a file 464 if fs_entry != 'DICOMDIR': 465 # not named "DICOMDIR" -> not a DICOMDIR DIRENTRY 466 return False 467 468 # must be named DICOMDIR -> that's the only file allowed (and required) in ./ 469 found_DICOMDIR = True 470 return found_DICOMDIR
471 472 is_DICOM_directory = property(_is_DICOM_directory) 473 474 #--------------------------------------------------------
475 - def _has_files_in_root(self):
476 """True if there are files in the root directory.""" 477 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 478 for fs_entry in os.listdir(local_fs_path): 479 if os.path.isfile(fs_entry): 480 _log.debug('has files in top level: %s', local_fs_path) 481 return True 482 return False
483 484 has_files_in_root = property(_has_files_in_root)
485 486 #------------------------------------------------------------
487 -def get_export_items(order_by=None, pk_identity=None, designation=None, return_pks=False):
488 489 args = { 490 'pat': pk_identity, 491 'desig': gmTools.coalesce(designation, PRINT_JOB_DESIGNATION) 492 } 493 where_parts = [] 494 if pk_identity is not None: 495 where_parts.append('pk_identity = %(pat)s') 496 # note that invalidly linked items will be 497 # auto-healed when instantiated 498 if designation is None: 499 where_parts.append("designation IS DISTINCT FROM %(desig)s") 500 else: 501 where_parts.append('designation = %(desig)s') 502 503 if order_by is None: 504 order_by = '' 505 else: 506 order_by = ' ORDER BY %s' % order_by 507 508 cmd = (_SQL_get_export_items % ' AND '.join(where_parts)) + order_by 509 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 510 if return_pks: 511 return [ r['pk_export_item'] for r in rows ] 512 return [ cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'}) for r in rows ]
513 514 #------------------------------------------------------------
515 -def get_print_jobs(order_by=None, pk_identity=None):
516 return get_export_items(order_by = order_by, pk_identity = pk_identity, designation = PRINT_JOB_DESIGNATION)
517 518 #------------------------------------------------------------
519 -def create_export_item(description=None, pk_identity=None, pk_doc_obj=None, filename=None):
520 521 args = { 522 'desc': description, 523 'pk_obj': pk_doc_obj, 524 'pk_pat': pk_identity, 525 'fname': filename 526 } 527 cmd = """ 528 INSERT INTO clin.export_item ( 529 description, 530 fk_doc_obj, 531 fk_identity, 532 data, 533 filename 534 ) VALUES ( 535 gm.nullify_empty_string(%(desc)s), 536 %(pk_obj)s, 537 %(pk_pat)s, 538 (CASE 539 WHEN %(pk_obj)s IS NULL THEN %(fname)s::bytea 540 ELSE NULL::bytea 541 END), 542 (CASE 543 WHEN %(pk_obj)s IS NULL THEN gm.nullify_empty_string(%(fname)s) 544 ELSE NULL 545 END) 546 ) 547 RETURNING pk 548 """ 549 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 550 551 return cExportItem(aPK_obj = rows[0]['pk'])
552 553 #------------------------------------------------------------
554 -def delete_export_item(pk_export_item=None):
555 args = {'pk': pk_export_item} 556 cmd = "DELETE FROM clin.export_item WHERE pk = %(pk)s" 557 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 558 return True
559 560 #============================================================ 561 _FRONTPAGE_HTML_CONTENT = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 562 "http://www.w3.org/TR/html4/loose.dtd"> 563 <html> 564 <head> 565 <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 566 <link rel="icon" type="image/x-icon" href="gnumed.ico"> 567 <title>%(html_title_header)s %(html_title_patient)s</title> 568 </head> 569 <body> 570 571 <h1>%(title)s</h1> 572 573 <p> 574 (%(date)s)<br> 575 </p> 576 577 <h2><a href="patient.vcf">Patient</a></h2> 578 579 <p> 580 %(pat_name)s<br> 581 %(pat_dob)s 582 </p> 583 584 <p><img src="%(mugshot_url)s" alt="%(mugshot_alt)s" title="%(mugshot_title)s" width="200" border="2"></p> 585 586 <h2>%(docs_title)s</h2> 587 588 <ul> 589 <li><a href="./">%(browse_root)s</a></li> 590 <li><a href="%(doc_subdir)s/">%(browse_docs)s</a></li> 591 %(browse_dicomdir)s 592 %(run_dicom_viewer)s 593 </ul> 594 595 <ul> 596 %(docs_list)s 597 </ul> 598 599 <h2><a href="praxis.vcf">Praxis</a></h2> 600 601 <p> 602 %(branch)s @ %(praxis)s 603 %(adr)s 604 </p> 605 606 <p>(<a href="http://www.gnumed.de">GNUmed</a> version %(gm_ver)s)</p> 607 608 </body> 609 </html> 610 """ 611 612 _INDEX_HTML_CONTENT = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 613 "http://www.w3.org/TR/html4/loose.dtd"> 614 <html> 615 <head> 616 <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 617 <link rel="icon" type="image/x-icon" href="gnumed.ico"> 618 <title>%(html_title_header)s</title> 619 </head> 620 <body> 621 622 <h1>%(title)s</h1> 623 624 <p> 625 (%(date)s)<br> 626 </p> 627 628 This is an encrypted patient data excerpt created by the GNUmed Electronic Medical Record. 629 630 <p> 631 For decryption you will need to 632 633 <ul> 634 <li>install decryption software and</li> 635 <li>obtain relevant passwords from the creator or holder of this media</li> 636 </ul> 637 638 <h2>Decryption software</h2> 639 640 For files ending in 641 642 <ul> 643 <li>.asc: install <a href="https://gnupg.org">GNU Privacy Guard</a></li> 644 <li>.7z: install <a href="https://www.7-zip.org">7-zip</a> or <a href="https://www.winzip.com">WinZip</a></li> 645 </ul> 646 647 648 <h2>%(docs_title)s</h2> 649 650 <ul> 651 <li><a href="./frontpage.html">front page (after decryption)</a></li> 652 <li><a href="./%(frontpage_fname)s">front page (if decryption from browser is supported)</a></li> 653 654 <li><a href="./">%(browse_root)s</a></li> 655 <li><a href="%(doc_subdir)s/">%(browse_docs)s</a></li> 656 </ul> 657 658 659 <h2><a href="praxis.vcf">Praxis</a></h2> 660 661 <p> 662 %(branch)s @ %(praxis)s 663 %(adr)s 664 </p> 665 666 <p>(<a href="http://www.gnumed.de">GNUmed</a> version %(gm_ver)s)</p> 667 668 </body> 669 </html> 670 """ 671 672 #------------------------------------------------------------
673 -class cExportArea(object):
674
675 - def __init__(self, pk_identity):
676 self.__pk_identity = pk_identity
677 678 #--------------------------------------------------------
679 - def add_form(self, form=None, designation=None):
680 681 if len(form.final_output_filenames) == 0: 682 return True 683 684 items = [] 685 for fname in form.final_output_filenames: 686 item = self.add_file(filename = fname) 687 if item is None: 688 for prev_item in items: 689 delete_export_item(pk_export_item = prev_item['pk_export_item']) 690 return False 691 items.append(item) 692 item['description'] = _('form: %s %s (%s)') % (form.template['name_long'], form.template['external_version'], fname) 693 item['designation'] = designation 694 item.save() 695 696 return True
697 698 #--------------------------------------------------------
699 - def add_forms(self, forms=None, designation=None):
700 all_ok = True 701 for form in forms: 702 all_ok = all_ok and self.add_form(form = form, designation = designation) 703 704 return all_ok
705 706 #--------------------------------------------------------
707 - def add_path(self, path, comment=None):
708 """Add a DIR entry to the export area. 709 710 This sort of entry points to a certain directory on a 711 certain machine. The content of the the directory 712 will be included in exports, the directory *itself* 713 will not. For *that*, use a disposable top-level 714 directory into which you put the directory to include 715 as a subdirectory. 716 """ 717 assert (os.path.isdir(path)), '<path> must exist: %s' % path 718 719 path_item_data = 'DIR::%s::%s/' % (platform.node(), path.rstrip('/')) 720 _log.debug('attempting to add path item [%s]', path_item_data) 721 item = self.path_item_exists(path_item_data) 722 if item is not None: 723 _log.debug('[%s] already in export area', path) 724 return item 725 726 if comment is None: 727 comment = _('path [%s/] on computer "%s"') % ( 728 path.rstrip('/'), 729 platform.node() 730 ) 731 else: 732 comment += _(' (on "%s")') % platform.node() 733 734 item = create_export_item ( 735 description = comment, 736 pk_identity = self.__pk_identity, 737 filename = path_item_data 738 ) 739 try: 740 README = open(os.path.join(path, DIRENTRY_README_NAME), mode = 'wt', encoding = 'utf8') 741 README.write('GNUmed DIRENTRY information\n') 742 README.write('created: %s\n' % gmDateTime.pydt_now_here()) 743 README.write('machine: %s\n' % platform.node()) 744 README.write('path: %s\n' % path) 745 README.close() 746 except OSError: 747 _log.exception('READONLY DIRENTRY [%s]', path) 748 749 return item
750 751 #--------------------------------------------------------
752 - def path_item_exists(self, path_item_data):
753 754 assert (path_item_data.startswith('DIR::')), 'invalid <path_item_data> [%s]' % path_item_data 755 756 where_parts = [ 757 'pk_identity = %(pat)s', 758 'filename = %(fname)s', 759 'pk_doc_obj IS NULL' 760 ] 761 args = { 762 'pat': self.__pk_identity, 763 'fname': path_item_data 764 } 765 SQL = _SQL_get_export_items % ' AND '.join(where_parts) 766 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': SQL, 'args': args}], get_col_idx = True) 767 if len(rows) == 0: 768 return None 769 770 r = rows[0] 771 return cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'})
772 773 #--------------------------------------------------------
774 - def add_file(self, filename=None, hint=None):
775 try: 776 open(filename).close() 777 except Exception: 778 _log.exception('cannot open file <%s>', filename) 779 return None 780 781 file_md5 = gmTools.file2md5(filename = filename, return_hex = True) 782 existing_item = self.md5_exists(md5 = file_md5, include_document_parts = False) 783 if existing_item is not None: 784 _log.debug('md5 match (%s): %s already in export area', file_md5, filename) 785 return existing_item 786 787 path, basename = os.path.split(filename) 788 item = create_export_item ( 789 description = '%s: %s (%s/)' % ( 790 gmTools.coalesce(hint, _('file'), '%s'), 791 basename, 792 path 793 ), 794 pk_identity = self.__pk_identity, 795 filename = filename 796 ) 797 798 if item.update_data_from_file(filename = filename): 799 return item 800 801 # failed to insert data, hence remove export item entry 802 delete_export_item(pk_export_item = item['pk_export_item']) 803 return None
804 805 #--------------------------------------------------------
806 - def add_files(self, filenames=None, hint=None):
807 all_ok = True 808 for fname in filenames: 809 all_ok = all_ok and (self.add_file(filename = fname, hint = hint) is not None) 810 811 return all_ok
812 813 #--------------------------------------------------------
814 - def add_documents(self, documents=None):
815 for doc in documents: 816 doc_tag = _('%s (%s)%s') % ( 817 doc['l10n_type'], 818 gmDateTime.pydt_strftime(doc['clin_when'], '%Y %b %d'), 819 gmTools.coalesce(doc['comment'], '', ' "%s"') 820 ) 821 for obj in doc.parts: 822 if self.document_part_item_exists(pk_part = obj['pk_obj']): 823 continue 824 f_ext = '' 825 if obj['filename'] is not None: 826 f_ext = os.path.splitext(obj['filename'])[1].strip('.').strip() 827 if f_ext != '': 828 f_ext = ' .' + f_ext.upper() 829 obj_tag = _('part %s (%s%s)%s') % ( 830 obj['seq_idx'], 831 gmTools.size2str(obj['size']), 832 f_ext, 833 gmTools.coalesce(obj['obj_comment'], '', ' "%s"') 834 ) 835 create_export_item ( 836 description = '%s - %s' % (doc_tag, obj_tag), 837 pk_doc_obj = obj['pk_obj'] 838 )
839 840 #--------------------------------------------------------
841 - def document_part_item_exists(self, pk_part=None):
842 cmd = "SELECT EXISTS (SELECT 1 FROM clin.export_item WHERE fk_doc_obj = %(pk_obj)s)" 843 args = {'pk_obj': pk_part} 844 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 845 return rows[0][0]
846 847 #--------------------------------------------------------
848 - def md5_exists(self, md5=None, include_document_parts=False):
849 where_parts = [ 850 'pk_identity = %(pat)s', 851 'md5_sum = %(md5)s' 852 ] 853 args = { 854 'pat': self.__pk_identity, 855 'md5': md5 856 } 857 858 if not include_document_parts: 859 where_parts.append('pk_doc_obj IS NULL') 860 861 cmd = _SQL_get_export_items % ' AND '.join(where_parts) 862 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 863 864 if len(rows) == 0: 865 return None 866 867 r = rows[0] 868 return cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'})
869 870 #--------------------------------------------------------
871 - def remove_item(self, item):
872 if item.is_valid_DIRENTRY: 873 tag, node, local_fs_path = item['filename'].split('::', 2) 874 gmTools.remove_file(os.path.join(local_fs_path, DIRENTRY_README_NAME)) 875 elif item.is_DIRENTRY: 876 return False 877 878 return delete_export_item(pk_export_item = item['pk_export_item'])
879 880 #--------------------------------------------------------
881 - def dump_items_to_disk(self, base_dir=None, items=None, passphrase=None, convert2pdf=False):
882 if items is None: 883 items = self.items 884 if len(items) == 0: 885 return None 886 887 if base_dir is None: 888 from Gnumed.business.gmPerson import cPatient 889 pat = cPatient(aPK_obj = self.__pk_identity) 890 base_dir = os.path.join(gmTools.mk_sandbox_dir(), pat.subdir_name) 891 gmTools.mkdir(base_dir) 892 _log.debug('dumping export items to: %s', base_dir) 893 for item in items: 894 if item.save_to_file(directory = base_dir, passphrase = passphrase, convert2pdf = convert2pdf) is None: 895 return None 896 897 return base_dir
898 899 #--------------------------------------------------------
900 - def dump_items_to_disk_as_zip(self, base_dir=None, items=None, passphrase=None):
901 _log.debug('target dir: %s', base_dir) 902 dump_dir = self.dump_items_to_disk(base_dir = base_dir, items = items) 903 if dump_dir is None: 904 _log.error('cannot dump export area items') 905 return None 906 zip_file = gmTools.create_zip_archive_from_dir ( 907 dump_dir, 908 comment = _('GNUmed Patient Media'), 909 overwrite = True, 910 passphrase = passphrase, 911 verbose = _cfg.get(option = 'debug') 912 ) 913 if zip_file is None: 914 _log.error('cannot zip export area items dump') 915 return None 916 return zip_file
917 918 #--------------------------------------------------------
919 - def export(self, base_dir=None, items=None, passphrase=None):
920 921 if items is None: 922 items = self.items 923 if len(items) == 0: 924 return None 925 926 from Gnumed.business.gmPerson import cPatient 927 pat = cPatient(aPK_obj = self.__pk_identity) 928 target_base_dir = base_dir 929 if target_base_dir is None: 930 target_base_dir = gmTools.mk_sandbox_dir(prefix = '%s-' % pat.subdir_name) 931 gmTools.mkdir(target_base_dir) 932 _log.debug('patient media base dir: %s', target_base_dir) 933 if not gmTools.dir_is_empty(target_base_dir): 934 _log.error('patient media base dir is not empty') 935 return False 936 937 from Gnumed.business.gmPraxis import gmCurrentPraxisBranch 938 prax = gmCurrentPraxisBranch() 939 940 html_data = {} 941 942 # 1) assemble everything in a sandbox 943 # - setup sandbox 944 sandbox_dir = gmTools.mk_sandbox_dir() 945 _log.debug('sandbox dir: %s', sandbox_dir) 946 doc_dir = os.path.join(sandbox_dir, DOCUMENTS_SUBDIR) 947 gmTools.mkdir(doc_dir) 948 # - export mugshot 949 mugshot = pat.document_folder.latest_mugshot 950 if mugshot is not None: 951 mugshot_fname = mugshot.save_to_file(directory = doc_dir, adjust_extension = True) 952 fname = os.path.split(mugshot_fname)[1] 953 html_data['mugshot_url'] = os.path.join(DOCUMENTS_SUBDIR, fname) 954 html_data['mugshot_alt'] =_('patient photograph from %s') % gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y') 955 html_data['mugshot_title'] = gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y') 956 # - export patient demographics as GDT/XML/VCF/MCF 957 pat.export_as_gdt(filename = os.path.join(sandbox_dir, 'patient.gdt')) 958 pat.export_as_xml_linuxmednews(filename = os.path.join(sandbox_dir, 'patient.xml')) 959 pat.export_as_vcard(filename = os.path.join(sandbox_dir, 'patient.vcf')) 960 pat.export_as_mecard(filename = os.path.join(sandbox_dir, u'patient.mcf')) 961 # - create CD.INF 962 self._create_cd_inf(pat, sandbox_dir) 963 # - export items 964 docs_list = [] 965 for item in items: 966 # if it is a dicomdir - put it into the root of the target media 967 if item.is_DICOM_directory: 968 _log.debug('exporting DICOMDIR DIRENTRY') 969 # save into base dir 970 item_fname = item.save_to_file(directory = sandbox_dir) 971 # do not include into ./documents/ listing 972 continue 973 if item.is_valid_DIRENTRY: 974 _log.debug('exporting DIRENTRY') 975 # if there are files in the root dir: put it into a 976 # subdir of ./documents/ where subdir is the leaf 977 # of the the item .filename 978 if item.has_files_in_root: 979 tag, node, local_fs_path = item['filename'].split('::', 2) 980 subdir = local_fs_path.rstrip('/').split('/')[-1] 981 subdir = os.path.join(doc_dir, subdir) 982 gmTools.mkdir(subdir) 983 item_fname = item.save_to_file(directory = subdir) 984 # if it is subdirs only - put it into documents/ directly 985 else: 986 item_fname = item.save_to_file(directory = doc_dir) 987 # include into ./documents/ listing 988 fname = os.path.split(item_fname)[1] 989 docs_list.append([fname, gmTools.html_escape_string(item['description'])]) 990 continue 991 if item.is_DIRENTRY: 992 # DIRENTRY but not valid: skip 993 continue 994 # normal entry, doc obj links or data item 995 item_fname = item.save_to_file(directory = doc_dir) 996 fname = os.path.split(item_fname)[1] 997 # collect items so we can later correlate items and descriptions 998 # actually we should link to encrypted items, and to unencrypted ones 999 docs_list.append([fname, gmTools.html_escape_string(item['description'])]) 1000 # - link to DICOMDIR if exists 1001 if 'DICOMDIR' in os.listdir(sandbox_dir): # in root path 1002 has_dicomdir = True 1003 html_data['browse_dicomdir'] = '<li><a href="./DICOMDIR">%s</a></li>' % _('show DICOMDIR file') 1004 else: 1005 has_dicomdir = False 1006 # - include DWV link if there is a DICOMDIR 1007 if has_dicomdir: 1008 # do not clone into media base to avoid encryption, 1009 # DWV will be cloned to there later on 1010 dwv_sandbox_dir = self._clone_dwv() 1011 if dwv_sandbox_dir is not None: 1012 html_data['run_dicom_viewer'] = '<li><a href="./dwv/viewers/mobile-local/index.html">%s</a></li>' % _('run Radiology Images (DICOM) Viewer') 1013 # - create frontpage.html 1014 frontpage_fname = self._create_frontpage_html(pat, prax, sandbox_dir, html_data, docs_list) 1015 # - start.html (just a convenience copy of frontpage.html) 1016 # (later overwritten, if encryption is requested) 1017 start_fname = os.path.join(sandbox_dir, 'start.html') 1018 try: 1019 shutil.copy2(frontpage_fname, start_fname) 1020 except Exception: 1021 _log.exception('cannot copy %s to %s', frontpage_fname, start_fname) 1022 # - index.html (just a convenience copy of frontpage.html) 1023 # (later overwritten if encryption is requested) 1024 index_fname = os.path.join(sandbox_dir, 'index.html') 1025 try: 1026 shutil.copy2(frontpage_fname, index_fname) 1027 except Exception: 1028 _log.exception('cannot copy %s to %s', frontpage_fname, index_fname) 1029 1030 # 2) encrypt content of sandbox 1031 if passphrase is not None: 1032 encrypted = gmCrypto.encrypt_directory_content ( 1033 directory = sandbox_dir, 1034 receiver_key_ids = None, 1035 passphrase = passphrase, 1036 comment = None, 1037 verbose = _cfg.get(option = 'debug'), 1038 remove_unencrypted = True 1039 ) 1040 if not encrypted: 1041 _log.errror('cannot encrypt data in sandbox dir') 1042 return False 1043 1044 # 3) add never-to-be-encrypted data 1045 # - AUTORUN.INF 1046 # - README 1047 if passphrase is None: 1048 self._create_autorun_inf(pat, sandbox_dir) 1049 self._create_readme(pat, sandbox_dir) 1050 else: 1051 self._create_autorun_inf(None, sandbox_dir) 1052 self._create_readme(None, sandbox_dir) 1053 # - praxis VCF/MCF 1054 shutil.move(prax.vcf, os.path.join(sandbox_dir, 'praxis.vcf')) 1055 prax.export_as_mecard(filename = os.path.join(sandbox_dir, u'praxis.mcf')) 1056 # - include DWV code 1057 if has_dicomdir: 1058 self._clone_dwv(target_dir = sandbox_dir) 1059 # - index.html as boilerplate for decryption 1060 if passphrase is not None: 1061 index_fname = self._create_index_html(prax, sandbox_dir, html_data) 1062 1063 # 4) move sandbox to target dir 1064 target_dir = gmTools.copy_tree_content(sandbox_dir, target_base_dir) 1065 if target_dir is None: 1066 _log.error('cannot fill target base dir') 1067 return False 1068 1069 return target_dir
1070 1071 #--------------------------------------------------------
1072 - def export_as_zip(self, base_dir=None, items=None, passphrase=None):
1073 _log.debug('target dir: %s', base_dir) 1074 export_dir = self.export(base_dir = base_dir, items = items) 1075 if export_dir is None: 1076 _log.debug('cannot export items') 1077 return None 1078 if passphrase is None: 1079 zip_file = gmCrypto.create_zip_archive_from_dir ( 1080 export_dir, 1081 comment = _('GNUmed Patient Media'), 1082 overwrite = True, 1083 verbose = _cfg.get(option = 'debug') 1084 ) 1085 else: 1086 zip_file = gmCrypto.create_encrypted_zip_archive_from_dir ( 1087 export_dir, 1088 comment = _('GNUmed Patient Media'), 1089 overwrite = True, 1090 passphrase = passphrase, 1091 verbose = _cfg.get(option = 'debug') 1092 ) 1093 if zip_file is None: 1094 _log.debug('cannot create zip archive') 1095 return None 1096 return zip_file
1097 1098 #--------------------------------------------------------
1099 - def _create_index_html(self, praxis, directory, data):
1100 # header part 1101 _HTML_data = { 1102 'html_title_header': _('Patient data'), 1103 'title': _('Patient data excerpt'), 1104 'docs_title': _('Documents'), 1105 'browse_root': _('browse storage medium'), 1106 'browse_docs': _('browse documents area'), 1107 'doc_subdir': DOCUMENTS_SUBDIR, 1108 'date' : gmTools.html_escape_string(gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y %B %d')) 1109 } 1110 frontpage_fname_enc = 'frontpage.html.asc' 1111 if os.path.isfile(os.path.join(directory, frontpage_fname_enc)): 1112 _HTML_data['frontpage_fname'] = frontpage_fname_enc 1113 frontpage_fname_enc = 'frontpage.html.7z' 1114 if os.path.isfile(os.path.join(directory, frontpage_fname_enc)): 1115 _HTML_data['frontpage_fname'] = frontpage_fname_enc 1116 # footer part 1117 lines = [] 1118 adr = praxis.branch.org_unit.address 1119 if adr is not None: 1120 lines.extend(adr.format()) 1121 for comm in praxis.branch.org_unit.comm_channels: 1122 if comm['is_confidential'] is True: 1123 continue 1124 lines.append('%s: %s' % ( 1125 comm['l10n_comm_type'], 1126 comm['url'] 1127 )) 1128 adr = '' 1129 if len(lines) > 0: 1130 adr = gmTools.html_escape_string('\n'.join(lines), replace_eol = True, keep_visual_eol = True) 1131 _HTML_data['branch'] = gmTools.html_escape_string(praxis['branch']) 1132 _HTML_data['praxis'] = gmTools.html_escape_string(praxis['praxis']) 1133 _HTML_data['gm_ver'] = gmTools.html_escape_string(gmTools.coalesce(_cfg.get(option = 'client_version'), 'git HEAD')) 1134 _HTML_data['adr'] = adr 1135 # create file 1136 index_fname = os.path.join(directory, 'index.html') 1137 index_file = io.open(index_fname, mode = 'wt', encoding = 'utf8') 1138 index_file.write(_INDEX_HTML_CONTENT % _HTML_data) 1139 index_file.close() 1140 return index_fname
1141 1142 #--------------------------------------------------------
1143 - def _create_frontpage_html(self, patient, praxis, directory, data, docs_list):
1144 # <li><a href="documents/filename-1.ext">document 1 description</a></li> 1145 _HTML_LIST_ITEM = ' <li><a href="%s">%s</a></li>' 1146 # header part 1147 _HTML_data = { 1148 'html_title_header': _('Patient data for'), 1149 'html_title_patient': gmTools.html_escape_string(patient.get_description_gender(with_nickname = False) + ', ' + _('born') + ' ' + patient.get_formatted_dob('%Y %B %d')), 1150 'title': _('Patient data excerpt'), 1151 'pat_name': gmTools.html_escape_string(patient.get_description_gender(with_nickname = False)), 1152 'pat_dob': gmTools.html_escape_string(_('born') + ' ' + patient.get_formatted_dob('%Y %B %d')), 1153 'mugshot_url': 'documents/no-such-file.png', 1154 'mugshot_alt': _('no patient photograph available'), 1155 'mugshot_title': '', 1156 'docs_title': _('Documents'), 1157 'browse_root': _('browse storage medium'), 1158 'browse_docs': _('browse documents area'), 1159 'doc_subdir': DOCUMENTS_SUBDIR, 1160 'browse_dicomdir': '', 1161 'run_dicom_viewer': '', 1162 'date' : gmTools.html_escape_string(gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y %B %d')) 1163 } 1164 for key in data: 1165 _HTML_data[key] = data[key] 1166 1167 # documents part 1168 _HTML_docs_list = [] 1169 for doc in docs_list: 1170 subdir = os.path.join(directory, DOCUMENTS_SUBDIR, doc[0]) 1171 if os.path.isdir(subdir): 1172 _HTML_docs_list.append(_HTML_LIST_ITEM % (os.path.join(DOCUMENTS_SUBDIR, doc[0]), _('DIRECTORY: %s/%s/') % (DOCUMENTS_SUBDIR, doc[0]))) 1173 _HTML_docs_list.append(' <ul>') 1174 for fname in os.listdir(subdir): 1175 tmp = os.path.join(subdir, fname) 1176 if os.path.isdir(tmp): 1177 _HTML_docs_list.append(' <li><a href="%s">%s</a></li>' % (os.path.join(DOCUMENTS_SUBDIR, doc[0], fname), _('DIRECTORY: %s/%s/%s/') % (DOCUMENTS_SUBDIR, doc[0], fname))) 1178 else: 1179 _HTML_docs_list.append(' <li><a href="%s">%s</a></li>' % (os.path.join(DOCUMENTS_SUBDIR, doc[0], fname), fname)) 1180 _HTML_docs_list.append(' </ul>') 1181 else: 1182 _HTML_docs_list.append(_HTML_LIST_ITEM % (os.path.join(DOCUMENTS_SUBDIR, doc[0]), doc[1])) 1183 _HTML_data['docs_list'] = u'\n '.join(_HTML_docs_list) 1184 1185 # footer part 1186 lines = [] 1187 adr = praxis.branch.org_unit.address 1188 if adr is not None: 1189 lines.extend(adr.format()) 1190 for comm in praxis.branch.org_unit.comm_channels: 1191 if comm['is_confidential'] is True: 1192 continue 1193 lines.append('%s: %s' % ( 1194 comm['l10n_comm_type'], 1195 comm['url'] 1196 )) 1197 adr = '' 1198 if len(lines) > 0: 1199 adr = gmTools.html_escape_string('\n'.join(lines), replace_eol = True, keep_visual_eol = True) 1200 _HTML_data['branch'] = gmTools.html_escape_string(praxis['branch']) 1201 _HTML_data['praxis'] = gmTools.html_escape_string(praxis['praxis']) 1202 _HTML_data['gm_ver'] = gmTools.html_escape_string(gmTools.coalesce(_cfg.get(option = 'client_version'), 'git HEAD')) 1203 _HTML_data['adr'] = adr 1204 # create file 1205 frontpage_fname = os.path.join(directory, 'frontpage.html') 1206 frontpage_file = io.open(frontpage_fname, mode = 'wt', encoding = 'utf8') 1207 frontpage_file.write(_FRONTPAGE_HTML_CONTENT % _HTML_data) 1208 frontpage_file.close() 1209 return frontpage_fname
1210 1211 #--------------------------------------------------------
1212 - def _clone_dwv(self, target_dir=None):
1213 _log.debug('cloning dwv') 1214 # find DWV 1215 dwv_src_dir = os.path.join(gmTools.gmPaths().local_base_dir, 'resources', 'dwv4export') 1216 if not os.path.isdir(dwv_src_dir): 1217 _log.debug('[%s] not found', dwv_src_dir) 1218 dwv_src_dir = os.path.join(gmTools.gmPaths().system_app_data_dir, 'resources', 'dwv4export') 1219 if not os.path.isdir(dwv_src_dir): 1220 _log.debug('[%s] not found', dwv_src_dir) 1221 return None 1222 # clone it 1223 if target_dir is None: 1224 target_dir = gmTools.mk_sandbox_dir() 1225 dwv_target_dir = os.path.join(target_dir, 'dwv') 1226 gmTools.rmdir(dwv_target_dir) 1227 try: 1228 shutil.copytree(dwv_src_dir, dwv_target_dir) 1229 except (shutil.Error, OSError): 1230 _log.exception('cannot include DWV, skipping') 1231 return None 1232 1233 return dwv_target_dir
1234 1235 #--------------------------------------------------------
1236 - def _create_readme(self, patient, directory):
1237 _README_CONTENT = ( 1238 'This is a patient data excerpt created by the GNUmed Electronic Medical Record.\n' 1239 '\n' 1240 'Patient: %s\n' 1241 '\n' 1242 'Please display <frontpage.html> to browse patient data.\n' 1243 '\n' 1244 'Individual documents are stored in the subdirectory\n' 1245 '\n' 1246 ' documents/\n' 1247 '\n' 1248 '\n' 1249 'Data may need to be decrypted with either GNU Privacy\n' 1250 'Guard or 7zip/WinZip.\n' 1251 '\n' 1252 '.asc:\n' 1253 ' https://gnupg.org\n' 1254 '\n' 1255 '.7z:\n' 1256 ' https://www.7-zip.org\n' 1257 ' https://www.winzip.com\n' 1258 '\n' 1259 'To obtain any needed keys you will have to get in touch with\n' 1260 'the creator or the owner of this patient media excerpt.\n' 1261 ) 1262 readme_fname = os.path.join(directory, 'README') 1263 readme_file = io.open(readme_fname, mode = 'wt', encoding = 'utf8') 1264 if patient is None: 1265 pat_str = _('<protected>') 1266 else: 1267 pat_str = patient.get_description_gender(with_nickname = False) + ', ' + _('born') + ' ' + patient.get_formatted_dob('%Y %B %d') 1268 readme_file.write(_README_CONTENT % pat_str) 1269 readme_file.close() 1270 return readme_fname
1271 1272 #--------------------------------------------------------
1273 - def _create_cd_inf(self, patient, directory):
1274 _CD_INF_CONTENT = ( 1275 '[Patient Info]\r\n' # needs \r\n for Windows 1276 'PatientName=%s, %s\r\n' 1277 'Gender=%s\r\n' 1278 'BirthDate=%s\r\n' 1279 'CreationDate=%s\r\n' 1280 'PID=%s\r\n' 1281 'EMR=GNUmed\r\n' 1282 'Version=%s\r\n' 1283 '#StudyDate=\r\n' 1284 '#VNRInfo=<body part>\r\n' 1285 '\r\n' 1286 '# name format: lastnames, firstnames\r\n' 1287 '# date format: YYYY-MM-DD (ISO 8601)\r\n' 1288 '# gender format: %s\r\n' 1289 ) 1290 fname = os.path.join(directory, 'CD.INF') 1291 cd_inf = io.open(fname, mode = 'wt', encoding = 'utf8') 1292 cd_inf.write(_CD_INF_CONTENT % ( 1293 patient['lastnames'], 1294 patient['firstnames'], 1295 gmTools.coalesce(patient['gender'], '?'), 1296 patient.get_formatted_dob('%Y-%m-%d'), 1297 gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y-%m-%d'), 1298 patient.ID, 1299 _cfg.get(option = 'client_version'), 1300 ' / '.join([ '%s = %s (%s)' % (g['tag'], g['label'], g['l10n_label']) for g in patient.gender_list ]) 1301 )) 1302 cd_inf.close() 1303 return fname
1304 1305 #--------------------------------------------------------
1306 - def _create_autorun_inf(self, patient, directory):
1307 _AUTORUN_INF_CONTENT = ( # needs \r\n for Windows 1308 '[AutoRun.Amd64]\r\n' # 64 bit 1309 'label=%(label)s\r\n' # patient name/DOB 1310 'shellexecute=index.html\r\n' 1311 'action=%(action)s\r\n' # % _('Browse patient data') 1312 '%(icon)s\r\n' # "icon=gnumed.ico" or "" 1313 'UseAutoPlay=1\r\n' 1314 '\r\n' 1315 '[AutoRun]\r\n' # 32 bit 1316 'label=%(label)s\r\n' # patient name/DOB 1317 'shellexecute=index.html\r\n' 1318 'action=%(action)s\r\n' # % _('Browse patient data') 1319 '%(icon)s\r\n' # "icon=gnumed.ico" or "" 1320 'UseAutoPlay=1\r\n' 1321 '\r\n' 1322 '[Content]\r\n' 1323 'PictureFiles=yes\r\n' 1324 'VideoFiles=yes\r\n' 1325 'MusicFiles=no\r\n' 1326 '\r\n' 1327 '[IgnoreContentPaths]\r\n' 1328 '\documents\r\n' 1329 '\r\n' 1330 '[unused]\r\n' 1331 'open=requires explicit executable\r\n' 1332 ) 1333 autorun_dict = { 1334 'label': self._compute_autorun_inf_label(patient), 1335 'action': _('Browse patient data'), 1336 'icon': '' 1337 } 1338 media_icon_kwd = '$$gnumed_patient_media_export_icon' 1339 media_icon_kwd_exp = gmKeywordExpansion.get_expansion ( 1340 keyword = media_icon_kwd, 1341 textual_only = False, 1342 binary_only = True 1343 ) 1344 icon_tmp_fname = media_icon_kwd_exp.save_to_file ( 1345 target_mime = 'image/x-icon', 1346 target_extension = '.ico', 1347 ignore_conversion_problems = True 1348 ) 1349 if icon_tmp_fname is None: 1350 _log.error('cannot retrieve <%s>', media_icon_kwd) 1351 else: 1352 media_icon_fname = os.path.join(directory, 'gnumed.ico') 1353 try: 1354 shutil.copy2(icon_tmp_fname, media_icon_fname) 1355 autorun_dict['icon'] = 'icon=gnumed.ico' 1356 except Exception: 1357 _log.exception('cannot move %s to %s', icon_tmp_fname, media_icon_fname) 1358 autorun_fname = os.path.join(directory, 'AUTORUN.INF') 1359 autorun_file = io.open(autorun_fname, mode = 'wt', encoding = 'cp1252', errors = 'replace') 1360 autorun_file.write(_AUTORUN_INF_CONTENT % autorun_dict) 1361 autorun_file.close() 1362 return autorun_fname
1363 1364 #--------------------------------------------------------
1365 - def _compute_autorun_inf_label(self, patient):
1366 if patient is None: 1367 # if no patient provided - assume the target media 1368 # is to be encrypted and thusly do not expose patient data, 1369 # AUTORUN.INF itself will not be encrypted because 1370 # Windows must be able to parse it 1371 return _('GNUmed patient data excerpt')[:32] 1372 1373 LABEL_MAX_LEN = 32 1374 dob = patient.get_formatted_dob(format = ' %Y%m%d', none_string = '', honor_estimation = False) 1375 if dob == '': 1376 gender_template = ' (%s)' 1377 else: 1378 gender_template = ' %s' 1379 gender = gmTools.coalesce(patient['gender'], '', gender_template) 1380 name_max_len = LABEL_MAX_LEN - len(gender) - len(dob) # they already include appropriate padding 1381 name = patient.active_name 1382 last = name['lastnames'].strip() 1383 first = name['firstnames'].strip() 1384 len_last = len(last) 1385 len_first = len(first) 1386 while (len_last + len_first + 1) > name_max_len: 1387 if len_first > 6: 1388 len_first -= 1 1389 if first[len_first - 1] == ' ': 1390 len_first -= 1 1391 continue 1392 len_last -= 1 1393 if last[len_last - 1] == ' ': 1394 len_last -= 1 1395 last = last[:len_last].strip().upper() 1396 first = first[:len_first].strip() 1397 # max 32 chars, supposedly ASCII, but CP1252 likely works pretty well 1398 label = (('%s %s%s%s' % (last, first, dob, gender)).strip())[:32] 1399 return label
1400 1401 #-------------------------------------------------------- 1402 # properties 1403 #--------------------------------------------------------
1404 - def get_items(self, designation=None, order_by='designation, description'):
1405 return get_export_items(order_by = order_by, pk_identity = self.__pk_identity, designation = designation)
1406 1407 items = property(get_items, lambda x:x) 1408 1409 #--------------------------------------------------------
1410 - def get_printouts(self, order_by='designation, description'):
1411 return get_print_jobs(order_by = order_by, pk_identity = self.__pk_identity)
1412 1413 printouts = property(get_printouts, lambda x:x)
1414 1415 #============================================================ 1416 if __name__ == '__main__': 1417 1418 if len(sys.argv) < 2: 1419 sys.exit() 1420 1421 if sys.argv[1] != 'test': 1422 sys.exit() 1423 1424 from Gnumed.pycommon import gmI18N 1425 gmI18N.activate_locale() 1426 gmI18N.install_domain() 1427 1428 from Gnumed.business import gmPraxis 1429 1430 #---------------------------------------
1431 - def test_export_items():
1432 # items = get_export_items() 1433 # for item in items: 1434 # print item.format() 1435 import random 1436 create_export_item(description = 'description %s' % random.random(), pk_identity = 12, pk_doc_obj = None, filename = 'dummy.dat') 1437 items = get_export_items() 1438 for item in items: 1439 print(item.format()) 1440 item['pk_doc_obj'] = 1 1441 item.save() 1442 print(item)
1443 1444 #---------------------------------------
1445 - def test_export_area():
1446 exp = cExportArea(12) 1447 #print exp.export_with_meta_data() 1448 #print exp.items 1449 #exp.add_file(sys.argv[2]) 1450 prax = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.cPraxisBranch(1)) 1451 #print(prax) 1452 #print(prax.branch) 1453 try: 1454 pwd = sys.argv[2] 1455 except IndexError: 1456 pwd = None 1457 print(exp.export(passphrase = pwd))
1458 1459 #---------------------------------------
1460 - def test_label():
1461 1462 from Gnumed.business.gmPerson import cPatient 1463 from Gnumed.business.gmPersonSearch import ask_for_patient 1464 1465 #while ask_for_patient() is not None: 1466 pat_min = 1 1467 pat_max = 100 1468 try: 1469 pat_min = int(sys.argv[2]) 1470 pat_max = int(sys.argv[3]) 1471 except Exception: 1472 pass 1473 cPatient(aPK_obj = pat_min) 1474 f = io.open('x-auto_inf_labels.txt', mode = 'w', encoding = 'utf8') 1475 f.write('--------------------------------\n') 1476 f.write('12345678901234567890123456789012\n') 1477 f.write('--------------------------------\n') 1478 for pat_id in range(pat_min, pat_max): 1479 try: 1480 exp_area = cExportArea(pat_id) 1481 pat = cPatient(aPK_obj = pat_id) 1482 except Exception: 1483 continue 1484 f.write(exp_area._compute_autorun_inf_label(pat) + '\n') 1485 f.close() 1486 return
1487 1488 #--------------------------------------- 1489 #test_export_items() 1490 test_export_area() 1491 #test_label() 1492 1493 sys.exit(0) 1494 1495 #============================================================ 1496 # CDROM "run.bat": 1497 # 1498 #@echo off 1499 # 1500 #if defined ProgramFiles(x86) ( 1501 # ::64-bit 1502 # start /B x64\mdicom.exe /scan . 1503 #) else ( 1504 # ::32-bit 1505 # start /B win32\mdicom.exe /scan . 1506 #) 1507 # 1508 #-------------------------------------------------- 1509