Trees | Indices | Help |
|
---|
|
1 # -*- coding: latin-1 -*- 2 """GNUmed forms classes 3 4 Business layer for printing all manners of forms, letters, scripts etc. 5 6 license: GPL 7 """ 8 #============================================================ 9 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/business/gmForms.py,v $ 10 # $Id: gmForms.py,v 1.79 2010/01/31 16:33:32 ncq Exp $ 11 __version__ = "$Revision: 1.79 $" 12 __author__ ="Ian Haywood <ihaywood@gnu.org>, karsten.hilbert@gmx.net" 13 14 15 import os, sys, time, os.path, logging, codecs, re as regex, shutil, random, platform 16 #, libxml2, libxslt 17 18 19 if __name__ == '__main__': 20 sys.path.insert(0, '../../') 21 from Gnumed.pycommon import gmTools, gmBorg, gmMatchProvider, gmExceptions, gmDispatcher 22 from Gnumed.pycommon import gmPG2, gmBusinessDBObject, gmCfg, gmShellAPI, gmMimeLib, gmLog2 23 from Gnumed.business import gmPerson, gmSurgery 24 25 26 _log = logging.getLogger('gm.forms') 27 _log.info(__version__) 28 29 #============================================================ 30 # this order is also used in choice boxes for the engine 31 form_engine_abbrevs = [u'O', u'L'] 32 33 form_engine_names = { 34 u'O': 'OpenOffice', 35 u'L': 'LaTeX' 36 } 37 38 # is filled in further below after each engine is defined 39 form_engines = {} 40 41 #============================================================ 42 # match providers 43 #============================================================4555 #============================================================47 48 query = u""" 49 select name_long, name_long 50 from ref.v_paperwork_templates 51 where name_long %(fragment_condition)s 52 order by name_long 53 """ 54 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])5767 #============================================================59 60 query = u""" 61 select name_short, name_short 62 from ref.v_paperwork_templates 63 where name_short %(fragment_condition)s 64 order by name_short 65 """ 66 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])6985 #============================================================71 72 query = u""" 73 select * from ( 74 select pk, _(name) as l10n_name from ref.form_types 75 where _(name) %(fragment_condition)s 76 77 union 78 79 select pk, _(name) as l10n_name from ref.form_types 80 where name %(fragment_condition)s 81 ) as union_result 82 order by l10n_name 83 """ 84 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])87 88 _cmd_fetch_payload = u'select * from ref.v_paperwork_templates where pk_paperwork_template = %s' 89 90 _cmds_store_payload = [ 91 u"""update ref.paperwork_templates set 92 name_short = %(name_short)s, 93 name_long = %(name_long)s, 94 fk_template_type = %(pk_template_type)s, 95 instance_type = %(instance_type)s, 96 engine = %(engine)s, 97 in_use = %(in_use)s, 98 filename = %(filename)s, 99 external_version = %(external_version)s 100 where 101 pk = %(pk_paperwork_template)s and 102 xmin = %(xmin_paperwork_template)s 103 """, 104 u"""select xmin_paperwork_template from ref.v_paperwork_templates where pk_paperwork_template = %(pk_paperwork_template)s""" 105 ] 106 107 _updatable_fields = [ 108 u'name_short', 109 u'name_long', 110 u'external_version', 111 u'pk_template_type', 112 u'instance_type', 113 u'engine', 114 u'in_use', 115 u'filename' 116 ] 117 118 _suffix4engine = { 119 u'O': u'.ott', 120 u'L': u'.tex', 121 u'T': u'.txt', 122 u'X': u'.xslt' 123 } 124 125 #--------------------------------------------------------192 #============================================================127 """The template itself better not be arbitrarily large unless you can handle that. 128 129 Note that the data type returned will be a buffer.""" 130 131 cmd = u'SELECT data FROM ref.paperwork_templates WHERE pk = %(pk)s' 132 rows, idx = gmPG2.run_ro_queries (queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False) 133 134 if len(rows) == 0: 135 raise gmExceptions.NoSuchBusinessObjectError('cannot retrieve data for template pk = %s' % self.pk_obj) 136 137 return rows[0][0]138 139 template_data = property(_get_template_data, lambda x:x) 140 #--------------------------------------------------------142 """Export form template from database into file.""" 143 144 if filename is None: 145 if self._payload[self._idx['filename']] is None: 146 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]] 147 else: 148 suffix = os.path.splitext(self._payload[self._idx['filename']].strip())[1].strip() 149 if suffix in [u'', u'.']: 150 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]] 151 152 filename = gmTools.get_unique_filename ( 153 prefix = 'gm-%s-Template-' % self._payload[self._idx['engine']], 154 suffix = suffix, 155 tmp_dir = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')) 156 ) 157 158 data_query = { 159 'cmd': u'SELECT substring(data from %(start)s for %(size)s) FROM ref.paperwork_templates WHERE pk = %(pk)s', 160 'args': {'pk': self.pk_obj} 161 } 162 163 data_size_query = { 164 'cmd': u'select octet_length(data) from ref.paperwork_templates where pk = %(pk)s', 165 'args': {'pk': self.pk_obj} 166 } 167 168 result = gmPG2.bytea2file ( 169 data_query = data_query, 170 filename = filename, 171 data_size_query = data_size_query, 172 chunk_size = chunksize 173 ) 174 if result is False: 175 return None 176 177 return filename178 #--------------------------------------------------------180 gmPG2.file2bytea ( 181 filename = filename, 182 query = u'update ref.paperwork_templates set data = %(data)s::bytea where pk = %(pk)s and xmin = %(xmin)s', 183 args = {'pk': self.pk_obj, 'xmin': self._payload[self._idx['xmin_paperwork_template']]} 184 ) 185 # adjust for xmin change 186 self.refetch_payload()187 #--------------------------------------------------------189 fname = self.export_to_file() 190 engine = form_engines[self._payload[self._idx['engine']]] 191 return engine(template_file = fname)194 cmd = u'select pk from ref.paperwork_templates where name_long = %(lname)s and external_version = %(ver)s' 195 args = {'lname': name_long, 'ver': external_version} 196 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 197 198 if len(rows) == 0: 199 _log.error('cannot load form template [%s - %s]', name_long, external_version) 200 return None 201 202 return cFormTemplate(aPK_obj = rows[0]['pk'])203 #------------------------------------------------------------205 """Load form templates.""" 206 207 args = {'eng': engine, 'in_use': active_only} 208 209 where_parts = [] 210 if engine is not None: 211 where_parts.append(u'engine = %(eng)s') 212 213 if active_only: 214 where_parts.append(u'in_use is True') 215 216 if len(where_parts) == 0: 217 cmd = u"select * from ref.v_paperwork_templates order by in_use desc, name_long" 218 else: 219 cmd = u"select * from ref.v_paperwork_templates where %s order by in_use desc, name_long" % u'and'.join(where_parts) 220 221 rows, idx = gmPG2.run_ro_queries ( 222 queries = [{'cmd': cmd, 'args': args}], 223 get_col_idx = True 224 ) 225 templates = [ cFormTemplate(row = {'pk_field': 'pk_paperwork_template', 'data': r, 'idx': idx}) for r in rows ] 226 227 return templates228 #------------------------------------------------------------230 231 cmd = u'insert into ref.paperwork_templates (fk_template_type, name_short, name_long, external_version) values (%(type)s, %(nshort)s, %(nlong)s, %(ext_version)s)' 232 rows, idx = gmPG2.run_rw_queries ( 233 queries = [ 234 {'cmd': cmd, 'args': {'type': template_type, 'nshort': name_short, 'nlong': name_long, 'ext_version': 'new'}}, 235 {'cmd': u"select currval(pg_get_serial_sequence('ref.paperwork_templates', 'pk'))"} 236 ], 237 return_data = True 238 ) 239 template = cFormTemplate(aPK_obj = rows[0][0]) 240 return template241 #------------------------------------------------------------243 rows, idx = gmPG2.run_rw_queries ( 244 queries = [ 245 {'cmd': u'delete from ref.paperwork_templates where pk=%(pk)s', 'args': {'pk': template['pk_paperwork_template']}} 246 ] 247 ) 248 return True249 #============================================================ 250 # OpenOffice API 251 #============================================================ 252 uno = None 253 cOOoDocumentCloseListener = None 254256 """FIXME: consider this: 257 258 try: 259 import uno 260 except: 261 print "This Script needs to be run with the python from OpenOffice.org" 262 print "Example: /opt/OpenOffice.org/program/python %s" % ( 263 os.path.basename(sys.argv[0])) 264 print "Or you need to insert the right path at the top, where uno.py is." 265 print "Default: %s" % default_path 266 """ 267 global uno 268 if uno is not None: 269 return 270 271 global unohelper, oooXCloseListener, oooNoConnectException, oooPropertyValue 272 273 import uno, unohelper 274 from com.sun.star.util import XCloseListener as oooXCloseListener 275 from com.sun.star.connection import NoConnectException as oooNoConnectException 276 from com.sun.star.beans import PropertyValue as oooPropertyValue 277 278 #---------------------------------- 279 class _cOOoDocumentCloseListener(unohelper.Base, oooXCloseListener): 280 """Listens for events sent by OOo during the document closing 281 sequence and notifies the GNUmed client GUI so it can 282 import the closed document into the database. 283 """ 284 def __init__(self, document=None): 285 self.document = document286 287 def queryClosing(self, evt, owner): 288 # owner is True/False whether I am the owner of the doc 289 pass 290 291 def notifyClosing(self, evt): 292 pass 293 294 def disposing(self, evt): 295 self.document.on_disposed_by_ooo() 296 self.document = None 297 #---------------------------------- 298 299 global cOOoDocumentCloseListener 300 cOOoDocumentCloseListener = _cOOoDocumentCloseListener 301 302 _log.debug('python UNO bridge successfully initialized') 303 304 #------------------------------------------------------------306 """This class handles the connection to OOo. 307 308 Its Singleton instance stays around once initialized. 309 """ 310 # FIXME: need to detect closure of OOo !398 #------------------------------------------------------------312 313 init_ooo() 314 315 #self.ooo_start_cmd = 'oowriter -invisible -accept="socket,host=localhost,port=2002;urp;"' 316 #self.remote_context_uri = "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" 317 318 pipe_name = "uno-gm2ooo-%s" % str(random.random())[2:] 319 self.ooo_start_cmd = 'oowriter -invisible -norestore -accept="pipe,name=%s;urp"' % pipe_name 320 self.remote_context_uri = "uno:pipe,name=%s;urp;StarOffice.ComponentContext" % pipe_name 321 322 _log.debug('pipe name: %s', pipe_name) 323 _log.debug('startup command: %s', self.ooo_start_cmd) 324 _log.debug('remote context URI: %s', self.remote_context_uri) 325 326 self.resolver_uri = "com.sun.star.bridge.UnoUrlResolver" 327 self.desktop_uri = "com.sun.star.frame.Desktop" 328 329 self.local_context = uno.getComponentContext() 330 self.uri_resolver = self.local_context.ServiceManager.createInstanceWithContext(self.resolver_uri, self.local_context) 331 332 self.__desktop = None333 #--------------------------------------------------------335 if self.__desktop is None: 336 _log.debug('no desktop, no cleanup') 337 return 338 339 try: 340 self.__desktop.terminate() 341 except: 342 _log.exception('cannot terminate OOo desktop')343 #--------------------------------------------------------345 """<filename> must be absolute""" 346 347 if self.desktop is None: 348 _log.error('cannot access OOo desktop') 349 return None 350 351 filename = os.path.expanduser(filename) 352 filename = os.path.abspath(filename) 353 document_uri = uno.systemPathToFileUrl(filename) 354 355 _log.debug('%s -> %s', filename, document_uri) 356 357 doc = self.desktop.loadComponentFromURL(document_uri, "_blank", 0, ()) 358 return doc359 #-------------------------------------------------------- 360 # internal helpers 361 #--------------------------------------------------------363 # later factor this out ! 364 dbcfg = gmCfg.cCfgSQL() 365 self.ooo_startup_settle_time = dbcfg.get2 ( 366 option = u'external.ooo.startup_settle_time', 367 workplace = gmSurgery.gmCurrentPractice().active_workplace, 368 bias = u'workplace', 369 default = 3.0 370 )371 #-------------------------------------------------------- 372 # properties 373 #--------------------------------------------------------375 if self.__desktop is not None: 376 return self.__desktop 377 378 try: 379 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri) 380 except oooNoConnectException: 381 _log.exception('cannot connect to OOo server') 382 _log.info('trying to start OOo server') 383 os.system(self.ooo_start_cmd) 384 self.__get_startup_settle_time() 385 _log.debug('waiting %s seconds for OOo to start up', self.ooo_startup_settle_time) 386 time.sleep(self.ooo_startup_settle_time) # OOo sometimes needs a bit 387 try: 388 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri) 389 except oooNoConnectException: 390 _log.exception('cannot start (or connect to started) OOo server') 391 return None 392 393 self.__desktop = self.remote_context.ServiceManager.createInstanceWithContext(self.desktop_uri, self.remote_context) 394 _log.debug('connection seems established') 395 return self.__desktop396 397 desktop = property(_get_desktop, lambda x:x)400505 #-------------------------------------------------------- 506 # internal helpers 507 #-------------------------------------------------------- 508 509 #============================================================402 403 self.template_file = template_file 404 self.instance_type = instance_type 405 self.ooo_doc = None406 #-------------------------------------------------------- 407 # external API 408 #--------------------------------------------------------410 # connect to OOo 411 ooo_srv = gmOOoConnector() 412 413 # open doc in OOo 414 self.ooo_doc = ooo_srv.open_document(filename = self.template_file) 415 if self.ooo_doc is None: 416 _log.error('cannot open document in OOo') 417 return False 418 419 # listen for close events 420 pat = gmPerson.gmCurrentPatient() 421 pat.locked = True 422 listener = cOOoDocumentCloseListener(document = self) 423 self.ooo_doc.addCloseListener(listener) 424 425 return True426 #-------------------------------------------------------- 429 #--------------------------------------------------------431 432 # new style embedded, implicit placeholders 433 searcher = self.ooo_doc.createSearchDescriptor() 434 searcher.SearchCaseSensitive = False 435 searcher.SearchRegularExpression = True 436 searcher.SearchWords = True 437 searcher.SearchString = handler.placeholder_regex 438 439 placeholder_instance = self.ooo_doc.findFirst(searcher) 440 while placeholder_instance is not None: 441 try: 442 val = handler[placeholder_instance.String] 443 except: 444 _log.exception(val) 445 val = _('error with placeholder [%s]' % placeholder_instance.String) 446 447 if val is None: 448 val = _('error with placeholder [%s]' % placeholder_instance.String) 449 450 placeholder_instance.String = val 451 placeholder_instance = self.ooo_doc.findNext(placeholder_instance.End, searcher) 452 453 if not old_style_too: 454 return 455 456 # old style "explicit" placeholders 457 text_fields = self.ooo_doc.getTextFields().createEnumeration() 458 while text_fields.hasMoreElements(): 459 text_field = text_fields.nextElement() 460 461 # placeholder ? 462 if not text_field.supportsService('com.sun.star.text.TextField.JumpEdit'): 463 continue 464 # placeholder of type text ? 465 if text_field.PlaceHolderType != 0: 466 continue 467 468 replacement = handler[text_field.PlaceHolder] 469 if replacement is None: 470 continue 471 472 text_field.Anchor.setString(replacement)473 #--------------------------------------------------------475 if filename is not None: 476 target_url = uno.systemPathToFileUrl(os.path.abspath(os.path.expanduser(filename))) 477 save_args = ( 478 oooPropertyValue('Overwrite', 0, True, 0), 479 oooPropertyValue('FormatFilter', 0, 'swriter: StarOffice XML (Writer)', 0) 480 481 ) 482 # "store AS url" stores the doc, marks it unmodified and updates 483 # the internal media descriptor - as opposed to "store TO url" 484 self.ooo_doc.storeAsURL(target_url, save_args) 485 else: 486 self.ooo_doc.store()487 #--------------------------------------------------------489 self.ooo_doc.dispose() 490 pat = gmPerson.gmCurrentPatient() 491 pat.locked = False 492 self.ooo_doc = None493 #--------------------------------------------------------495 # get current file name from OOo, user may have used Save As 496 filename = uno.fileUrlToSystemPath(self.ooo_doc.URL) 497 # tell UI to import the file 498 gmDispatcher.send ( 499 signal = u'import_document_from_file', 500 filename = filename, 501 document_type = self.instance_type, 502 unlock_patient = True 503 ) 504 self.ooo_doc = None511 """Ancestor for forms.""" 512 515 #--------------------------------------------------------594 595 #================================================================ 596 # OOo template forms 597 #----------------------------------------------------------------517 """Parse the template into an instance and replace placeholders with values.""" 518 raise NotImplementedError519 #-------------------------------------------------------- 523 #--------------------------------------------------------525 """Generate output suitable for further processing outside this class, e.g. printing.""" 526 raise NotImplementedError527 #-------------------------------------------------------- 532 #--------------------------------------------------------534 """ 535 A sop to TeX which can't act as a true filter: to delete temporary files 536 """ 537 pass538 #--------------------------------------------------------540 """ 541 Executes the provided command. 542 If command cotains %F. it is substituted with the filename 543 Otherwise, the file is fed in on stdin 544 """ 545 pass546 #--------------------------------------------------------548 """Stores the parameters in the backend. 549 550 - link_obj can be a cursor, a connection or a service name 551 - assigning a cursor to link_obj allows the calling code to 552 group the call to store() into an enclosing transaction 553 (for an example see gmReferral.send_referral()...) 554 """ 555 # some forms may not have values ... 556 if params is None: 557 params = {} 558 patient_clinical = self.patient.get_emr() 559 encounter = patient_clinical.active_encounter['pk_encounter'] 560 # FIXME: get_active_episode is no more 561 #episode = patient_clinical.get_active_episode()['pk_episode'] 562 # generate "forever unique" name 563 cmd = "select name_short || ': <' || name_long || '::' || external_version || '>' from paperwork_templates where pk=%s"; 564 rows = gmPG.run_ro_query('reference', cmd, None, self.pk_def) 565 form_name = None 566 if rows is None: 567 _log.error('error retrieving form def for [%s]' % self.pk_def) 568 elif len(rows) == 0: 569 _log.error('no form def for [%s]' % self.pk_def) 570 else: 571 form_name = rows[0][0] 572 # we didn't get a name but want to store the form anyhow 573 if form_name is None: 574 form_name=time.time() # hopefully unique enough 575 # in one transaction 576 queries = [] 577 # - store form instance in form_instance 578 cmd = "insert into form_instances(fk_form_def, form_name, fk_episode, fk_encounter) values (%s, %s, %s, %s)" 579 queries.append((cmd, [self.pk_def, form_name, episode, encounter])) 580 # - store params in form_data 581 for key in params.keys(): 582 cmd = """ 583 insert into form_data(fk_instance, place_holder, value) 584 values ((select currval('form_instances_pk_seq')), %s, %s::text) 585 """ 586 queries.append((cmd, [key, params[key]])) 587 # - get inserted PK 588 queries.append(("select currval ('form_instances_pk_seq')", [])) 589 status, err = gmPG.run_commit('historica', queries, True) 590 if status is None: 591 _log.error('failed to store form [%s] (%s): %s' % (self.pk_def, form_name, err)) 592 return None 593 return status599 """A forms engine wrapping OOo.""" 600609 610 #================================================================ 611 # LaTeX template forms 612 #----------------------------------------------------------------602 super(self.__class__, self).__init__(template_file = template_file) 603 604 605 path, ext = os.path.splitext(self.template_filename) 606 if ext in [r'', r'.']: 607 ext = r'.tex' 608 self.instance_filename = r'%s-instance%s' % (path, ext)614 """A forms engine wrapping LaTeX.""" 615738 #-------------------------------------------------------- 739 # internal helpers 740 #-------------------------------------------------------- 741 742 #------------------------------------------------------------ 743 form_engines[u'L'] = cLaTeXForm 744 #------------------------------------------------------------ 745 #------------------------------------------------------------617 super(self.__class__, self).__init__(template_file = template_file) 618 path, ext = os.path.splitext(self.template_filename) 619 if ext in [r'', r'.']: 620 ext = r'.tex' 621 self.instance_filename = r'%s-instance%s' % (path, ext)622 #--------------------------------------------------------624 625 template_file = codecs.open(self.template_filename, 'rU', 'utf8') 626 instance_file = codecs.open(self.instance_filename, 'wb', 'utf8') 627 628 for line in template_file: 629 630 if line.strip() in [u'', u'\r', u'\n', u'\r\n']: 631 instance_file.write(line) 632 continue 633 634 # 1) find placeholders in this line 635 placeholders_in_line = regex.findall(data_source.placeholder_regex, line, regex.IGNORECASE) 636 # 2) and replace them 637 for placeholder in placeholders_in_line: 638 #line = line.replace(placeholder, self._texify_string(data_source[placeholder])) 639 try: 640 val = data_source[placeholder] 641 except: 642 _log.exception(val) 643 val = _('error with placeholder [%s]' % placeholder) 644 645 if val is None: 646 val = _('error with placeholder [%s]' % placeholder) 647 648 line = line.replace(placeholder, val) 649 650 instance_file.write(line) 651 652 instance_file.close() 653 template_file.close() 654 655 return656 #--------------------------------------------------------658 659 mimetypes = [ 660 u'application/x-latex', 661 u'application/x-tex', 662 u'text/plain' 663 ] 664 665 for mimetype in mimetypes: 666 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename) 667 668 if editor_cmd is None: 669 editor_cmd = u'sensible-editor %s' % self.instance_filename 670 671 return gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)672 #--------------------------------------------------------674 675 if instance_file is None: 676 instance_file = self.instance_filename 677 678 try: 679 open(instance_file, 'r').close() 680 except: 681 _log.exception('cannot access form instance file [%s]', instance_file) 682 gmLog2.log_stack_trace() 683 return None 684 685 self.instance_filename = instance_file 686 687 _log.debug('ignoring <format> directive [%s], generating PDF', format) 688 689 # create sandbox for LaTeX to play in 690 sandbox_dir = os.path.splitext(self.template_filename)[0] 691 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir) 692 693 old_cwd = os.getcwd() 694 _log.debug('CWD: [%s]', old_cwd) 695 696 gmTools.mkdir(sandbox_dir) 697 os.chdir(sandbox_dir) 698 699 sandboxed_instance_filename = os.path.join(sandbox_dir, os.path.split(self.instance_filename)[1]) 700 shutil.move(self.instance_filename, sandboxed_instance_filename) 701 702 # LaTeX can need up to three runs to get cross-references et al right 703 if platform.system() == 'Windows': 704 cmd = r'pdflatex.exe -interaction nonstopmode %s' % sandboxed_instance_filename 705 else: 706 cmd = r'pdflatex -interaction nonstopmode %s' % sandboxed_instance_filename 707 for run in [1, 2, 3]: 708 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = True): 709 _log.error('problem running pdflatex, cannot generate form output') 710 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True) 711 return None 712 713 os.chdir(old_cwd) 714 pdf_name = u'%s.pdf' % os.path.splitext(sandboxed_instance_filename)[0] 715 shutil.move(pdf_name, os.path.split(self.instance_filename)[0]) 716 pdf_name = u'%s.pdf' % os.path.splitext(self.instance_filename)[0] 717 718 # cleanup LaTeX sandbox ? 719 if cleanup: 720 for fname in os.listdir(sandbox_dir): 721 os.remove(os.path.join(sandbox_dir, fname)) 722 os.rmdir(sandbox_dir) 723 724 try: 725 open(pdf_name, 'r').close() 726 return pdf_name 727 except IOError: 728 _log.exception('cannot open target PDF: %s', pdf_name) 729 730 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True) 731 return None732 #--------------------------------------------------------734 try: 735 os.remove(self.template_filename) 736 except: 737 _log.debug(u'cannot remove template file [%s]', self.template_filename)747 """A forms engine wrapping LaTeX. 748 """ 752805 806 807 808 809 #================================================================ 810 # define a class for HTML forms (for printing) 811 #================================================================754 try: 755 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params]) 756 # create a 'sandbox' directory for LaTeX to play in 757 self.tmp = tempfile.mktemp () 758 os.makedirs (self.tmp) 759 self.oldcwd = os.getcwd () 760 os.chdir (self.tmp) 761 stdin = os.popen ("latex", "w", 2048) 762 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout 763 # FIXME: send LaTeX output to the logger 764 stdin.close () 765 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True): 766 raise FormError ('DVIPS returned error') 767 except EnvironmentError, e: 768 _log.error(e.strerror) 769 raise FormError (e.strerror) 770 return file ("texput.ps")771773 """ 774 For testing purposes, runs Xdvi on the intermediate TeX output 775 WARNING: don't try this on Windows 776 """ 777 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)778780 if "%F" in command: 781 command.replace ("%F", "texput.ps") 782 else: 783 command = "%s < texput.ps" % command 784 try: 785 if not gmShellAPI.run_command_in_shell(command, blocking=True): 786 _log.error("external command %s returned non-zero" % command) 787 raise FormError ('external command %s returned error' % command) 788 except EnvironmentError, e: 789 _log.error(e.strerror) 790 raise FormError (e.strerror) 791 return True792794 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print') 795 self.exe (command)796813 """This class can create XML document from requested data, 814 then process it with XSLT template and display results 815 """ 816 817 # FIXME: make the path configurable ? 818 _preview_program = u'oowriter ' #this program must be in the system PATH 819896 897 898 #===================================================== 899 engines = { 900 u'L': cLaTeXForm 901 } 902 #===================================================== 903 #class LaTeXFilter(Cheetah.Filters.Filter):821 822 if template is None: 823 raise ValueError(u'%s: cannot create form instance without a template' % __name__) 824 825 cFormEngine.__init__(self, template = template) 826 827 self._FormData = None 828 829 # here we know/can assume that the template was stored as a utf-8 830 # encoded string so use that conversion to create unicode: 831 #self._XSLTData = unicode(str(template.template_data), 'UTF-8') 832 # but in fact, unicode() knows how to handle buffers, so simply: 833 self._XSLTData = unicode(self.template.template_data, 'UTF-8', 'strict') 834 835 # we must still devise a method of extracting the SQL query: 836 # - either by retrieving it from a particular tag in the XSLT or 837 # - by making the stored template actually be a dict which, unpickled, 838 # has the keys "xslt" and "sql" 839 self._SQL_query = u'select 1' #this sql query must output valid xml840 #-------------------------------------------------------- 841 # external API 842 #--------------------------------------------------------844 """get data from backend and process it with XSLT template to produce readable output""" 845 846 # extract SQL (this is wrong but displays what is intended) 847 xslt = libxml2.parseDoc(self._XSLTData) 848 root = xslt.children 849 for child in root: 850 if child.type == 'element': 851 self._SQL_query = child.content 852 break 853 854 # retrieve data from backend 855 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False) 856 857 __header = '<?xml version="1.0" encoding="UTF-8"?>\n' 858 __body = rows[0][0] 859 860 # process XML data according to supplied XSLT, producing HTML 861 self._XMLData =__header + __body 862 style = libxslt.parseStylesheetDoc(xslt) 863 xml = libxml2.parseDoc(self._XMLData) 864 html = style.applyStylesheet(xml, None) 865 self._FormData = html.serialize() 866 867 style.freeStylesheet() 868 xml.freeDoc() 869 html.freeDoc()870 #--------------------------------------------------------872 if self._FormData is None: 873 raise ValueError, u'Preview request for empty form. Make sure the form is properly initialized and process() was performed' 874 875 fname = gmTools.get_unique_filename(prefix = u'gm_XSLT_form-', suffix = u'.html') 876 #html_file = os.open(fname, 'wb') 877 #html_file.write(self._FormData.encode('UTF-8')) 878 html_file = codecs.open(fname, 'wb', 'utf8', 'strict') # or 'replace' ? 879 html_file.write(self._FormData) 880 html_file.close() 881 882 cmd = u'%s %s' % (self.__class__._preview_program, fname) 883 884 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False): 885 _log.error('%s: cannot launch report preview program' % __name__) 886 return False 887 888 #os.unlink(self.filename) #delete file 889 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK) 890 891 return True892 #--------------------------------------------------------943 944 945 #=========================================================== 948 949 #============================================================ 950 # convenience functions 951 #------------------------------------------------------------906 """ 907 Convience function to escape ISO-Latin-1 strings for TeX output 908 WARNING: not all ISO-Latin-1 characters are expressible in TeX 909 FIXME: nevertheless, there are a few more we could support 910 911 Also intelligently convert lists and tuples into TeX-style table lines 912 """ 913 if type (item) is types.UnicodeType or type (item) is types.StringType: 914 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX? 915 item = item.replace ("&", "\\&") 916 item = item.replace ("$", "\\$") 917 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now 918 item = item.replace ("\n", "\\\\ ") 919 if len (item.strip ()) == 0: 920 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it 921 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX 922 if type (item) is types.UnicodeType: 923 item = item.encode ('latin-1', 'replace') 924 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}', 925 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions 926 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`', 927 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}', 928 '\xc7':'\\c{C}', '\xc8':'\\`{E}', 929 '\xa1': '!`', 930 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent'} 931 for k, i in trans.items (): 932 item = item.replace (k, i) 933 elif type (item) is types.ListType or type (item) is types.TupleType: 934 item = string.join ([self.filter (i, ' & ') for i in item], table_sep) 935 elif item is None: 936 item = '\\relax % Python None\n' 937 elif type (item) is types.IntType or type (item) is types.FloatType: 938 item = str (item) 939 else: 940 item = str (item) 941 _log.warning("unknown type %s, string %s" % (type (item), item)) 942 return item953 """ 954 Instantiates a FormEngine based on the form ID or name from the backend 955 """ 956 try: 957 # it's a number: match to form ID 958 id = int (id) 959 cmd = 'select template, engine, pk from paperwork_templates where pk = %s' 960 except ValueError: 961 # it's a string, match to the form's name 962 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ? 963 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s' 964 result = gmPG.run_ro_query ('reference', cmd, None, id) 965 if result is None: 966 _log.error('error getting form [%s]' % id) 967 raise gmExceptions.FormError ('error getting form [%s]' % id) 968 if len(result) == 0: 969 _log.error('no form [%s] found' % id) 970 raise gmExceptions.FormError ('no such form found [%s]' % id) 971 if result[0][1] == 'L': 972 return LaTeXForm (result[0][2], result[0][0]) 973 elif result[0][1] == 'T': 974 return TextForm (result[0][2], result[0][0]) 975 else: 976 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id)) 977 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))978 #------------------------------------------------------------- 985 #------------------------------------------------------------- 986 987 test_letter = """ 988 \\documentclass{letter} 989 \\address{ $DOCTOR \\\\ 990 $DOCTORADDRESS} 991 \\signature{$DOCTOR} 992 993 \\begin{document} 994 \\begin{letter}{$RECIPIENTNAME \\\\ 995 $RECIPIENTADDRESS} 996 997 \\opening{Dear $RECIPIENTNAME} 998 999 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\ 1000 1001 $TEXT 1002 1003 \\ifnum$INCLUDEMEDS>0 1004 \\textbf{Medications List} 1005 1006 \\begin{tabular}{lll} 1007 $MEDSLIST 1008 \\end{tabular} 1009 \\fi 1010 1011 \\ifnum$INCLUDEDISEASES>0 1012 \\textbf{Disease List} 1013 1014 \\begin{tabular}{l} 1015 $DISEASELIST 1016 \\end{tabular} 1017 \\fi 1018 1019 \\closing{$CLOSING} 1020 1021 \\end{letter} 1022 \\end{document} 1023 """ 1024 10251027 f = open('../../test-area/ian/terry-form.tex') 1028 params = { 1029 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle", 1030 'DOCTORSNAME': 'Ian Haywood', 1031 'DOCTORSADDRESS': '1 Smith St\nMelbourne', 1032 'PATIENTNAME':'Joe Bloggs', 1033 'PATIENTADDRESS':'18 Fred St\nMelbourne', 1034 'REQUEST':'echocardiogram', 1035 'THERAPY':'on warfarin', 1036 'CLINICALNOTES':"""heard new murmur 1037 Here's some 1038 crap to demonstrate how it can cover multiple lines.""", 1039 'COPYADDRESS':'Karsten Hilbert\nLeipzig, Germany', 1040 'ROUTINE':1, 1041 'URGENT':0, 1042 'FAX':1, 1043 'PHONE':1, 1044 'PENSIONER':1, 1045 'VETERAN':0, 1046 'PADS':0, 1047 'INSTRUCTIONS':u'Take the blue pill, Neo' 1048 } 1049 form = LaTeXForm (1, f.read()) 1050 form.process (params) 1051 form.xdvi () 1052 form.cleanup ()10531055 form = LaTeXForm (2, test_letter) 1056 params = {'RECIPIENTNAME':'Dr. Richard Terry', 1057 'RECIPIENTADDRESS':'1 Main St\nNewcastle', 1058 'DOCTOR':'Dr. Ian Haywood', 1059 'DOCTORADDRESS':'1 Smith St\nMelbourne', 1060 'PATIENTNAME':'Joe Bloggs', 1061 'PATIENTADDRESS':'18 Fred St, Melbourne', 1062 'TEXT':"""This is the main text of the referral letter""", 1063 'DOB':'12/3/65', 1064 'INCLUDEMEDS':1, 1065 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]], 1066 'INCLUDEDISEASES':0, 'DISEASELIST':'', 1067 'CLOSING':'Yours sincerely,' 1068 } 1069 form.process (params) 1070 print os.getcwd () 1071 form.xdvi () 1072 form.cleanup ()1073 #------------------------------------------------------------1075 template = open('../../test-area/ian/Formularkopf-DE.tex') 1076 form = LaTeXForm(template=template.read()) 1077 params = { 1078 'PATIENT LASTNAME': 'Kirk', 1079 'PATIENT FIRSTNAME': 'James T.', 1080 'PATIENT STREET': 'Hauptstrasse', 1081 'PATIENT ZIP': '02999', 1082 'PATIENT TOWN': 'Gross Saerchen', 1083 'PATIENT DOB': '22.03.1931' 1084 } 1085 form.process(params) 1086 form.xdvi() 1087 form.cleanup()1088 1089 #============================================================ 1090 # main 1091 #------------------------------------------------------------ 1092 if __name__ == '__main__': 1093 1094 from Gnumed.pycommon import gmI18N, gmDateTime 1095 gmI18N.activate_locale() 1096 gmI18N.install_domain(domain='gnumed') 1097 gmDateTime.init() 1098 1099 #-------------------------------------------------------- 1100 # OOo 1101 #-------------------------------------------------------- 1106 #--------------------------------------------------------1108 srv = gmOOoConnector() 1109 doc = srv.open_document(filename = sys.argv[2]) 1110 print "document:", doc1111 #--------------------------------------------------------1113 doc = cOOoLetter(template_file = sys.argv[2]) 1114 doc.open_in_ooo() 1115 print "document:", doc 1116 raw_input('press <ENTER> to continue') 1117 doc.show() 1118 #doc.replace_placeholders() 1119 #doc.save_in_ooo('~/test_cOOoLetter.odt') 1120 # doc = None 1121 # doc.close_in_ooo() 1122 raw_input('press <ENTER> to continue')1123 #--------------------------------------------------------1125 try: 1126 doc = open_uri_in_ooo(filename=sys.argv[1]) 1127 except: 1128 _log.exception('cannot open [%s] in OOo' % sys.argv[1]) 1129 raise 1130 1131 class myCloseListener(unohelper.Base, oooXCloseListener): 1132 def disposing(self, evt): 1133 print "disposing:"1134 def notifyClosing(self, evt): 1135 print "notifyClosing:" 1136 def queryClosing(self, evt, owner): 1137 # owner is True/False whether I am the owner of the doc 1138 print "queryClosing:" 1139 1140 l = myCloseListener() 1141 doc.addCloseListener(l) 1142 1143 tfs = doc.getTextFields().createEnumeration() 1144 print tfs 1145 print dir(tfs) 1146 while tfs.hasMoreElements(): 1147 tf = tfs.nextElement() 1148 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'): 1149 print tf.getPropertyValue('PlaceHolder') 1150 print " ", tf.getPropertyValue('Hint') 1151 1152 # doc.close(True) # closes but leaves open the dedicated OOo window 1153 doc.dispose() # closes and disposes of the OOo window 1154 #--------------------------------------------------------1156 pat = gmPerson.ask_for_patient() 1157 if pat is None: 1158 return 1159 gmPerson.set_active_patient(patient = pat) 1160 1161 doc = cOOoLetter(template_file = sys.argv[2]) 1162 doc.open_in_ooo() 1163 print doc 1164 doc.show() 1165 #doc.replace_placeholders() 1166 #doc.save_in_ooo('~/test_cOOoLetter.odt') 1167 doc = None 1168 # doc.close_in_ooo() 1169 raw_input('press <ENTER> to continue')1170 #-------------------------------------------------------- 1171 # other 1172 #--------------------------------------------------------1174 template = cFormTemplate(aPK_obj = sys.argv[2]) 1175 print template 1176 print template.export_to_file()1177 #--------------------------------------------------------1179 template = cFormTemplate(aPK_obj = sys.argv[2]) 1180 template.update_template_from_file(filename = sys.argv[3])1181 #--------------------------------------------------------1183 pat = gmPerson.ask_for_patient() 1184 if pat is None: 1185 return 1186 gmPerson.set_active_patient(patient = pat) 1187 1188 gmPerson.gmCurrentProvider(provider = gmPerson.cStaff()) 1189 1190 path = os.path.abspath(sys.argv[2]) 1191 form = cLaTeXForm(template_file = path) 1192 1193 from Gnumed.wxpython import gmMacro 1194 ph = gmMacro.gmPlaceholderHandler() 1195 ph.debug = True 1196 instance_file = form.substitute_placeholders(data_source = ph) 1197 pdf_name = form.generate_output(instance_file = instance_file, cleanup = False) 1198 print "final PDF file is:", pdf_name1199 1200 #-------------------------------------------------------- 1201 #-------------------------------------------------------- 1202 if len(sys.argv) > 1 and sys.argv[1] == 'test': 1203 # now run the tests 1204 #test_au() 1205 #test_de() 1206 1207 # OOo 1208 #test_ooo_connect() 1209 #test_open_ooo_doc_from_srv() 1210 #test_open_ooo_doc_from_letter() 1211 #play_with_ooo() 1212 #test_cOOoLetter() 1213 1214 #test_cFormTemplate() 1215 #set_template_from_file() 1216 test_latex_form() 1217 1218 #============================================================ 1219 # $Log: gmForms.py,v $ 1220 # Revision 1.79 2010/01/31 16:33:32 ncq 1221 # - OOo can't search non-greedy :-( 1222 # - always return a string from placeholder replacement as OOo doesn't know what to do with None 1223 # 1224 # Revision 1.78 2010/01/21 08:40:38 ncq 1225 # - better logging, again 1226 # 1227 # Revision 1.77 2010/01/15 12:42:18 ncq 1228 # - factor out texify_string into gmTools 1229 # - handle None-return on placeholders in LaTeX engine 1230 # 1231 # Revision 1.76 2010/01/11 22:49:29 ncq 1232 # - Windows likely has pdflatex.exe 1233 # 1234 # Revision 1.75 2010/01/11 22:02:18 ncq 1235 # - properly log stack trace 1236 # 1237 # Revision 1.74 2010/01/09 18:28:49 ncq 1238 # - switch OOo access to named pipes 1239 # - better logging 1240 # 1241 # Revision 1.73 2010/01/08 13:49:14 ncq 1242 # - better logging 1243 # 1244 # Revision 1.72 2010/01/06 14:30:23 ncq 1245 # - start going from sockets to named pipes on OOo connection 1246 # - improved problem detection no PDF generation 1247 # 1248 # Revision 1.71 2010/01/03 18:17:30 ncq 1249 # - implement edit() on LaTeX forms 1250 # 1251 # Revision 1.70 2009/12/26 19:55:12 ncq 1252 # - wrong keyword 1253 # 1254 # Revision 1.69 2009/12/26 19:05:58 ncq 1255 # - start OOo wrapper 1256 # - check pdflatex return code 1257 # 1258 # Revision 1.68 2009/12/25 21:37:01 ncq 1259 # - properly make forms engine access generic 1260 # 1261 # Revision 1.67 2009/12/21 20:26:05 ncq 1262 # - instantiate() on templates 1263 # - cleanup 1264 # - improve form engine base class 1265 # - LaTeX form template engine 1266 # 1267 # Revision 1.66 2009/11/24 19:55:25 ncq 1268 # - comment out libxml2/libxslt for now 1269 # 1270 # Revision 1.65 2009/10/27 11:46:10 ncq 1271 # - crawl towards extracting SQL from XSLT 1272 # 1273 # Revision 1.64 2009/10/20 10:24:19 ncq 1274 # - inject Jerzys form code 1275 # 1276 # Revision 1.63 2009/09/13 18:25:54 ncq 1277 # - no more get-active-encounter() 1278 # 1279 # Revision 1.62 2009/03/10 14:18:11 ncq 1280 # - support new-style simpler placeholders in OOo docs 1281 # 1282 # Revision 1.61 2009/02/18 13:43:37 ncq 1283 # - get_unique_filename API change 1284 # 1285 # Revision 1.60 2008/09/02 18:59:01 ncq 1286 # - add "invisible" to ooo startup command as suggested by Jerzy 1287 # 1288 # Revision 1.59 2008/08/29 20:54:28 ncq 1289 # - cleanup 1290 # 1291 # Revision 1.58 2008/04/29 18:27:44 ncq 1292 # - cOOoConnector -> gmOOoConnector 1293 # 1294 # Revision 1.57 2008/02/25 17:31:41 ncq 1295 # - logging cleanup 1296 # 1297 # Revision 1.56 2008/01/30 13:34:50 ncq 1298 # - switch to std lib logging 1299 # 1300 # Revision 1.55 2007/11/10 20:49:22 ncq 1301 # - handle failing to connect to OOo much more gracefully 1302 # 1303 # Revision 1.54 2007/10/21 20:12:42 ncq 1304 # - make OOo startup settle time configurable 1305 # 1306 # Revision 1.53 2007/10/07 12:27:08 ncq 1307 # - workplace property now on gmSurgery.gmCurrentPractice() borg 1308 # 1309 # Revision 1.52 2007/09/01 23:31:36 ncq 1310 # - fix form template type phrasewheel query 1311 # - settable of external_version 1312 # - delete_form_template() 1313 # 1314 # Revision 1.51 2007/08/31 23:03:45 ncq 1315 # - improved docs 1316 # 1317 # Revision 1.50 2007/08/31 14:29:52 ncq 1318 # - optionalized UNO import 1319 # - create_form_template() 1320 # 1321 # Revision 1.49 2007/08/29 14:32:25 ncq 1322 # - remove data_modified property 1323 # - adjust to external_version 1324 # 1325 # Revision 1.48 2007/08/20 14:19:48 ncq 1326 # - engine_names 1327 # - match providers 1328 # - fix active_only logic in get_form_templates() and sort properly 1329 # - adjust to renamed database fields 1330 # - cleanup 1331 # 1332 # Revision 1.47 2007/08/15 09:18:07 ncq 1333 # - cleanup 1334 # - cOOoLetter.show() 1335 # 1336 # Revision 1.46 2007/08/13 22:04:32 ncq 1337 # - factor out placeholder handler 1338 # - use view in get_form_templates() 1339 # - add cFormTemplate() and test 1340 # - move export_form_template() to cFormTemplate.export_to_file() 1341 # 1342 # Revision 1.45 2007/08/11 23:44:01 ncq 1343 # - improve document close listener, get_form_templates(), cOOoLetter() 1344 # - better test suite 1345 # 1346 # Revision 1.44 2007/07/22 08:59:19 ncq 1347 # - get_form_templates() 1348 # - export_form_template() 1349 # - absolutize -> os.path.abspath 1350 # 1351 # Revision 1.43 2007/07/13 21:00:55 ncq 1352 # - apply uno.absolutize() 1353 # 1354 # Revision 1.42 2007/07/13 12:08:38 ncq 1355 # - do not touch unknown placeholders unless debugging is on, user might 1356 # want to use them elsewise 1357 # - use close listener 1358 # 1359 # Revision 1.41 2007/07/13 09:15:52 ncq 1360 # - fix faulty imports 1361 # 1362 # Revision 1.40 2007/07/11 21:12:50 ncq 1363 # - gmPlaceholderHandler() 1364 # - OOo API with test suite 1365 # 1366 # Revision 1.39 2007/02/17 14:08:52 ncq 1367 # - gmPerson.gmCurrentProvider.workplace now a property 1368 # 1369 # Revision 1.38 2006/12/23 15:23:11 ncq 1370 # - use gmShellAPI 1371 # 1372 # Revision 1.37 2006/10/25 07:17:40 ncq 1373 # - no more gmPG 1374 # - no more cClinItem 1375 # 1376 # Revision 1.36 2006/05/14 21:44:22 ncq 1377 # - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof 1378 # - remove use of gmWhoAmI.py 1379 # 1380 # Revision 1.35 2006/05/12 12:03:01 ncq 1381 # - whoami -> whereami 1382 # 1383 # Revision 1.34 2006/05/04 09:49:20 ncq 1384 # - get_clinical_record() -> get_emr() 1385 # - adjust to changes in set_active_patient() 1386 # - need explicit set_active_patient() after ask_for_patient() if wanted 1387 # 1388 # Revision 1.33 2005/12/31 18:01:54 ncq 1389 # - spelling of GNUmed 1390 # - clean up imports 1391 # 1392 # Revision 1.32 2005/11/06 12:31:30 ihaywood 1393 # I've discovered that most of what I'm trying to do with forms involves 1394 # re-implementing Cheetah (www.cheetahtemplate.org), so switch to using this. 1395 # 1396 # If this new dependency annoys you, don't import the module: it's not yet 1397 # used for any end-user functionality. 1398 # 1399 # Revision 1.31 2005/04/03 20:06:51 ncq 1400 # - comment on emr.get_active_episode being no more 1401 # 1402 # Revision 1.30 2005/03/06 08:17:02 ihaywood 1403 # forms: back to the old way, with support for LaTeX tables 1404 # 1405 # business objects now support generic linked tables, demographics 1406 # uses them to the same functionality as before (loading, no saving) 1407 # They may have no use outside of demographics, but saves much code already. 1408 # 1409 # Revision 1.29 2005/02/03 20:17:18 ncq 1410 # - get_demographic_record() -> get_identity() 1411 # 1412 # Revision 1.28 2005/02/01 10:16:07 ihaywood 1413 # refactoring of gmDemographicRecord and follow-on changes as discussed. 1414 # 1415 # gmTopPanel moves to gmHorstSpace 1416 # gmRichardSpace added -- example code at present, haven't even run it myself 1417 # (waiting on some icon .pngs from Richard) 1418 # 1419 # Revision 1.27 2005/01/31 10:37:26 ncq 1420 # - gmPatient.py -> gmPerson.py 1421 # 1422 # Revision 1.26 2004/08/20 13:19:06 ncq 1423 # - use getDBParam() 1424 # 1425 # Revision 1.25 2004/07/19 11:50:42 ncq 1426 # - cfg: what used to be called "machine" really is "workplace", so fix 1427 # 1428 # Revision 1.24 2004/06/28 12:18:52 ncq 1429 # - more id_* -> fk_* 1430 # 1431 # Revision 1.23 2004/06/26 07:33:55 ncq 1432 # - id_episode -> fk/pk_episode 1433 # 1434 # Revision 1.22 2004/06/18 13:32:37 ncq 1435 # - just some whitespace cleanup 1436 # 1437 # Revision 1.21 2004/06/17 11:36:13 ihaywood 1438 # Changes to the forms layer. 1439 # Now forms can have arbitrary Python expressions embedded in @..@ markup. 1440 # A proper forms HOWTO will appear in the wiki soon 1441 # 1442 # Revision 1.20 2004/06/08 00:56:39 ncq 1443 # - even if we don't need parameters we need to pass an 1444 # empty param list to gmPG.run_commit() 1445 # 1446 # Revision 1.19 2004/06/05 12:41:39 ihaywood 1447 # some more comments for gmForms.py 1448 # minor change to gmReferral.py: print last so bugs don't waste toner ;-) 1449 # 1450 # Revision 1.18 2004/05/28 13:13:15 ncq 1451 # - move currval() inside transaction in gmForm.store() 1452 # 1453 # Revision 1.17 2004/05/27 13:40:21 ihaywood 1454 # more work on referrals, still not there yet 1455 # 1456 # Revision 1.16 2004/04/21 22:26:48 ncq 1457 # - it is form_data.place_holder, not placeholder 1458 # 1459 # Revision 1.15 2004/04/21 22:05:28 ncq 1460 # - better error reporting 1461 # 1462 # Revision 1.14 2004/04/21 22:01:15 ncq 1463 # - generic store() for storing instance in form_data/form_instances 1464 # 1465 # Revision 1.13 2004/04/18 08:39:57 ihaywood 1466 # new config options 1467 # 1468 # Revision 1.12 2004/04/11 10:15:56 ncq 1469 # - load title in get_names() and use it superceding getFullName 1470 # 1471 # Revision 1.11 2004/04/10 01:48:31 ihaywood 1472 # can generate referral letters, output to xdvi at present 1473 # 1474 # Revision 1.10 2004/03/12 15:23:36 ncq 1475 # - cleanup, test_de 1476 # 1477 # Revision 1.9 2004/03/12 13:20:29 ncq 1478 # - remove unneeded import 1479 # - log keyword 1480 # 1481
Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:04 2010 | http://epydoc.sourceforge.net |