1
2 """GNUmed macro primitives.
3
4 This module implements functions a macro can legally use.
5 """
6
7 __version__ = "$Revision: 1.51 $"
8 __author__ = "K.Hilbert <karsten.hilbert@gmx.net>"
9
10 import sys, time, random, types, logging
11
12
13 import wx
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmI18N
19 if __name__ == '__main__':
20 gmI18N.activate_locale()
21 gmI18N.install_domain()
22 from Gnumed.pycommon import gmGuiBroker
23 from Gnumed.pycommon import gmTools
24 from Gnumed.pycommon import gmBorg
25 from Gnumed.pycommon import gmExceptions
26 from Gnumed.pycommon import gmCfg2
27 from Gnumed.pycommon import gmDateTime
28
29 from Gnumed.business import gmPerson
30 from Gnumed.business import gmStaff
31 from Gnumed.business import gmDemographicRecord
32 from Gnumed.business import gmMedication
33 from Gnumed.business import gmPathLab
34 from Gnumed.business import gmPersonSearch
35 from Gnumed.business import gmVaccination
36 from Gnumed.business import gmPersonSearch
37
38 from Gnumed.wxpython import gmGuiHelpers
39 from Gnumed.wxpython import gmNarrativeWidgets
40 from Gnumed.wxpython import gmPatSearchWidgets
41 from Gnumed.wxpython import gmPersonContactWidgets
42 from Gnumed.wxpython import gmPlugin
43 from Gnumed.wxpython import gmEMRStructWidgets
44 from Gnumed.wxpython import gmListWidgets
45
46
47 _log = logging.getLogger('gm.scripting')
48 _cfg = gmCfg2.gmCfgData()
49
50
51 known_placeholders = [
52 'lastname',
53 'firstname',
54 'title',
55 'date_of_birth',
56 'progress_notes',
57 'soap',
58 'soap_s',
59 'soap_o',
60 'soap_a',
61 'soap_p',
62 'soap_u',
63 u'client_version',
64 u'current_provider',
65 u'primary_praxis_provider',
66 u'allergy_state'
67 ]
68
69
70
71
72
73 _injectable_placeholders = {
74 u'form_name_long': None,
75 u'form_name_short': None,
76 u'form_version': None
77 }
78
79
80
81 known_variant_placeholders = [
82 u'soap',
83 u'progress_notes',
84
85
86 u'emr_journal',
87
88
89
90
91
92
93
94 u'date_of_birth',
95
96 u'patient_address',
97 u'adr_street',
98 u'adr_number',
99 u'adr_location',
100 u'adr_postcode',
101 u'adr_region',
102 u'adr_country',
103
104 u'patient_comm',
105 u'external_id',
106 u'gender_mapper',
107
108
109 u'current_meds',
110 u'current_meds_table',
111
112 u'current_meds_notes',
113 u'lab_table',
114 u'latest_vaccs_table',
115 u'vaccination_history',
116 u'today',
117 u'tex_escape',
118 u'allergies',
119 u'allergy_list',
120 u'problems',
121 u'name',
122 u'free_text',
123 u'soap_for_encounters',
124 u'encounter_list',
125 u'current_provider_external_id',
126 u'primary_praxis_provider_external_id',
127
128 u'bill',
129 u'bill_item'
130 ]
131
132 default_placeholder_regex = r'\$<.+?>\$'
133
134
135
136
137
138
139
140
141 default_placeholder_start = u'$<'
142 default_placeholder_end = u'>$'
143
145 """Returns values for placeholders.
146
147 - patient related placeholders operate on the currently active patient
148 - is passed to the forms handling code, for example
149
150 Return values when .debug is False:
151 - errors with placeholders return None
152 - placeholders failing to resolve to a value return an empty string
153
154 Return values when .debug is True:
155 - errors with placeholders return an error string
156 - placeholders failing to resolve to a value return a warning string
157
158 There are several types of placeholders:
159
160 simple static placeholders
161 - those are listed in known_placeholders
162 - they are used as-is
163
164 extended static placeholders
165 - those are, effectively, static placeholders
166 with a maximum length attached (after "::::")
167
168 injectable placeholders
169 - they must be set up before use by set_placeholder()
170 - they should be removed after use by unset_placeholder()
171 - the syntax is like extended static placeholders
172 - they are listed in _injectable_placeholders
173
174 variant placeholders
175 - those are listed in known_variant_placeholders
176 - they are parsed into placeholder, data, and maximum length
177 - the length is optional
178 - data is passed to the handler
179
180 Note that this cannot be called from a non-gui thread unless
181 wrapped in wx.CallAfter().
182 """
184
185 self.pat = gmPerson.gmCurrentPatient()
186 self.debug = False
187
188 self.invalid_placeholder_template = _('invalid placeholder [%s]')
189
190 self.__cache = {}
191
192
193
197
201
203 self.__cache[key] = value
204
206 del self.__cache[key]
207
208
209
211 """Map self['placeholder'] to self.placeholder.
212
213 This is useful for replacing placeholders parsed out
214 of documents as strings.
215
216 Unknown/invalid placeholders still deliver a result but
217 it will be glaringly obvious if debugging is enabled.
218 """
219 _log.debug('replacing [%s]', placeholder)
220
221 original_placeholder = placeholder
222
223 if placeholder.startswith(default_placeholder_start):
224 placeholder = placeholder[len(default_placeholder_start):]
225 if placeholder.endswith(default_placeholder_end):
226 placeholder = placeholder[:-len(default_placeholder_end)]
227 else:
228 _log.debug('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
229 if self.debug:
230 return self.invalid_placeholder_template % original_placeholder
231 return None
232
233
234 if placeholder in known_placeholders:
235 return getattr(self, placeholder)
236
237
238 parts = placeholder.split('::::', 1)
239 if len(parts) == 2:
240 name, lng = parts
241 unknown_injectable = False
242 try:
243 val = _injectable_placeholders[name]
244 except KeyError:
245 unknown_injectable = True
246 except:
247 _log.exception('placeholder handling error: %s', original_placeholder)
248 if self.debug:
249 return self.invalid_placeholder_template % original_placeholder
250 return None
251 if not unknown_injectable:
252 if val is None:
253 if self.debug:
254 return u'injectable placeholder [%s]: no value available' % name
255 return placeholder
256 return val[:int(lng)]
257
258
259 parts = placeholder.split('::::', 1)
260 if len(parts) == 2:
261 name, lng = parts
262 try:
263 return getattr(self, name)[:int(lng)]
264 except:
265 _log.exception('placeholder handling error: %s', original_placeholder)
266 if self.debug:
267 return self.invalid_placeholder_template % original_placeholder
268 return None
269
270
271 parts = placeholder.split('::')
272
273 if len(parts) == 1:
274 _log.warning('invalid placeholder layout: %s', original_placeholder)
275 if self.debug:
276 return self.invalid_placeholder_template % original_placeholder
277 return None
278
279 if len(parts) == 2:
280 name, data = parts
281 lng = None
282
283 if len(parts) == 3:
284 name, data, lng = parts
285 try:
286 lng = int(lng)
287 except (TypeError, ValueError):
288 _log.error('placeholder length definition error: %s, discarding length: >%s<', original_placeholder, lng)
289 lng = None
290
291 if len(parts) > 3:
292 _log.warning('invalid placeholder layout: %s', original_placeholder)
293 if self.debug:
294 return self.invalid_placeholder_template % original_placeholder
295 return None
296
297 handler = getattr(self, '_get_variant_%s' % name, None)
298 if handler is None:
299 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
300 if self.debug:
301 return self.invalid_placeholder_template % original_placeholder
302 return None
303
304 try:
305 if lng is None:
306 return handler(data = data)
307 return handler(data = data)[:lng]
308 except:
309 _log.exception('placeholder handling error: %s', original_placeholder)
310 if self.debug:
311 return self.invalid_placeholder_template % original_placeholder
312 return None
313
314 _log.error('something went wrong, should never get here')
315 return None
316
317
318
319
320
322 """This does nothing, used as a NOOP properties setter."""
323 pass
324
327
330
333
335 return self._get_variant_date_of_birth(data='%x')
336
338 return self._get_variant_soap()
339
341 return self._get_variant_soap(data = u's')
342
344 return self._get_variant_soap(data = u'o')
345
347 return self._get_variant_soap(data = u'a')
348
350 return self._get_variant_soap(data = u'p')
351
353 return self._get_variant_soap(data = u'u')
354
356 return self._get_variant_soap(soap_cats = None)
357
359 return gmTools.coalesce (
360 _cfg.get(option = u'client_version'),
361 u'%s' % self.__class__.__name__
362 )
363
381
397
399 allg_state = self.pat.get_emr().allergy_state
400
401 if allg_state['last_confirmed'] is None:
402 date_confirmed = u''
403 else:
404 date_confirmed = u' (%s)' % allg_state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding())
405
406 tmp = u'%s%s' % (
407 allg_state.state_string,
408 date_confirmed
409 )
410 return tmp
411
412
413
414 placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop)
415
416
417
418
419 lastname = property(_get_lastname, _setter_noop)
420 firstname = property(_get_firstname, _setter_noop)
421 title = property(_get_title, _setter_noop)
422 date_of_birth = property(_get_dob, _setter_noop)
423
424 progress_notes = property(_get_progress_notes, _setter_noop)
425 soap = property(_get_progress_notes, _setter_noop)
426 soap_s = property(_get_soap_s, _setter_noop)
427 soap_o = property(_get_soap_o, _setter_noop)
428 soap_a = property(_get_soap_a, _setter_noop)
429 soap_p = property(_get_soap_p, _setter_noop)
430 soap_u = property(_get_soap_u, _setter_noop)
431 soap_admin = property(_get_soap_admin, _setter_noop)
432
433 allergy_state = property(_get_allergy_state, _setter_noop)
434
435 client_version = property(_get_client_version, _setter_noop)
436
437 current_provider = property(_get_current_provider, _setter_noop)
438 primary_praxis_provider = property(_get_primary_praxis_provider, _setter_noop)
439
440
441
443
444 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
445 if not encounters:
446 return u''
447
448 template = data
449
450 lines = []
451 for enc in encounters:
452 try:
453 lines.append(template % enc)
454 except:
455 lines.append(u'error formatting encounter')
456 _log.exception('problem formatting encounter list')
457 _log.error('template: %s', template)
458 _log.error('encounter: %s', encounter)
459
460 return u'\n'.join(lines)
461
463 """Select encounters from list and format SOAP thereof.
464
465 data: soap_cats (' ' -> None -> admin) // date format
466 """
467
468 cats = None
469 date_format = None
470
471 if data is not None:
472 data_parts = data.split('//')
473
474
475 if len(data_parts[0]) > 0:
476 cats = []
477 if u' ' in data_parts[0]:
478 cats.append(None)
479 data_parts[0] = data_parts[0].replace(u' ', u'')
480 cats.extend(list(data_parts[0]))
481
482
483 if len(data_parts) > 1:
484 if len(data_parts[1]) > 0:
485 date_format = data_parts[1]
486
487 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
488 if not encounters:
489 return u''
490
491 chunks = []
492 for enc in encounters:
493 chunks.append(enc.format_latex (
494 date_format = date_format,
495 soap_cats = cats,
496 soap_order = u'soap_rank, date'
497 ))
498
499 return u''.join(chunks)
500
502
503 cats = list(u'soapu')
504 cats.append(None)
505 template = u'%s'
506 interactive = True
507 line_length = 9999
508 target_format = None
509 time_range = None
510
511 if data is not None:
512 data_parts = data.split('//')
513
514
515 cats = []
516
517 for c in list(data_parts[0]):
518 if c == u' ':
519 c = None
520 cats.append(c)
521
522 if cats == u'':
523 cats = list(u'soapu').append(None)
524
525
526 if len(data_parts) > 1:
527 template = data_parts[1]
528
529
530 if len(data_parts) > 2:
531 try:
532 line_length = int(data_parts[2])
533 except:
534 line_length = 9999
535
536
537 if len(data_parts) > 3:
538 try:
539 time_range = 7 * int(data_parts[3])
540 except:
541 time_range = None
542
543
544 if len(data_parts) > 4:
545 target_format = data_parts[4]
546
547
548 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range)
549
550 if len(narr) == 0:
551 return u''
552
553 if target_format == u'tex':
554 keys = narr[0].keys()
555 lines = []
556 line_dict = {}
557 for n in narr:
558 for key in keys:
559 if isinstance(n[key], basestring):
560 line_dict[key] = gmTools.tex_escape_string(text = n[key])
561 continue
562 line_dict[key] = n[key]
563 try:
564 lines.append((template % line_dict)[:line_length])
565 except KeyError:
566 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
567 else:
568 try:
569 lines = [ (template % n)[:line_length] for n in narr ]
570 except KeyError:
571 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
572
573 return u'\n'.join(lines)
574
576 return self._get_variant_soap(data=data)
577
579
580
581 cats = list(u'soapu')
582 cats.append(None)
583 template = u'%s'
584
585 if data is not None:
586 data_parts = data.split('//')
587
588
589 cats = []
590
591 for cat in list(data_parts[0]):
592 if cat == u' ':
593 cat = None
594 cats.append(cat)
595
596 if cats == u'':
597 cats = list(u'soapu')
598 cats.append(None)
599
600
601 if len(data_parts) > 1:
602 template = data_parts[1]
603
604
605 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
606
607 if narr is None:
608 return u''
609
610 if len(narr) == 0:
611 return u''
612
613 try:
614 narr = [ template % n['narrative'] for n in narr ]
615 except KeyError:
616 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
617
618 return u'\n'.join(narr)
619
638
641
642
644 values = data.split('//', 2)
645
646 if len(values) == 2:
647 male_value, female_value = values
648 other_value = u'<unkown gender>'
649 elif len(values) == 3:
650 male_value, female_value, other_value = values
651 else:
652 return _('invalid gender mapping layout: [%s]') % data
653
654 if self.pat['gender'] == u'm':
655 return male_value
656
657 if self.pat['gender'] == u'f':
658 return female_value
659
660 return other_value
661
662
663
665
666 data_parts = data.split(u'//')
667
668
669 adr_type = data_parts[0].strip()
670 orig_type = adr_type
671 if adr_type != u'':
672 adrs = self.pat.get_addresses(address_type = adr_type)
673 if len(adrs) == 0:
674 _log.warning('no address for type [%s]', adr_type)
675 adr_type = u''
676 if adr_type == u'':
677 _log.debug('asking user for address type')
678 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
679 if adr is None:
680 if self.debug:
681 return _('no address type replacement selected')
682 return u''
683 adr_type = adr['address_type']
684 adr = self.pat.get_addresses(address_type = adr_type)[0]
685
686
687 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
688 if len(data_parts) > 1:
689 if data_parts[1].strip() != u'':
690 template = data_parts[1]
691
692 try:
693 return template % adr.fields_as_dict()
694 except StandardError:
695 _log.exception('error formatting address')
696 _log.error('template: %s', template)
697
698 return None
699
701 adr_type = data.strip()
702 orig_type = adr_type
703 if adr_type != u'':
704 adrs = self.pat.get_addresses(address_type = adr_type)
705 if len(adrs) == 0:
706 _log.warning('no street for address type [%s]', adr_type)
707 adr_type = u''
708 if adr_type == u'':
709 _log.debug('asking user for address type')
710 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
711 if adr is None:
712 if self.debug:
713 return _('no address type replacement selected')
714 return u''
715 adr_type = adr['address_type']
716 return self.pat.get_addresses(address_type = adr_type)[0]['street']
717
719 adr_type = data.strip()
720 orig_type = adr_type
721 if adr_type != u'':
722 adrs = self.pat.get_addresses(address_type = adr_type)
723 if len(adrs) == 0:
724 _log.warning('no number for address type [%s]', adr_type)
725 adr_type = u''
726 if adr_type == u'':
727 _log.debug('asking user for address type')
728 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
729 if adr is None:
730 if self.debug:
731 return _('no address type replacement selected')
732 return u''
733 adr_type = adr['address_type']
734 return self.pat.get_addresses(address_type = adr_type)[0]['number']
735
737 adr_type = data.strip()
738 orig_type = adr_type
739 if adr_type != u'':
740 adrs = self.pat.get_addresses(address_type = adr_type)
741 if len(adrs) == 0:
742 _log.warning('no location for address type [%s]', adr_type)
743 adr_type = u''
744 if adr_type == u'':
745 _log.debug('asking user for address type')
746 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
747 if adr is None:
748 if self.debug:
749 return _('no address type replacement selected')
750 return u''
751 adr_type = adr['address_type']
752 return self.pat.get_addresses(address_type = adr_type)[0]['urb']
753
754 - def _get_variant_adr_postcode(self, data=u'?'):
755 adr_type = data.strip()
756 orig_type = adr_type
757 if adr_type != u'':
758 adrs = self.pat.get_addresses(address_type = adr_type)
759 if len(adrs) == 0:
760 _log.warning('no postcode for address type [%s]', adr_type)
761 adr_type = u''
762 if adr_type == u'':
763 _log.debug('asking user for address type')
764 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
765 if adr is None:
766 if self.debug:
767 return _('no address type replacement selected')
768 return u''
769 adr_type = adr['address_type']
770 return self.pat.get_addresses(address_type = adr_type)[0]['postcode']
771
773 adr_type = data.strip()
774 orig_type = adr_type
775 if adr_type != u'':
776 adrs = self.pat.get_addresses(address_type = adr_type)
777 if len(adrs) == 0:
778 _log.warning('no region for address type [%s]', adr_type)
779 adr_type = u''
780 if adr_type == u'':
781 _log.debug('asking user for address type')
782 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
783 if adr is None:
784 if self.debug:
785 return _('no address type replacement selected')
786 return u''
787 adr_type = adr['address_type']
788 return self.pat.get_addresses(address_type = adr_type)[0]['l10n_state']
789
791 adr_type = data.strip()
792 orig_type = adr_type
793 if adr_type != u'':
794 adrs = self.pat.get_addresses(address_type = adr_type)
795 if len(adrs) == 0:
796 _log.warning('no country for old men / address type [%s]', adr_type)
797 adr_type = u''
798 if adr_type == u'':
799 _log.debug('asking user for address type')
800 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
801 if adr is None:
802 if self.debug:
803 return _('no address type replacement selected')
804 return u''
805 adr_type = adr['address_type']
806 return self.pat.get_addresses(address_type = adr_type)[0]['l10n_country']
807
809 comms = self.pat.get_comm_channels(comm_medium = data)
810 if len(comms) == 0:
811 if self.debug:
812 return _('no URL for comm channel [%s]') % data
813 return u''
814 return comms[0]['url']
815
817 data_parts = data.split(u'//')
818 if len(data_parts) < 2:
819 return u'current provider external ID: template is missing'
820
821 id_type = data_parts[0].strip()
822 if id_type == u'':
823 return u'current provider external ID: type is missing'
824
825 issuer = data_parts[1].strip()
826 if issuer == u'':
827 return u'current provider external ID: issuer is missing'
828
829 prov = gmStaff.gmCurrentProvider()
830 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
831
832 if len(ids) == 0:
833 if self.debug:
834 return _('no external ID [%s] by [%s]') % (id_type, issuer)
835 return u''
836
837 return ids[0]['value']
838
840 data_parts = data.split(u'//')
841 if len(data_parts) < 2:
842 return u'primary in-praxis provider external ID: template is missing'
843
844 id_type = data_parts[0].strip()
845 if id_type == u'':
846 return u'primary in-praxis provider external ID: type is missing'
847
848 issuer = data_parts[1].strip()
849 if issuer == u'':
850 return u'primary in-praxis provider external ID: issuer is missing'
851
852 prov = self.pat.primary_provider
853 if prov is None:
854 if self.debug:
855 return _('no primary in-praxis provider')
856 return u''
857
858 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
859
860 if len(ids) == 0:
861 if self.debug:
862 return _('no external ID [%s] by [%s]') % (id_type, issuer)
863 return u''
864
865 return ids[0]['value']
866
868 data_parts = data.split(u'//')
869 if len(data_parts) < 2:
870 return u'patient external ID: template is missing'
871
872 id_type = data_parts[0].strip()
873 if id_type == u'':
874 return u'patient external ID: type is missing'
875
876 issuer = data_parts[1].strip()
877 if issuer == u'':
878 return u'patient external ID: issuer is missing'
879
880 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
881
882 if len(ids) == 0:
883 if self.debug:
884 return _('no external ID [%s] by [%s]') % (id_type, issuer)
885 return u''
886
887 return ids[0]['value']
888
890 if data is None:
891 return [_('template is missing')]
892
893 template, separator = data.split('//', 2)
894
895 emr = self.pat.get_emr()
896 return separator.join([ template % a for a in emr.get_allergies() ])
897
905
907
908 if data is None:
909 return [_('template is missing')]
910
911 emr = self.pat.get_emr()
912 current_meds = emr.get_current_substance_intake (
913 include_inactive = False,
914 include_unapproved = False,
915 order_by = u'brand, substance'
916 )
917
918 return u'\n'.join([ data % m.fields_as_dict(date_format = '%Y %B %d') for m in current_meds ])
919
921
922 options = data.split('//')
923
924 if u'latex' in options:
925 return gmMedication.format_substance_intake (
926 emr = self.pat.get_emr(),
927 output_format = u'latex',
928 table_type = u'by-brand'
929 )
930
931 _log.error('no known current medications table formatting style in [%s]', data)
932 return _('unknown current medication table formatting style')
933
935
936 options = data.split('//')
937
938 if u'latex' in options:
939 return gmMedication.format_substance_intake_notes (
940 emr = self.pat.get_emr(),
941 output_format = u'latex',
942 table_type = u'by-brand'
943 )
944
945 _log.error('no known current medications notes formatting style in [%s]', data)
946 return _('unknown current medication notes formatting style')
947
962
974
976 options = data.split('//')
977 template = options[0]
978 if len(options) > 1:
979 date_format = options[1]
980 else:
981 date_format = u'%Y %B %d'
982
983 emr = self.pat.get_emr()
984 vaccs = emr.get_vaccinations(order_by = u'date_given DESC, vaccine')
985
986 return u'\n'.join([ template % v.fields_as_dict(date_format = date_format) for v in vaccs ])
987
989
990 if data is None:
991 return [_('template is missing')]
992
993 probs = self.pat.get_emr().get_problems()
994
995 return u'\n'.join([ data % p for p in probs ])
996
999
1002
1003 - def _get_variant_free_text(self, data=u'tex//'):
1004
1005
1006
1007
1008 data_parts = data.split('//')
1009 format = data_parts[0]
1010 if len(data_parts) > 1:
1011 msg = data_parts[1]
1012 else:
1013 msg = _('generic text')
1014
1015 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
1016 None,
1017 -1,
1018 title = _('Replacing <free_text> placeholder'),
1019 msg = _('Below you can enter free text.\n\n [%s]') % msg
1020 )
1021 dlg.enable_user_formatting = True
1022 decision = dlg.ShowModal()
1023
1024 if decision != wx.ID_SAVE:
1025 dlg.Destroy()
1026 if self.debug:
1027 return _('Text input cancelled by user.')
1028 return u''
1029
1030 text = dlg.value.strip()
1031 if dlg.is_user_formatted:
1032 dlg.Destroy()
1033 return text
1034
1035 dlg.Destroy()
1036
1037 if format == u'tex':
1038 return gmTools.tex_escape_string(text = text)
1039
1040 return text
1041
1055
1069
1070
1071
1074
1076 """Functions a macro can legally use.
1077
1078 An instance of this class is passed to the GNUmed scripting
1079 listener. Hence, all actions a macro can legally take must
1080 be defined in this class. Thus we achieve some screening for
1081 security and also thread safety handling.
1082 """
1083
1084 - def __init__(self, personality = None):
1085 if personality is None:
1086 raise gmExceptions.ConstructorError, 'must specify personality'
1087 self.__personality = personality
1088 self.__attached = 0
1089 self._get_source_personality = None
1090 self.__user_done = False
1091 self.__user_answer = 'no answer yet'
1092 self.__pat = gmPerson.gmCurrentPatient()
1093
1094 self.__auth_cookie = str(random.random())
1095 self.__pat_lock_cookie = str(random.random())
1096 self.__lock_after_load_cookie = str(random.random())
1097
1098 _log.info('slave mode personality is [%s]', personality)
1099
1100
1101
1102 - def attach(self, personality = None):
1103 if self.__attached:
1104 _log.error('attach with [%s] rejected, already serving a client', personality)
1105 return (0, _('attach rejected, already serving a client'))
1106 if personality != self.__personality:
1107 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
1108 return (0, _('attach to personality [%s] rejected') % personality)
1109 self.__attached = 1
1110 self.__auth_cookie = str(random.random())
1111 return (1, self.__auth_cookie)
1112
1113 - def detach(self, auth_cookie=None):
1114 if not self.__attached:
1115 return 1
1116 if auth_cookie != self.__auth_cookie:
1117 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
1118 return 0
1119 self.__attached = 0
1120 return 1
1121
1123 if not self.__attached:
1124 return 1
1125 self.__user_done = False
1126
1127 wx.CallAfter(self._force_detach)
1128 return 1
1129
1131 ver = _cfg.get(option = u'client_version')
1132 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
1133
1135 """Shuts down this client instance."""
1136 if not self.__attached:
1137 return 0
1138 if auth_cookie != self.__auth_cookie:
1139 _log.error('non-authenticated shutdown_gnumed()')
1140 return 0
1141 wx.CallAfter(self._shutdown_gnumed, forced)
1142 return 1
1143
1145 """Raise ourselves to the top of the desktop."""
1146 if not self.__attached:
1147 return 0
1148 if auth_cookie != self.__auth_cookie:
1149 _log.error('non-authenticated raise_gnumed()')
1150 return 0
1151 return "cMacroPrimitives.raise_gnumed() not implemented"
1152
1154 if not self.__attached:
1155 return 0
1156 if auth_cookie != self.__auth_cookie:
1157 _log.error('non-authenticated get_loaded_plugins()')
1158 return 0
1159 gb = gmGuiBroker.GuiBroker()
1160 return gb['horstspace.notebook.gui'].keys()
1161
1163 """Raise a notebook plugin within GNUmed."""
1164 if not self.__attached:
1165 return 0
1166 if auth_cookie != self.__auth_cookie:
1167 _log.error('non-authenticated raise_notebook_plugin()')
1168 return 0
1169
1170 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
1171 return 1
1172
1174 """Load external patient, perhaps create it.
1175
1176 Callers must use get_user_answer() to get status information.
1177 It is unsafe to proceed without knowing the completion state as
1178 the controlled client may be waiting for user input from a
1179 patient selection list.
1180 """
1181 if not self.__attached:
1182 return (0, _('request rejected, you are not attach()ed'))
1183 if auth_cookie != self.__auth_cookie:
1184 _log.error('non-authenticated load_patient_from_external_source()')
1185 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
1186 if self.__pat.locked:
1187 _log.error('patient is locked, cannot load from external source')
1188 return (0, _('current patient is locked'))
1189 self.__user_done = False
1190 wx.CallAfter(self._load_patient_from_external_source)
1191 self.__lock_after_load_cookie = str(random.random())
1192 return (1, self.__lock_after_load_cookie)
1193
1195 if not self.__attached:
1196 return (0, _('request rejected, you are not attach()ed'))
1197 if auth_cookie != self.__auth_cookie:
1198 _log.error('non-authenticated lock_load_patient()')
1199 return (0, _('rejected lock_load_patient(), not authenticated'))
1200
1201 if lock_after_load_cookie != self.__lock_after_load_cookie:
1202 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
1203 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
1204 self.__pat.locked = True
1205 self.__pat_lock_cookie = str(random.random())
1206 return (1, self.__pat_lock_cookie)
1207
1209 if not self.__attached:
1210 return (0, _('request rejected, you are not attach()ed'))
1211 if auth_cookie != self.__auth_cookie:
1212 _log.error('non-authenticated lock_into_patient()')
1213 return (0, _('rejected lock_into_patient(), not authenticated'))
1214 if self.__pat.locked:
1215 _log.error('patient is already locked')
1216 return (0, _('already locked into a patient'))
1217 searcher = gmPersonSearch.cPatientSearcher_SQL()
1218 if type(search_params) == types.DictType:
1219 idents = searcher.get_identities(search_dict=search_params)
1220 raise StandardError("must use dto, not search_dict")
1221 else:
1222 idents = searcher.get_identities(search_term=search_params)
1223 if idents is None:
1224 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
1225 if len(idents) == 0:
1226 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
1227
1228 if len(idents) > 1:
1229 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
1230 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
1231 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
1232 self.__pat.locked = True
1233 self.__pat_lock_cookie = str(random.random())
1234 return (1, self.__pat_lock_cookie)
1235
1237 if not self.__attached:
1238 return (0, _('request rejected, you are not attach()ed'))
1239 if auth_cookie != self.__auth_cookie:
1240 _log.error('non-authenticated unlock_patient()')
1241 return (0, _('rejected unlock_patient, not authenticated'))
1242
1243 if not self.__pat.locked:
1244 return (1, '')
1245
1246 if unlock_cookie != self.__pat_lock_cookie:
1247 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
1248 return (0, 'patient unlock request rejected, wrong cookie provided')
1249 self.__pat.locked = False
1250 return (1, '')
1251
1253 if not self.__attached:
1254 return 0
1255 if auth_cookie != self.__auth_cookie:
1256 _log.error('non-authenticated select_identity()')
1257 return 0
1258 return "cMacroPrimitives.assume_staff_identity() not implemented"
1259
1261 if not self.__user_done:
1262 return (0, 'still waiting')
1263 self.__user_done = False
1264 return (1, self.__user_answer)
1265
1266
1267
1269 msg = _(
1270 'Someone tries to forcibly break the existing\n'
1271 'controlling connection. This may or may not\n'
1272 'have legitimate reasons.\n\n'
1273 'Do you want to allow breaking the connection ?'
1274 )
1275 can_break_conn = gmGuiHelpers.gm_show_question (
1276 aMessage = msg,
1277 aTitle = _('forced detach attempt')
1278 )
1279 if can_break_conn:
1280 self.__user_answer = 1
1281 else:
1282 self.__user_answer = 0
1283 self.__user_done = True
1284 if can_break_conn:
1285 self.__pat.locked = False
1286 self.__attached = 0
1287 return 1
1288
1290 top_win = wx.GetApp().GetTopWindow()
1291 if forced:
1292 top_win.Destroy()
1293 else:
1294 top_win.Close()
1295
1304
1305
1306
1307 if __name__ == '__main__':
1308
1309 if len(sys.argv) < 2:
1310 sys.exit()
1311
1312 if sys.argv[1] != 'test':
1313 sys.exit()
1314
1315 gmI18N.activate_locale()
1316 gmI18N.install_domain()
1317
1318
1320 handler = gmPlaceholderHandler()
1321 handler.debug = True
1322
1323 for placeholder in ['a', 'b']:
1324 print handler[placeholder]
1325
1326 pat = gmPersonSearch.ask_for_patient()
1327 if pat is None:
1328 return
1329
1330 gmPatSearchWidgets.set_active_patient(patient = pat)
1331
1332 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1333
1334 app = wx.PyWidgetTester(size = (200, 50))
1335 for placeholder in known_placeholders:
1336 print placeholder, "=", handler[placeholder]
1337
1338 ph = 'progress_notes::ap'
1339 print '%s: %s' % (ph, handler[ph])
1340
1342
1343 tests = [
1344
1345 '$<lastname>$',
1346 '$<lastname::::3>$',
1347 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
1348
1349
1350 'lastname',
1351 '$<lastname',
1352 '$<lastname::',
1353 '$<lastname::>$',
1354 '$<lastname::abc>$',
1355 '$<lastname::abc::>$',
1356 '$<lastname::abc::3>$',
1357 '$<lastname::abc::xyz>$',
1358 '$<lastname::::>$',
1359 '$<lastname::::xyz>$',
1360
1361 '$<date_of_birth::%Y-%m-%d>$',
1362 '$<date_of_birth::%Y-%m-%d::3>$',
1363 '$<date_of_birth::%Y-%m-%d::>$',
1364
1365
1366 '$<adr_location::home::35>$',
1367 '$<gender_mapper::male//female//other::5>$',
1368 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
1369 '$<allergy_list::%(descriptor)s, >$',
1370 '$<current_meds_table::latex//by-brand>$'
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385 ]
1386
1387
1388
1389
1390
1391 pat = gmPersonSearch.ask_for_patient()
1392 if pat is None:
1393 return
1394
1395 gmPatSearchWidgets.set_active_patient(patient = pat)
1396
1397 handler = gmPlaceholderHandler()
1398 handler.debug = True
1399
1400 for placeholder in tests:
1401 print placeholder, "=>", handler[placeholder]
1402 print "--------------"
1403 raw_input()
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1416 from Gnumed.pycommon import gmScriptingListener
1417 import xmlrpclib
1418 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1419
1420 s = xmlrpclib.ServerProxy('http://localhost:9999')
1421 print "should fail:", s.attach()
1422 print "should fail:", s.attach('wrong cookie')
1423 print "should work:", s.version()
1424 print "should fail:", s.raise_gnumed()
1425 print "should fail:", s.raise_notebook_plugin('test plugin')
1426 print "should fail:", s.lock_into_patient('kirk, james')
1427 print "should fail:", s.unlock_patient()
1428 status, conn_auth = s.attach('unit test')
1429 print "should work:", status, conn_auth
1430 print "should work:", s.version()
1431 print "should work:", s.raise_gnumed(conn_auth)
1432 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1433 print "should work:", status, pat_auth
1434 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1435 print "should work", s.unlock_patient(conn_auth, pat_auth)
1436 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1437 status, pat_auth = s.lock_into_patient(conn_auth, data)
1438 print "should work:", status, pat_auth
1439 print "should work", s.unlock_patient(conn_auth, pat_auth)
1440 print s.detach('bogus detach cookie')
1441 print s.detach(conn_auth)
1442 del s
1443
1444 listener.shutdown()
1445
1447
1448 import re as regex
1449
1450 tests = [
1451 ' $<lastname>$ ',
1452 ' $<lastname::::3>$ ',
1453
1454
1455 '$<date_of_birth::%Y-%m-%d>$',
1456 '$<date_of_birth::%Y-%m-%d::3>$',
1457 '$<date_of_birth::%Y-%m-%d::>$',
1458
1459 '$<adr_location::home::35>$',
1460 '$<gender_mapper::male//female//other::5>$',
1461 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1462 '$<allergy_list::%(descriptor)s, >$',
1463
1464 '\\noindent Patient: $<lastname>$, $<firstname>$',
1465 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1466 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1467 ]
1468
1469 tests = [
1470
1471 'junk $<lastname::::3>$ junk',
1472 'junk $<lastname::abc::3>$ junk',
1473 'junk $<lastname::abc>$ junk',
1474 'junk $<lastname>$ junk',
1475
1476 'junk $<lastname>$ junk $<firstname>$ junk',
1477 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1478 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1479 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1480
1481 ]
1482
1483 print "testing placeholder regex:", default_placeholder_regex
1484 print ""
1485
1486 for t in tests:
1487 print 'line: "%s"' % t
1488 print "placeholders:"
1489 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE):
1490 print ' => "%s"' % p
1491 print " "
1492
1537
1538
1539
1540
1541
1542
1543 test_placeholder()
1544
1545
1546