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.78 2010/01/21 08:40:38 ncq Exp $ 11 __version__ = "$Revision: 1.78 $" 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)400495 #-------------------------------------------------------- 496 # internal helpers 497 #-------------------------------------------------------- 498 499 #============================================================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.SearchString = handler.placeholder_regex 437 438 placeholder_instance = self.ooo_doc.findFirst(searcher) 439 while placeholder_instance is not None: 440 placeholder_instance.String = handler[placeholder_instance.String] 441 placeholder_instance = self.ooo_doc.findNext(placeholder_instance.End, searcher) 442 443 if not old_style_too: 444 return 445 446 # old style "explicit" placeholders 447 text_fields = self.ooo_doc.getTextFields().createEnumeration() 448 while text_fields.hasMoreElements(): 449 text_field = text_fields.nextElement() 450 451 # placeholder ? 452 if not text_field.supportsService('com.sun.star.text.TextField.JumpEdit'): 453 continue 454 # placeholder of type text ? 455 if text_field.PlaceHolderType != 0: 456 continue 457 458 replacement = handler[text_field.PlaceHolder] 459 if replacement is None: 460 continue 461 462 text_field.Anchor.setString(replacement)463 #--------------------------------------------------------465 if filename is not None: 466 target_url = uno.systemPathToFileUrl(os.path.abspath(os.path.expanduser(filename))) 467 save_args = ( 468 oooPropertyValue('Overwrite', 0, True, 0), 469 oooPropertyValue('FormatFilter', 0, 'swriter: StarOffice XML (Writer)', 0) 470 471 ) 472 # "store AS url" stores the doc, marks it unmodified and updates 473 # the internal media descriptor - as opposed to "store TO url" 474 self.ooo_doc.storeAsURL(target_url, save_args) 475 else: 476 self.ooo_doc.store()477 #--------------------------------------------------------479 self.ooo_doc.dispose() 480 pat = gmPerson.gmCurrentPatient() 481 pat.locked = False 482 self.ooo_doc = None483 #--------------------------------------------------------485 # get current file name from OOo, user may have used Save As 486 filename = uno.fileUrlToSystemPath(self.ooo_doc.URL) 487 # tell UI to import the file 488 gmDispatcher.send ( 489 signal = u'import_document_from_file', 490 filename = filename, 491 document_type = self.instance_type, 492 unlock_patient = True 493 ) 494 self.ooo_doc = None501 """Ancestor for forms.""" 502 505 #--------------------------------------------------------584 585 #================================================================ 586 # OOo template forms 587 #----------------------------------------------------------------507 """Parse the template into an instance and replace placeholders with values.""" 508 raise NotImplementedError509 #-------------------------------------------------------- 513 #--------------------------------------------------------515 """Generate output suitable for further processing outside this class, e.g. printing.""" 516 raise NotImplementedError517 #-------------------------------------------------------- 522 #--------------------------------------------------------524 """ 525 A sop to TeX which can't act as a true filter: to delete temporary files 526 """ 527 pass528 #--------------------------------------------------------530 """ 531 Executes the provided command. 532 If command cotains %F. it is substituted with the filename 533 Otherwise, the file is fed in on stdin 534 """ 535 pass536 #--------------------------------------------------------538 """Stores the parameters in the backend. 539 540 - link_obj can be a cursor, a connection or a service name 541 - assigning a cursor to link_obj allows the calling code to 542 group the call to store() into an enclosing transaction 543 (for an example see gmReferral.send_referral()...) 544 """ 545 # some forms may not have values ... 546 if params is None: 547 params = {} 548 patient_clinical = self.patient.get_emr() 549 encounter = patient_clinical.active_encounter['pk_encounter'] 550 # FIXME: get_active_episode is no more 551 #episode = patient_clinical.get_active_episode()['pk_episode'] 552 # generate "forever unique" name 553 cmd = "select name_short || ': <' || name_long || '::' || external_version || '>' from paperwork_templates where pk=%s"; 554 rows = gmPG.run_ro_query('reference', cmd, None, self.pk_def) 555 form_name = None 556 if rows is None: 557 _log.error('error retrieving form def for [%s]' % self.pk_def) 558 elif len(rows) == 0: 559 _log.error('no form def for [%s]' % self.pk_def) 560 else: 561 form_name = rows[0][0] 562 # we didn't get a name but want to store the form anyhow 563 if form_name is None: 564 form_name=time.time() # hopefully unique enough 565 # in one transaction 566 queries = [] 567 # - store form instance in form_instance 568 cmd = "insert into form_instances(fk_form_def, form_name, fk_episode, fk_encounter) values (%s, %s, %s, %s)" 569 queries.append((cmd, [self.pk_def, form_name, episode, encounter])) 570 # - store params in form_data 571 for key in params.keys(): 572 cmd = """ 573 insert into form_data(fk_instance, place_holder, value) 574 values ((select currval('form_instances_pk_seq')), %s, %s::text) 575 """ 576 queries.append((cmd, [key, params[key]])) 577 # - get inserted PK 578 queries.append(("select currval ('form_instances_pk_seq')", [])) 579 status, err = gmPG.run_commit('historica', queries, True) 580 if status is None: 581 _log.error('failed to store form [%s] (%s): %s' % (self.pk_def, form_name, err)) 582 return None 583 return status589 """A forms engine wrapping OOo.""" 590599 600 #================================================================ 601 # LaTeX template forms 602 #----------------------------------------------------------------592 super(self.__class__, self).__init__(template_file = template_file) 593 594 595 path, ext = os.path.splitext(self.template_filename) 596 if ext in [r'', r'.']: 597 ext = r'.tex' 598 self.instance_filename = r'%s-instance%s' % (path, ext)604 """A forms engine wrapping LaTeX.""" 605728 #-------------------------------------------------------- 729 # internal helpers 730 #-------------------------------------------------------- 731 732 #------------------------------------------------------------ 733 form_engines[u'L'] = cLaTeXForm 734 #------------------------------------------------------------ 735 #------------------------------------------------------------607 super(self.__class__, self).__init__(template_file = template_file) 608 path, ext = os.path.splitext(self.template_filename) 609 if ext in [r'', r'.']: 610 ext = r'.tex' 611 self.instance_filename = r'%s-instance%s' % (path, ext)612 #--------------------------------------------------------614 615 template_file = codecs.open(self.template_filename, 'rU', 'utf8') 616 instance_file = codecs.open(self.instance_filename, 'wb', 'utf8') 617 618 for line in template_file: 619 620 if line.strip() in [u'', u'\r', u'\n', u'\r\n']: 621 instance_file.write(line) 622 continue 623 624 # 1) find placeholders in this line 625 placeholders_in_line = regex.findall(data_source.placeholder_regex, line, regex.IGNORECASE) 626 # 2) and replace them 627 for placeholder in placeholders_in_line: 628 #line = line.replace(placeholder, self._texify_string(data_source[placeholder])) 629 try: 630 val = data_source[placeholder] 631 except: 632 _log.exception(val) 633 val = _('error with placeholder [%s]' % placeholder) 634 635 if val is None: 636 val = _('error with placeholder [%s]' % placeholder) 637 638 line = line.replace(placeholder, val) 639 640 instance_file.write(line) 641 642 instance_file.close() 643 template_file.close() 644 645 return646 #--------------------------------------------------------648 649 mimetypes = [ 650 u'application/x-latex', 651 u'application/x-tex', 652 u'text/plain' 653 ] 654 655 for mimetype in mimetypes: 656 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename) 657 658 if editor_cmd is None: 659 editor_cmd = u'sensible-editor %s' % self.instance_filename 660 661 return gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)662 #--------------------------------------------------------664 665 if instance_file is None: 666 instance_file = self.instance_filename 667 668 try: 669 open(instance_file, 'r').close() 670 except: 671 _log.exception('cannot access form instance file [%s]', instance_file) 672 gmLog2.log_stack_trace() 673 return None 674 675 self.instance_filename = instance_file 676 677 _log.debug('ignoring <format> directive [%s], generating PDF', format) 678 679 # create sandbox for LaTeX to play in 680 sandbox_dir = os.path.splitext(self.template_filename)[0] 681 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir) 682 683 old_cwd = os.getcwd() 684 _log.debug('CWD: [%s]', old_cwd) 685 686 gmTools.mkdir(sandbox_dir) 687 os.chdir(sandbox_dir) 688 689 sandboxed_instance_filename = os.path.join(sandbox_dir, os.path.split(self.instance_filename)[1]) 690 shutil.move(self.instance_filename, sandboxed_instance_filename) 691 692 # LaTeX can need up to three runs to get cross-references et al right 693 if platform.system() == 'Windows': 694 cmd = r'pdflatex.exe -interaction nonstopmode %s' % sandboxed_instance_filename 695 else: 696 cmd = r'pdflatex -interaction nonstopmode %s' % sandboxed_instance_filename 697 for run in [1, 2, 3]: 698 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = True): 699 _log.error('problem running pdflatex, cannot generate form output') 700 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True) 701 return None 702 703 os.chdir(old_cwd) 704 pdf_name = u'%s.pdf' % os.path.splitext(sandboxed_instance_filename)[0] 705 shutil.move(pdf_name, os.path.split(self.instance_filename)[0]) 706 pdf_name = u'%s.pdf' % os.path.splitext(self.instance_filename)[0] 707 708 # cleanup LaTeX sandbox ? 709 if cleanup: 710 for fname in os.listdir(sandbox_dir): 711 os.remove(os.path.join(sandbox_dir, fname)) 712 os.rmdir(sandbox_dir) 713 714 try: 715 open(pdf_name, 'r').close() 716 return pdf_name 717 except IOError: 718 _log.exception('cannot open target PDF: %s', pdf_name) 719 720 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True) 721 return None722 #--------------------------------------------------------724 try: 725 os.remove(self.template_filename) 726 except: 727 _log.debug(u'cannot remove template file [%s]', self.template_filename)737 """A forms engine wrapping LaTeX. 738 """ 742795 796 797 798 799 #================================================================ 800 # define a class for HTML forms (for printing) 801 #================================================================744 try: 745 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params]) 746 # create a 'sandbox' directory for LaTeX to play in 747 self.tmp = tempfile.mktemp () 748 os.makedirs (self.tmp) 749 self.oldcwd = os.getcwd () 750 os.chdir (self.tmp) 751 stdin = os.popen ("latex", "w", 2048) 752 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout 753 # FIXME: send LaTeX output to the logger 754 stdin.close () 755 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True): 756 raise FormError ('DVIPS returned error') 757 except EnvironmentError, e: 758 _log.error(e.strerror) 759 raise FormError (e.strerror) 760 return file ("texput.ps")761763 """ 764 For testing purposes, runs Xdvi on the intermediate TeX output 765 WARNING: don't try this on Windows 766 """ 767 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)768770 if "%F" in command: 771 command.replace ("%F", "texput.ps") 772 else: 773 command = "%s < texput.ps" % command 774 try: 775 if not gmShellAPI.run_command_in_shell(command, blocking=True): 776 _log.error("external command %s returned non-zero" % command) 777 raise FormError ('external command %s returned error' % command) 778 except EnvironmentError, e: 779 _log.error(e.strerror) 780 raise FormError (e.strerror) 781 return True782784 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print') 785 self.exe (command)786803 """This class can create XML document from requested data, 804 then process it with XSLT template and display results 805 """ 806 807 # FIXME: make the path configurable ? 808 _preview_program = u'oowriter ' #this program must be in the system PATH 809886 887 888 #===================================================== 889 engines = { 890 u'L': cLaTeXForm 891 } 892 #===================================================== 893 #class LaTeXFilter(Cheetah.Filters.Filter):811 812 if template is None: 813 raise ValueError(u'%s: cannot create form instance without a template' % __name__) 814 815 cFormEngine.__init__(self, template = template) 816 817 self._FormData = None 818 819 # here we know/can assume that the template was stored as a utf-8 820 # encoded string so use that conversion to create unicode: 821 #self._XSLTData = unicode(str(template.template_data), 'UTF-8') 822 # but in fact, unicode() knows how to handle buffers, so simply: 823 self._XSLTData = unicode(self.template.template_data, 'UTF-8', 'strict') 824 825 # we must still devise a method of extracting the SQL query: 826 # - either by retrieving it from a particular tag in the XSLT or 827 # - by making the stored template actually be a dict which, unpickled, 828 # has the keys "xslt" and "sql" 829 self._SQL_query = u'select 1' #this sql query must output valid xml830 #-------------------------------------------------------- 831 # external API 832 #--------------------------------------------------------834 """get data from backend and process it with XSLT template to produce readable output""" 835 836 # extract SQL (this is wrong but displays what is intended) 837 xslt = libxml2.parseDoc(self._XSLTData) 838 root = xslt.children 839 for child in root: 840 if child.type == 'element': 841 self._SQL_query = child.content 842 break 843 844 # retrieve data from backend 845 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False) 846 847 __header = '<?xml version="1.0" encoding="UTF-8"?>\n' 848 __body = rows[0][0] 849 850 # process XML data according to supplied XSLT, producing HTML 851 self._XMLData =__header + __body 852 style = libxslt.parseStylesheetDoc(xslt) 853 xml = libxml2.parseDoc(self._XMLData) 854 html = style.applyStylesheet(xml, None) 855 self._FormData = html.serialize() 856 857 style.freeStylesheet() 858 xml.freeDoc() 859 html.freeDoc()860 #--------------------------------------------------------862 if self._FormData is None: 863 raise ValueError, u'Preview request for empty form. Make sure the form is properly initialized and process() was performed' 864 865 fname = gmTools.get_unique_filename(prefix = u'gm_XSLT_form-', suffix = u'.html') 866 #html_file = os.open(fname, 'wb') 867 #html_file.write(self._FormData.encode('UTF-8')) 868 html_file = codecs.open(fname, 'wb', 'utf8', 'strict') # or 'replace' ? 869 html_file.write(self._FormData) 870 html_file.close() 871 872 cmd = u'%s %s' % (self.__class__._preview_program, fname) 873 874 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False): 875 _log.error('%s: cannot launch report preview program' % __name__) 876 return False 877 878 #os.unlink(self.filename) #delete file 879 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK) 880 881 return True882 #--------------------------------------------------------933 934 935 #=========================================================== 938 939 #============================================================ 940 # convenience functions 941 #------------------------------------------------------------896 """ 897 Convience function to escape ISO-Latin-1 strings for TeX output 898 WARNING: not all ISO-Latin-1 characters are expressible in TeX 899 FIXME: nevertheless, there are a few more we could support 900 901 Also intelligently convert lists and tuples into TeX-style table lines 902 """ 903 if type (item) is types.UnicodeType or type (item) is types.StringType: 904 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX? 905 item = item.replace ("&", "\\&") 906 item = item.replace ("$", "\\$") 907 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now 908 item = item.replace ("\n", "\\\\ ") 909 if len (item.strip ()) == 0: 910 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it 911 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX 912 if type (item) is types.UnicodeType: 913 item = item.encode ('latin-1', 'replace') 914 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}', 915 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions 916 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`', 917 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}', 918 '\xc7':'\\c{C}', '\xc8':'\\`{E}', 919 '\xa1': '!`', 920 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent'} 921 for k, i in trans.items (): 922 item = item.replace (k, i) 923 elif type (item) is types.ListType or type (item) is types.TupleType: 924 item = string.join ([self.filter (i, ' & ') for i in item], table_sep) 925 elif item is None: 926 item = '\\relax % Python None\n' 927 elif type (item) is types.IntType or type (item) is types.FloatType: 928 item = str (item) 929 else: 930 item = str (item) 931 _log.warning("unknown type %s, string %s" % (type (item), item)) 932 return item943 """ 944 Instantiates a FormEngine based on the form ID or name from the backend 945 """ 946 try: 947 # it's a number: match to form ID 948 id = int (id) 949 cmd = 'select template, engine, pk from paperwork_templates where pk = %s' 950 except ValueError: 951 # it's a string, match to the form's name 952 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ? 953 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s' 954 result = gmPG.run_ro_query ('reference', cmd, None, id) 955 if result is None: 956 _log.error('error getting form [%s]' % id) 957 raise gmExceptions.FormError ('error getting form [%s]' % id) 958 if len(result) == 0: 959 _log.error('no form [%s] found' % id) 960 raise gmExceptions.FormError ('no such form found [%s]' % id) 961 if result[0][1] == 'L': 962 return LaTeXForm (result[0][2], result[0][0]) 963 elif result[0][1] == 'T': 964 return TextForm (result[0][2], result[0][0]) 965 else: 966 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id)) 967 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))968 #------------------------------------------------------------- 975 #------------------------------------------------------------- 976 977 test_letter = """ 978 \\documentclass{letter} 979 \\address{ $DOCTOR \\\\ 980 $DOCTORADDRESS} 981 \\signature{$DOCTOR} 982 983 \\begin{document} 984 \\begin{letter}{$RECIPIENTNAME \\\\ 985 $RECIPIENTADDRESS} 986 987 \\opening{Dear $RECIPIENTNAME} 988 989 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\ 990 991 $TEXT 992 993 \\ifnum$INCLUDEMEDS>0 994 \\textbf{Medications List} 995 996 \\begin{tabular}{lll} 997 $MEDSLIST 998 \\end{tabular} 999 \\fi 1000 1001 \\ifnum$INCLUDEDISEASES>0 1002 \\textbf{Disease List} 1003 1004 \\begin{tabular}{l} 1005 $DISEASELIST 1006 \\end{tabular} 1007 \\fi 1008 1009 \\closing{$CLOSING} 1010 1011 \\end{letter} 1012 \\end{document} 1013 """ 1014 10151017 f = open('../../test-area/ian/terry-form.tex') 1018 params = { 1019 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle", 1020 'DOCTORSNAME': 'Ian Haywood', 1021 'DOCTORSADDRESS': '1 Smith St\nMelbourne', 1022 'PATIENTNAME':'Joe Bloggs', 1023 'PATIENTADDRESS':'18 Fred St\nMelbourne', 1024 'REQUEST':'echocardiogram', 1025 'THERAPY':'on warfarin', 1026 'CLINICALNOTES':"""heard new murmur 1027 Here's some 1028 crap to demonstrate how it can cover multiple lines.""", 1029 'COPYADDRESS':'Karsten Hilbert\nLeipzig, Germany', 1030 'ROUTINE':1, 1031 'URGENT':0, 1032 'FAX':1, 1033 'PHONE':1, 1034 'PENSIONER':1, 1035 'VETERAN':0, 1036 'PADS':0, 1037 'INSTRUCTIONS':u'Take the blue pill, Neo' 1038 } 1039 form = LaTeXForm (1, f.read()) 1040 form.process (params) 1041 form.xdvi () 1042 form.cleanup ()10431045 form = LaTeXForm (2, test_letter) 1046 params = {'RECIPIENTNAME':'Dr. Richard Terry', 1047 'RECIPIENTADDRESS':'1 Main St\nNewcastle', 1048 'DOCTOR':'Dr. Ian Haywood', 1049 'DOCTORADDRESS':'1 Smith St\nMelbourne', 1050 'PATIENTNAME':'Joe Bloggs', 1051 'PATIENTADDRESS':'18 Fred St, Melbourne', 1052 'TEXT':"""This is the main text of the referral letter""", 1053 'DOB':'12/3/65', 1054 'INCLUDEMEDS':1, 1055 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]], 1056 'INCLUDEDISEASES':0, 'DISEASELIST':'', 1057 'CLOSING':'Yours sincerely,' 1058 } 1059 form.process (params) 1060 print os.getcwd () 1061 form.xdvi () 1062 form.cleanup ()1063 #------------------------------------------------------------1065 template = open('../../test-area/ian/Formularkopf-DE.tex') 1066 form = LaTeXForm(template=template.read()) 1067 params = { 1068 'PATIENT LASTNAME': 'Kirk', 1069 'PATIENT FIRSTNAME': 'James T.', 1070 'PATIENT STREET': 'Hauptstrasse', 1071 'PATIENT ZIP': '02999', 1072 'PATIENT TOWN': 'Gross Saerchen', 1073 'PATIENT DOB': '22.03.1931' 1074 } 1075 form.process(params) 1076 form.xdvi() 1077 form.cleanup()1078 1079 #============================================================ 1080 # main 1081 #------------------------------------------------------------ 1082 if __name__ == '__main__': 1083 1084 from Gnumed.pycommon import gmI18N, gmDateTime 1085 gmI18N.activate_locale() 1086 gmI18N.install_domain(domain='gnumed') 1087 gmDateTime.init() 1088 1089 #-------------------------------------------------------- 1090 # OOo 1091 #-------------------------------------------------------- 1096 #--------------------------------------------------------1098 srv = gmOOoConnector() 1099 doc = srv.open_document(filename = sys.argv[2]) 1100 print "document:", doc1101 #--------------------------------------------------------1103 doc = cOOoLetter(template_file = sys.argv[2]) 1104 doc.open_in_ooo() 1105 print "document:", doc 1106 raw_input('press <ENTER> to continue') 1107 doc.show() 1108 #doc.replace_placeholders() 1109 #doc.save_in_ooo('~/test_cOOoLetter.odt') 1110 # doc = None 1111 # doc.close_in_ooo() 1112 raw_input('press <ENTER> to continue')1113 #--------------------------------------------------------1115 try: 1116 doc = open_uri_in_ooo(filename=sys.argv[1]) 1117 except: 1118 _log.exception('cannot open [%s] in OOo' % sys.argv[1]) 1119 raise 1120 1121 class myCloseListener(unohelper.Base, oooXCloseListener): 1122 def disposing(self, evt): 1123 print "disposing:"1124 def notifyClosing(self, evt): 1125 print "notifyClosing:" 1126 def queryClosing(self, evt, owner): 1127 # owner is True/False whether I am the owner of the doc 1128 print "queryClosing:" 1129 1130 l = myCloseListener() 1131 doc.addCloseListener(l) 1132 1133 tfs = doc.getTextFields().createEnumeration() 1134 print tfs 1135 print dir(tfs) 1136 while tfs.hasMoreElements(): 1137 tf = tfs.nextElement() 1138 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'): 1139 print tf.getPropertyValue('PlaceHolder') 1140 print " ", tf.getPropertyValue('Hint') 1141 1142 # doc.close(True) # closes but leaves open the dedicated OOo window 1143 doc.dispose() # closes and disposes of the OOo window 1144 #--------------------------------------------------------1146 pat = gmPerson.ask_for_patient() 1147 if pat is None: 1148 return 1149 gmPerson.set_active_patient(patient = pat) 1150 1151 doc = cOOoLetter(template_file = sys.argv[2]) 1152 doc.open_in_ooo() 1153 print doc 1154 doc.show() 1155 #doc.replace_placeholders() 1156 #doc.save_in_ooo('~/test_cOOoLetter.odt') 1157 doc = None 1158 # doc.close_in_ooo() 1159 raw_input('press <ENTER> to continue')1160 #-------------------------------------------------------- 1161 # other 1162 #--------------------------------------------------------1164 template = cFormTemplate(aPK_obj = sys.argv[2]) 1165 print template 1166 print template.export_to_file()1167 #--------------------------------------------------------1169 template = cFormTemplate(aPK_obj = sys.argv[2]) 1170 template.update_template_from_file(filename = sys.argv[3])1171 #--------------------------------------------------------1173 pat = gmPerson.ask_for_patient() 1174 if pat is None: 1175 return 1176 gmPerson.set_active_patient(patient = pat) 1177 1178 gmPerson.gmCurrentProvider(provider = gmPerson.cStaff()) 1179 1180 path = os.path.abspath(sys.argv[2]) 1181 form = cLaTeXForm(template_file = path) 1182 1183 from Gnumed.wxpython import gmMacro 1184 ph = gmMacro.gmPlaceholderHandler() 1185 ph.debug = True 1186 instance_file = form.substitute_placeholders(data_source = ph) 1187 pdf_name = form.generate_output(instance_file = instance_file, cleanup = False) 1188 print "final PDF file is:", pdf_name1189 1190 #-------------------------------------------------------- 1191 #-------------------------------------------------------- 1192 if len(sys.argv) > 1 and sys.argv[1] == 'test': 1193 # now run the tests 1194 #test_au() 1195 #test_de() 1196 1197 # OOo 1198 #test_ooo_connect() 1199 #test_open_ooo_doc_from_srv() 1200 #test_open_ooo_doc_from_letter() 1201 #play_with_ooo() 1202 #test_cOOoLetter() 1203 1204 #test_cFormTemplate() 1205 #set_template_from_file() 1206 test_latex_form() 1207 1208 #============================================================ 1209 # $Log: gmForms.py,v $ 1210 # Revision 1.78 2010/01/21 08:40:38 ncq 1211 # - better logging, again 1212 # 1213 # Revision 1.77 2010/01/15 12:42:18 ncq 1214 # - factor out texify_string into gmTools 1215 # - handle None-return on placeholders in LaTeX engine 1216 # 1217 # Revision 1.76 2010/01/11 22:49:29 ncq 1218 # - Windows likely has pdflatex.exe 1219 # 1220 # Revision 1.75 2010/01/11 22:02:18 ncq 1221 # - properly log stack trace 1222 # 1223 # Revision 1.74 2010/01/09 18:28:49 ncq 1224 # - switch OOo access to named pipes 1225 # - better logging 1226 # 1227 # Revision 1.73 2010/01/08 13:49:14 ncq 1228 # - better logging 1229 # 1230 # Revision 1.72 2010/01/06 14:30:23 ncq 1231 # - start going from sockets to named pipes on OOo connection 1232 # - improved problem detection no PDF generation 1233 # 1234 # Revision 1.71 2010/01/03 18:17:30 ncq 1235 # - implement edit() on LaTeX forms 1236 # 1237 # Revision 1.70 2009/12/26 19:55:12 ncq 1238 # - wrong keyword 1239 # 1240 # Revision 1.69 2009/12/26 19:05:58 ncq 1241 # - start OOo wrapper 1242 # - check pdflatex return code 1243 # 1244 # Revision 1.68 2009/12/25 21:37:01 ncq 1245 # - properly make forms engine access generic 1246 # 1247 # Revision 1.67 2009/12/21 20:26:05 ncq 1248 # - instantiate() on templates 1249 # - cleanup 1250 # - improve form engine base class 1251 # - LaTeX form template engine 1252 # 1253 # Revision 1.66 2009/11/24 19:55:25 ncq 1254 # - comment out libxml2/libxslt for now 1255 # 1256 # Revision 1.65 2009/10/27 11:46:10 ncq 1257 # - crawl towards extracting SQL from XSLT 1258 # 1259 # Revision 1.64 2009/10/20 10:24:19 ncq 1260 # - inject Jerzys form code 1261 # 1262 # Revision 1.63 2009/09/13 18:25:54 ncq 1263 # - no more get-active-encounter() 1264 # 1265 # Revision 1.62 2009/03/10 14:18:11 ncq 1266 # - support new-style simpler placeholders in OOo docs 1267 # 1268 # Revision 1.61 2009/02/18 13:43:37 ncq 1269 # - get_unique_filename API change 1270 # 1271 # Revision 1.60 2008/09/02 18:59:01 ncq 1272 # - add "invisible" to ooo startup command as suggested by Jerzy 1273 # 1274 # Revision 1.59 2008/08/29 20:54:28 ncq 1275 # - cleanup 1276 # 1277 # Revision 1.58 2008/04/29 18:27:44 ncq 1278 # - cOOoConnector -> gmOOoConnector 1279 # 1280 # Revision 1.57 2008/02/25 17:31:41 ncq 1281 # - logging cleanup 1282 # 1283 # Revision 1.56 2008/01/30 13:34:50 ncq 1284 # - switch to std lib logging 1285 # 1286 # Revision 1.55 2007/11/10 20:49:22 ncq 1287 # - handle failing to connect to OOo much more gracefully 1288 # 1289 # Revision 1.54 2007/10/21 20:12:42 ncq 1290 # - make OOo startup settle time configurable 1291 # 1292 # Revision 1.53 2007/10/07 12:27:08 ncq 1293 # - workplace property now on gmSurgery.gmCurrentPractice() borg 1294 # 1295 # Revision 1.52 2007/09/01 23:31:36 ncq 1296 # - fix form template type phrasewheel query 1297 # - settable of external_version 1298 # - delete_form_template() 1299 # 1300 # Revision 1.51 2007/08/31 23:03:45 ncq 1301 # - improved docs 1302 # 1303 # Revision 1.50 2007/08/31 14:29:52 ncq 1304 # - optionalized UNO import 1305 # - create_form_template() 1306 # 1307 # Revision 1.49 2007/08/29 14:32:25 ncq 1308 # - remove data_modified property 1309 # - adjust to external_version 1310 # 1311 # Revision 1.48 2007/08/20 14:19:48 ncq 1312 # - engine_names 1313 # - match providers 1314 # - fix active_only logic in get_form_templates() and sort properly 1315 # - adjust to renamed database fields 1316 # - cleanup 1317 # 1318 # Revision 1.47 2007/08/15 09:18:07 ncq 1319 # - cleanup 1320 # - cOOoLetter.show() 1321 # 1322 # Revision 1.46 2007/08/13 22:04:32 ncq 1323 # - factor out placeholder handler 1324 # - use view in get_form_templates() 1325 # - add cFormTemplate() and test 1326 # - move export_form_template() to cFormTemplate.export_to_file() 1327 # 1328 # Revision 1.45 2007/08/11 23:44:01 ncq 1329 # - improve document close listener, get_form_templates(), cOOoLetter() 1330 # - better test suite 1331 # 1332 # Revision 1.44 2007/07/22 08:59:19 ncq 1333 # - get_form_templates() 1334 # - export_form_template() 1335 # - absolutize -> os.path.abspath 1336 # 1337 # Revision 1.43 2007/07/13 21:00:55 ncq 1338 # - apply uno.absolutize() 1339 # 1340 # Revision 1.42 2007/07/13 12:08:38 ncq 1341 # - do not touch unknown placeholders unless debugging is on, user might 1342 # want to use them elsewise 1343 # - use close listener 1344 # 1345 # Revision 1.41 2007/07/13 09:15:52 ncq 1346 # - fix faulty imports 1347 # 1348 # Revision 1.40 2007/07/11 21:12:50 ncq 1349 # - gmPlaceholderHandler() 1350 # - OOo API with test suite 1351 # 1352 # Revision 1.39 2007/02/17 14:08:52 ncq 1353 # - gmPerson.gmCurrentProvider.workplace now a property 1354 # 1355 # Revision 1.38 2006/12/23 15:23:11 ncq 1356 # - use gmShellAPI 1357 # 1358 # Revision 1.37 2006/10/25 07:17:40 ncq 1359 # - no more gmPG 1360 # - no more cClinItem 1361 # 1362 # Revision 1.36 2006/05/14 21:44:22 ncq 1363 # - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof 1364 # - remove use of gmWhoAmI.py 1365 # 1366 # Revision 1.35 2006/05/12 12:03:01 ncq 1367 # - whoami -> whereami 1368 # 1369 # Revision 1.34 2006/05/04 09:49:20 ncq 1370 # - get_clinical_record() -> get_emr() 1371 # - adjust to changes in set_active_patient() 1372 # - need explicit set_active_patient() after ask_for_patient() if wanted 1373 # 1374 # Revision 1.33 2005/12/31 18:01:54 ncq 1375 # - spelling of GNUmed 1376 # - clean up imports 1377 # 1378 # Revision 1.32 2005/11/06 12:31:30 ihaywood 1379 # I've discovered that most of what I'm trying to do with forms involves 1380 # re-implementing Cheetah (www.cheetahtemplate.org), so switch to using this. 1381 # 1382 # If this new dependency annoys you, don't import the module: it's not yet 1383 # used for any end-user functionality. 1384 # 1385 # Revision 1.31 2005/04/03 20:06:51 ncq 1386 # - comment on emr.get_active_episode being no more 1387 # 1388 # Revision 1.30 2005/03/06 08:17:02 ihaywood 1389 # forms: back to the old way, with support for LaTeX tables 1390 # 1391 # business objects now support generic linked tables, demographics 1392 # uses them to the same functionality as before (loading, no saving) 1393 # They may have no use outside of demographics, but saves much code already. 1394 # 1395 # Revision 1.29 2005/02/03 20:17:18 ncq 1396 # - get_demographic_record() -> get_identity() 1397 # 1398 # Revision 1.28 2005/02/01 10:16:07 ihaywood 1399 # refactoring of gmDemographicRecord and follow-on changes as discussed. 1400 # 1401 # gmTopPanel moves to gmHorstSpace 1402 # gmRichardSpace added -- example code at present, haven't even run it myself 1403 # (waiting on some icon .pngs from Richard) 1404 # 1405 # Revision 1.27 2005/01/31 10:37:26 ncq 1406 # - gmPatient.py -> gmPerson.py 1407 # 1408 # Revision 1.26 2004/08/20 13:19:06 ncq 1409 # - use getDBParam() 1410 # 1411 # Revision 1.25 2004/07/19 11:50:42 ncq 1412 # - cfg: what used to be called "machine" really is "workplace", so fix 1413 # 1414 # Revision 1.24 2004/06/28 12:18:52 ncq 1415 # - more id_* -> fk_* 1416 # 1417 # Revision 1.23 2004/06/26 07:33:55 ncq 1418 # - id_episode -> fk/pk_episode 1419 # 1420 # Revision 1.22 2004/06/18 13:32:37 ncq 1421 # - just some whitespace cleanup 1422 # 1423 # Revision 1.21 2004/06/17 11:36:13 ihaywood 1424 # Changes to the forms layer. 1425 # Now forms can have arbitrary Python expressions embedded in @..@ markup. 1426 # A proper forms HOWTO will appear in the wiki soon 1427 # 1428 # Revision 1.20 2004/06/08 00:56:39 ncq 1429 # - even if we don't need parameters we need to pass an 1430 # empty param list to gmPG.run_commit() 1431 # 1432 # Revision 1.19 2004/06/05 12:41:39 ihaywood 1433 # some more comments for gmForms.py 1434 # minor change to gmReferral.py: print last so bugs don't waste toner ;-) 1435 # 1436 # Revision 1.18 2004/05/28 13:13:15 ncq 1437 # - move currval() inside transaction in gmForm.store() 1438 # 1439 # Revision 1.17 2004/05/27 13:40:21 ihaywood 1440 # more work on referrals, still not there yet 1441 # 1442 # Revision 1.16 2004/04/21 22:26:48 ncq 1443 # - it is form_data.place_holder, not placeholder 1444 # 1445 # Revision 1.15 2004/04/21 22:05:28 ncq 1446 # - better error reporting 1447 # 1448 # Revision 1.14 2004/04/21 22:01:15 ncq 1449 # - generic store() for storing instance in form_data/form_instances 1450 # 1451 # Revision 1.13 2004/04/18 08:39:57 ihaywood 1452 # new config options 1453 # 1454 # Revision 1.12 2004/04/11 10:15:56 ncq 1455 # - load title in get_names() and use it superceding getFullName 1456 # 1457 # Revision 1.11 2004/04/10 01:48:31 ihaywood 1458 # can generate referral letters, output to xdvi at present 1459 # 1460 # Revision 1.10 2004/03/12 15:23:36 ncq 1461 # - cleanup, test_de 1462 # 1463 # Revision 1.9 2004/03/12 13:20:29 ncq 1464 # - remove unneeded import 1465 # - log keyword 1466 # 1467
Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Sat Jan 23 04:06:15 2010 | http://epydoc.sourceforge.net |