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
395
396 lastname = property(_get_lastname, _setter_noop)
397 firstname = property(_get_firstname, _setter_noop)
398 title = property(_get_title, _setter_noop)
399 date_of_birth = property(_get_dob, _setter_noop)
400
401 progress_notes = property(_get_progress_notes, _setter_noop)
402 soap = property(_get_progress_notes, _setter_noop)
403 soap_s = property(_get_soap_s, _setter_noop)
404 soap_o = property(_get_soap_o, _setter_noop)
405 soap_a = property(_get_soap_a, _setter_noop)
406 soap_p = property(_get_soap_p, _setter_noop)
407 soap_u = property(_get_soap_u, _setter_noop)
408 soap_admin = property(_get_soap_admin, _setter_noop)
409
410 allergy_state = property(_get_allergy_state, _setter_noop)
411
412 client_version = property(_get_client_version, _setter_noop)
413
414 current_provider = property(_get_current_provider, _setter_noop)
415 primary_praxis_provider = property(_get_primary_praxis_provider, _setter_noop)
416
417
418
420
421 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
422 if not encounters:
423 return u''
424
425 template = data
426
427 lines = []
428 for enc in encounters:
429 try:
430 lines.append(template % enc)
431 except:
432 lines.append(u'error formatting encounter')
433 _log.exception('problem formatting encounter list')
434 _log.error('template: %s', template)
435 _log.error('encounter: %s', encounter)
436
437 return u'\n'.join(lines)
438
440 """Select encounters from list and format SOAP thereof.
441
442 data: soap_cats (' ' -> None -> admin) // date format
443 """
444
445 cats = None
446 date_format = None
447
448 if data is not None:
449 data_parts = data.split('//')
450
451
452 if len(data_parts[0]) > 0:
453 cats = []
454 if u' ' in data_parts[0]:
455 cats.append(None)
456 data_parts[0] = data_parts[0].replace(u' ', u'')
457 cats.extend(list(data_parts[0]))
458
459
460 if len(data_parts) > 1:
461 if len(data_parts[1]) > 0:
462 date_format = data_parts[1]
463
464 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
465 if not encounters:
466 return u''
467
468 chunks = []
469 for enc in encounters:
470 chunks.append(enc.format_latex (
471 date_format = date_format,
472 soap_cats = cats,
473 soap_order = u'soap_rank, date'
474 ))
475
476 return u''.join(chunks)
477
479
480 cats = list(u'soapu')
481 cats.append(None)
482 template = u'%s'
483 interactive = True
484 line_length = 9999
485 target_format = None
486 time_range = None
487
488 if data is not None:
489 data_parts = data.split('//')
490
491
492 cats = []
493
494 for c in list(data_parts[0]):
495 if c == u' ':
496 c = None
497 cats.append(c)
498
499 if cats == u'':
500 cats = list(u'soapu').append(None)
501
502
503 if len(data_parts) > 1:
504 template = data_parts[1]
505
506
507 if len(data_parts) > 2:
508 try:
509 line_length = int(data_parts[2])
510 except:
511 line_length = 9999
512
513
514 if len(data_parts) > 3:
515 try:
516 time_range = 7 * int(data_parts[3])
517 except:
518 time_range = None
519
520
521 if len(data_parts) > 4:
522 target_format = data_parts[4]
523
524
525 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range)
526
527 if len(narr) == 0:
528 return u''
529
530 if target_format == u'tex':
531 keys = narr[0].keys()
532 lines = []
533 line_dict = {}
534 for n in narr:
535 for key in keys:
536 if isinstance(n[key], basestring):
537 line_dict[key] = gmTools.tex_escape_string(text = n[key])
538 continue
539 line_dict[key] = n[key]
540 try:
541 lines.append((template % line_dict)[:line_length])
542 except KeyError:
543 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
544 else:
545 try:
546 lines = [ (template % n)[:line_length] for n in narr ]
547 except KeyError:
548 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
549
550 return u'\n'.join(lines)
551
553 return self._get_variant_soap(data=data)
554
556
557
558 cats = list(u'soapu')
559 cats.append(None)
560 template = u'%s'
561
562 if data is not None:
563 data_parts = data.split('//')
564
565
566 cats = []
567
568 for cat in list(data_parts[0]):
569 if cat == u' ':
570 cat = None
571 cats.append(cat)
572
573 if cats == u'':
574 cats = list(u'soapu')
575 cats.append(None)
576
577
578 if len(data_parts) > 1:
579 template = data_parts[1]
580
581
582 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
583
584 if narr is None:
585 return u''
586
587 if len(narr) == 0:
588 return u''
589
590 try:
591 narr = [ template % n['narrative'] for n in narr ]
592 except KeyError:
593 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
594
595 return u'\n'.join(narr)
596
615
618
619
621 values = data.split('//', 2)
622
623 if len(values) == 2:
624 male_value, female_value = values
625 other_value = u'<unkown gender>'
626 elif len(values) == 3:
627 male_value, female_value, other_value = values
628 else:
629 return _('invalid gender mapping layout: [%s]') % data
630
631 if self.pat['gender'] == u'm':
632 return male_value
633
634 if self.pat['gender'] == u'f':
635 return female_value
636
637 return other_value
638
639
640
642
643 data_parts = data.split(u'//')
644
645 if data_parts[0].strip() == u'':
646 adr_type = u'home'
647 else:
648 adr_type = data_parts[0]
649
650 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
651 if len(data_parts) > 1:
652 if data_parts[1].strip() != u'':
653 template = data_parts[1]
654
655 adrs = self.pat.get_addresses(address_type = adr_type)
656 if len(adrs) == 0:
657 if self.debug:
658 return _('no address for type [%s]') % adr_type
659 return u''
660
661 adr = adrs[0]
662 data = {
663 'street': adr['street'],
664 'notes_street': gmTools.coalesce(adr['notes_street'], u''),
665 'postcode': adr['postcode'],
666 'number': adr['number'],
667 'subunit': gmTools.coalesce(adr['subunit'], u''),
668 'notes_subunit': gmTools.coalesce(adr['notes_subunit'], u''),
669 'urb': adr['urb'],
670 'suburb': gmTools.coalesce(adr['suburb'], u''),
671 'l10n_state': adr['l10n_state'],
672 'l10n_country': adr['l10n_country'],
673 'code_state': adr['code_state'],
674 'code_country': adr['code_country']
675 }
676
677 try:
678 return template % data
679 except StandardError:
680 _log.exception('error formatting address')
681 _log.error('template: %s', template)
682
683 return None
684
686 adrs = self.pat.get_addresses(address_type=data)
687 if len(adrs) == 0:
688 if self.debug:
689 return _('no street for address type [%s]') % data
690 return u''
691 return adrs[0]['street']
692
694 adrs = self.pat.get_addresses(address_type=data)
695 if len(adrs) == 0:
696 if self.debug:
697 return _('no number for address type [%s]') % data
698 return u''
699 return adrs[0]['number']
700
702 adrs = self.pat.get_addresses(address_type=data)
703 if len(adrs) == 0:
704 if self.debug:
705 return _('no location for address type [%s]') % data
706 return u''
707 return adrs[0]['urb']
708
709 - def _get_variant_adr_postcode(self, data=u'?'):
710 adrs = self.pat.get_addresses(address_type = data)
711 if len(adrs) == 0:
712 if self.debug:
713 return _('no postcode for address type [%s]') % data
714 return u''
715 return adrs[0]['postcode']
716
718 adrs = self.pat.get_addresses(address_type = data)
719 if len(adrs) == 0:
720 if self.debug:
721 return _('no region for address type [%s]') % data
722 return u''
723 return adrs[0]['l10n_state']
724
726 adrs = self.pat.get_addresses(address_type = data)
727 if len(adrs) == 0:
728 if self.debug:
729 return _('no country for address type [%s]') % data
730 return u''
731 return adrs[0]['l10n_country']
732
734 comms = self.pat.get_comm_channels(comm_medium = data)
735 if len(comms) == 0:
736 if self.debug:
737 return _('no URL for comm channel [%s]') % data
738 return u''
739 return comms[0]['url']
740
742 data_parts = data.split(u'//')
743 if len(data_parts) < 2:
744 return u'current provider external ID: template is missing'
745
746 id_type = data_parts[0].strip()
747 if id_type == u'':
748 return u'current provider external ID: type is missing'
749
750 issuer = data_parts[1].strip()
751 if issuer == u'':
752 return u'current provider external ID: issuer is missing'
753
754 prov = gmStaff.gmCurrentProvider()
755 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
756
757 if len(ids) == 0:
758 if self.debug:
759 return _('no external ID [%s] by [%s]') % (id_type, issuer)
760 return u''
761
762 return ids[0]['value']
763
765 data_parts = data.split(u'//')
766 if len(data_parts) < 2:
767 return u'primary in-praxis provider external ID: template is missing'
768
769 id_type = data_parts[0].strip()
770 if id_type == u'':
771 return u'primary in-praxis provider external ID: type is missing'
772
773 issuer = data_parts[1].strip()
774 if issuer == u'':
775 return u'primary in-praxis provider external ID: issuer is missing'
776
777 prov = self.pat.primary_provider
778 if prov is None:
779 if self.debug:
780 return _('no primary in-praxis provider')
781 return u''
782
783 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
784
785 if len(ids) == 0:
786 if self.debug:
787 return _('no external ID [%s] by [%s]') % (id_type, issuer)
788 return u''
789
790 return ids[0]['value']
791
793 data_parts = data.split(u'//')
794 if len(data_parts) < 2:
795 return u'patient external ID: template is missing'
796
797 id_type = data_parts[0].strip()
798 if id_type == u'':
799 return u'patient external ID: type is missing'
800
801 issuer = data_parts[1].strip()
802 if issuer == u'':
803 return u'patient external ID: issuer is missing'
804
805 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
806
807 if len(ids) == 0:
808 if self.debug:
809 return _('no external ID [%s] by [%s]') % (id_type, issuer)
810 return u''
811
812 return ids[0]['value']
813
815 if data is None:
816 return [_('template is missing')]
817
818 template, separator = data.split('//', 2)
819
820 emr = self.pat.get_emr()
821 return separator.join([ template % a for a in emr.get_allergies() ])
822
830
832
833 if data is None:
834 return [_('template is missing')]
835
836 emr = self.pat.get_emr()
837 current_meds = emr.get_current_substance_intake (
838 include_inactive = False,
839 include_unapproved = False,
840 order_by = u'brand, substance'
841 )
842
843 return u'\n'.join([ data % m.fields_as_dict(date_format = '%Y %B %d') for m in current_meds ])
844
846
847 options = data.split('//')
848
849 if u'latex' in options:
850 return gmMedication.format_substance_intake (
851 emr = self.pat.get_emr(),
852 output_format = u'latex',
853 table_type = u'by-brand'
854 )
855
856 _log.error('no known current medications table formatting style in [%s]', data)
857 return _('unknown current medication table formatting style')
858
860
861 options = data.split('//')
862
863 if u'latex' in options:
864 return gmMedication.format_substance_intake_notes (
865 emr = self.pat.get_emr(),
866 output_format = u'latex',
867 table_type = u'by-brand'
868 )
869
870 _log.error('no known current medications notes formatting style in [%s]', data)
871 return _('unknown current medication notes formatting style')
872
887
899
901
902 if data is None:
903 return [_('template is missing')]
904
905 probs = self.pat.get_emr().get_problems()
906
907 return u'\n'.join([ data % p for p in probs ])
908
911
914
915 - def _get_variant_free_text(self, data=u'tex//'):
916
917
918
919
920 data_parts = data.split('//')
921 format = data_parts[0]
922 if len(data_parts) > 1:
923 msg = data_parts[1]
924 else:
925 msg = _('generic text')
926
927 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
928 None,
929 -1,
930 title = _('Replacing <free_text> placeholder'),
931 msg = _('Below you can enter free text.\n\n [%s]') % msg
932 )
933 dlg.enable_user_formatting = True
934 decision = dlg.ShowModal()
935
936 if decision != wx.ID_SAVE:
937 dlg.Destroy()
938 if self.debug:
939 return _('Text input cancelled by user.')
940 return u''
941
942 text = dlg.value.strip()
943 if dlg.is_user_formatted:
944 dlg.Destroy()
945 return text
946
947 dlg.Destroy()
948
949 if format == u'tex':
950 return gmTools.tex_escape_string(text = text)
951
952 return text
953
954
955
956
957
959 """Functions a macro can legally use.
960
961 An instance of this class is passed to the GNUmed scripting
962 listener. Hence, all actions a macro can legally take must
963 be defined in this class. Thus we achieve some screening for
964 security and also thread safety handling.
965 """
966
967 - def __init__(self, personality = None):
968 if personality is None:
969 raise gmExceptions.ConstructorError, 'must specify personality'
970 self.__personality = personality
971 self.__attached = 0
972 self._get_source_personality = None
973 self.__user_done = False
974 self.__user_answer = 'no answer yet'
975 self.__pat = gmPerson.gmCurrentPatient()
976
977 self.__auth_cookie = str(random.random())
978 self.__pat_lock_cookie = str(random.random())
979 self.__lock_after_load_cookie = str(random.random())
980
981 _log.info('slave mode personality is [%s]', personality)
982
983
984
985 - def attach(self, personality = None):
986 if self.__attached:
987 _log.error('attach with [%s] rejected, already serving a client', personality)
988 return (0, _('attach rejected, already serving a client'))
989 if personality != self.__personality:
990 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
991 return (0, _('attach to personality [%s] rejected') % personality)
992 self.__attached = 1
993 self.__auth_cookie = str(random.random())
994 return (1, self.__auth_cookie)
995
996 - def detach(self, auth_cookie=None):
997 if not self.__attached:
998 return 1
999 if auth_cookie != self.__auth_cookie:
1000 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
1001 return 0
1002 self.__attached = 0
1003 return 1
1004
1006 if not self.__attached:
1007 return 1
1008 self.__user_done = False
1009
1010 wx.CallAfter(self._force_detach)
1011 return 1
1012
1014 ver = _cfg.get(option = u'client_version')
1015 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
1016
1018 """Shuts down this client instance."""
1019 if not self.__attached:
1020 return 0
1021 if auth_cookie != self.__auth_cookie:
1022 _log.error('non-authenticated shutdown_gnumed()')
1023 return 0
1024 wx.CallAfter(self._shutdown_gnumed, forced)
1025 return 1
1026
1028 """Raise ourselves to the top of the desktop."""
1029 if not self.__attached:
1030 return 0
1031 if auth_cookie != self.__auth_cookie:
1032 _log.error('non-authenticated raise_gnumed()')
1033 return 0
1034 return "cMacroPrimitives.raise_gnumed() not implemented"
1035
1037 if not self.__attached:
1038 return 0
1039 if auth_cookie != self.__auth_cookie:
1040 _log.error('non-authenticated get_loaded_plugins()')
1041 return 0
1042 gb = gmGuiBroker.GuiBroker()
1043 return gb['horstspace.notebook.gui'].keys()
1044
1046 """Raise a notebook plugin within GNUmed."""
1047 if not self.__attached:
1048 return 0
1049 if auth_cookie != self.__auth_cookie:
1050 _log.error('non-authenticated raise_notebook_plugin()')
1051 return 0
1052
1053 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
1054 return 1
1055
1057 """Load external patient, perhaps create it.
1058
1059 Callers must use get_user_answer() to get status information.
1060 It is unsafe to proceed without knowing the completion state as
1061 the controlled client may be waiting for user input from a
1062 patient selection list.
1063 """
1064 if not self.__attached:
1065 return (0, _('request rejected, you are not attach()ed'))
1066 if auth_cookie != self.__auth_cookie:
1067 _log.error('non-authenticated load_patient_from_external_source()')
1068 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
1069 if self.__pat.locked:
1070 _log.error('patient is locked, cannot load from external source')
1071 return (0, _('current patient is locked'))
1072 self.__user_done = False
1073 wx.CallAfter(self._load_patient_from_external_source)
1074 self.__lock_after_load_cookie = str(random.random())
1075 return (1, self.__lock_after_load_cookie)
1076
1078 if not self.__attached:
1079 return (0, _('request rejected, you are not attach()ed'))
1080 if auth_cookie != self.__auth_cookie:
1081 _log.error('non-authenticated lock_load_patient()')
1082 return (0, _('rejected lock_load_patient(), not authenticated'))
1083
1084 if lock_after_load_cookie != self.__lock_after_load_cookie:
1085 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
1086 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
1087 self.__pat.locked = True
1088 self.__pat_lock_cookie = str(random.random())
1089 return (1, self.__pat_lock_cookie)
1090
1092 if not self.__attached:
1093 return (0, _('request rejected, you are not attach()ed'))
1094 if auth_cookie != self.__auth_cookie:
1095 _log.error('non-authenticated lock_into_patient()')
1096 return (0, _('rejected lock_into_patient(), not authenticated'))
1097 if self.__pat.locked:
1098 _log.error('patient is already locked')
1099 return (0, _('already locked into a patient'))
1100 searcher = gmPersonSearch.cPatientSearcher_SQL()
1101 if type(search_params) == types.DictType:
1102 idents = searcher.get_identities(search_dict=search_params)
1103 raise StandardError("must use dto, not search_dict")
1104 else:
1105 idents = searcher.get_identities(search_term=search_params)
1106 if idents is None:
1107 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
1108 if len(idents) == 0:
1109 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
1110
1111 if len(idents) > 1:
1112 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
1113 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
1114 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
1115 self.__pat.locked = True
1116 self.__pat_lock_cookie = str(random.random())
1117 return (1, self.__pat_lock_cookie)
1118
1120 if not self.__attached:
1121 return (0, _('request rejected, you are not attach()ed'))
1122 if auth_cookie != self.__auth_cookie:
1123 _log.error('non-authenticated unlock_patient()')
1124 return (0, _('rejected unlock_patient, not authenticated'))
1125
1126 if not self.__pat.locked:
1127 return (1, '')
1128
1129 if unlock_cookie != self.__pat_lock_cookie:
1130 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
1131 return (0, 'patient unlock request rejected, wrong cookie provided')
1132 self.__pat.locked = False
1133 return (1, '')
1134
1136 if not self.__attached:
1137 return 0
1138 if auth_cookie != self.__auth_cookie:
1139 _log.error('non-authenticated select_identity()')
1140 return 0
1141 return "cMacroPrimitives.assume_staff_identity() not implemented"
1142
1144 if not self.__user_done:
1145 return (0, 'still waiting')
1146 self.__user_done = False
1147 return (1, self.__user_answer)
1148
1149
1150
1152 msg = _(
1153 'Someone tries to forcibly break the existing\n'
1154 'controlling connection. This may or may not\n'
1155 'have legitimate reasons.\n\n'
1156 'Do you want to allow breaking the connection ?'
1157 )
1158 can_break_conn = gmGuiHelpers.gm_show_question (
1159 aMessage = msg,
1160 aTitle = _('forced detach attempt')
1161 )
1162 if can_break_conn:
1163 self.__user_answer = 1
1164 else:
1165 self.__user_answer = 0
1166 self.__user_done = True
1167 if can_break_conn:
1168 self.__pat.locked = False
1169 self.__attached = 0
1170 return 1
1171
1173 top_win = wx.GetApp().GetTopWindow()
1174 if forced:
1175 top_win.Destroy()
1176 else:
1177 top_win.Close()
1178
1187
1188
1189
1190 if __name__ == '__main__':
1191
1192 if len(sys.argv) < 2:
1193 sys.exit()
1194
1195 if sys.argv[1] != 'test':
1196 sys.exit()
1197
1198 gmI18N.activate_locale()
1199 gmI18N.install_domain()
1200
1201
1203 handler = gmPlaceholderHandler()
1204 handler.debug = True
1205
1206 for placeholder in ['a', 'b']:
1207 print handler[placeholder]
1208
1209 pat = gmPersonSearch.ask_for_patient()
1210 if pat is None:
1211 return
1212
1213 gmPatSearchWidgets.set_active_patient(patient = pat)
1214
1215 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1216
1217 app = wx.PyWidgetTester(size = (200, 50))
1218 for placeholder in known_placeholders:
1219 print placeholder, "=", handler[placeholder]
1220
1221 ph = 'progress_notes::ap'
1222 print '%s: %s' % (ph, handler[ph])
1223
1225
1226 tests = [
1227
1228 '$<lastname>$',
1229 '$<lastname::::3>$',
1230 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
1231
1232
1233 'lastname',
1234 '$<lastname',
1235 '$<lastname::',
1236 '$<lastname::>$',
1237 '$<lastname::abc>$',
1238 '$<lastname::abc::>$',
1239 '$<lastname::abc::3>$',
1240 '$<lastname::abc::xyz>$',
1241 '$<lastname::::>$',
1242 '$<lastname::::xyz>$',
1243
1244 '$<date_of_birth::%Y-%m-%d>$',
1245 '$<date_of_birth::%Y-%m-%d::3>$',
1246 '$<date_of_birth::%Y-%m-%d::>$',
1247
1248
1249 '$<adr_location::home::35>$',
1250 '$<gender_mapper::male//female//other::5>$',
1251 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
1252 '$<allergy_list::%(descriptor)s, >$',
1253 '$<current_meds_table::latex//by-brand>$'
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268 ]
1269
1270 tests = [
1271 '$<latest_vaccs_table::latex>$'
1272 ]
1273
1274 pat = gmPersonSearch.ask_for_patient()
1275 if pat is None:
1276 return
1277
1278 gmPatSearchWidgets.set_active_patient(patient = pat)
1279
1280 handler = gmPlaceholderHandler()
1281 handler.debug = True
1282
1283 for placeholder in tests:
1284 print placeholder, "=>", handler[placeholder]
1285 print "--------------"
1286 raw_input()
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1299 from Gnumed.pycommon import gmScriptingListener
1300 import xmlrpclib
1301 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1302
1303 s = xmlrpclib.ServerProxy('http://localhost:9999')
1304 print "should fail:", s.attach()
1305 print "should fail:", s.attach('wrong cookie')
1306 print "should work:", s.version()
1307 print "should fail:", s.raise_gnumed()
1308 print "should fail:", s.raise_notebook_plugin('test plugin')
1309 print "should fail:", s.lock_into_patient('kirk, james')
1310 print "should fail:", s.unlock_patient()
1311 status, conn_auth = s.attach('unit test')
1312 print "should work:", status, conn_auth
1313 print "should work:", s.version()
1314 print "should work:", s.raise_gnumed(conn_auth)
1315 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1316 print "should work:", status, pat_auth
1317 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1318 print "should work", s.unlock_patient(conn_auth, pat_auth)
1319 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1320 status, pat_auth = s.lock_into_patient(conn_auth, data)
1321 print "should work:", status, pat_auth
1322 print "should work", s.unlock_patient(conn_auth, pat_auth)
1323 print s.detach('bogus detach cookie')
1324 print s.detach(conn_auth)
1325 del s
1326
1327 listener.shutdown()
1328
1330
1331 import re as regex
1332
1333 tests = [
1334 ' $<lastname>$ ',
1335 ' $<lastname::::3>$ ',
1336
1337
1338 '$<date_of_birth::%Y-%m-%d>$',
1339 '$<date_of_birth::%Y-%m-%d::3>$',
1340 '$<date_of_birth::%Y-%m-%d::>$',
1341
1342 '$<adr_location::home::35>$',
1343 '$<gender_mapper::male//female//other::5>$',
1344 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1345 '$<allergy_list::%(descriptor)s, >$',
1346
1347 '\\noindent Patient: $<lastname>$, $<firstname>$',
1348 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1349 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1350 ]
1351
1352 tests = [
1353
1354 'junk $<lastname::::3>$ junk',
1355 'junk $<lastname::abc::3>$ junk',
1356 'junk $<lastname::abc>$ junk',
1357 'junk $<lastname>$ junk',
1358
1359 'junk $<lastname>$ junk $<firstname>$ junk',
1360 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1361 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1362 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1363
1364 ]
1365
1366 print "testing placeholder regex:", default_placeholder_regex
1367 print ""
1368
1369 for t in tests:
1370 print 'line: "%s"' % t
1371 print "placeholders:"
1372 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE):
1373 print ' => "%s"' % p
1374 print " "
1375
1415
1416
1417
1418
1419
1420
1421
1422 test_placeholder()
1423
1424
1425