| 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 #============================================================
45
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])
55 #============================================================
57
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])
67 #============================================================
69
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])
85 #============================================================
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 #--------------------------------------------------------
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 filename
178 #--------------------------------------------------------
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)
192 #============================================================
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 templates
228 #------------------------------------------------------------
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 template
241 #------------------------------------------------------------
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 True
249 #============================================================
250 # OpenOffice API
251 #============================================================
252 uno = None
253 cOOoDocumentCloseListener = None
254
256 """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 = document
286
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 !
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 = None
333 #--------------------------------------------------------
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 doc
359 #--------------------------------------------------------
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.__desktop
396
397 desktop = property(_get_desktop, lambda x:x)
398 #------------------------------------------------------------
400
402
403 self.template_file = template_file
404 self.instance_type = instance_type
405 self.ooo_doc = None
406 #--------------------------------------------------------
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 True
426 #--------------------------------------------------------
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 = None
493 #--------------------------------------------------------
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 = None
505 #--------------------------------------------------------
506 # internal helpers
507 #--------------------------------------------------------
508
509 #============================================================
511 """Ancestor for forms."""
512
515 #--------------------------------------------------------
517 """Parse the template into an instance and replace placeholders with values."""
518 raise NotImplementedError
519 #--------------------------------------------------------
523 #--------------------------------------------------------
525 """Generate output suitable for further processing outside this class, e.g. printing."""
526 raise NotImplementedError
527 #--------------------------------------------------------
532 #--------------------------------------------------------
534 """
535 A sop to TeX which can't act as a true filter: to delete temporary files
536 """
537 pass
538 #--------------------------------------------------------
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 pass
546 #--------------------------------------------------------
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 status
594
595 #================================================================
596 # OOo template forms
597 #----------------------------------------------------------------
599 """A forms engine wrapping OOo."""
600
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)
609
610 #================================================================
611 # LaTeX template forms
612 #----------------------------------------------------------------
614 """A forms engine wrapping LaTeX."""
615
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 return
656 #--------------------------------------------------------
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 None
732 #--------------------------------------------------------
734 try:
735 os.remove(self.template_filename)
736 except:
737 _log.debug(u'cannot remove template file [%s]', self.template_filename)
738 #--------------------------------------------------------
739 # internal helpers
740 #--------------------------------------------------------
741
742 #------------------------------------------------------------
743 form_engines[u'L'] = cLaTeXForm
744 #------------------------------------------------------------
745 #------------------------------------------------------------
747 """A forms engine wrapping LaTeX.
748 """
752
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")
771
773 """
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)
778
780 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 True
792
794 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print')
795 self.exe (command)
796
805
806
807
808
809 #================================================================
810 # define a class for HTML forms (for printing)
811 #================================================================
813 """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
819
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 xml
840 #--------------------------------------------------------
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 True
892 #--------------------------------------------------------
896
897
898 #=====================================================
899 engines = {
900 u'L': cLaTeXForm
901 }
902 #=====================================================
903 #class LaTeXFilter(Cheetah.Filters.Filter):
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 item
943
944
945 #===========================================================
948
949 #============================================================
950 # convenience functions
951 #------------------------------------------------------------
953 """
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
1025
1027 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 ()
1053
1055 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:", doc
1111 #--------------------------------------------------------
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_name
1199
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 |