| Home | Trees | Indices | Help |
|
|---|
|
|
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 # those must satisfy the pattern "$name::args::optional length$" when used
60 known_variant_placeholders = [
61 u'soap',
62 u'progress_notes', # "data" holds: categories//template
63 # categories: string with "soap ", " " == None == admin
64 # template: u'something %s something' (do not include // in template !)
65 u'emr_journal', # "data" format: <categories>//<template>//<line length>//<time range>//<target format>
66 # categories: string with any of "s", "o", "a", "p", " ";
67 # (" " == None == admin category)
68 # template: something %s something else
69 # (Do not include // in the template !)
70 # line length: the length of individual lines, not the total placeholder length
71 # time range: the number of weeks going back in time
72 # target format: "tex" or anything else, if "tex", data will be tex-escaped
73 u'date_of_birth',
74 u'adr_street', # "data" holds: type of address
75 u'adr_number',
76 u'adr_location',
77 u'adr_postcode',
78 u'gender_mapper', # "data" holds: value for male // value for female
79 u'current_meds', # "data" holds: line template
80 u'current_meds_table', # "data" holds: format, options
81 u'current_meds_notes', # "data" holds: format, options
82 u'lab_table', # "data" holds: format (currently "latex" only)
83 u'latest_vaccs_table', # "data" holds: format, options
84 u'today', # "data" holds: strftime format
85 u'tex_escape', # "data" holds: string to escape
86 u'allergies', # "data" holds: line template, one allergy per line
87 u'allergy_list', # "data" holds: template per allergy, allergies on one line
88 u'problems', # "data" holds: line template, one problem per line
89 u'name', # "data" holds: template for name parts arrangement
90 u'free_text', # show a dialog for entering some free text
91 u'soap_for_encounters' # "data" holds: soap cats // strftime date format
92 ]
93
94 default_placeholder_regex = r'\$<.+?>\$' # this one works (except that OOo cannot be non-greedy |-( )
95
96 #_regex_parts = [
97 # r'\$<\w+::.*(?::)\d+>\$',
98 # r'\$<\w+::.+(?!>\$)>\$',
99 # r'\$<\w+?>\$'
100 #]
101 #default_placeholder_regex = r'|'.join(_regex_parts)
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 # __getitem__ API
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 # simple static placeholder ?
164 if placeholder in known_placeholders:
165 return getattr(self, placeholder)
166
167 # extended static placeholder ?
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 # variable placeholders
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 # properties actually handling placeholders
218 #--------------------------------------------------------
219 # property helpers
220 #--------------------------------------------------------
224 #--------------------------------------------------------
226 return self.pat.get_active_name()['lastnames']
227 #--------------------------------------------------------
229 return self.pat.get_active_name()['firstnames']
230 #--------------------------------------------------------
233 #--------------------------------------------------------
235 return self._get_variant_date_of_birth(data='%x')
236 #--------------------------------------------------------
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 #--------------------------------------------------------
254 #--------------------------------------------------------
256 return gmTools.coalesce (
257 _cfg.get(option = u'client_version'),
258 u'%s' % self.__class__.__name__
259 )
260 #--------------------------------------------------------
262 prov = gmPerson.gmCurrentProvider()
263
264 title = gmTools.coalesce (
265 prov['title'],
266 gmPerson.map_gender2salutation(prov['gender'])
267 )
268
269 tmp = u'%s %s. %s' % (
270 title,
271 prov['firstnames'][:1],
272 prov['lastnames']
273 )
274
275 return tmp
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 # property definitions for static placeholders
292 #--------------------------------------------------------
293 placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop)
294
295 # placeholders
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 # variant handlers
316 #--------------------------------------------------------
318 """Select encounters from list and format SOAP thereof.
319
320 data: soap_cats (' ' -> None -> admin) // date format
321 """
322 # defaults
323 cats = None
324 date_format = None
325
326 if data is not None:
327 data_parts = data.split('//')
328
329 # part[0]: categories
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 # part[1]: date format
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 # default: all categories, neutral template
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 # part[0]: categories
364 cats = []
365 # ' ' -> None == admin
366 for c in list(data_parts[0]):
367 if c == u' ':
368 c = None
369 cats.append(c)
370 # '' -> SOAP + None
371 if cats == u'':
372 cats = list(u'soap').append(None)
373
374 # part[1]: template
375 if len(data_parts) > 0:
376 template = data_parts[1]
377
378 # part[2]: line length
379 if len(data_parts) > 1:
380 try:
381 line_length = int(data_parts[2])
382 except:
383 line_length = 9999
384
385 # part[3]: weeks going back in time
386 if len(data_parts) > 2:
387 try:
388 time_range = 7 * int(data_parts[3])
389 except:
390 time_range = None
391
392 # part[4]: output format
393 if len(data_parts) > 3:
394 target_format = data_parts[4]
395
396 # FIXME: will need to be a generator later on
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 #--------------------------------------------------------
426 #--------------------------------------------------------
428
429 # default: all categories, neutral template
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 # part[0]: categories
438 cats = []
439 # ' ' -> None == admin
440 for cat in list(data_parts[0]):
441 if cat == u' ':
442 cat = None
443 cats.append(cat)
444 # '' -> SOAP + None
445 if cats == u'':
446 cats = list(u'soap')
447 cats.append(None)
448
449 # part[1]: template
450 if len(data_parts) > 0:
451 template = data_parts[1]
452
453 #narr = gmNarrativeWidgets.select_narrative_from_episodes_new(soap_cats = cats)
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 #--------------------------------------------------------
470 if data is None:
471 return [_('template is missing')]
472
473 name = self.pat.get_active_name()
474
475 parts = {
476 'title': gmTools.coalesce(name['title'], u''),
477 'firstnames': name['firstnames'],
478 'lastnames': name['lastnames'],
479 'preferred': gmTools.coalesce (
480 initial = name['preferred'],
481 instead = u' ',
482 template_initial = u' "%s" '
483 )
484 }
485
486 return data % parts
487 #--------------------------------------------------------
490 #--------------------------------------------------------
491 # FIXME: extend to all supported genders
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 # if data == u'?':
513 # types = xxxxxxxxxxx
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 #--------------------------------------------------------
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 # FIXME: we should be dealing with translating None to u'' here
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 #--------------------------------------------------------
599
600 options = data.split('//')
601
602 emr = self.pat.get_emr()
603
604 if u'latex' in options:
605 return gmPathLab.format_test_results (
606 results = emr.get_test_results_by_date(),
607 output_format = u'latex'
608 )
609
610 _log.error('no known test results table formatting style in [%s]', data)
611 return _('unknown test results table formatting style [%s]') % data
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 #--------------------------------------------------------
641 # <data>:
642 # format: tex (only, currently)
643 # message: shown in input dialog, must not contain "//" or "::"
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 # internal helpers
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 #-----------------------------------------------------------------
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 # public API
702 #-----------------------------------------------------------------
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 #-----------------------------------------------------------------
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 # FIXME: use self.__sync_cookie for syncing with user interaction
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 # FIXME: use semaphore
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 # FIXME: ask user what to do about wrong cookie
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 # FIXME: let user select patient
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 # we ain't locked anyways, so succeed
845 if not self.__pat.locked:
846 return (1, '')
847 # FIXME: ask user what to do about wrong cookie
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 #-----------------------------------------------------------------
854 - def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
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 # internal API
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 #-----------------------------------------------------------------
899 patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True)
900 if patient is not None:
901 self.__user_answer = 1
902 else:
903 self.__user_answer = 0
904 self.__user_done = True
905 return 1
906 #=====================================================================
907 # main
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 # should work:
947 '$<lastname>$',
948 '$<lastname::::3>$',
949 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
950
951 # should fail:
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 # should work:
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 # 'firstname',
975 # 'title',
976 # 'date_of_birth',
977 # 'progress_notes',
978 # 'soap',
979 # 'soap_s',
980 # 'soap_o',
981 # 'soap_a',
982 # 'soap_p',
983
984 # 'soap',
985 # 'progress_notes',
986 # 'date_of_birth'
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 # print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
1008
1009 # app = wx.PyWidgetTester(size = (200, 50))
1010 # for placeholder in known_placeholders:
1011 # print placeholder, "=", handler[placeholder]
1012
1013 # ph = 'progress_notes::ap'
1014 # print '%s: %s' % (ph, handler[ph])
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 # should fail:
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 #--------------------------------------------------------
1096
1097 #ph = u'emr_journal::soap //%(date)s %(modified_by)s %(soap_cat)s %(narrative)s//30::'
1098 #ph = u'free_text::latex//placeholder test::9999'
1099 ph = u'soap_for_encounters:://::9999'
1100
1101 handler = gmPlaceholderHandler()
1102 handler.debug = True
1103
1104 pat = gmPersonSearch.ask_for_patient()
1105 if pat is None:
1106 return
1107
1108 gmPatSearchWidgets.set_active_patient(patient = pat)
1109
1110 app = wx.PyWidgetTester(size = (200, 50))
1111 print u'%s => %s' % (ph, handler[ph])
1112 #--------------------------------------------------------
1113
1114 #test_placeholders()
1115 #test_new_variant_placeholders()
1116 #test_scripting()
1117 #test_placeholder_regex()
1118 test_placeholder()
1119
1120 #=====================================================================
1121
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Apr 12 03:58:46 2011 | http://epydoc.sourceforge.net |