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