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