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 gmPlugin
42 from Gnumed.wxpython import gmEMRStructWidgets
43
44
45 _log = logging.getLogger('gm.scripting')
46 _cfg = gmCfg2.gmCfgData()
47
48
49 known_placeholders = [
50 'lastname',
51 'firstname',
52 'title',
53 'date_of_birth',
54 'progress_notes',
55 'soap',
56 'soap_s',
57 'soap_o',
58 'soap_a',
59 'soap_p',
60 'soap_u',
61 u'client_version',
62 u'current_provider',
63 u'primary_praxis_provider',
64 u'allergy_state'
65 ]
66
67
68
69
70
71 _injectable_placeholders = {
72 u'form_name_long': None,
73 u'form_name_short': None,
74 u'form_version': None
75 }
76
77
78
79 known_variant_placeholders = [
80 u'soap',
81 u'progress_notes',
82
83
84 u'emr_journal',
85
86
87
88
89
90
91
92 u'date_of_birth',
93
94 u'patient_address',
95 u'adr_street',
96 u'adr_number',
97 u'adr_location',
98 u'adr_postcode',
99 u'adr_region',
100 u'adr_country',
101
102 u'patient_comm',
103 u'external_id',
104 u'gender_mapper',
105
106
107 u'current_meds',
108 u'current_meds_table',
109
110 u'current_meds_notes',
111 u'lab_table',
112 u'latest_vaccs_table',
113 u'today',
114 u'tex_escape',
115 u'allergies',
116 u'allergy_list',
117 u'problems',
118 u'name',
119 u'free_text',
120 u'soap_for_encounters',
121 u'encounter_list',
122 u'current_provider_external_id',
123 u'primary_praxis_provider_external_id'
124 ]
125
126 default_placeholder_regex = r'\$<.+?>\$'
127
128
129
130
131
132
133
134
135 default_placeholder_start = u'$<'
136 default_placeholder_end = u'>$'
137
139 """Returns values for placeholders.
140
141 - patient related placeholders operate on the currently active patient
142 - is passed to the forms handling code, for example
143
144 Return values when .debug is False:
145 - errors with placeholders return None
146 - placeholders failing to resolve to a value return an empty string
147
148 Return values when .debug is True:
149 - errors with placeholders return an error string
150 - placeholders failing to resolve to a value return a warning string
151
152 There are several types of placeholders:
153
154 simple static placeholders
155 - those are listed in known_placeholders
156 - they are used as-is
157
158 extended static placeholders
159 - those are, effectively, static placeholders
160 with a maximum length attached (after "::::")
161
162 injectable placeholders
163 - they must be set up before use by set_placeholder()
164 - they should be removed after use by unset_placeholder()
165 - the syntax is like extended static placeholders
166 - they are listed in _injectable_placeholders
167
168 variant placeholders
169 - those are listed in known_variant_placeholders
170 - they are parsed into placeholder, data, and maximum length
171 - the length is optional
172 - data is passed to the handler
173
174 Note that this cannot be called from a non-gui thread unless
175 wrapped in wx.CallAfter().
176 """
178
179 self.pat = gmPerson.gmCurrentPatient()
180 self.debug = False
181
182 self.invalid_placeholder_template = _('invalid placeholder [%s]')
183
184
185
189
193
194
195
197 """Map self['placeholder'] to self.placeholder.
198
199 This is useful for replacing placeholders parsed out
200 of documents as strings.
201
202 Unknown/invalid placeholders still deliver a result but
203 it will be glaringly obvious if debugging is enabled.
204 """
205 _log.debug('replacing [%s]', placeholder)
206
207 original_placeholder = placeholder
208
209 if placeholder.startswith(default_placeholder_start):
210 placeholder = placeholder[len(default_placeholder_start):]
211 if placeholder.endswith(default_placeholder_end):
212 placeholder = placeholder[:-len(default_placeholder_end)]
213 else:
214 _log.debug('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
215 if self.debug:
216 return self.invalid_placeholder_template % original_placeholder
217 return None
218
219
220 if placeholder in known_placeholders:
221 return getattr(self, placeholder)
222
223
224 parts = placeholder.split('::::', 1)
225 if len(parts) == 2:
226 name, lng = parts
227 unknown_injectable = False
228 try:
229 val = _injectable_placeholders[name]
230 except KeyError:
231 unknown_injectable = True
232 except:
233 _log.exception('placeholder handling error: %s', original_placeholder)
234 if self.debug:
235 return self.invalid_placeholder_template % original_placeholder
236 return None
237 if not unknown_injectable:
238 if val is None:
239 if self.debug:
240 return u'injectable placeholder [%s]: no value available' % name
241 return placeholder
242 return val[:int(lng)]
243
244
245 parts = placeholder.split('::::', 1)
246 if len(parts) == 2:
247 name, lng = parts
248 try:
249 return getattr(self, name)[:int(lng)]
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
256
257 parts = placeholder.split('::')
258 if len(parts) == 2:
259 name, data = parts
260 lng = None
261 if len(parts) == 3:
262 name, data, lng = parts
263 try:
264 lng = int(lng)
265 except (TypeError, ValueError):
266 _log.error('placeholder length definition error: %s, discarding length: >%s<', original_placeholder, lng)
267 lng = None
268 if len(parts) > 3:
269 _log.warning('invalid placeholder layout: %s', original_placeholder)
270 if self.debug:
271 return self.invalid_placeholder_template % original_placeholder
272 return None
273
274 handler = getattr(self, '_get_variant_%s' % name, None)
275 if handler is None:
276 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
277 if self.debug:
278 return self.invalid_placeholder_template % original_placeholder
279 return None
280
281 try:
282 if lng is None:
283 return handler(data = data)
284 return handler(data = data)[:lng]
285 except:
286 _log.exception('placeholder handling error: %s', original_placeholder)
287 if self.debug:
288 return self.invalid_placeholder_template % original_placeholder
289 return None
290
291 _log.error('something went wrong, should never get here')
292 return None
293
294
295
296
297
299 """This does nothing, used as a NOOP properties setter."""
300 pass
301
304
307
310
312 return self._get_variant_date_of_birth(data='%x')
313
315 return self._get_variant_soap()
316
318 return self._get_variant_soap(data = u's')
319
321 return self._get_variant_soap(data = u'o')
322
324 return self._get_variant_soap(data = u'a')
325
327 return self._get_variant_soap(data = u'p')
328
330 return self._get_variant_soap(data = u'u')
331
333 return self._get_variant_soap(soap_cats = None)
334
336 return gmTools.coalesce (
337 _cfg.get(option = u'client_version'),
338 u'%s' % self.__class__.__name__
339 )
340
358
374
376 allg_state = self.pat.get_emr().allergy_state
377
378 if allg_state['last_confirmed'] is None:
379 date_confirmed = u''
380 else:
381 date_confirmed = u' (%s)' % allg_state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding())
382
383 tmp = u'%s%s' % (
384 allg_state.state_string,
385 date_confirmed
386 )
387 return tmp
388
389
390
391 placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop)
392
393
394 lastname = property(_get_lastname, _setter_noop)
395 firstname = property(_get_firstname, _setter_noop)
396 title = property(_get_title, _setter_noop)
397 date_of_birth = property(_get_dob, _setter_noop)
398
399 progress_notes = property(_get_progress_notes, _setter_noop)
400 soap = property(_get_progress_notes, _setter_noop)
401 soap_s = property(_get_soap_s, _setter_noop)
402 soap_o = property(_get_soap_o, _setter_noop)
403 soap_a = property(_get_soap_a, _setter_noop)
404 soap_p = property(_get_soap_p, _setter_noop)
405 soap_u = property(_get_soap_u, _setter_noop)
406 soap_admin = property(_get_soap_admin, _setter_noop)
407
408 allergy_state = property(_get_allergy_state, _setter_noop)
409
410 client_version = property(_get_client_version, _setter_noop)
411
412 current_provider = property(_get_current_provider, _setter_noop)
413 primary_praxis_provider = property(_get_primary_praxis_provider, _setter_noop)
414
415
416
418
419 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
420 if not encounters:
421 return u''
422
423 template = data
424
425 lines = []
426 for enc in encounters:
427 try:
428 lines.append(template % enc)
429 except:
430 lines.append(u'error formatting encounter')
431 _log.exception('problem formatting encounter list')
432 _log.error('template: %s', template)
433 _log.error('encounter: %s', encounter)
434
435 return u'\n'.join(lines)
436
438 """Select encounters from list and format SOAP thereof.
439
440 data: soap_cats (' ' -> None -> admin) // date format
441 """
442
443 cats = None
444 date_format = None
445
446 if data is not None:
447 data_parts = data.split('//')
448
449
450 if len(data_parts[0]) > 0:
451 cats = []
452 if u' ' in data_parts[0]:
453 cats.append(None)
454 data_parts[0] = data_parts[0].replace(u' ', u'')
455 cats.extend(list(data_parts[0]))
456
457
458 if len(data_parts) > 1:
459 if len(data_parts[1]) > 0:
460 date_format = data_parts[1]
461
462 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
463 if not encounters:
464 return u''
465
466 chunks = []
467 for enc in encounters:
468 chunks.append(enc.format_latex (
469 date_format = date_format,
470 soap_cats = cats,
471 soap_order = u'soap_rank, date'
472 ))
473
474 return u''.join(chunks)
475
477
478 cats = list(u'soapu')
479 cats.append(None)
480 template = u'%s'
481 interactive = True
482 line_length = 9999
483 target_format = None
484 time_range = None
485
486 if data is not None:
487 data_parts = data.split('//')
488
489
490 cats = []
491
492 for c in list(data_parts[0]):
493 if c == u' ':
494 c = None
495 cats.append(c)
496
497 if cats == u'':
498 cats = list(u'soapu').append(None)
499
500
501 if len(data_parts) > 1:
502 template = data_parts[1]
503
504
505 if len(data_parts) > 2:
506 try:
507 line_length = int(data_parts[2])
508 except:
509 line_length = 9999
510
511
512 if len(data_parts) > 3:
513 try:
514 time_range = 7 * int(data_parts[3])
515 except:
516 time_range = None
517
518
519 if len(data_parts) > 4:
520 target_format = data_parts[4]
521
522
523 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range)
524
525 if len(narr) == 0:
526 return u''
527
528 if target_format == u'tex':
529 keys = narr[0].keys()
530 lines = []
531 line_dict = {}
532 for n in narr:
533 for key in keys:
534 if isinstance(n[key], basestring):
535 line_dict[key] = gmTools.tex_escape_string(text = n[key])
536 continue
537 line_dict[key] = n[key]
538 try:
539 lines.append((template % line_dict)[:line_length])
540 except KeyError:
541 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
542 else:
543 try:
544 lines = [ (template % n)[:line_length] for n in narr ]
545 except KeyError:
546 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
547
548 return u'\n'.join(lines)
549
551 return self._get_variant_soap(data=data)
552
554
555
556 cats = list(u'soapu')
557 cats.append(None)
558 template = u'%s'
559
560 if data is not None:
561 data_parts = data.split('//')
562
563
564 cats = []
565
566 for cat in list(data_parts[0]):
567 if cat == u' ':
568 cat = None
569 cats.append(cat)
570
571 if cats == u'':
572 cats = list(u'soapu')
573 cats.append(None)
574
575
576 if len(data_parts) > 1:
577 template = data_parts[1]
578
579
580 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
581
582 if narr is None:
583 return u''
584
585 if len(narr) == 0:
586 return u''
587
588 try:
589 narr = [ template % n['narrative'] for n in narr ]
590 except KeyError:
591 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
592
593 return u'\n'.join(narr)
594
613
616
617
619 values = data.split('//', 2)
620
621 if len(values) == 2:
622 male_value, female_value = values
623 other_value = u'<unkown gender>'
624 elif len(values) == 3:
625 male_value, female_value, other_value = values
626 else:
627 return _('invalid gender mapping layout: [%s]') % data
628
629 if self.pat['gender'] == u'm':
630 return male_value
631
632 if self.pat['gender'] == u'f':
633 return female_value
634
635 return other_value
636
637
638
640
641 data_parts = data.split(u'//')
642
643 if data_parts[0].strip() == u'':
644 adr_type = u'home'
645 else:
646 adr_type = data_parts[0]
647
648 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
649 if len(data_parts) > 1:
650 if data_parts[1].strip() != u'':
651 template = data_parts[1]
652
653 adrs = self.pat.get_addresses(address_type = adr_type)
654 if len(adrs) == 0:
655 if self.debug:
656 return _('no address for type [%s]') % adr_type
657 return u''
658
659 adr = adrs[0]
660 data = {
661 'street': adr['street'],
662 'notes_street': gmTools.coalesce(adr['notes_street'], u''),
663 'postcode': adr['postcode'],
664 'number': adr['number'],
665 'subunit': gmTools.coalesce(adr['subunit'], u''),
666 'notes_subunit': gmTools.coalesce(adr['notes_subunit'], u''),
667 'urb': adr['urb'],
668 'suburb': gmTools.coalesce(adr['suburb'], u''),
669 'l10n_state': adr['l10n_state'],
670 'l10n_country': adr['l10n_country'],
671 'code_state': adr['code_state'],
672 'code_country': adr['code_country']
673 }
674
675 try:
676 return template % data
677 except StandardError:
678 _log.exception('error formatting address')
679 _log.error('template: %s', template)
680
681 return None
682
684 adrs = self.pat.get_addresses(address_type=data)
685 if len(adrs) == 0:
686 if self.debug:
687 return _('no street for address type [%s]') % data
688 return u''
689 return adrs[0]['street']
690
692 adrs = self.pat.get_addresses(address_type=data)
693 if len(adrs) == 0:
694 if self.debug:
695 return _('no number for address type [%s]') % data
696 return u''
697 return adrs[0]['number']
698
700 adrs = self.pat.get_addresses(address_type=data)
701 if len(adrs) == 0:
702 if self.debug:
703 return _('no location for address type [%s]') % data
704 return u''
705 return adrs[0]['urb']
706
707 - def _get_variant_adr_postcode(self, data=u'?'):
708 adrs = self.pat.get_addresses(address_type = data)
709 if len(adrs) == 0:
710 if self.debug:
711 return _('no postcode for address type [%s]') % data
712 return u''
713 return adrs[0]['postcode']
714
716 adrs = self.pat.get_addresses(address_type = data)
717 if len(adrs) == 0:
718 if self.debug:
719 return _('no region for address type [%s]') % data
720 return u''
721 return adrs[0]['l10n_state']
722
724 adrs = self.pat.get_addresses(address_type = data)
725 if len(adrs) == 0:
726 if self.debug:
727 return _('no country for address type [%s]') % data
728 return u''
729 return adrs[0]['l10n_country']
730
732 comms = self.pat.get_comm_channels(comm_medium = data)
733 if len(comms) == 0:
734 if self.debug:
735 return _('no URL for comm channel [%s]') % data
736 return u''
737 return comms[0]['url']
738
740 data_parts = data.split(u'//')
741 if len(data_parts) < 2:
742 return u'current provider external ID: template is missing'
743
744 id_type = data_parts[0].strip()
745 if id_type == u'':
746 return u'current provider external ID: type is missing'
747
748 issuer = data_parts[1].strip()
749 if issuer == u'':
750 return u'current provider external ID: issuer is missing'
751
752 prov = gmStaff.gmCurrentProvider()
753 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
754
755 if len(ids) == 0:
756 if self.debug:
757 return _('no external ID [%s] by [%s]') % (id_type, issuer)
758 return u''
759
760 return ids[0]['value']
761
763 data_parts = data.split(u'//')
764 if len(data_parts) < 2:
765 return u'primary in-praxis provider external ID: template is missing'
766
767 id_type = data_parts[0].strip()
768 if id_type == u'':
769 return u'primary in-praxis provider external ID: type is missing'
770
771 issuer = data_parts[1].strip()
772 if issuer == u'':
773 return u'primary in-praxis provider external ID: issuer is missing'
774
775 prov = self.pat.primary_provider
776 if prov is None:
777 if self.debug:
778 return _('no primary in-praxis provider')
779 return u''
780
781 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
782
783 if len(ids) == 0:
784 if self.debug:
785 return _('no external ID [%s] by [%s]') % (id_type, issuer)
786 return u''
787
788 return ids[0]['value']
789
791 data_parts = data.split(u'//')
792 if len(data_parts) < 2:
793 return u'patient external ID: template is missing'
794
795 id_type = data_parts[0].strip()
796 if id_type == u'':
797 return u'patient external ID: type is missing'
798
799 issuer = data_parts[1].strip()
800 if issuer == u'':
801 return u'patient external ID: issuer is missing'
802
803 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
804
805 if len(ids) == 0:
806 if self.debug:
807 return _('no external ID [%s] by [%s]') % (id_type, issuer)
808 return u''
809
810 return ids[0]['value']
811
813 if data is None:
814 return [_('template is missing')]
815
816 template, separator = data.split('//', 2)
817
818 emr = self.pat.get_emr()
819 return separator.join([ template % a for a in emr.get_allergies() ])
820
828
830
831 if data is None:
832 return [_('template is missing')]
833
834 emr = self.pat.get_emr()
835 current_meds = emr.get_current_substance_intake (
836 include_inactive = False,
837 include_unapproved = False,
838 order_by = u'brand, substance'
839 )
840
841 return u'\n'.join([ data % m.fields_as_dict(date_format = '%Y %B %d') for m in current_meds ])
842
844
845 options = data.split('//')
846
847 if u'latex' in options:
848 return gmMedication.format_substance_intake (
849 emr = self.pat.get_emr(),
850 output_format = u'latex',
851 table_type = u'by-brand'
852 )
853
854 _log.error('no known current medications table formatting style in [%s]', data)
855 return _('unknown current medication table formatting style')
856
858
859 options = data.split('//')
860
861 if u'latex' in options:
862 return gmMedication.format_substance_intake_notes (
863 emr = self.pat.get_emr(),
864 output_format = u'latex',
865 table_type = u'by-brand'
866 )
867
868 _log.error('no known current medications notes formatting style in [%s]', data)
869 return _('unknown current medication notes formatting style')
870
885
897
899
900 if data is None:
901 return [_('template is missing')]
902
903 probs = self.pat.get_emr().get_problems()
904
905 return u'\n'.join([ data % p for p in probs ])
906
909
912
913 - def _get_variant_free_text(self, data=u'tex//'):
914
915
916
917
918 data_parts = data.split('//')
919 format = data_parts[0]
920 if len(data_parts) > 1:
921 msg = data_parts[1]
922 else:
923 msg = _('generic text')
924
925 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
926 None,
927 -1,
928 title = _('Replacing <free_text> placeholder'),
929 msg = _('Below you can enter free text.\n\n [%s]') % msg
930 )
931 dlg.enable_user_formatting = True
932 decision = dlg.ShowModal()
933
934 if decision != wx.ID_SAVE:
935 dlg.Destroy()
936 if self.debug:
937 return _('Text input cancelled by user.')
938 return u''
939
940 text = dlg.value.strip()
941 if dlg.is_user_formatted:
942 dlg.Destroy()
943 return text
944
945 dlg.Destroy()
946
947 if format == u'tex':
948 return gmTools.tex_escape_string(text = text)
949
950 return text
951
952
953
954
955
957 """Functions a macro can legally use.
958
959 An instance of this class is passed to the GNUmed scripting
960 listener. Hence, all actions a macro can legally take must
961 be defined in this class. Thus we achieve some screening for
962 security and also thread safety handling.
963 """
964
965 - def __init__(self, personality = None):
966 if personality is None:
967 raise gmExceptions.ConstructorError, 'must specify personality'
968 self.__personality = personality
969 self.__attached = 0
970 self._get_source_personality = None
971 self.__user_done = False
972 self.__user_answer = 'no answer yet'
973 self.__pat = gmPerson.gmCurrentPatient()
974
975 self.__auth_cookie = str(random.random())
976 self.__pat_lock_cookie = str(random.random())
977 self.__lock_after_load_cookie = str(random.random())
978
979 _log.info('slave mode personality is [%s]', personality)
980
981
982
983 - def attach(self, personality = None):
984 if self.__attached:
985 _log.error('attach with [%s] rejected, already serving a client', personality)
986 return (0, _('attach rejected, already serving a client'))
987 if personality != self.__personality:
988 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
989 return (0, _('attach to personality [%s] rejected') % personality)
990 self.__attached = 1
991 self.__auth_cookie = str(random.random())
992 return (1, self.__auth_cookie)
993
994 - def detach(self, auth_cookie=None):
995 if not self.__attached:
996 return 1
997 if auth_cookie != self.__auth_cookie:
998 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
999 return 0
1000 self.__attached = 0
1001 return 1
1002
1004 if not self.__attached:
1005 return 1
1006 self.__user_done = False
1007
1008 wx.CallAfter(self._force_detach)
1009 return 1
1010
1012 ver = _cfg.get(option = u'client_version')
1013 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
1014
1016 """Shuts down this client instance."""
1017 if not self.__attached:
1018 return 0
1019 if auth_cookie != self.__auth_cookie:
1020 _log.error('non-authenticated shutdown_gnumed()')
1021 return 0
1022 wx.CallAfter(self._shutdown_gnumed, forced)
1023 return 1
1024
1026 """Raise ourselves to the top of the desktop."""
1027 if not self.__attached:
1028 return 0
1029 if auth_cookie != self.__auth_cookie:
1030 _log.error('non-authenticated raise_gnumed()')
1031 return 0
1032 return "cMacroPrimitives.raise_gnumed() not implemented"
1033
1035 if not self.__attached:
1036 return 0
1037 if auth_cookie != self.__auth_cookie:
1038 _log.error('non-authenticated get_loaded_plugins()')
1039 return 0
1040 gb = gmGuiBroker.GuiBroker()
1041 return gb['horstspace.notebook.gui'].keys()
1042
1044 """Raise a notebook plugin within GNUmed."""
1045 if not self.__attached:
1046 return 0
1047 if auth_cookie != self.__auth_cookie:
1048 _log.error('non-authenticated raise_notebook_plugin()')
1049 return 0
1050
1051 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
1052 return 1
1053
1055 """Load external patient, perhaps create it.
1056
1057 Callers must use get_user_answer() to get status information.
1058 It is unsafe to proceed without knowing the completion state as
1059 the controlled client may be waiting for user input from a
1060 patient selection list.
1061 """
1062 if not self.__attached:
1063 return (0, _('request rejected, you are not attach()ed'))
1064 if auth_cookie != self.__auth_cookie:
1065 _log.error('non-authenticated load_patient_from_external_source()')
1066 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
1067 if self.__pat.locked:
1068 _log.error('patient is locked, cannot load from external source')
1069 return (0, _('current patient is locked'))
1070 self.__user_done = False
1071 wx.CallAfter(self._load_patient_from_external_source)
1072 self.__lock_after_load_cookie = str(random.random())
1073 return (1, self.__lock_after_load_cookie)
1074
1076 if not self.__attached:
1077 return (0, _('request rejected, you are not attach()ed'))
1078 if auth_cookie != self.__auth_cookie:
1079 _log.error('non-authenticated lock_load_patient()')
1080 return (0, _('rejected lock_load_patient(), not authenticated'))
1081
1082 if lock_after_load_cookie != self.__lock_after_load_cookie:
1083 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
1084 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
1085 self.__pat.locked = True
1086 self.__pat_lock_cookie = str(random.random())
1087 return (1, self.__pat_lock_cookie)
1088
1090 if not self.__attached:
1091 return (0, _('request rejected, you are not attach()ed'))
1092 if auth_cookie != self.__auth_cookie:
1093 _log.error('non-authenticated lock_into_patient()')
1094 return (0, _('rejected lock_into_patient(), not authenticated'))
1095 if self.__pat.locked:
1096 _log.error('patient is already locked')
1097 return (0, _('already locked into a patient'))
1098 searcher = gmPersonSearch.cPatientSearcher_SQL()
1099 if type(search_params) == types.DictType:
1100 idents = searcher.get_identities(search_dict=search_params)
1101 raise StandardError("must use dto, not search_dict")
1102 else:
1103 idents = searcher.get_identities(search_term=search_params)
1104 if idents is None:
1105 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
1106 if len(idents) == 0:
1107 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
1108
1109 if len(idents) > 1:
1110 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
1111 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
1112 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
1113 self.__pat.locked = True
1114 self.__pat_lock_cookie = str(random.random())
1115 return (1, self.__pat_lock_cookie)
1116
1118 if not self.__attached:
1119 return (0, _('request rejected, you are not attach()ed'))
1120 if auth_cookie != self.__auth_cookie:
1121 _log.error('non-authenticated unlock_patient()')
1122 return (0, _('rejected unlock_patient, not authenticated'))
1123
1124 if not self.__pat.locked:
1125 return (1, '')
1126
1127 if unlock_cookie != self.__pat_lock_cookie:
1128 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
1129 return (0, 'patient unlock request rejected, wrong cookie provided')
1130 self.__pat.locked = False
1131 return (1, '')
1132
1134 if not self.__attached:
1135 return 0
1136 if auth_cookie != self.__auth_cookie:
1137 _log.error('non-authenticated select_identity()')
1138 return 0
1139 return "cMacroPrimitives.assume_staff_identity() not implemented"
1140
1142 if not self.__user_done:
1143 return (0, 'still waiting')
1144 self.__user_done = False
1145 return (1, self.__user_answer)
1146
1147
1148
1150 msg = _(
1151 'Someone tries to forcibly break the existing\n'
1152 'controlling connection. This may or may not\n'
1153 'have legitimate reasons.\n\n'
1154 'Do you want to allow breaking the connection ?'
1155 )
1156 can_break_conn = gmGuiHelpers.gm_show_question (
1157 aMessage = msg,
1158 aTitle = _('forced detach attempt')
1159 )
1160 if can_break_conn:
1161 self.__user_answer = 1
1162 else:
1163 self.__user_answer = 0
1164 self.__user_done = True
1165 if can_break_conn:
1166 self.__pat.locked = False
1167 self.__attached = 0
1168 return 1
1169
1171 top_win = wx.GetApp().GetTopWindow()
1172 if forced:
1173 top_win.Destroy()
1174 else:
1175 top_win.Close()
1176
1185
1186
1187
1188 if __name__ == '__main__':
1189
1190 if len(sys.argv) < 2:
1191 sys.exit()
1192
1193 if sys.argv[1] != 'test':
1194 sys.exit()
1195
1196 gmI18N.activate_locale()
1197 gmI18N.install_domain()
1198
1199
1201 handler = gmPlaceholderHandler()
1202 handler.debug = True
1203
1204 for placeholder in ['a', 'b']:
1205 print handler[placeholder]
1206
1207 pat = gmPersonSearch.ask_for_patient()
1208 if pat is None:
1209 return
1210
1211 gmPatSearchWidgets.set_active_patient(patient = pat)
1212
1213 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1214
1215 app = wx.PyWidgetTester(size = (200, 50))
1216 for placeholder in known_placeholders:
1217 print placeholder, "=", handler[placeholder]
1218
1219 ph = 'progress_notes::ap'
1220 print '%s: %s' % (ph, handler[ph])
1221
1223
1224 tests = [
1225
1226 '$<lastname>$',
1227 '$<lastname::::3>$',
1228 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
1229
1230
1231 'lastname',
1232 '$<lastname',
1233 '$<lastname::',
1234 '$<lastname::>$',
1235 '$<lastname::abc>$',
1236 '$<lastname::abc::>$',
1237 '$<lastname::abc::3>$',
1238 '$<lastname::abc::xyz>$',
1239 '$<lastname::::>$',
1240 '$<lastname::::xyz>$',
1241
1242 '$<date_of_birth::%Y-%m-%d>$',
1243 '$<date_of_birth::%Y-%m-%d::3>$',
1244 '$<date_of_birth::%Y-%m-%d::>$',
1245
1246
1247 '$<adr_location::home::35>$',
1248 '$<gender_mapper::male//female//other::5>$',
1249 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
1250 '$<allergy_list::%(descriptor)s, >$',
1251 '$<current_meds_table::latex//by-brand>$'
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266 ]
1267
1268 tests = [
1269 '$<latest_vaccs_table::latex>$'
1270 ]
1271
1272 pat = gmPersonSearch.ask_for_patient()
1273 if pat is None:
1274 return
1275
1276 gmPatSearchWidgets.set_active_patient(patient = pat)
1277
1278 handler = gmPlaceholderHandler()
1279 handler.debug = True
1280
1281 for placeholder in tests:
1282 print placeholder, "=>", handler[placeholder]
1283 print "--------------"
1284 raw_input()
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1297 from Gnumed.pycommon import gmScriptingListener
1298 import xmlrpclib
1299 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1300
1301 s = xmlrpclib.ServerProxy('http://localhost:9999')
1302 print "should fail:", s.attach()
1303 print "should fail:", s.attach('wrong cookie')
1304 print "should work:", s.version()
1305 print "should fail:", s.raise_gnumed()
1306 print "should fail:", s.raise_notebook_plugin('test plugin')
1307 print "should fail:", s.lock_into_patient('kirk, james')
1308 print "should fail:", s.unlock_patient()
1309 status, conn_auth = s.attach('unit test')
1310 print "should work:", status, conn_auth
1311 print "should work:", s.version()
1312 print "should work:", s.raise_gnumed(conn_auth)
1313 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1314 print "should work:", status, pat_auth
1315 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1316 print "should work", s.unlock_patient(conn_auth, pat_auth)
1317 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1318 status, pat_auth = s.lock_into_patient(conn_auth, data)
1319 print "should work:", status, pat_auth
1320 print "should work", s.unlock_patient(conn_auth, pat_auth)
1321 print s.detach('bogus detach cookie')
1322 print s.detach(conn_auth)
1323 del s
1324
1325 listener.shutdown()
1326
1328
1329 import re as regex
1330
1331 tests = [
1332 ' $<lastname>$ ',
1333 ' $<lastname::::3>$ ',
1334
1335
1336 '$<date_of_birth::%Y-%m-%d>$',
1337 '$<date_of_birth::%Y-%m-%d::3>$',
1338 '$<date_of_birth::%Y-%m-%d::>$',
1339
1340 '$<adr_location::home::35>$',
1341 '$<gender_mapper::male//female//other::5>$',
1342 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1343 '$<allergy_list::%(descriptor)s, >$',
1344
1345 '\\noindent Patient: $<lastname>$, $<firstname>$',
1346 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1347 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1348 ]
1349
1350 tests = [
1351
1352 'junk $<lastname::::3>$ junk',
1353 'junk $<lastname::abc::3>$ junk',
1354 'junk $<lastname::abc>$ junk',
1355 'junk $<lastname>$ junk',
1356
1357 'junk $<lastname>$ junk $<firstname>$ junk',
1358 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1359 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1360 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1361
1362 ]
1363
1364 print "testing placeholder regex:", default_placeholder_regex
1365 print ""
1366
1367 for t in tests:
1368 print 'line: "%s"' % t
1369 print "placeholders:"
1370 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE):
1371 print ' => "%s"' % p
1372 print " "
1373
1413
1414
1415
1416
1417
1418
1419
1420 test_placeholder()
1421
1422
1423