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, gmDemographicRecord, gmMedication, gmPathLab, gmPersonSearch
29 from Gnumed.business import gmVaccination, gmPersonSearch
30
31 from Gnumed.wxpython import gmGuiHelpers
32 from Gnumed.wxpython import gmNarrativeWidgets
33 from Gnumed.wxpython import gmPatSearchWidgets
34 from Gnumed.wxpython import gmPlugin
35 from Gnumed.wxpython import gmEMRStructWidgets
36
37
38 _log = logging.getLogger('gm.scripting')
39 _cfg = gmCfg2.gmCfgData()
40
41
42 known_placeholders = [
43 'lastname',
44 'firstname',
45 'title',
46 'date_of_birth',
47 'progress_notes',
48 'soap',
49 'soap_s',
50 'soap_o',
51 'soap_a',
52 'soap_p',
53 u'client_version',
54 u'current_provider',
55 u'allergy_state'
56 ]
57
58
59
60 known_variant_placeholders = [
61 u'soap',
62 u'progress_notes',
63
64
65 u'emr_journal',
66
67
68
69
70
71
72
73 u'date_of_birth',
74 u'adr_street',
75 u'adr_number',
76 u'adr_location',
77 u'adr_postcode',
78 u'gender_mapper',
79 u'current_meds',
80 u'current_meds_table',
81 u'current_meds_notes',
82 u'lab_table',
83 u'latest_vaccs_table',
84 u'today',
85 u'tex_escape',
86 u'allergies',
87 u'allergy_list',
88 u'problems',
89 u'name',
90 u'free_text',
91 u'soap_for_encounters'
92 ]
93
94 default_placeholder_regex = r'\$<.+?>\$'
95
96
97
98
99
100
101
102
103 default_placeholder_start = u'$<'
104 default_placeholder_end = u'>$'
105
107 """Replaces placeholders in forms, fields, etc.
108
109 - patient related placeholders operate on the currently active patient
110 - is passed to the forms handling code, for example
111
112 Note that this cannot be called from a non-gui thread unless
113 wrapped in wx.CallAfter.
114
115 There are currently three types of placeholders:
116
117 simple static placeholders
118 - those are listed in known_placeholders
119 - they are used as-is
120
121 extended static placeholders
122 - those are like the static ones but have "::::<NUMBER>" appended
123 where <NUMBER> is the maximum length
124
125 variant placeholders
126 - those are listed in known_variant_placeholders
127 - they are parsed into placeholder, data, and maximum length
128 - the length is optional
129 - data is passed to the handler
130 """
132
133 self.pat = gmPerson.gmCurrentPatient()
134 self.debug = False
135
136 self.invalid_placeholder_template = _('invalid placeholder [%s]')
137
138
139
141 """Map self['placeholder'] to self.placeholder.
142
143 This is useful for replacing placeholders parsed out
144 of documents as strings.
145
146 Unknown/invalid placeholders still deliver a result but
147 it will be glaringly obvious if debugging is enabled.
148 """
149 _log.debug('replacing [%s]', placeholder)
150
151 original_placeholder = placeholder
152
153 if placeholder.startswith(default_placeholder_start):
154 placeholder = placeholder[len(default_placeholder_start):]
155 if placeholder.endswith(default_placeholder_end):
156 placeholder = placeholder[:-len(default_placeholder_end)]
157 else:
158 _log.debug('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
159 if self.debug:
160 return self.invalid_placeholder_template % original_placeholder
161 return None
162
163
164 if placeholder in known_placeholders:
165 return getattr(self, placeholder)
166
167
168 parts = placeholder.split('::::', 1)
169 if len(parts) == 2:
170 name, lng = parts
171 try:
172 return getattr(self, name)[:int(lng)]
173 except:
174 _log.exception('placeholder handling error: %s', original_placeholder)
175 if self.debug:
176 return self.invalid_placeholder_template % original_placeholder
177 return None
178
179
180 parts = placeholder.split('::')
181 if len(parts) == 2:
182 name, data = parts
183 lng = None
184 if len(parts) == 3:
185 name, data, lng = parts
186 try:
187 lng = int(lng)
188 except:
189 _log.exception('placeholder length definition error: %s, discarding length', original_placeholder)
190 lng = None
191 if len(parts) > 3:
192 _log.warning('invalid placeholder layout: %s', original_placeholder)
193 if self.debug:
194 return self.invalid_placeholder_template % original_placeholder
195 return None
196
197 handler = getattr(self, '_get_variant_%s' % name, None)
198 if handler is None:
199 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
200 if self.debug:
201 return self.invalid_placeholder_template % original_placeholder
202 return None
203
204 try:
205 if lng is None:
206 return handler(data = data)
207 return handler(data = data)[:lng]
208 except:
209 _log.exception('placeholder handling error: %s', original_placeholder)
210 if self.debug:
211 return self.invalid_placeholder_template % original_placeholder
212 return None
213
214 _log.error('something went wrong, should never get here')
215 return None
216
217
218
219
220
222 """This does nothing, used as a NOOP properties setter."""
223 pass
224
227
230
233
235 return self._get_variant_date_of_birth(data='%x')
236
238 return self._get_variant_soap()
239
241 return self._get_variant_soap(data = u's')
242
244 return self._get_variant_soap(data = u'o')
245
247 return self._get_variant_soap(data = u'a')
248
250 return self._get_variant_soap(data = u'p')
251
253 return self._get_variant_soap(soap_cats = None)
254
256 return gmTools.coalesce (
257 _cfg.get(option = u'client_version'),
258 u'%s' % self.__class__.__name__
259 )
260
276
278 allg_state = self.pat.get_emr().allergy_state
279
280 if allg_state['last_confirmed'] is None:
281 date_confirmed = u''
282 else:
283 date_confirmed = u' (%s)' % allg_state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding())
284
285 tmp = u'%s%s' % (
286 allg_state.state_string,
287 date_confirmed
288 )
289 return tmp
290
291
292
293 placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop)
294
295
296 lastname = property(_get_lastname, _setter_noop)
297 firstname = property(_get_firstname, _setter_noop)
298 title = property(_get_title, _setter_noop)
299 date_of_birth = property(_get_dob, _setter_noop)
300
301 progress_notes = property(_get_progress_notes, _setter_noop)
302 soap = property(_get_progress_notes, _setter_noop)
303 soap_s = property(_get_soap_s, _setter_noop)
304 soap_o = property(_get_soap_o, _setter_noop)
305 soap_a = property(_get_soap_a, _setter_noop)
306 soap_p = property(_get_soap_p, _setter_noop)
307 soap_admin = property(_get_soap_admin, _setter_noop)
308
309 allergy_state = property(_get_allergy_state, _setter_noop)
310
311 client_version = property(_get_client_version, _setter_noop)
312
313 current_provider = property(_get_current_provider, _setter_noop)
314
315
316
318 """Select encounters from list and format SOAP thereof.
319
320 data: soap_cats (' ' -> None -> admin) // date format
321 """
322
323 cats = None
324 date_format = None
325
326 if data is not None:
327 data_parts = data.split('//')
328
329
330 if len(data_parts[0]) > 0:
331 cats = []
332 if u' ' in data_parts[0]:
333 cats.append(None)
334 data_parts[0] = data_parts[0].replace(u' ', u'')
335 cats.extend(list(data_parts[0]))
336
337
338 if len(data_parts) > 0:
339 if len(data_parts[1]) > 0:
340 date_format = data_parts[1]
341
342 encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
343
344 chunks = []
345 for enc in encounters:
346 chunks.append(enc.format_latex(date_format = date_format, soap_cats = cats))
347
348 return u''.join(chunks)
349
351
352 cats = list(u'soap')
353 cats.append(None)
354 template = u'%s'
355 interactive = True
356 line_length = 9999
357 target_format = None
358 time_range = None
359
360 if data is not None:
361 data_parts = data.split('//')
362
363
364 cats = []
365
366 for c in list(data_parts[0]):
367 if c == u' ':
368 c = None
369 cats.append(c)
370
371 if cats == u'':
372 cats = list(u'soap').append(None)
373
374
375 if len(data_parts) > 0:
376 template = data_parts[1]
377
378
379 if len(data_parts) > 1:
380 try:
381 line_length = int(data_parts[2])
382 except:
383 line_length = 9999
384
385
386 if len(data_parts) > 2:
387 try:
388 time_range = 7 * int(data_parts[3])
389 except:
390 time_range = None
391
392
393 if len(data_parts) > 3:
394 target_format = data_parts[4]
395
396
397 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range)
398
399 if len(narr) == 0:
400 return u''
401
402 if target_format == u'tex':
403 keys = narr[0].keys()
404 lines = []
405 line_dict = {}
406 for n in narr:
407 for key in keys:
408 if isinstance(n[key], basestring):
409 line_dict[key] = gmTools.tex_escape_string(text = n[key])
410 continue
411 line_dict[key] = n[key]
412 try:
413 lines.append((template % line_dict)[:line_length])
414 except KeyError:
415 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
416 else:
417 try:
418 lines = [ (template % n)[:line_length] for n in narr ]
419 except KeyError:
420 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
421
422 return u'\n'.join(lines)
423
425 return self._get_variant_soap(data=data)
426
428
429
430 cats = list(u'soap')
431 cats.append(None)
432 template = u'%s'
433
434 if data is not None:
435 data_parts = data.split('//')
436
437
438 cats = []
439
440 for cat in list(data_parts[0]):
441 if cat == u' ':
442 cat = None
443 cats.append(cat)
444
445 if cats == u'':
446 cats = list(u'soap')
447 cats.append(None)
448
449
450 if len(data_parts) > 0:
451 template = data_parts[1]
452
453
454 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
455
456 if narr is None:
457 return u''
458
459 if len(narr) == 0:
460 return u''
461
462 try:
463 narr = [ template % n['narrative'] for n in narr ]
464 except KeyError:
465 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
466
467 return u'\n'.join(narr)
468
487
490
491
493 values = data.split('//', 2)
494
495 if len(values) == 2:
496 male_value, female_value = values
497 other_value = u'<unkown gender>'
498 elif len(values) == 3:
499 male_value, female_value, other_value = values
500 else:
501 return _('invalid gender mapping layout: [%s]') % data
502
503 if self.pat['gender'] == u'm':
504 return male_value
505
506 if self.pat['gender'] == u'f':
507 return female_value
508
509 return other_value
510
512
513
514 adrs = self.pat.get_addresses(address_type=data)
515 if len(adrs) == 0:
516 return _('no street for address type [%s]') % data
517 return adrs[0]['street']
518
520 adrs = self.pat.get_addresses(address_type=data)
521 if len(adrs) == 0:
522 return _('no number for address type [%s]') % data
523 return adrs[0]['number']
524
526 adrs = self.pat.get_addresses(address_type=data)
527 if len(adrs) == 0:
528 return _('no location for address type [%s]') % data
529 return adrs[0]['urb']
530
531 - def _get_variant_adr_postcode(self, data=u'?'):
532 adrs = self.pat.get_addresses(address_type=data)
533 if len(adrs) == 0:
534 return _('no postcode for address type [%s]') % data
535 return adrs[0]['postcode']
536
538 if data is None:
539 return [_('template is missing')]
540
541 template, separator = data.split('//', 2)
542
543 emr = self.pat.get_emr()
544 return separator.join([ template % a for a in emr.get_allergies() ])
545
547
548 if data is None:
549 return [_('template is missing')]
550
551 emr = self.pat.get_emr()
552 return u'\n'.join([ data % a for a in emr.get_allergies() ])
553
555
556 if data is None:
557 return [_('template is missing')]
558
559 emr = self.pat.get_emr()
560 current_meds = emr.get_current_substance_intake (
561 include_inactive = False,
562 include_unapproved = False,
563 order_by = u'brand, substance'
564 )
565
566
567
568 return u'\n'.join([ data % m for m in current_meds ])
569
571
572 options = data.split('//')
573
574 if u'latex' in options:
575 return gmMedication.format_substance_intake (
576 emr = self.pat.get_emr(),
577 output_format = u'latex',
578 table_type = u'by-brand'
579 )
580
581 _log.error('no known current medications table formatting style in [%]', data)
582 return _('unknown current medication table formatting style')
583
585
586 options = data.split('//')
587
588 if u'latex' in options:
589 return gmMedication.format_substance_intake_notes (
590 emr = self.pat.get_emr(),
591 output_format = u'latex',
592 table_type = u'by-brand'
593 )
594
595 _log.error('no known current medications notes formatting style in [%]', data)
596 return _('unknown current medication notes formatting style')
597
612
614
615 options = data.split('//')
616
617 emr = self.pat.get_emr()
618
619 if u'latex' in options:
620 return gmVaccination.format_latest_vaccinations(output_format = u'latex', emr = emr)
621
622 _log.error('no known vaccinations table formatting style in [%s]', data)
623 return _('unknown vaccinations table formatting style [%s]') % data
624
626
627 if data is None:
628 return [_('template is missing')]
629
630 probs = self.pat.get_emr().get_problems()
631
632 return u'\n'.join([ data % p for p in probs ])
633
636
639
640 - def _get_variant_free_text(self, data=u'tex//'):
641
642
643
644
645 format, msg = data.split('//')
646
647 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
648 None,
649 -1,
650 title = _('Replacing <free_text> placeholder'),
651 msg = _('Below you can enter free text.\n\n [%s]') % msg
652 )
653 dlg.enable_user_formatting = True
654 decision = dlg.ShowModal()
655
656 if decision != wx.ID_SAVE:
657 dlg.Destroy()
658 return _('Text input cancelled by user.')
659
660 text = dlg.value.strip()
661 if dlg.is_user_formatted:
662 dlg.Destroy()
663 return text
664
665 dlg.Destroy()
666
667 if format == u'tex':
668 return gmTools.tex_escape_string(text = text)
669
670 return text
671
672
673
674
675
677 """Functions a macro can legally use.
678
679 An instance of this class is passed to the GNUmed scripting
680 listener. Hence, all actions a macro can legally take must
681 be defined in this class. Thus we achieve some screening for
682 security and also thread safety handling.
683 """
684
685 - def __init__(self, personality = None):
686 if personality is None:
687 raise gmExceptions.ConstructorError, 'must specify personality'
688 self.__personality = personality
689 self.__attached = 0
690 self._get_source_personality = None
691 self.__user_done = False
692 self.__user_answer = 'no answer yet'
693 self.__pat = gmPerson.gmCurrentPatient()
694
695 self.__auth_cookie = str(random.random())
696 self.__pat_lock_cookie = str(random.random())
697 self.__lock_after_load_cookie = str(random.random())
698
699 _log.info('slave mode personality is [%s]', personality)
700
701
702
703 - def attach(self, personality = None):
704 if self.__attached:
705 _log.error('attach with [%s] rejected, already serving a client', personality)
706 return (0, _('attach rejected, already serving a client'))
707 if personality != self.__personality:
708 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
709 return (0, _('attach to personality [%s] rejected') % personality)
710 self.__attached = 1
711 self.__auth_cookie = str(random.random())
712 return (1, self.__auth_cookie)
713
714 - def detach(self, auth_cookie=None):
715 if not self.__attached:
716 return 1
717 if auth_cookie != self.__auth_cookie:
718 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
719 return 0
720 self.__attached = 0
721 return 1
722
724 if not self.__attached:
725 return 1
726 self.__user_done = False
727
728 wx.CallAfter(self._force_detach)
729 return 1
730
732 ver = _cfg.get(option = u'client_version')
733 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
734
736 """Shuts down this client instance."""
737 if not self.__attached:
738 return 0
739 if auth_cookie != self.__auth_cookie:
740 _log.error('non-authenticated shutdown_gnumed()')
741 return 0
742 wx.CallAfter(self._shutdown_gnumed, forced)
743 return 1
744
746 """Raise ourselves to the top of the desktop."""
747 if not self.__attached:
748 return 0
749 if auth_cookie != self.__auth_cookie:
750 _log.error('non-authenticated raise_gnumed()')
751 return 0
752 return "cMacroPrimitives.raise_gnumed() not implemented"
753
755 if not self.__attached:
756 return 0
757 if auth_cookie != self.__auth_cookie:
758 _log.error('non-authenticated get_loaded_plugins()')
759 return 0
760 gb = gmGuiBroker.GuiBroker()
761 return gb['horstspace.notebook.gui'].keys()
762
764 """Raise a notebook plugin within GNUmed."""
765 if not self.__attached:
766 return 0
767 if auth_cookie != self.__auth_cookie:
768 _log.error('non-authenticated raise_notebook_plugin()')
769 return 0
770
771 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
772 return 1
773
775 """Load external patient, perhaps create it.
776
777 Callers must use get_user_answer() to get status information.
778 It is unsafe to proceed without knowing the completion state as
779 the controlled client may be waiting for user input from a
780 patient selection list.
781 """
782 if not self.__attached:
783 return (0, _('request rejected, you are not attach()ed'))
784 if auth_cookie != self.__auth_cookie:
785 _log.error('non-authenticated load_patient_from_external_source()')
786 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
787 if self.__pat.locked:
788 _log.error('patient is locked, cannot load from external source')
789 return (0, _('current patient is locked'))
790 self.__user_done = False
791 wx.CallAfter(self._load_patient_from_external_source)
792 self.__lock_after_load_cookie = str(random.random())
793 return (1, self.__lock_after_load_cookie)
794
796 if not self.__attached:
797 return (0, _('request rejected, you are not attach()ed'))
798 if auth_cookie != self.__auth_cookie:
799 _log.error('non-authenticated lock_load_patient()')
800 return (0, _('rejected lock_load_patient(), not authenticated'))
801
802 if lock_after_load_cookie != self.__lock_after_load_cookie:
803 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
804 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
805 self.__pat.locked = True
806 self.__pat_lock_cookie = str(random.random())
807 return (1, self.__pat_lock_cookie)
808
810 if not self.__attached:
811 return (0, _('request rejected, you are not attach()ed'))
812 if auth_cookie != self.__auth_cookie:
813 _log.error('non-authenticated lock_into_patient()')
814 return (0, _('rejected lock_into_patient(), not authenticated'))
815 if self.__pat.locked:
816 _log.error('patient is already locked')
817 return (0, _('already locked into a patient'))
818 searcher = gmPersonSearch.cPatientSearcher_SQL()
819 if type(search_params) == types.DictType:
820 idents = searcher.get_identities(search_dict=search_params)
821 print "must use dto, not search_dict"
822 print xxxxxxxxxxxxxxxxx
823 else:
824 idents = searcher.get_identities(search_term=search_params)
825 if idents is None:
826 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
827 if len(idents) == 0:
828 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
829
830 if len(idents) > 1:
831 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
832 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
833 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
834 self.__pat.locked = True
835 self.__pat_lock_cookie = str(random.random())
836 return (1, self.__pat_lock_cookie)
837
839 if not self.__attached:
840 return (0, _('request rejected, you are not attach()ed'))
841 if auth_cookie != self.__auth_cookie:
842 _log.error('non-authenticated unlock_patient()')
843 return (0, _('rejected unlock_patient, not authenticated'))
844
845 if not self.__pat.locked:
846 return (1, '')
847
848 if unlock_cookie != self.__pat_lock_cookie:
849 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
850 return (0, 'patient unlock request rejected, wrong cookie provided')
851 self.__pat.locked = False
852 return (1, '')
853
855 if not self.__attached:
856 return 0
857 if auth_cookie != self.__auth_cookie:
858 _log.error('non-authenticated select_identity()')
859 return 0
860 return "cMacroPrimitives.assume_staff_identity() not implemented"
861
863 if not self.__user_done:
864 return (0, 'still waiting')
865 self.__user_done = False
866 return (1, self.__user_answer)
867
868
869
871 msg = _(
872 'Someone tries to forcibly break the existing\n'
873 'controlling connection. This may or may not\n'
874 'have legitimate reasons.\n\n'
875 'Do you want to allow breaking the connection ?'
876 )
877 can_break_conn = gmGuiHelpers.gm_show_question (
878 aMessage = msg,
879 aTitle = _('forced detach attempt')
880 )
881 if can_break_conn:
882 self.__user_answer = 1
883 else:
884 self.__user_answer = 0
885 self.__user_done = True
886 if can_break_conn:
887 self.__pat.locked = False
888 self.__attached = 0
889 return 1
890
892 top_win = wx.GetApp().GetTopWindow()
893 if forced:
894 top_win.Destroy()
895 else:
896 top_win.Close()
897
906
907
908
909 if __name__ == '__main__':
910
911 if len(sys.argv) < 2:
912 sys.exit()
913
914 if sys.argv[1] != 'test':
915 sys.exit()
916
917 gmI18N.activate_locale()
918 gmI18N.install_domain()
919
920
922 handler = gmPlaceholderHandler()
923 handler.debug = True
924
925 for placeholder in ['a', 'b']:
926 print handler[placeholder]
927
928 pat = gmPersonSearch.ask_for_patient()
929 if pat is None:
930 return
931
932 gmPatSearchWidgets.set_active_patient(patient = pat)
933
934 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
935
936 app = wx.PyWidgetTester(size = (200, 50))
937 for placeholder in known_placeholders:
938 print placeholder, "=", handler[placeholder]
939
940 ph = 'progress_notes::ap'
941 print '%s: %s' % (ph, handler[ph])
942
944
945 tests = [
946
947 '$<lastname>$',
948 '$<lastname::::3>$',
949 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
950
951
952 'lastname',
953 '$<lastname',
954 '$<lastname::',
955 '$<lastname::>$',
956 '$<lastname::abc>$',
957 '$<lastname::abc::>$',
958 '$<lastname::abc::3>$',
959 '$<lastname::abc::xyz>$',
960 '$<lastname::::>$',
961 '$<lastname::::xyz>$',
962
963 '$<date_of_birth::%Y-%m-%d>$',
964 '$<date_of_birth::%Y-%m-%d::3>$',
965 '$<date_of_birth::%Y-%m-%d::>$',
966
967
968 '$<adr_location::home::35>$',
969 '$<gender_mapper::male//female//other::5>$',
970 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
971 '$<allergy_list::%(descriptor)s, >$',
972 '$<current_meds_table::latex//by-brand>$'
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987 ]
988
989 tests = [
990 '$<latest_vaccs_table::latex>$'
991 ]
992
993 pat = gmPersonSearch.ask_for_patient()
994 if pat is None:
995 return
996
997 gmPatSearchWidgets.set_active_patient(patient = pat)
998
999 handler = gmPlaceholderHandler()
1000 handler.debug = True
1001
1002 for placeholder in tests:
1003 print placeholder, "=>", handler[placeholder]
1004 print "--------------"
1005 raw_input()
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1018 from Gnumed.pycommon import gmScriptingListener
1019 import xmlrpclib
1020 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
1021
1022 s = xmlrpclib.ServerProxy('http://localhost:9999')
1023 print "should fail:", s.attach()
1024 print "should fail:", s.attach('wrong cookie')
1025 print "should work:", s.version()
1026 print "should fail:", s.raise_gnumed()
1027 print "should fail:", s.raise_notebook_plugin('test plugin')
1028 print "should fail:", s.lock_into_patient('kirk, james')
1029 print "should fail:", s.unlock_patient()
1030 status, conn_auth = s.attach('unit test')
1031 print "should work:", status, conn_auth
1032 print "should work:", s.version()
1033 print "should work:", s.raise_gnumed(conn_auth)
1034 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
1035 print "should work:", status, pat_auth
1036 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
1037 print "should work", s.unlock_patient(conn_auth, pat_auth)
1038 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
1039 status, pat_auth = s.lock_into_patient(conn_auth, data)
1040 print "should work:", status, pat_auth
1041 print "should work", s.unlock_patient(conn_auth, pat_auth)
1042 print s.detach('bogus detach cookie')
1043 print s.detach(conn_auth)
1044 del s
1045
1046 listener.shutdown()
1047
1049
1050 import re as regex
1051
1052 tests = [
1053 ' $<lastname>$ ',
1054 ' $<lastname::::3>$ ',
1055
1056
1057 '$<date_of_birth::%Y-%m-%d>$',
1058 '$<date_of_birth::%Y-%m-%d::3>$',
1059 '$<date_of_birth::%Y-%m-%d::>$',
1060
1061 '$<adr_location::home::35>$',
1062 '$<gender_mapper::male//female//other::5>$',
1063 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
1064 '$<allergy_list::%(descriptor)s, >$',
1065
1066 '\\noindent Patient: $<lastname>$, $<firstname>$',
1067 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
1068 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
1069 ]
1070
1071 tests = [
1072
1073 'junk $<lastname::::3>$ junk',
1074 'junk $<lastname::abc::3>$ junk',
1075 'junk $<lastname::abc>$ junk',
1076 'junk $<lastname>$ junk',
1077
1078 'junk $<lastname>$ junk $<firstname>$ junk',
1079 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
1080 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
1081 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
1082
1083 ]
1084
1085 print "testing placeholder regex:", default_placeholder_regex
1086 print ""
1087
1088 for t in tests:
1089 print 'line: "%s"' % t
1090 print "placeholders:"
1091 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE):
1092 print ' => "%s"' % p
1093 print " "
1094
1112
1113
1114
1115
1116
1117
1118 test_placeholder()
1119
1120
1121