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
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
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
101
102 self._is_modified = True
103 self.save()
104 self.refetch_payload(ignore_changes = False, link_obj = link_obj)
105
106
107
108
109
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
121 self.refetch_payload()
122 return True
123
124
126
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
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
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
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
185 if self.is_DIRENTRY:
186
187 return None
188
189
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
230
231
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
236 if self._payload[self._idx['filename']].startswith('DIR::'):
237
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
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
265 patient_part = ''
266 if patient is not None:
267 patient_part = '-%s' % patient.subdir_name
268
269
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
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
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
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
391
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
402
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
427 """Check whether this item is a _valid_ DIRENTRY."""
428 if not self.is_DIRENTRY:
429 return False
430
431 try:
432 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2)
433 except ValueError:
434
435
436 _log.exception('DIRENTRY [%s]: malformed', self._payload[self._idx['filename']])
437 return False
438
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
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
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
460 if os.path.isdir(os.path.join(local_fs_path, fs_entry)):
461
462 continue
463
464 if fs_entry != 'DICOMDIR':
465
466 return False
467
468
469 found_DICOMDIR = True
470 return found_DICOMDIR
471
472 is_DICOM_directory = property(_is_DICOM_directory)
473
474
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
497
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
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
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
674
676 self.__pk_identity = pk_identity
677
678
697
698
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
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
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
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
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
879
880
881 - def dump_items_to_disk(self, base_dir=None, items=None, passphrase=None, convert2pdf=False):
898
899
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
943
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
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
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
962 self._create_cd_inf(pat, sandbox_dir)
963
964 docs_list = []
965 for item in items:
966
967 if item.is_DICOM_directory:
968 _log.debug('exporting DICOMDIR DIRENTRY')
969
970 item_fname = item.save_to_file(directory = sandbox_dir)
971
972 continue
973 if item.is_valid_DIRENTRY:
974 _log.debug('exporting DIRENTRY')
975
976
977
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
985 else:
986 item_fname = item.save_to_file(directory = doc_dir)
987
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
993 continue
994
995 item_fname = item.save_to_file(directory = doc_dir)
996 fname = os.path.split(item_fname)[1]
997
998
999 docs_list.append([fname, gmTools.html_escape_string(item['description'])])
1000
1001 if 'DICOMDIR' in os.listdir(sandbox_dir):
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
1007 if has_dicomdir:
1008
1009
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
1014 frontpage_fname = self._create_frontpage_html(pat, prax, sandbox_dir, html_data, docs_list)
1015
1016
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
1023
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
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
1045
1046
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
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
1057 if has_dicomdir:
1058 self._clone_dwv(target_dir = sandbox_dir)
1059
1060 if passphrase is not None:
1061 index_fname = self._create_index_html(prax, sandbox_dir, html_data)
1062
1063
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
1100
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
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
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
1145 _HTML_LIST_ITEM = ' <li><a href="%s">%s</a></li>'
1146
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
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
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
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
1213 _log.debug('cloning dwv')
1214
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
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
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
1274 _CD_INF_CONTENT = (
1275 '[Patient Info]\r\n'
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
1307 _AUTORUN_INF_CONTENT = (
1308 '[AutoRun.Amd64]\r\n'
1309 'label=%(label)s\r\n'
1310 'shellexecute=index.html\r\n'
1311 'action=%(action)s\r\n'
1312 '%(icon)s\r\n'
1313 'UseAutoPlay=1\r\n'
1314 '\r\n'
1315 '[AutoRun]\r\n'
1316 'label=%(label)s\r\n'
1317 'shellexecute=index.html\r\n'
1318 'action=%(action)s\r\n'
1319 '%(icon)s\r\n'
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
1366 if patient is None:
1367
1368
1369
1370
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)
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
1398 label = (('%s %s%s%s' % (last, first, dob, gender)).strip())[:32]
1399 return label
1400
1401
1402
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
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
1443
1444
1458
1459
1461
1462 from Gnumed.business.gmPerson import cPatient
1463 from Gnumed.business.gmPersonSearch import ask_for_patient
1464
1465
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
1490 test_export_area()
1491
1492
1493 sys.exit(0)
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509