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