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