1 """GNUmed macro primitives.
2
3 This module implements functions a macro can legally use.
4 """
5
6 __version__ = "$Revision: 1.51 $"
7 __author__ = "K.Hilbert <karsten.hilbert@gmx.net>"
8
9 import sys, time, random, types, logging
10
11
12 import wx
13
14
15 if __name__ == '__main__':
16 sys.path.insert(0, '../../')
17 from Gnumed.pycommon import gmI18N
18 if __name__ == '__main__':
19 gmI18N.activate_locale()
20 gmI18N.install_domain()
21 from Gnumed.pycommon import gmGuiBroker
22 from Gnumed.pycommon import gmTools
23 from Gnumed.pycommon import gmBorg
24 from Gnumed.pycommon import gmExceptions
25 from Gnumed.pycommon import gmCfg2
26 from Gnumed.pycommon import gmDateTime
27
28 from Gnumed.business import gmPerson
29 from Gnumed.business import gmDemographicRecord
30 from Gnumed.business import gmMedication
31 from Gnumed.business import gmPathLab
32 from Gnumed.business import gmPersonSearch
33 from Gnumed.business import gmVaccination
34 from Gnumed.business import gmPersonSearch
35
36 from Gnumed.wxpython import gmGuiHelpers
37 from Gnumed.wxpython import gmNarrativeWidgets
38 from Gnumed.wxpython import gmPatSearchWidgets
39 from Gnumed.wxpython import gmPlugin
40 from Gnumed.wxpython import gmEMRStructWidgets
41
42
43 _log = logging.getLogger('gm.scripting')
44 _cfg = gmCfg2.gmCfgData()
45
46
47 known_placeholders = [
48 'lastname',
49 'firstname',
50 'title',
51 'date_of_birth',
52 'progress_notes',
53 'soap',
54 'soap_s',
55 'soap_o',
56 'soap_a',
57 'soap_p',
58 u'client_version',
59 u'current_provider',
60 u'primary_praxis_provider',
61 u'allergy_state'
62 ]
63
64
65
66 known_variant_placeholders = [
67 u'soap',
68 u'progress_notes',
69
70
71 u'emr_journal',
72
73
74
75
76
77
78
79 u'date_of_birth',
80
81 u'patient_address',
82 u'adr_street',
83 u'adr_number',
84 u'adr_location',
85 u'adr_postcode',
86 u'adr_region',
87 u'adr_country',
88
89 u'patient_comm',
90 u'external_id',
91 u'gender_mapper',
92
93
94 u'current_meds',
95 u'current_meds_table',
96
97 u'current_meds_notes',
98 u'lab_table',
99 u'latest_vaccs_table',
100 u'today',
101 u'tex_escape',
102 u'allergies',
103 u'allergy_list',
104 u'problems',
105 u'name',
106 u'free_text',
107 u'soap_for_encounters',
108 u'encounter_list'
109 ]
110
111 default_placeholder_regex = r'\$<.+?>\$'
112
113
114
115
116
117
118
119
120 default_placeholder_start = u'$<'
121 default_placeholder_end = u'>$'
122
124 """Replaces placeholders in forms, fields, etc.
125
126 - patient related placeholders operate on the currently active patient
127 - is passed to the forms handling code, for example
128
129 Note that this cannot be called from a non-gui thread unless
130 wrapped in wx.CallAfter().
131
132 There are currently two types of placeholders:
133
134 simple static placeholders
135 - those are listed in known_placeholders
136 - they are used as-is
137
138 variant placeholders
139 - those are listed in known_variant_placeholders
140 - they are parsed into placeholder, data, and maximum length
141 - the length is optional
142 - data is passed to the handler
143 """
145
146 self.pat = gmPerson.gmCurrentPatient()
147 self.debug = False
148
149 self.invalid_placeholder_template = _('invalid placeholder [%s]')
150
151
152
154 """Map self['placeholder'] to self.placeholder.
155
156 This is useful for replacing placeholders parsed out
157 of documents as strings.
158
159 Unknown/invalid placeholders still deliver a result but
160 it will be glaringly obvious if debugging is enabled.
161 """
162 _log.debug('replacing [%s]', placeholder)
163
164 original_placeholder = placeholder
165
166 if placeholder.startswith(default_placeholder_start):
167 placeholder = placeholder[len(default_placeholder_start):]
168 if placeholder.endswith(default_placeholder_end):
169 placeholder = placeholder[:-len(default_placeholder_end)]
170 else:
171 _log.debug('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
172 if self.debug:
173 return self.invalid_placeholder_template % original_placeholder
174 return None
175
176
177 if placeholder in known_placeholders:
178 return getattr(self, placeholder)
179
180
181 parts = placeholder.split('::::', 1)
182 if len(parts) == 2:
183 name, lng = parts
184 try:
185 return getattr(self, name)[:int(lng)]
186 except:
187 _log.exception('placeholder handling error: %s', original_placeholder)
188 if self.debug:
189 return self.invalid_placeholder_template % original_placeholder
190 return None
191
192
193 parts = placeholder.split('::')
194 if len(parts) == 2:
195 name, data = parts
196 lng = None
197 if len(parts) == 3:
198 name, data, lng = parts
199 try:
200 lng = int(lng)
201 except (TypeError, ValueError):
202 _log.error('placeholder length definition error: %s, discarding length: >%s<', original_placeholder, lng)
203 lng = None
204 if len(parts) > 3:
205 _log.warning('invalid placeholder layout: %s', original_placeholder)
206 if self.debug:
207 return self.invalid_placeholder_template % original_placeholder
208 return None
209
210 handler = getattr(self, '_get_variant_%s' % name, None)
211 if handler is None:
212 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
213 if self.debug:
214 return self.invalid_placeholder_template % original_placeholder
215 return None
216
217 try:
218 if lng is None:
219 return handler(data = data)
220 return handler(data = data)[:lng]
221 except:
222 _log.exception('placeholder handling error: %s', original_placeholder)
223 if self.debug:
224 return self.invalid_placeholder_template % original_placeholder
225 return None
226
227 _log.error('something went wrong, should never get here')
228 return None
229
230
231
232
233
235 """This does nothing, used as a NOOP properties setter."""
236 pass
237
240
243
246
248 return self._get_variant_date_of_birth(data='%x')
249
251 return self._get_variant_soap()
252
254 return self._get_variant_soap(data = u's')
255
257 return self._get_variant_soap(data = u'o')
258
260 return self._get_variant_soap(data = u'a')
261
263 return self._get_variant_soap(data = u'p')
264
266 return self._get_variant_soap(soap_cats = None)
267
269 return gmTools.coalesce (
270 _cfg.get(option = u'client_version'),
271 u'%s' % self.__class__.__name__
272 )
273
291
307
309 allg_state = self.pat.get_emr().allergy_state
310
311 if allg_state['last_confirmed'] is None:
312 date_confirmed = u''
313 else:
314 date_confirmed = u' (%s)' % allg_state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding())
315
316 tmp = u'%s%s' % (
317 allg_state.state_string,
318 date_confirmed
319 )
320 return tmp
321
322
323
324 placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop)
325
326
327 lastname = property(_get_lastname, _setter_noop)
328 firstname = property(_get_firstname, _setter_noop)
329 title = property(_get_title, _setter_noop)
330 date_of_birth = property(_get_dob, _setter_noop)
331
332 progress_notes = property(_get_progress_notes, _setter_noop)
333 soap = property(_get_progress_notes, _setter_noop)
334 soap_s = property(_get_soap_s, _setter_noop)
335 soap_o = property(_get_soap_o, _setter_noop)
336 soap_a = property(_get_soap_a, _setter_noop)
337 soap_p = property(_get_soap_p, _setter_noop)
338 soap_admin = property(_get_soap_admin, _setter_noop)
339
340 allergy_state = property(_get_allergy_state, _setter_noop)
341
342 client_version = property(_get_client_version, _setter_noop)
343
344 current_provider = property(_get_current_provider, _setter_noop)
345 primary_praxis_provider = property(_get_primary_praxis_provider, _setter_noop)
346
347
348
350
351 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
352 if not encounters:
353 return u''
354
355 template = data
356
357 lines = []
358 for enc in encounters:
359 try:
360 lines.append(template % enc)
361 except:
362 lines.append(u'error formatting encounter')
363 _log.exception('problem formatting encounter list')
364 _log.error('template: %s', template)
365 _log.error('encounter: %s', encounter)
366
367 return u'\n'.join(lines)
368
370 """Select encounters from list and format SOAP thereof.
371
372 data: soap_cats (' ' -> None -> admin) // date format
373 """
374
375 cats = None
376 date_format = None
377
378 if data is not None:
379 data_parts = data.split('//')
380
381
382 if len(data_parts[0]) > 0:
383 cats = []
384 if u' ' in data_parts[0]:
385 cats.append(None)
386 data_parts[0] = data_parts[0].replace(u' ', u'')
387 cats.extend(list(data_parts[0]))
388
389
390 if len(data_parts) > 1:
391 if len(data_parts[1]) > 0:
392 date_format = data_parts[1]
393
394 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
395 if not encounters:
396 return u''
397
398 chunks = []
399 for enc in encounters:
400 chunks.append(enc.format_latex (
401 date_format = date_format,
402 soap_cats = cats,
403 soap_order = u'soap_rank, date'
404 ))
405
406 return u''.join(chunks)
407
409
410 cats = list(u'soap')
411 cats.append(None)
412 template = u'%s'
413 interactive = True
414 line_length = 9999
415 target_format = None
416 time_range = None
417
418 if data is not None:
419 data_parts = data.split('//')
420
421
422 cats = []
423
424 for c in list(data_parts[0]):
425 if c == u' ':
426 c = None
427 cats.append(c)
428
429 if cats == u'':
430 cats = list(u'soap').append(None)
431
432
433 if len(data_parts) > 1:
434 template = data_parts[1]
435
436
437 if len(data_parts) > 2:
438 try:
439 line_length = int(data_parts[2])
440 except:
441 line_length = 9999
442
443
444 if len(data_parts) > 3:
445 try:
446 time_range = 7 * int(data_parts[3])
447 except:
448 time_range = None
449
450
451 if len(data_parts) > 4:
452 target_format = data_parts[4]
453
454
455 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range)
456
457 if len(narr) == 0:
458 return u''
459
460 if target_format == u'tex':
461 keys = narr[0].keys()
462 lines = []
463 line_dict = {}
464 for n in narr:
465 for key in keys:
466 if isinstance(n[key], basestring):
467 line_dict[key] = gmTools.tex_escape_string(text = n[key])
468 continue
469 line_dict[key] = n[key]
470 try:
471 lines.append((template % line_dict)[:line_length])
472 except KeyError:
473 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
474 else:
475 try:
476 lines = [ (template % n)[:line_length] for n in narr ]
477 except KeyError:
478 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
479
480 return u'\n'.join(lines)
481
483 return self._get_variant_soap(data=data)
484
486
487
488 cats = list(u'soap')
489 cats.append(None)
490 template = u'%s'
491
492 if data is not None:
493 data_parts = data.split('//')
494
495
496 cats = []
497
498 for cat in list(data_parts[0]):
499 if cat == u' ':
500 cat = None
501 cats.append(cat)
502
503 if cats == u'':
504 cats = list(u'soap')
505 cats.append(None)
506
507
508 if len(data_parts) > 1:
509 template = data_parts[1]
510
511
512 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
513
514 if narr is None:
515 return u''
516
517 if len(narr) == 0:
518 return u''
519
520 try:
521 narr = [ template % n['narrative'] for n in narr ]
522 except KeyError:
523 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
524
525 return u'\n'.join(narr)
526
545
548
549
551 values = data.split('//', 2)
552
553 if len(values) == 2:
554 male_value, female_value = values
555 other_value = u'<unkown gender>'
556 elif len(values) == 3:
557 male_value, female_value, other_value = values
558 else:
559 return _('invalid gender mapping layout: [%s]') % data
560
561 if self.pat['gender'] == u'm':
562 return male_value
563
564 if self.pat['gender'] == u'f':
565 return female_value
566
567 return other_value
568
569
570
572
573 data_parts = data.split(u'//')
574
575 if data_parts[0].strip() == u'':
576 adr_type = u'home'
577 else:
578 adr_type = data_parts[0]
579
580 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
581 if len(data_parts) > 1:
582 if data_parts[1].strip() != u'':
583 template = data_parts[1]
584
585 adrs = self.pat.get_addresses(address_type = adr_type)
586 if len(adrs) == 0:
587 return _('no address for type [%s]') % adr_type
588
589 adr = adrs[0]
590 data = {
591 'street': adr['street'],
592 'notes_street': gmTools.coalesce(adr['notes_street'], u''),
593 'postcode': adr['postcode'],
594 'number': adr['number'],
595 'subunit': gmTools.coalesce(adr['subunit'], u''),
596 'notes_subunit': gmTools.coalesce(adr['notes_subunit'], u''),
597 'urb': adr['urb'],
598 'suburb': gmTools.coalesce(adr['suburb'], u''),
599 'l10n_state': adr['l10n_state'],
600 'l10n_country': adr['l10n_country'],
601 'code_state': adr['code_state'],
602 'code_country': adr['code_country']
603 }
604
605 try:
606 return template % data
607 except StandardError:
608 _log.exception('error formatting address')
609 _log.error('template: %s', template)
610
611 return None
612
614 adrs = self.pat.get_addresses(address_type=data)
615 if len(adrs) == 0:
616 return _('no street for address type [%s]') % data
617 return adrs[0]['street']
618
620 adrs = self.pat.get_addresses(address_type=data)
621 if len(adrs) == 0:
622 return _('no number for address type [%s]') % data
623 return adrs[0]['number']
624
626 adrs = self.pat.get_addresses(address_type=data)
627 if len(adrs) == 0:
628 return _('no location for address type [%s]') % data
629 return adrs[0]['urb']
630
631 - def _get_variant_adr_postcode(self, data=u'?'):
632 adrs = self.pat.get_addresses(address_type = data)
633 if len(adrs) == 0:
634 return _('no postcode for address type [%s]') % data
635 return adrs[0]['postcode']
636
638 adrs = self.pat.get_addresses(address_type = data)
639 if len(adrs) == 0:
640 return _('no region for address type [%s]') % data
641 return adrs[0]['l10n_state']
642
644 adrs = self.pat.get_addresses(address_type = data)
645 if len(adrs) == 0:
646 return _('no country for address type [%s]') % data
647 return adrs[0]['l10n_country']
648
650 comms = self.pat.get_comm_channels(comm_medium = data)
651 if len(comms) == 0:
652 return _('no URL for comm channel [%s]') % data
653 return comms[0]['url']
654
656 data_parts = data.split(u'//')
657 if len(data_parts) < 2:
658 return None
659
660 id_type = data_parts[0].strip()
661 if id_type == u'':
662 return None
663
664 issuer = data_parts[1].strip()
665 if issuer == u'':
666 return None
667
668 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
669
670 if len(ids) == 0:
671 return _('no external ID [%s] by [%s]') % (id_type, issuer)
672
673 return ids[0]['value']
674
676 if data is None:
677 return [_('template is missing')]
678
679 template, separator = data.split('//', 2)
680
681 emr = self.pat.get_emr()
682 return separator.join([ template % a for a in emr.get_allergies() ])
683
685
686 if data is None:
687 return [_('template is missing')]
688
689 emr = self.pat.get_emr()
690 return u'\n'.join([ data % a for a in emr.get_allergies() ])
691
693
694 if data is None:
695 return [_('template is missing')]
696
697 emr = self.pat.get_emr()
698 current_meds = emr.get_current_substance_intake (
699 include_inactive = False,
700 include_unapproved = False,
701 order_by = u'brand, substance'
702 )
703
704
705
706 return u'\n'.join([ data % m for m in current_meds ])
707
709
710 options = data.split('//')
711
712 if u'latex' in options:
713 return gmMedication.format_substance_intake (
714 emr = self.pat.get_emr(),
715 output_format = u'latex',
716 table_type = u'by-brand'
717 )
718
719 _log.error('no known current medications table formatting style in [%]', data)
720 return _('unknown current medication table formatting style')
721
723
724 options = data.split('//')
725
726 if u'latex' in options:
727 return gmMedication.format_substance_intake_notes (
728 emr = self.pat.get_emr(),
729 output_format = u'latex',
730 table_type = u'by-brand'
731 )
732
733 _log.error('no known current medications notes formatting style in [%]', data)
734 return _('unknown current medication notes formatting style')
735
750
752
753 options = data.split('//')
754
755 emr = self.pat.get_emr()
756
757 if u'latex' in options:
758 return gmVaccination.format_latest_vaccinations(output_format = u'latex', emr = emr)
759
760 _log.error('no known vaccinations table formatting style in [%s]', data)
761 return _('unknown vaccinations table formatting style [%s]') % data
762
764
765 if data is None:
766 return [_('template is missing')]
767
768 probs = self.pat.get_emr().get_problems()
769
770 return u'\n'.join([ data % p for p in probs ])
771
774
777
778 - def _get_variant_free_text(self, data=u'tex//'):
779
780
781
782
783 data_parts = data.split('//')
784 format = data_parts[0]
785 if len(data_parts) > 1:
786 msg = data_parts[1]
787 else:
788 msg = _('generic text')
789
790 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
791 None,
792 -1,
793 title = _('Replacing <free_text> placeholder'),
794 msg = _('Below you can enter free text.\n\n [%s]') % msg
795 )
796 dlg.enable_user_formatting = True
797 decision = dlg.ShowModal()
798
799 if decision != wx.ID_SAVE:
800 dlg.Destroy()
801 return _('Text input cancelled by user.')
802
803 text = dlg.value.strip()
804 if dlg.is_user_formatted:
805 dlg.Destroy()
806 return text
807
808 dlg.Destroy()
809
810 if format == u'tex':
811 return gmTools.tex_escape_string(text = text)
812
813 return text
814
815
816
817
818
820 """Functions a macro can legally use.
821
822 An instance of this class is passed to the GNUmed scripting
823 listener. Hence, all actions a macro can legally take must
824 be defined in this class. Thus we achieve some screening for
825 security and also thread safety handling.
826 """
827
828 - def __init__(self, personality = None):
829 if personality is None:
830 raise gmExceptions.ConstructorError, 'must specify personality'
831 self.__personality = personality
832 self.__attached = 0
833 self._get_source_personality = None
834 self.__user_done = False
835 self.__user_answer = 'no answer yet'
836 self.__pat = gmPerson.gmCurrentPatient()
837
838 self.__auth_cookie = str(random.random())
839 self.__pat_lock_cookie = str(random.random())
840 self.__lock_after_load_cookie = str(random.random())
841
842 _log.info('slave mode personality is [%s]', personality)
843
844
845
846 - def attach(self, personality = None):
847 if self.__attached:
848 _log.error('attach with [%s] rejected, already serving a client', personality)
849 return (0, _('attach rejected, already serving a client'))
850 if personality != self.__personality:
851 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
852 return (0, _('attach to personality [%s] rejected') % personality)
853 self.__attached = 1
854 self.__auth_cookie = str(random.random())
855 return (1, self.__auth_cookie)
856
857 - def detach(self, auth_cookie=None):
858 if not self.__attached:
859 return 1
860 if auth_cookie != self.__auth_cookie:
861 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
862 return 0
863 self.__attached = 0
864 return 1
865
867 if not self.__attached:
868 return 1
869 self.__user_done = False
870
871 wx.CallAfter(self._force_detach)
872 return 1
873
875 ver = _cfg.get(option = u'client_version')
876 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
877
879 """Shuts down this client instance."""
880 if not self.__attached:
881 return 0
882 if auth_cookie != self.__auth_cookie:
883 _log.error('non-authenticated shutdown_gnumed()')
884 return 0
885 wx.CallAfter(self._shutdown_gnumed, forced)
886 return 1
887
889 """Raise ourselves to the top of the desktop."""
890 if not self.__attached:
891 return 0
892 if auth_cookie != self.__auth_cookie:
893 _log.error('non-authenticated raise_gnumed()')
894 return 0
895 return "cMacroPrimitives.raise_gnumed() not implemented"
896
898 if not self.__attached:
899 return 0
900 if auth_cookie != self.__auth_cookie:
901 _log.error('non-authenticated get_loaded_plugins()')
902 return 0
903 gb = gmGuiBroker.GuiBroker()
904 return gb['horstspace.notebook.gui'].keys()
905
907 """Raise a notebook plugin within GNUmed."""
908 if not self.__attached:
909 return 0
910 if auth_cookie != self.__auth_cookie:
911 _log.error('non-authenticated raise_notebook_plugin()')
912 return 0
913
914 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
915 return 1
916
918 """Load external patient, perhaps create it.
919
920 Callers must use get_user_answer() to get status information.
921 It is unsafe to proceed without knowing the completion state as
922 the controlled client may be waiting for user input from a
923 patient selection list.
924 """
925 if not self.__attached:
926 return (0, _('request rejected, you are not attach()ed'))
927 if auth_cookie != self.__auth_cookie:
928 _log.error('non-authenticated load_patient_from_external_source()')
929 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
930 if self.__pat.locked:
931 _log.error('patient is locked, cannot load from external source')
932 return (0, _('current patient is locked'))
933 self.__user_done = False
934 wx.CallAfter(self._load_patient_from_external_source)
935 self.__lock_after_load_cookie = str(random.random())
936 return (1, self.__lock_after_load_cookie)
937
939 if not self.__attached:
940 return (0, _('request rejected, you are not attach()ed'))
941 if auth_cookie != self.__auth_cookie:
942 _log.error('non-authenticated lock_load_patient()')
943 return (0, _('rejected lock_load_patient(), not authenticated'))
944
945 if lock_after_load_cookie != self.__lock_after_load_cookie:
946 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
947 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
948 self.__pat.locked = True
949 self.__pat_lock_cookie = str(random.random())
950 return (1, self.__pat_lock_cookie)
951
953 if not self.__attached:
954 return (0, _('request rejected, you are not attach()ed'))
955 if auth_cookie != self.__auth_cookie:
956 _log.error('non-authenticated lock_into_patient()')
957 return (0, _('rejected lock_into_patient(), not authenticated'))
958 if self.__pat.locked:
959 _log.error('patient is already locked')
960 return (0, _('already locked into a patient'))
961 searcher = gmPersonSearch.cPatientSearcher_SQL()
962 if type(search_params) == types.DictType:
963 idents = searcher.get_identities(search_dict=search_params)
964 raise StandardError("must use dto, not search_dict")
965 else:
966 idents = searcher.get_identities(search_term=search_params)
967 if idents is None:
968 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
969 if len(idents) == 0:
970 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
971
972 if len(idents) > 1:
973 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
974 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
975 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
976 self.__pat.locked = True
977 self.__pat_lock_cookie = str(random.random())
978 return (1, self.__pat_lock_cookie)
979
981 if not self.__attached:
982 return (0, _('request rejected, you are not attach()ed'))
983 if auth_cookie != self.__auth_cookie:
984 _log.error('non-authenticated unlock_patient()')
985 return (0, _('rejected unlock_patient, not authenticated'))
986
987 if not self.__pat.locked:
988 return (1, '')
989
990 if unlock_cookie != self.__pat_lock_cookie:
991 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
992 return (0, 'patient unlock request rejected, wrong cookie provided')
993 self.__pat.locked = False
994 return (1, '')
995
997 if not self.__attached:
998 return 0
999 if auth_cookie != self.__auth_cookie:
1000 _log.error('non-authenticated select_identity()')
1001 return 0
1002 return "cMacroPrimitives.assume_staff_identity() not implemented"
1003
1005 if not self.__user_done:
1006 return (0, 'still waiting')
1007 self.__user_done = False
1008 return (1, self.__user_answer)
1009
1010
1011
1013 msg = _(
1014 'Someone tries to forcibly break the existing\n'
1015 'controlling connection. This may or may not\n'
1016 'have legitimate reasons.\n\n'
1017 'Do you want to allow breaking the connection ?'
1018 )
1019 can_break_conn = gmGuiHelpers.gm_show_question (
1020 aMessage = msg,
1021 aTitle = _('forced detach attempt')
1022 )
1023 if can_break_conn:
1024 self.__user_answer = 1
1025 else:
1026 self.__user_answer = 0
1027 self.__user_done = True
1028 if can_break_conn:
1029 self.__pat.locked = False
1030 self.__attached = 0
1031 return 1
1032
1034 top_win = wx.GetApp().GetTopWindow()
1035 if forced:
1036 top_win.Destroy()
1037 else:
1038 top_win.Close()
1039
1048
1049
1050
1051 if __name__ == '__main__':
1052
1053 if len(sys.argv) < 2:
1054 sys.exit()
1055
1056 if sys.argv[1] != 'test':
1057 sys.exit()
1058
1059 gmI18N.activate_locale()
1060 gmI18N.install_domain()
1061
1062
1064 handler = gmPlaceholderHandler()
1065 handler.debug = True
1066
1067 for placeholder in ['a', 'b']:
1068 print handler[placeholder]
1069
1070 pat = gmPersonSearch.ask_for_patient()
1071 if pat is None:
1072 return
1073
1074 gmPatSearchWidgets.set_active_patient(patient = pat)
1075
1076 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1077
1078 app = wx.PyWidgetTester(size = (200, 50))
1079 for placeholder in known_placeholders:
1080 print placeholder, "=", handler[placeholder]
1081
1082 ph = 'progress_notes::ap'
1083 print '%s: %s' % (ph, handler[ph])
1084
1086
1087 tests = [
1088
1089 '$<lastname>$',
1090 '$<lastname::::3>$',
1091 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
1092
1093
1094 'lastname',
1095 '$<lastname',
1096 '$<lastname::',
1097 '$<lastname::>$',
1098 '$<lastname::abc>$',
1099 '$<lastname::abc::>$',
1100 '$<lastname::abc::3>$',
1101 '$<lastname::abc::xyz>$',
1102 '$<lastname::::>$',
1103 '$<lastname::::xyz>$',
1104
1105 '$<date_of_birth::%Y-%m-%d>$',
1106 '$<date_of_birth::%Y-%m-%d::3>$',
1107 '$<date_of_birth::%Y-%m-%d::>$',
1108
1109
1110 '$<adr_location::home::35>$',
1111 '$<gender_mapper::male//female//other::5>$',
1112 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
1113 '$<allergy_list::%(descriptor)s, >$',
1114 '$<current_meds_table::latex//by-brand>$'
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129 ]
1130
1131 tests = [
1132 '$<latest_vaccs_table::latex>$'
1133 ]
1134
1135 pat = gmPersonSearch.ask_for_patient()
1136 if pat is None:
1137 return
1138
1139 gmPatSearchWidgets.set_active_patient(patient = pat)
1140
1141 handler = gmPlaceholderHandler()
1142 handler.debug = True
1143
1144 for placeholder in tests:
1145 print placeholder, "=>", handler[placeholder]
1146 print "--------------"
1147 raw_input()
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1160 from Gnumed.pycommon import gmScriptingListener
1161 import xmlrpclib
1162 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1163
1164 s = xmlrpclib.ServerProxy('http://localhost:9999')
1165 print "should fail:", s.attach()
1166 print "should fail:", s.attach('wrong cookie')
1167 print "should work:", s.version()
1168 print "should fail:", s.raise_gnumed()
1169 print "should fail:", s.raise_notebook_plugin('test plugin')
1170 print "should fail:", s.lock_into_patient('kirk, james')
1171 print "should fail:", s.unlock_patient()
1172 status, conn_auth = s.attach('unit test')
1173 print "should work:", status, conn_auth
1174 print "should work:", s.version()
1175 print "should work:", s.raise_gnumed(conn_auth)
1176 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1177 print "should work:", status, pat_auth
1178 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1179 print "should work", s.unlock_patient(conn_auth, pat_auth)
1180 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1181 status, pat_auth = s.lock_into_patient(conn_auth, data)
1182 print "should work:", status, pat_auth
1183 print "should work", s.unlock_patient(conn_auth, pat_auth)
1184 print s.detach('bogus detach cookie')
1185 print s.detach(conn_auth)
1186 del s
1187
1188 listener.shutdown()
1189
1191
1192 import re as regex
1193
1194 tests = [
1195 ' $<lastname>$ ',
1196 ' $<lastname::::3>$ ',
1197
1198
1199 '$<date_of_birth::%Y-%m-%d>$',
1200 '$<date_of_birth::%Y-%m-%d::3>$',
1201 '$<date_of_birth::%Y-%m-%d::>$',
1202
1203 '$<adr_location::home::35>$',
1204 '$<gender_mapper::male//female//other::5>$',
1205 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1206 '$<allergy_list::%(descriptor)s, >$',
1207
1208 '\\noindent Patient: $<lastname>$, $<firstname>$',
1209 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1210 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1211 ]
1212
1213 tests = [
1214
1215 'junk $<lastname::::3>$ junk',
1216 'junk $<lastname::abc::3>$ junk',
1217 'junk $<lastname::abc>$ junk',
1218 'junk $<lastname>$ junk',
1219
1220 'junk $<lastname>$ junk $<firstname>$ junk',
1221 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1222 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1223 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1224
1225 ]
1226
1227 print "testing placeholder regex:", default_placeholder_regex
1228 print ""
1229
1230 for t in tests:
1231 print 'line: "%s"' % t
1232 print "placeholders:"
1233 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE):
1234 print ' => "%s"' % p
1235 print " "
1236
1267
1268
1269
1270
1271
1272
1273 test_placeholder()
1274
1275
1276