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