1
2 """GNUmed macro primitives.
3
4 This module implements functions a macro can legally use.
5 """
6
7 __author__ = "K.Hilbert <karsten.hilbert@gmx.net>"
8
9 import sys
10 import time
11 import random
12 import types
13 import logging
14 import os
15 import codecs
16
17
18 import wx
19
20
21 if __name__ == '__main__':
22 sys.path.insert(0, '../../')
23 from Gnumed.pycommon import gmI18N
24 if __name__ == '__main__':
25 gmI18N.activate_locale()
26 gmI18N.install_domain()
27 from Gnumed.pycommon import gmGuiBroker
28 from Gnumed.pycommon import gmTools
29 from Gnumed.pycommon import gmBorg
30 from Gnumed.pycommon import gmExceptions
31 from Gnumed.pycommon import gmCfg2
32 from Gnumed.pycommon import gmDateTime
33 from Gnumed.pycommon import gmMimeLib
34
35 from Gnumed.business import gmPerson
36 from Gnumed.business import gmStaff
37 from Gnumed.business import gmDemographicRecord
38 from Gnumed.business import gmMedication
39 from Gnumed.business import gmPathLab
40 from Gnumed.business import gmPersonSearch
41 from Gnumed.business import gmVaccination
42 from Gnumed.business import gmKeywordExpansion
43 from Gnumed.business import gmPraxis
44
45 from Gnumed.wxpython import gmGuiHelpers
46 from Gnumed.wxpython import gmNarrativeWidgets
47 from Gnumed.wxpython import gmPatSearchWidgets
48 from Gnumed.wxpython import gmPersonContactWidgets
49 from Gnumed.wxpython import gmPlugin
50 from Gnumed.wxpython import gmEMRStructWidgets
51 from Gnumed.wxpython import gmEncounterWidgets
52 from Gnumed.wxpython import gmListWidgets
53 from Gnumed.wxpython import gmDemographicsWidgets
54 from Gnumed.wxpython import gmDocumentWidgets
55 from Gnumed.wxpython import gmKeywordExpansionWidgets
56 from Gnumed.wxpython import gmPraxisWidgets
57
58
59 _log = logging.getLogger('gm.scripting')
60 _cfg = gmCfg2.gmCfgData()
61
62
63
64
65
66
67 _injectable_placeholders = {
68 u'form_name_long': None,
69 u'form_name_short': None,
70 u'form_version': None
71 }
72
73
74
75 __known_variant_placeholders = {
76
77 u'free_text': u"""show a dialog for entering some free text:
78 args: <message> shown in input dialog, must not contain '//' or '::'""",
79 u'text_snippet': u"""a text snippet, taken from the keyword expansion mechanism:
80 args: <snippet name>//<template>""",
81 u'data_snippet': u"""a binary snippet, taken from the keyword expansion mechanism:
82 args: <snippet name>//<template>//<optional target mime type>//<optional target extension>
83 returns full path to an exported copy of the
84 data rather than the data itself,
85 template: string template for outputting the path
86 target mime type: a mime type into which to convert the image, no conversion if not given
87 target extension: target file name extension, derived from target mime type if not given
88 """,
89 u'tex_escape': u"args: string to escape",
90 u'today': u"args: strftime format",
91 u'gender_mapper': u"""maps gender of patient to a string:
92 args: <value when person is male> // <is female> // <is other>
93 eg. 'male//female//other'
94 or: 'Lieber Patient//Liebe Patientin'""",
95 u'client_version': u"the version of the current client as a string (no 'v' in front)",
96
97
98
99 u'name': u"args: template for name parts arrangement",
100 u'date_of_birth': u"args: strftime date/time format directive",
101
102 u'patient_address': u"args: <type of address>//<optional formatting template>",
103 u'adr_street': u"args: <type of address>, cached per type",
104 u'adr_number': u"args: <type of address>, cached per type",
105 u'adr_subunit': u"args: <type of address>, cached per type",
106 u'adr_location': u"args: <type of address>, cached per type",
107 u'adr_suburb': u"args: <type of address>, cached per type",
108 u'adr_postcode': u"args: <type of address>, cached per type",
109 u'adr_region': u"args: <type of address>, cached per type",
110 u'adr_country': u"args: <type of address>, cached per type",
111
112 u'patient_comm': u"args: <comm channel type as per database>//<%(field)s-template>",
113 u'patient_tags': u"args: <%(field)s-template>//<separator>",
114
115 u'patient_photo': u"""outputs URL to exported patient photo:
116 args: <template>//<optional target mime type>//<optional target extension>,
117 returns full path to an exported copy of the
118 image rather than the image data itself,
119 returns u'' if no mugshot available,
120 template: string template for outputting the path
121 target mime type: a mime type into which to convert the image, no conversion if not given
122 target extension: target file name extension, derived from target mime type if not given""",
123 u'external_id': u"args: <type of ID>//<issuer of ID>",
124
125
126
127 u'soap': u"get all of SOAPU/ADMIN, no template in args needed",
128 u'soap_s': u"get subset of SOAPU/ADMIN, no template in args needed",
129 u'soap_o': u"get subset of SOAPU/ADMIN, no template in args needed",
130 u'soap_a': u"get subset of SOAPU/ADMIN, no template in args needed",
131 u'soap_p': u"get subset of SOAPU/ADMIN, no template in args needed",
132 u'soap_u': u"get subset of SOAPU/ADMIN, no template in args needed",
133 u'soap_admin': u"get subset of SOAPU/ADMIN, no template in args needed",
134
135 u'progress_notes': u"""get progress notes:
136 args: categories//template
137 categories: string with 'soapu '; ' ' == None == admin
138 template: u'something %s something' (do not include // in template !)""",
139
140 u'soap_for_encounters': u"""lets the user select a list of encounters for which:
141 LaTeX formatted progress notes are emitted,
142 args: soap categories // strftime date format""",
143
144 u'soap_by_issue': u"""lets the user select a list of issues and then SOAP entries from those issues:
145 args: soap categories // strftime date format // template""",
146
147 u'soap_by_episode': u"""lets the user select a list of episodes and then SOAP entries from those episodes:
148 args: soap categories // strftime date format // template""",
149
150 u'emr_journal': u"""returns EMR journal view entries:
151 args format: <categories>//<template>//<line length>//<time range>//<target format>
152 categories: string with any of "s", "o", "a", "p", "u", " "; (" " == None == admin category)
153 template: something %s something else (Do not include // in the template !)
154 line length: the maximum length of individual lines, not the total placeholder length
155 time range: the number of weeks going back in time if given as a single number, or else it must be a valid PostgreSQL interval definition (w/o the ::interval)""",
156
157 u'current_meds': u"""returns current medications:
158 args: line template//<select>
159 <select>: if this is present the user will be asked which meds to export""",
160
161 u'current_meds_for_rx': u"""formats substance intakes either by substance (non-brand intakes or by brand (once per brand intake, even if multi-component):
162 args: <line template>
163 <line_template>: template into which to insert each intake, keys from
164 clin.v_substance_intakes, special additional keys:
165 %(contains)s -- list of components
166 %(amount2dispense)s -- how much/many to dispense""",
167
168 u'current_meds_table': u"emits a LaTeX table, no arguments",
169 u'current_meds_notes': u"emits a LaTeX table, no arguments",
170 u'lab_table': u"emits a LaTeX table, no arguments",
171 u'test_results': u"args: <%(field)s-template>//<date format>//<line separator (EOL)>",
172 u'latest_vaccs_table': u"emits a LaTeX table, no arguments",
173 u'vaccination_history': u"args: <%(field)s-template//date format> to format one vaccination per line",
174 u'allergy_state': u"no arguments",
175 u'allergies': u"args: line template, one allergy per line",
176 u'allergy_list': u"args holds: template per allergy, all allergies on one line",
177 u'problems': u"args holds: line template, one problem per line",
178 u'PHX': u"Past medical HiXtory; args: line template//separator//strftime date format",
179 u'encounter_list': u"args: per-encounter template, each ends up on one line",
180
181 u'documents': u"""retrieves documents from the archive:
182 args: <select>//<description>//<template>//<path template>//<path>
183 select: let user select which documents to include, optional, if not given: all documents included
184 description: whether to include descriptions, optional
185 template: something %(field)s something else (do not include '//' or '::' itself in the template)
186 path template: the template for outputting the path to exported
187 copies of the document pages, if not given no pages are exported,
188 this template can contain "%(name)s" and/or "%(fullpath)s" which
189 is replaced by the appropriate value for each exported file
190 path: into which path to export copies of the document pages, temp dir if not given""",
191
192 u'reminders': u"""patient reminders:
193 args: <template>//<date format>
194 template: something %(field)s something else (do not include '//' or '::' itself in the template)""",
195
196
197
198 u'current_provider': u"no arguments",
199 u'current_provider_external_id': u"args: <type of ID>//<issuer of ID>",
200 u'primary_praxis_provider': u"primary provider for current patient in this praxis",
201 u'primary_praxis_provider_external_id': u"args: <type of ID>//<issuer of ID>",
202
203
204
205 u'praxis': u"""retrieve current branch of your praxis:
206 args: <template>//select
207 template: something %(field)s something else (do not include '//' or '::' itself in the template)
208 select: if this is present allow selection of the branch rather than using the current branch""",
209
210 u'praxis_address': u"args: <optional formatting template>",
211
212
213
214 u'bill': u"args: template for string replacement",
215 u'bill_item': u"args: template for string replacement"
216 }
217
218 known_variant_placeholders = __known_variant_placeholders.keys()
219 known_variant_placeholders.sort()
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235 default_placeholder_regex = r'\$<[^<:]+::.*?::\d*?>\$'
236 first_order_placeholder_regex = r'\$<<<[^<:]+?::.*::\d*?>>>\$'
237 second_order_placeholder_regex = r'\$<<[^<:]+?::.*::\d*?>>\$'
238 third_order_placeholder_regex = r'\$<[^<:]+::.*?::\d*?>\$'
239
240
241
242
243 default_placeholder_start = u'$<'
244 default_placeholder_end = u'>$'
245
270
272 """Returns values for placeholders.
273
274 - patient related placeholders operate on the currently active patient
275 - is passed to the forms handling code, for example
276
277 Return values when .debug is False:
278 - errors with placeholders return None
279 - placeholders failing to resolve to a value return an empty string
280
281 Return values when .debug is True:
282 - errors with placeholders return an error string
283 - placeholders failing to resolve to a value return a warning string
284
285 There are several types of placeholders:
286
287 injectable placeholders
288 - they must be set up before use by set_placeholder()
289 - they should be removed after use by unset_placeholder()
290 - the syntax is like extended static placeholders
291 - they are listed in _injectable_placeholders
292
293 variant placeholders
294 - those are listed in known_variant_placeholders
295 - they are parsed into placeholder, data, and maximum length
296 - the length is optional
297 - data is passed to the handler
298
299 Note that this cannot be called from a non-gui thread unless
300 wrapped in wx.CallAfter().
301 """
303
304 self.pat = gmPerson.gmCurrentPatient()
305 self.debug = False
306
307 self.invalid_placeholder_template = _('invalid placeholder >>>>>%s<<<<<')
308
309 self.__cache = {}
310
311 self.__esc_style = None
312 self.__esc_func = lambda x:x
313
314
315
319
323
325 self.__cache[key] = value
326
328 del self.__cache[key]
329
333
334 escape_style = property(lambda x:x, _set_escape_style)
335
344
345 escape_function = property(lambda x:x, _set_escape_function)
346
347 placeholder_regex = property(lambda x: default_placeholder_regex, lambda x:x)
348
349 first_order_placeholder_regex = property(lambda x: first_order_placeholder_regex, lambda x:x)
350 second_order_placeholder_regex = property(lambda x: second_order_placeholder_regex, lambda x:x)
351 third_order_placeholder_regex = property(lambda x: third_order_placeholder_regex, lambda x:x)
352
353
354
356 """Map self['placeholder'] to self.placeholder.
357
358 This is useful for replacing placeholders parsed out
359 of documents as strings.
360
361 Unknown/invalid placeholders still deliver a result but
362 it will be glaringly obvious if debugging is enabled.
363 """
364 _log.debug('replacing [%s]', placeholder)
365
366 original_placeholder = placeholder
367
368
369 if placeholder.startswith(default_placeholder_start):
370 placeholder = placeholder.lstrip('$').lstrip('<')
371 if placeholder.endswith(default_placeholder_end):
372 placeholder = placeholder.rstrip('$').rstrip('>')
373 else:
374 _log.error('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
375 if self.debug:
376 return self._escape(self.invalid_placeholder_template % original_placeholder)
377 return None
378
379
380 parts = placeholder.split('::::', 1)
381 if len(parts) == 2:
382 name, lng = parts
383 is_an_injectable = True
384 try:
385 val = _injectable_placeholders[name]
386 except KeyError:
387 is_an_injectable = False
388 except:
389 _log.exception('injectable placeholder handling error: %s', original_placeholder)
390 if self.debug:
391 return self._escape(self.invalid_placeholder_template % original_placeholder)
392 return None
393 if is_an_injectable:
394 if val is None:
395 if self.debug:
396 return self._escape(u'injectable placeholder [%s]: no value available' % name)
397 return placeholder
398 try:
399 lng = int(lng.strip())
400 except (TypeError, ValueError):
401 lng = len(val)
402 return val[:lng]
403
404
405 if len(placeholder.split('::', 2)) < 3:
406 _log.error('invalid placeholder structure: %s', original_placeholder)
407 if self.debug:
408 return self._escape(self.invalid_placeholder_template % original_placeholder)
409 return None
410
411 name, data = placeholder.split('::', 1)
412 data, lng_str = data.rsplit('::', 1)
413 _log.debug('placeholder parts: name=[%s]; length=[%s]; options=>>>%s<<<', name, lng_str, data)
414 try:
415 lng = int(lng_str.strip())
416 except (TypeError, ValueError):
417 lng = None
418
419 handler = getattr(self, '_get_variant_%s' % name, None)
420 if handler is None:
421 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
422 if self.debug:
423 return self._escape(self.invalid_placeholder_template % original_placeholder)
424 return None
425
426 try:
427 if lng is None:
428 return handler(data = data)
429 return handler(data = data)[:lng]
430 except:
431 _log.exception('placeholder handling error: %s', original_placeholder)
432 if self.debug:
433 return self._escape(self.invalid_placeholder_template % original_placeholder)
434 return None
435
436 _log.error('something went wrong, should never get here')
437 return None
438
439
440
442 return self._escape (
443 gmTools.coalesce (
444 _cfg.get(option = u'client_version'),
445 u'%s' % self.__class__.__name__
446 )
447 )
448
450
451 from Gnumed.wxpython import gmProviderInboxWidgets
452
453 template = _('due %(due_date)s: %(comment)s (%(interval_due)s)')
454 date_format = '%Y %b %d'
455
456 data_parts = data.split('//')
457
458 if len(data_parts) > 0:
459 if data_parts[0].strip() != u'':
460 template = data_parts[0]
461
462 if len(data_parts) > 1:
463 if data_parts[1].strip() != u'':
464 date_format = data_parts[1]
465
466 reminders = gmProviderInboxWidgets.manage_reminders(patient = self.pat.ID)
467
468 if reminders is None:
469 return u''
470
471 if len(reminders) == 0:
472 return u''
473
474 lines = [ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in reminders ]
475
476 return u'\n'.join(lines)
477
479
480 select = False
481 include_descriptions = False
482 template = u'%s'
483 path_template = None
484 export_path = None
485
486 data_parts = data.split('//')
487
488 if u'select' in data_parts:
489 select = True
490 data_parts.remove(u'select')
491
492 if u'description' in data_parts:
493 include_descriptions = True
494 data_parts.remove(u'description')
495
496 template = data_parts[0]
497
498 if len(data_parts) > 1:
499 path_template = data_parts[1]
500
501 if len(data_parts) > 2:
502 export_path = data_parts[2]
503
504
505 if export_path is not None:
506 export_path = os.path.normcase(os.path.expanduser(export_path))
507 gmTools.mkdir(export_path)
508
509
510 if select:
511 docs = gmDocumentWidgets.manage_documents(msg = _('Select the patient documents to reference from the new document.'), single_selection = False)
512 else:
513 docs = self.pat.document_folder.documents
514
515 if docs is None:
516 return u''
517
518 lines = []
519 for doc in docs:
520 lines.append(template % doc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style))
521 if include_descriptions:
522 for desc in doc.get_descriptions(max_lng = None):
523 lines.append(self._escape(desc['text'] + u'\n'))
524 if path_template is not None:
525 for part_name in doc.export_parts_to_files(export_dir = export_path):
526 path, name = os.path.split(part_name)
527 lines.append(path_template % {'fullpath': part_name, 'name': name})
528
529 return u'\n'.join(lines)
530
532
533 encounters = gmEncounterWidgets.select_encounters(single_selection = False)
534 if not encounters:
535 return u''
536
537 template = data
538
539 lines = []
540 for enc in encounters:
541 try:
542 lines.append(template % enc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style))
543 except:
544 lines.append(u'error formatting encounter')
545 _log.exception('problem formatting encounter list')
546 _log.error('template: %s', template)
547 _log.error('encounter: %s', encounter)
548
549 return u'\n'.join(lines)
550
552 """Select encounters from list and format SOAP thereof.
553
554 data: soap_cats (' ' -> None -> admin) // date format
555 """
556
557 cats = None
558 date_format = None
559
560 if data is not None:
561 data_parts = data.split('//')
562
563
564 if len(data_parts[0]) > 0:
565 cats = []
566 if u' ' in data_parts[0]:
567 cats.append(None)
568 data_parts[0] = data_parts[0].replace(u' ', u'')
569 cats.extend(list(data_parts[0]))
570
571
572 if len(data_parts) > 1:
573 if len(data_parts[1]) > 0:
574 date_format = data_parts[1]
575
576 encounters = gmEncounterWidgets.select_encounters(single_selection = False)
577 if not encounters:
578 return u''
579
580 chunks = []
581 for enc in encounters:
582 chunks.append(enc.format_latex (
583 date_format = date_format,
584 soap_cats = cats,
585 soap_order = u'soap_rank, date'
586 ))
587
588 return u''.join(chunks)
589
591
592 cats = list(u'soapu')
593 cats.append(None)
594 template = u'%s'
595 interactive = True
596 line_length = 9999
597 time_range = None
598
599 if data is not None:
600 data_parts = data.split('//')
601
602
603 cats = []
604
605 for c in list(data_parts[0]):
606 if c == u' ':
607 c = None
608 cats.append(c)
609
610 if cats == u'':
611 cats = list(u'soapu').append(None)
612
613
614 if len(data_parts) > 1:
615 template = data_parts[1]
616
617
618 if len(data_parts) > 2:
619 try:
620 line_length = int(data_parts[2])
621 except:
622 line_length = 9999
623
624
625 if len(data_parts) > 3:
626 try:
627 time_range = 7 * int(data_parts[3])
628 except:
629
630
631 time_range = data_parts[3]
632
633
634 narr = self.pat.emr.get_as_journal(soap_cats = cats, time_range = time_range)
635
636 if len(narr) == 0:
637 return u''
638
639 keys = narr[0].keys()
640 lines = []
641 line_dict = {}
642 for n in narr:
643 for key in keys:
644 if isinstance(n[key], basestring):
645 line_dict[key] = self._escape(text = n[key])
646 continue
647 line_dict[key] = n[key]
648 try:
649 lines.append((template % line_dict)[:line_length])
650 except KeyError:
651 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
652
653 return u'\n'.join(lines)
654
656 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = u'issue')
657
659 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = u'episode')
660
662
663
664 cats = list(u'soapu')
665 cats.append(None)
666
667 date_format = None
668 template = u'%s'
669
670 if data is not None:
671 data_parts = data.split('//')
672
673
674 if len(data_parts[0]) > 0:
675 cats = []
676 if u' ' in data_parts[0]:
677 cats.append(None)
678 cats.extend(list(data_parts[0].replace(u' ', u'')))
679
680
681 if len(data_parts) > 1:
682 if len(data_parts[1]) > 0:
683 date_format = data_parts[1]
684
685
686 if len(data_parts) > 2:
687 if len(data_parts[2]) > 0:
688 template = data_parts[2]
689
690 if mode == u'issue':
691 narr = gmNarrativeWidgets.select_narrative_by_issue(soap_cats = cats)
692 else:
693 narr = gmNarrativeWidgets.select_narrative_by_episode(soap_cats = cats)
694
695 if narr is None:
696 return u''
697
698 if len(narr) == 0:
699 return u''
700
701 try:
702 narr = [ template % n.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for n in narr ]
703 except KeyError:
704 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
705
706 return u'\n'.join(narr)
707
709 return self._get_variant_soap(data = data)
710
712 return self._get_variant_soap(data = u's')
713
715 return self._get_variant_soap(data = u'o')
716
718 return self._get_variant_soap(data = u'a')
719
721 return self._get_variant_soap(data = u'p')
722
724 return self._get_variant_soap(data = u'u')
725
727 return self._get_variant_soap(data = u' ')
728
730
731
732 cats = list(u'soapu')
733 cats.append(None)
734 template = u'%(narrative)s'
735
736 if data is not None:
737 data_parts = data.split('//')
738
739
740 cats = []
741
742 for cat in list(data_parts[0]):
743 if cat == u' ':
744 cat = None
745 cats.append(cat)
746
747 if cats == u'':
748 cats = list(u'soapu')
749 cats.append(None)
750
751
752 if len(data_parts) > 1:
753 template = data_parts[1]
754
755
756 narr = gmNarrativeWidgets.select_narrative(soap_cats = cats)
757
758 if narr is None:
759 return u''
760
761 if len(narr) == 0:
762 return u''
763
764
765
766
767 if u'%s' in template:
768 narr = [ self._escape(n['narrative']) for n in narr ]
769 else:
770 narr = [ n.fields_as_dict(escape_style = self.__esc_style) for n in narr ]
771
772 try:
773 narr = [ template % n for n in narr ]
774 except KeyError:
775 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
776 except TypeError:
777 return u'cannot mix "%%s" and "%%(field)s" in template [%s]' % template
778
779 return u'\n'.join(narr)
780
782 return self._get_variant_name(data = u'%(title)s')
783
785 return self._get_variant_name(data = u'%(firstnames)s')
786
788 return self._get_variant_name(data = u'%(lastnames)s')
789
791 if data is None:
792 return [_('template is missing')]
793
794 name = self.pat.get_active_name()
795
796 parts = {
797 'title': self._escape(gmTools.coalesce(name['title'], u'')),
798 'firstnames': self._escape(name['firstnames']),
799 'lastnames': self._escape(name['lastnames']),
800 'preferred': self._escape(gmTools.coalesce (
801 initial = name['preferred'],
802 instead = u' ',
803 template_initial = u' "%s" '
804 ))
805 }
806
807 return data % parts
808
811
812
814
815 values = data.split('//', 2)
816
817 if len(values) == 2:
818 male_value, female_value = values
819 other_value = u'<unkown gender>'
820 elif len(values) == 3:
821 male_value, female_value, other_value = values
822 else:
823 return _('invalid gender mapping layout: [%s]') % data
824
825 if self.pat['gender'] == u'm':
826 return self._escape(male_value)
827
828 if self.pat['gender'] == u'f':
829 return self._escape(female_value)
830
831 return self._escape(other_value)
832
833
834
836
837 data_parts = data.split(u'//')
838
839
840 adr_type = data_parts[0].strip()
841 orig_type = adr_type
842 if adr_type != u'':
843 adrs = self.pat.get_addresses(address_type = adr_type)
844 if len(adrs) == 0:
845 _log.warning('no address for type [%s]', adr_type)
846 adr_type = u''
847 if adr_type == u'':
848 _log.debug('asking user for address type')
849 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
850 if adr is None:
851 if self.debug:
852 return _('no address type replacement selected')
853 return u''
854 adr_type = adr['address_type']
855 adr = self.pat.get_addresses(address_type = adr_type)[0]
856
857
858 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
859 if len(data_parts) > 1:
860 if data_parts[1].strip() != u'':
861 template = data_parts[1]
862
863 try:
864 return template % adr.fields_as_dict(escape_style = self.__esc_style)
865 except StandardError:
866 _log.exception('error formatting address')
867 _log.error('template: %s', template)
868
869 return None
870
872 requested_type = data.strip()
873 cache_key = 'adr-type-%s' % requested_type
874 try:
875 type2use = self.__cache[cache_key]
876 _log.debug('cache hit (%s): [%s] -> [%s]', cache_key, requested_type, type2use)
877 except KeyError:
878 type2use = requested_type
879 if type2use != u'':
880 adrs = self.pat.get_addresses(address_type = type2use)
881 if len(adrs) == 0:
882 _log.warning('no address of type [%s] for <%s> field extraction', requested_type, part)
883 type2use = u''
884 if type2use == u'':
885 _log.debug('asking user for replacement address type')
886 adr = gmPersonContactWidgets.select_address(missing = requested_type, person = self.pat)
887 if adr is None:
888 _log.debug('no replacement selected')
889 if self.debug:
890 return self._escape(_('no address type replacement selected'))
891 return u''
892 type2use = adr['address_type']
893 self.__cache[cache_key] = type2use
894 _log.debug('caching (%s): [%s] -> [%s]', cache_key, requested_type, type2use)
895
896 return self._escape(self.pat.get_addresses(address_type = type2use)[0][part])
897
899 return self.__get_variant_adr_part(data = data, part = 'street')
900
902 return self.__get_variant_adr_part(data = data, part = 'number')
903
905 return self.__get_variant_adr_part(data = data, part = 'subunit')
906
908 return self.__get_variant_adr_part(data = data, part = 'urb')
909
911 return self.__get_variant_adr_part(data = data, part = 'suburb')
912
913 - def _get_variant_adr_postcode(self, data=u'?'):
914 return self.__get_variant_adr_part(data = data, part = 'postcode')
915
917 return self.__get_variant_adr_part(data = data, part = 'l10n_state')
918
920 return self.__get_variant_adr_part(data = data, part = 'l10n_country')
921
923 comm_type = None
924 template = u'%(url)s'
925 if data is not None:
926 data_parts = data.split(u'//')
927 if len(data_parts) > 0:
928 comm_type = data_parts[0]
929 if len(data_parts) > 1:
930 template = data_parts[1]
931
932 comms = self.pat.get_comm_channels(comm_medium = comm_type)
933 if len(comms) == 0:
934 if self.debug:
935 return template + u': ' + self._escape(_('no URL for comm channel [%s]') % data)
936 return u''
937
938 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
939
941
942 template = u'%s'
943 target_mime = None
944 target_ext = None
945 if data is not None:
946 parts = data.split(u'//')
947 template = parts[0]
948 if len(parts) > 1:
949 target_mime = parts[1].strip()
950 if len(parts) > 2:
951 target_ext = parts[2].strip()
952 if target_ext is None:
953 if target_mime is not None:
954 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
955
956 mugshot = self.pat.document_folder.latest_mugshot
957 if mugshot is None:
958 if self.debug:
959 return self._escape(_('no mugshot available'))
960 return u''
961
962 fname = mugshot.export_to_file (
963 target_mime = target_mime,
964 target_extension = target_ext,
965 ignore_conversion_problems = True
966 )
967 if fname is None:
968 if self.debug:
969 return self._escape(_('cannot export or convert latest mugshot'))
970 return u''
971
972 return template % fname
973
990
991
992
993
994
995
997 options = data.split(u'//')
998
999 if u'select' in options:
1000 options.remove(u'select')
1001 branch = 'select branch'
1002 else:
1003 branch = gmPraxis.cPraxisBranch(aPK_obj = gmPraxis.gmCurrentPraxisBranch()['pk_praxis_branch'])
1004
1005 template = u'%s'
1006 if len(options) > 0:
1007 template = options[0]
1008 if template.strip() == u'':
1009 template = u'%s'
1010
1011 return template % branch.fields_as_dict(escape_style = self.__esc_style)
1012
1013
1015
1016 options = data.split(u'//')
1017
1018
1019 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
1020 if len(options) > 0:
1021 if options[0].strip() != u'':
1022 template = options[0]
1023
1024 adr = gmPraxis.gmCurrentPraxisBranch().address
1025 if adr is None:
1026 if self.debug:
1027 return _('no address recorded')
1028 return u''
1029 try:
1030 return template % adr.fields_as_dict(escape_style = self.__esc_style)
1031 except StandardError:
1032 _log.exception('error formatting address')
1033 _log.error('template: %s', template)
1034
1035 return None
1036
1038 options = data.split(u'//')
1039 comm_type = options[0]
1040 template = u'%(url)s'
1041 if len(options) > 1:
1042 template = options[1]
1043
1044 comms = gmPraxis.gmCurrentPraxisBranch().get_comm_channels(comm_medium = comm_type)
1045 if len(comms) == 0:
1046 if self.debug:
1047 return template + u': ' + self._escape(_('no URL for comm channel [%s]') % data)
1048 return u''
1049
1050 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
1051
1052
1053
1068
1070 data_parts = data.split(u'//')
1071 if len(data_parts) < 2:
1072 return self._escape(u'current provider external ID: template is missing')
1073
1074 id_type = data_parts[0].strip()
1075 if id_type == u'':
1076 return self._escape(u'current provider external ID: type is missing')
1077
1078 issuer = data_parts[1].strip()
1079 if issuer == u'':
1080 return self._escape(u'current provider external ID: issuer is missing')
1081
1082 prov = gmStaff.gmCurrentProvider()
1083 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
1084
1085 if len(ids) == 0:
1086 if self.debug:
1087 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1088 return u''
1089
1090 return self._escape(ids[0]['value'])
1091
1093 prov = self.pat.primary_provider
1094 if prov is None:
1095 return self._get_variant_current_provider()
1096
1097 title = gmTools.coalesce (
1098 prov['title'],
1099 gmPerson.map_gender2salutation(prov['gender'])
1100 )
1101
1102 tmp = u'%s %s. %s' % (
1103 title,
1104 prov['firstnames'][:1],
1105 prov['lastnames']
1106 )
1107 return self._escape(tmp)
1108
1110 data_parts = data.split(u'//')
1111 if len(data_parts) < 2:
1112 return self._escape(u'primary in-praxis provider external ID: template is missing')
1113
1114 id_type = data_parts[0].strip()
1115 if id_type == u'':
1116 return self._escape(u'primary in-praxis provider external ID: type is missing')
1117
1118 issuer = data_parts[1].strip()
1119 if issuer == u'':
1120 return self._escape(u'primary in-praxis provider external ID: issuer is missing')
1121
1122 prov = self.pat.primary_provider
1123 if prov is None:
1124 if self.debug:
1125 return self._escape(_('no primary in-praxis provider'))
1126 return u''
1127
1128 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
1129
1130 if len(ids) == 0:
1131 if self.debug:
1132 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1133 return u''
1134
1135 return self._escape(ids[0]['value'])
1136
1138 data_parts = data.split(u'//')
1139 if len(data_parts) < 2:
1140 return self._escape(u'patient external ID: template is missing')
1141
1142 id_type = data_parts[0].strip()
1143 if id_type == u'':
1144 return self._escape(u'patient external ID: type is missing')
1145
1146 issuer = data_parts[1].strip()
1147 if issuer == u'':
1148 return self._escape(u'patient external ID: issuer is missing')
1149
1150 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
1151
1152 if len(ids) == 0:
1153 if self.debug:
1154 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1155 return u''
1156
1157 return self._escape(ids[0]['value'])
1158
1160 allg_state = self.pat.get_emr().allergy_state
1161
1162 if allg_state['last_confirmed'] is None:
1163 date_confirmed = u''
1164 else:
1165 date_confirmed = u' (%s)' % gmDateTime.pydt_strftime (
1166 allg_state['last_confirmed'],
1167 format = '%Y %B %d'
1168 )
1169
1170 tmp = u'%s%s' % (
1171 allg_state.state_string,
1172 date_confirmed
1173 )
1174 return self._escape(tmp)
1175
1177 if data is None:
1178 return self._escape(_('template is missing'))
1179
1180 template, separator = data.split('//', 2)
1181
1182 return separator.join([ template % a.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for a in self.pat.emr.get_allergies() ])
1183
1190
1192 if data is None:
1193 return self._escape(_('current_meds_for_rx: template is missing'))
1194
1195 emr = self.pat.get_emr()
1196 from Gnumed.wxpython import gmMedicationWidgets
1197 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr)
1198 if current_meds is None:
1199 return u''
1200
1201 intakes2show = {}
1202 for intake in current_meds:
1203 fields_dict = intake.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style)
1204 fields_dict['medically_formatted_start'] = self._escape(intake.medically_formatted_start)
1205 if intake['pk_brand'] is None:
1206 fields_dict['brand'] = self._escape(_('generic %s') % fields_dict['substance'])
1207 fields_dict['contains'] = self._escape(u'%s %s%s' % (fields_dict['substance'], fields_dict['amount'], fields_dict['unit']))
1208 intakes2show[fields_dict['brand']] = fields_dict
1209 else:
1210 comps = [ c.split('::') for c in intake.containing_drug['components'] ]
1211 fields_dict['contains'] = self._escape(u'; '.join([ u'%s %s%s' % (c[0], c[1], c[2]) for c in comps ]))
1212 intakes2show[intake['brand']] = fields_dict
1213
1214 intakes2dispense = {}
1215 for brand, intake in intakes2show.items():
1216 msg = _('Dispense how much/many of "%(brand)s (%(contains)s)" ?') % intake
1217 amount2dispense = wx.GetTextFromUser(msg, _('Amount to dispense ?'))
1218 if amount2dispense == u'':
1219 continue
1220 intake['amount2dispense'] = amount2dispense
1221 intakes2dispense[brand] = intake
1222
1223 return u'\n'.join([ data % intake for intake in intakes2dispense.values() ])
1224
1258
1265
1272
1278
1280
1281 template = u''
1282 date_format = '%Y %b %d %H:%M'
1283 separator = u'\n'
1284
1285 options = data.split(u'//')
1286 try:
1287 template = options[0].strip()
1288 date_format = options[1]
1289 separator = options[2]
1290 except IndexError:
1291 pass
1292
1293 if date_format.strip() == u'':
1294 date_format = '%Y %b %d %H:%M'
1295 if separator.strip() == u'':
1296 separator = u'\n'
1297
1298
1299 from Gnumed.wxpython.gmMeasurementWidgets import manage_measurements
1300 results = manage_measurements(single_selection = False, emr = self.pat.emr)
1301 if results is None:
1302 if self.debug:
1303 return self._escape(_('no results for this patient (available or selected)'))
1304 return u''
1305
1306 if template == u'':
1307 return (separator + separator).join([ self._escape(r.format(date_format = date_format)) for r in results ])
1308
1309 return separator.join([ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in results ])
1310
1316
1318 options = data.split('//')
1319 template = options[0]
1320 if len(options) > 1:
1321 date_format = options[1]
1322 else:
1323 date_format = u'%Y %b %d'
1324
1325 vaccs = self.pat.emr.get_vaccinations(order_by = u'date_given DESC, vaccine')
1326
1327 return u'\n'.join([ template % v.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for v in vaccs ])
1328
1330
1331 if data is None:
1332 if self.debug:
1333 _log.error('PHX: missing placeholder arguments')
1334 return self._escape(_('PHX: Invalid placeholder options.'))
1335 return u''
1336
1337 _log.debug('arguments: %s', data)
1338
1339 data_parts = data.split(u'//')
1340 template = u'%s'
1341 separator = u'\n'
1342 date_format = '%Y %b %d'
1343 try:
1344 template = data_parts[0]
1345 separator = data_parts[1]
1346 date_format = data_parts[2]
1347 except IndexError:
1348 pass
1349
1350 phxs = gmEMRStructWidgets.select_health_issues(emr = self.pat.emr)
1351 if phxs is None:
1352 if self.debug:
1353 return self._escape(_('no PHX for this patient (available or selected)'))
1354 return u''
1355
1356 return separator.join ([
1357 template % phx.fields_as_dict (
1358 date_format = date_format,
1359 escape_style = self.__esc_style,
1360 bool_strings = (self._escape(_('yes')), self._escape(_('no')))
1361 ) for phx in phxs
1362 ])
1363
1365
1366 if data is None:
1367 return self._escape(_('template is missing'))
1368
1369 probs = self.pat.emr.get_problems()
1370
1371 return u'\n'.join([ data % p.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for p in probs ])
1372
1375
1378
1379 - def _get_variant_text_snippet(self, data=None):
1380 data_parts = data.split(u'//')
1381 keyword = data_parts[0]
1382 template = u'%s'
1383 if len(data_parts) > 1:
1384 template = data_parts[1]
1385
1386 expansion = gmKeywordExpansionWidgets.expand_keyword(keyword = keyword, show_list = True)
1387
1388 if expansion is None:
1389 if self.debug:
1390 return self._escape(_('no textual expansion found for keyword <%s>') % keyword)
1391 return u''
1392
1393
1394 return template % expansion
1395
1451
1452 - def _get_variant_free_text(self, data=None):
1453
1454 if data is None:
1455 msg = _('generic text')
1456 else:
1457 msg = data
1458
1459 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
1460 None,
1461 -1,
1462 title = _('Replacing <free_text> placeholder'),
1463 msg = _('Below you can enter free text.\n\n [%s]') % msg
1464 )
1465 dlg.enable_user_formatting = True
1466 decision = dlg.ShowModal()
1467
1468 if decision != wx.ID_SAVE:
1469 dlg.Destroy()
1470 if self.debug:
1471 return self._escape(_('Text input cancelled by user.'))
1472 return u''
1473
1474 text = dlg.value.strip()
1475 if dlg.is_user_formatted:
1476 dlg.Destroy()
1477 return text
1478
1479 dlg.Destroy()
1480
1481 return self._escape(text)
1482
1496
1510
1511
1512
1514 if self.__esc_func is None:
1515 return text
1516 return self.__esc_func(text)
1517
1519 """Functions a macro can legally use.
1520
1521 An instance of this class is passed to the GNUmed scripting
1522 listener. Hence, all actions a macro can legally take must
1523 be defined in this class. Thus we achieve some screening for
1524 security and also thread safety handling.
1525 """
1526
1527 - def __init__(self, personality = None):
1528 if personality is None:
1529 raise gmExceptions.ConstructorError, 'must specify personality'
1530 self.__personality = personality
1531 self.__attached = 0
1532 self._get_source_personality = None
1533 self.__user_done = False
1534 self.__user_answer = 'no answer yet'
1535 self.__pat = gmPerson.gmCurrentPatient()
1536
1537 self.__auth_cookie = str(random.random())
1538 self.__pat_lock_cookie = str(random.random())
1539 self.__lock_after_load_cookie = str(random.random())
1540
1541 _log.info('slave mode personality is [%s]', personality)
1542
1543
1544
1545 - def attach(self, personality = None):
1546 if self.__attached:
1547 _log.error('attach with [%s] rejected, already serving a client', personality)
1548 return (0, _('attach rejected, already serving a client'))
1549 if personality != self.__personality:
1550 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
1551 return (0, _('attach to personality [%s] rejected') % personality)
1552 self.__attached = 1
1553 self.__auth_cookie = str(random.random())
1554 return (1, self.__auth_cookie)
1555
1556 - def detach(self, auth_cookie=None):
1557 if not self.__attached:
1558 return 1
1559 if auth_cookie != self.__auth_cookie:
1560 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
1561 return 0
1562 self.__attached = 0
1563 return 1
1564
1566 if not self.__attached:
1567 return 1
1568 self.__user_done = False
1569
1570 wx.CallAfter(self._force_detach)
1571 return 1
1572
1574 ver = _cfg.get(option = u'client_version')
1575 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
1576
1578 """Shuts down this client instance."""
1579 if not self.__attached:
1580 return 0
1581 if auth_cookie != self.__auth_cookie:
1582 _log.error('non-authenticated shutdown_gnumed()')
1583 return 0
1584 wx.CallAfter(self._shutdown_gnumed, forced)
1585 return 1
1586
1588 """Raise ourselves to the top of the desktop."""
1589 if not self.__attached:
1590 return 0
1591 if auth_cookie != self.__auth_cookie:
1592 _log.error('non-authenticated raise_gnumed()')
1593 return 0
1594 return "cMacroPrimitives.raise_gnumed() not implemented"
1595
1597 if not self.__attached:
1598 return 0
1599 if auth_cookie != self.__auth_cookie:
1600 _log.error('non-authenticated get_loaded_plugins()')
1601 return 0
1602 gb = gmGuiBroker.GuiBroker()
1603 return gb['horstspace.notebook.gui'].keys()
1604
1606 """Raise a notebook plugin within GNUmed."""
1607 if not self.__attached:
1608 return 0
1609 if auth_cookie != self.__auth_cookie:
1610 _log.error('non-authenticated raise_notebook_plugin()')
1611 return 0
1612
1613 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
1614 return 1
1615
1617 """Load external patient, perhaps create it.
1618
1619 Callers must use get_user_answer() to get status information.
1620 It is unsafe to proceed without knowing the completion state as
1621 the controlled client may be waiting for user input from a
1622 patient selection list.
1623 """
1624 if not self.__attached:
1625 return (0, _('request rejected, you are not attach()ed'))
1626 if auth_cookie != self.__auth_cookie:
1627 _log.error('non-authenticated load_patient_from_external_source()')
1628 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
1629 if self.__pat.locked:
1630 _log.error('patient is locked, cannot load from external source')
1631 return (0, _('current patient is locked'))
1632 self.__user_done = False
1633 wx.CallAfter(self._load_patient_from_external_source)
1634 self.__lock_after_load_cookie = str(random.random())
1635 return (1, self.__lock_after_load_cookie)
1636
1638 if not self.__attached:
1639 return (0, _('request rejected, you are not attach()ed'))
1640 if auth_cookie != self.__auth_cookie:
1641 _log.error('non-authenticated lock_load_patient()')
1642 return (0, _('rejected lock_load_patient(), not authenticated'))
1643
1644 if lock_after_load_cookie != self.__lock_after_load_cookie:
1645 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
1646 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
1647 self.__pat.locked = True
1648 self.__pat_lock_cookie = str(random.random())
1649 return (1, self.__pat_lock_cookie)
1650
1652 if not self.__attached:
1653 return (0, _('request rejected, you are not attach()ed'))
1654 if auth_cookie != self.__auth_cookie:
1655 _log.error('non-authenticated lock_into_patient()')
1656 return (0, _('rejected lock_into_patient(), not authenticated'))
1657 if self.__pat.locked:
1658 _log.error('patient is already locked')
1659 return (0, _('already locked into a patient'))
1660 searcher = gmPersonSearch.cPatientSearcher_SQL()
1661 if type(search_params) == types.DictType:
1662 idents = searcher.get_identities(search_dict=search_params)
1663 raise StandardError("must use dto, not search_dict")
1664 else:
1665 idents = searcher.get_identities(search_term=search_params)
1666 if idents is None:
1667 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
1668 if len(idents) == 0:
1669 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
1670
1671 if len(idents) > 1:
1672 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
1673 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
1674 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
1675 self.__pat.locked = True
1676 self.__pat_lock_cookie = str(random.random())
1677 return (1, self.__pat_lock_cookie)
1678
1680 if not self.__attached:
1681 return (0, _('request rejected, you are not attach()ed'))
1682 if auth_cookie != self.__auth_cookie:
1683 _log.error('non-authenticated unlock_patient()')
1684 return (0, _('rejected unlock_patient, not authenticated'))
1685
1686 if not self.__pat.locked:
1687 return (1, '')
1688
1689 if unlock_cookie != self.__pat_lock_cookie:
1690 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
1691 return (0, 'patient unlock request rejected, wrong cookie provided')
1692 self.__pat.locked = False
1693 return (1, '')
1694
1696 if not self.__attached:
1697 return 0
1698 if auth_cookie != self.__auth_cookie:
1699 _log.error('non-authenticated select_identity()')
1700 return 0
1701 return "cMacroPrimitives.assume_staff_identity() not implemented"
1702
1704 if not self.__user_done:
1705 return (0, 'still waiting')
1706 self.__user_done = False
1707 return (1, self.__user_answer)
1708
1709
1710
1712 msg = _(
1713 'Someone tries to forcibly break the existing\n'
1714 'controlling connection. This may or may not\n'
1715 'have legitimate reasons.\n\n'
1716 'Do you want to allow breaking the connection ?'
1717 )
1718 can_break_conn = gmGuiHelpers.gm_show_question (
1719 aMessage = msg,
1720 aTitle = _('forced detach attempt')
1721 )
1722 if can_break_conn:
1723 self.__user_answer = 1
1724 else:
1725 self.__user_answer = 0
1726 self.__user_done = True
1727 if can_break_conn:
1728 self.__pat.locked = False
1729 self.__attached = 0
1730 return 1
1731
1733 top_win = wx.GetApp().GetTopWindow()
1734 if forced:
1735 top_win.Destroy()
1736 else:
1737 top_win.Close()
1738
1747
1748
1749
1750 if __name__ == '__main__':
1751
1752 if len(sys.argv) < 2:
1753 sys.exit()
1754
1755 if sys.argv[1] != 'test':
1756 sys.exit()
1757
1758 gmI18N.activate_locale()
1759 gmI18N.install_domain()
1760
1761
1763 handler = gmPlaceholderHandler()
1764 handler.debug = True
1765
1766 for placeholder in ['a', 'b']:
1767 print handler[placeholder]
1768
1769 pat = gmPersonSearch.ask_for_patient()
1770 if pat is None:
1771 return
1772
1773 gmPatSearchWidgets.set_active_patient(patient = pat)
1774
1775 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1776
1777 app = wx.PyWidgetTester(size = (200, 50))
1778
1779 ph = 'progress_notes::ap'
1780 print '%s: %s' % (ph, handler[ph])
1781
1783
1784 tests = [
1785
1786 '$<lastname>$',
1787 '$<lastname::::3>$',
1788 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
1789
1790
1791 'lastname',
1792 '$<lastname',
1793 '$<lastname::',
1794 '$<lastname::>$',
1795 '$<lastname::abc>$',
1796 '$<lastname::abc::>$',
1797 '$<lastname::abc::3>$',
1798 '$<lastname::abc::xyz>$',
1799 '$<lastname::::>$',
1800 '$<lastname::::xyz>$',
1801
1802 '$<date_of_birth::%Y-%m-%d>$',
1803 '$<date_of_birth::%Y-%m-%d::3>$',
1804 '$<date_of_birth::%Y-%m-%d::>$',
1805
1806
1807 '$<adr_location::home::35>$',
1808 '$<gender_mapper::male//female//other::5>$',
1809 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
1810 '$<allergy_list::%(descriptor)s, >$',
1811 '$<current_meds_table::latex//by-brand>$'
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826 ]
1827
1828
1829
1830
1831
1832 pat = gmPersonSearch.ask_for_patient()
1833 if pat is None:
1834 return
1835
1836 gmPatSearchWidgets.set_active_patient(patient = pat)
1837
1838 handler = gmPlaceholderHandler()
1839 handler.debug = True
1840
1841 for placeholder in tests:
1842 print placeholder, "=>", handler[placeholder]
1843 print "--------------"
1844 raw_input()
1845
1846
1847
1848
1849
1850
1851
1852
1853
1855 from Gnumed.pycommon import gmScriptingListener
1856 import xmlrpclib
1857 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1858
1859 s = xmlrpclib.ServerProxy('http://localhost:9999')
1860 print "should fail:", s.attach()
1861 print "should fail:", s.attach('wrong cookie')
1862 print "should work:", s.version()
1863 print "should fail:", s.raise_gnumed()
1864 print "should fail:", s.raise_notebook_plugin('test plugin')
1865 print "should fail:", s.lock_into_patient('kirk, james')
1866 print "should fail:", s.unlock_patient()
1867 status, conn_auth = s.attach('unit test')
1868 print "should work:", status, conn_auth
1869 print "should work:", s.version()
1870 print "should work:", s.raise_gnumed(conn_auth)
1871 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1872 print "should work:", status, pat_auth
1873 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1874 print "should work", s.unlock_patient(conn_auth, pat_auth)
1875 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1876 status, pat_auth = s.lock_into_patient(conn_auth, data)
1877 print "should work:", status, pat_auth
1878 print "should work", s.unlock_patient(conn_auth, pat_auth)
1879 print s.detach('bogus detach cookie')
1880 print s.detach(conn_auth)
1881 del s
1882
1883 listener.shutdown()
1884
1886
1887 import re as regex
1888
1889 tests = [
1890 ' $<lastname>$ ',
1891 ' $<lastname::::3>$ ',
1892
1893
1894 '$<date_of_birth::%Y-%m-%d>$',
1895 '$<date_of_birth::%Y-%m-%d::3>$',
1896 '$<date_of_birth::%Y-%m-%d::>$',
1897
1898 '$<adr_location::home::35>$',
1899 '$<gender_mapper::male//female//other::5>$',
1900 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1901 '$<allergy_list::%(descriptor)s, >$',
1902
1903 '\\noindent Patient: $<lastname>$, $<firstname>$',
1904 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1905 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1906 ]
1907
1908 tests = [
1909
1910 'junk $<lastname::::3>$ junk',
1911 'junk $<lastname::abc::3>$ junk',
1912 'junk $<lastname::abc>$ junk',
1913 'junk $<lastname>$ junk',
1914
1915 'junk $<lastname>$ junk $<firstname>$ junk',
1916 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1917 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1918 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1919
1920 ]
1921
1922 tests = [
1923
1924
1925
1926
1927
1928 u'$<<<current_meds::%(brand)s (%(substance)s): Dispense $<free_text::Dispense how many of %(brand)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::250>>>$',
1929 ]
1930
1931 print "testing placeholder regex:", first_order_placeholder_regex
1932 print ""
1933
1934 for t in tests:
1935 print 'line: "%s"' % t
1936 phs = regex.findall(first_order_placeholder_regex, t, regex.IGNORECASE)
1937 print " %s placeholders:" % len(phs)
1938 for p in phs:
1939 print ' => "%s"' % p
1940 print " "
1941
2008
2009
2017
2020
2021
2022
2023
2024
2025
2026
2027
2028 test_show_phs()
2029
2030
2031