1 """Widgets dealing with patient demographics."""
2
3 __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>"
4 __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
5
6
7 import sys
8 import sys
9 import codecs
10 import re as regex
11 import logging
12 import os
13 import datetime as pydt
14
15
16 import wx
17 import wx.wizard
18 import wx.lib.imagebrowser as wx_imagebrowser
19 import wx.lib.statbmp as wx_genstatbmp
20
21
22
23 if __name__ == '__main__':
24 sys.path.insert(0, '../../')
25 from Gnumed.pycommon import gmDispatcher
26 from Gnumed.pycommon import gmI18N
27 from Gnumed.pycommon import gmMatchProvider
28 from Gnumed.pycommon import gmPG2
29 from Gnumed.pycommon import gmTools
30 from Gnumed.pycommon import gmCfg
31 from Gnumed.pycommon import gmDateTime
32 from Gnumed.pycommon import gmShellAPI
33 from Gnumed.pycommon import gmNetworkTools
34
35 from Gnumed.business import gmDemographicRecord
36 from Gnumed.business import gmPersonSearch
37 from Gnumed.business import gmPerson
38 from Gnumed.business import gmStaff
39
40 from Gnumed.wxpython import gmPhraseWheel
41 from Gnumed.wxpython import gmRegetMixin
42 from Gnumed.wxpython import gmAuthWidgets
43 from Gnumed.wxpython import gmPersonContactWidgets
44 from Gnumed.wxpython import gmEditArea
45 from Gnumed.wxpython import gmListWidgets
46 from Gnumed.wxpython import gmDateTimeInput
47 from Gnumed.wxpython import gmDataMiningWidgets
48 from Gnumed.wxpython import gmGuiHelpers
49
50
51
52 _log = logging.getLogger('gm.ui')
53
54
55 try:
56 _('dummy-no-need-to-translate-but-make-epydoc-happy')
57 except NameError:
58 _ = lambda x:x
59
60
61
62
64 if tag_image is not None:
65 if tag_image['is_in_use']:
66 gmGuiHelpers.gm_show_info (
67 aTitle = _('Editing tag'),
68 aMessage = _(
69 'Cannot edit the image tag\n'
70 '\n'
71 ' "%s"\n'
72 '\n'
73 'because it is currently in use.\n'
74 ) % tag_image['l10n_description']
75 )
76 return False
77
78 ea = cTagImageEAPnl(parent = parent, id = -1)
79 ea.data = tag_image
80 ea.mode = gmTools.coalesce(tag_image, 'new', 'edit')
81 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
82 dlg.SetTitle(gmTools.coalesce(tag_image, _('Adding new tag'), _('Editing tag')))
83 if dlg.ShowModal() == wx.ID_OK:
84 dlg.Destroy()
85 return True
86 dlg.Destroy()
87 return False
88
100
101 def edit(tag_image=None):
102 return edit_tag_image(parent = parent, tag_image = tag_image, single_entry = (tag_image is not None))
103
104 def delete(tag):
105 if tag['is_in_use']:
106 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this tag. It is in use.'), beep = True)
107 return False
108
109 return gmDemographicRecord.delete_tag_image(tag_image = tag['pk_tag_image'])
110
111 def refresh(lctrl):
112 tags = gmDemographicRecord.get_tag_images(order_by = u'l10n_description')
113 items = [ [
114 t['l10n_description'],
115 gmTools.bool2subst(t['is_in_use'], u'X', u''),
116 u'%s' % t['size'],
117 t['pk_tag_image']
118 ] for t in tags ]
119 lctrl.set_string_items(items)
120 lctrl.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE])
121 lctrl.set_data(tags)
122
123 msg = _('\nTags with images registered with GNUmed.\n')
124
125 tag = gmListWidgets.get_choices_from_list (
126 parent = parent,
127 msg = msg,
128 caption = _('Showing tags with images.'),
129 columns = [_('Tag name'), _('In use'), _('Image size'), u'#'],
130 single_selection = True,
131 new_callback = edit,
132 edit_callback = edit,
133 delete_callback = delete,
134 refresh_callback = refresh,
135 left_extra_button = (_('WWW'), _('Go to www.openclipart.org for images.'), go_to_openclipart_org)
136 )
137
138 return tag
139
140 from Gnumed.wxGladeWidgets import wxgTagImageEAPnl
141
142 -class cTagImageEAPnl(wxgTagImageEAPnl.wxgTagImageEAPnl, gmEditArea.cGenericEditAreaMixin):
143
161
162
163
165
166 valid = True
167
168 if self.mode == u'new':
169 if self.__selected_image_file is None:
170 valid = False
171 gmDispatcher.send(signal = 'statustext', msg = _('Must pick an image file for a new tag.'), beep = True)
172 self._BTN_pick_image.SetFocus()
173
174 if self.__selected_image_file is not None:
175 try:
176 open(self.__selected_image_file).close()
177 except StandardError:
178 valid = False
179 self.__selected_image_file = None
180 gmDispatcher.send(signal = 'statustext', msg = _('Cannot open the image file [%s].') % self.__selected_image_file, beep = True)
181 self._BTN_pick_image.SetFocus()
182
183 if self._TCTRL_description.GetValue().strip() == u'':
184 valid = False
185 self.display_tctrl_as_valid(self._TCTRL_description, False)
186 self._TCTRL_description.SetFocus()
187 else:
188 self.display_tctrl_as_valid(self._TCTRL_description, True)
189
190 return (valid is True)
191
210
230
232 self._TCTRL_description.SetValue(u'')
233 self._TCTRL_filename.SetValue(u'')
234 self._BMP_image.SetBitmap(bitmap = wx.EmptyBitmap(100, 100))
235
236 self.__selected_image_file = None
237
238 self._TCTRL_description.SetFocus()
239
241 self._refresh_as_new()
242
255
256
257
269
270
285
286 def delete(tag):
287 do_delete = gmGuiHelpers.gm_show_question (
288 title = _('Deleting patient tag'),
289 question = _('Do you really want to delete this patient tag ?')
290 )
291 if not do_delete:
292 return False
293 patient.remove_tag(tag = tag['pk_identity_tag'])
294 return True
295
296 def manage_available_tags(tag):
297 manage_tag_images(parent = parent)
298 return False
299
300 msg = _('Tags of patient: %s\n') % patient['description_gender']
301
302 return gmListWidgets.get_choices_from_list (
303 parent = parent,
304 msg = msg,
305 caption = _('Showing patient tags'),
306 columns = [_('Tag'), _('Comment')],
307 single_selection = False,
308 delete_callback = delete,
309 refresh_callback = refresh,
310 left_extra_button = (_('Manage'), _('Manage available tags.'), manage_available_tags)
311 )
312
313 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
314
316
318 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
319 self._SZR_bitmaps = self.GetSizer()
320 self.__bitmaps = []
321
322 self.__context_popup = wx.Menu()
323
324 item = self.__context_popup.Append(-1, _('&Edit comment'))
325 self.Bind(wx.EVT_MENU, self.__edit_tag, item)
326
327 item = self.__context_popup.Append(-1, _('&Remove tag'))
328 self.Bind(wx.EVT_MENU, self.__remove_tag, item)
329
330
331
333
334 self.clear()
335
336 for tag in patient.get_tags(order_by = u'l10n_description'):
337 fname = tag.export_image2file()
338 if fname is None:
339 _log.warning('cannot export image data of tag [%s]', tag['l10n_description'])
340 continue
341 img = gmGuiHelpers.file2scaled_image(filename = fname, height = 20)
342 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
343 bmp.SetToolTipString(u'%s%s' % (
344 tag['l10n_description'],
345 gmTools.coalesce(tag['comment'], u'', u'\n\n%s')
346 ))
347 bmp.tag = tag
348 bmp.Bind(wx.EVT_RIGHT_UP, self._on_bitmap_rightclicked)
349
350 self._SZR_bitmaps.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 1)
351 self.__bitmaps.append(bmp)
352
353 self.GetParent().Layout()
354
356 while len(self._SZR_bitmaps.GetChildren()) > 0:
357 self._SZR_bitmaps.Detach(0)
358
359
360 for bmp in self.__bitmaps:
361 bmp.Destroy()
362 self.__bitmaps = []
363
364
365
373
375 if self.__current_tag is None:
376 return
377
378 msg = _('Edit the comment on tag [%s]') % self.__current_tag['l10n_description']
379 comment = wx.GetTextFromUser (
380 message = msg,
381 caption = _('Editing tag comment'),
382 default_value = gmTools.coalesce(self.__current_tag['comment'], u''),
383 parent = self
384 )
385
386 if comment == u'':
387 return
388
389 if comment.strip() == self.__current_tag['comment']:
390 return
391
392 if comment == u' ':
393 self.__current_tag['comment'] = None
394 else:
395 self.__current_tag['comment'] = comment.strip()
396
397 self.__current_tag.save()
398
399
400
402 self.__current_tag = evt.GetEventObject().tag
403 self.PopupMenu(self.__context_popup, pos = wx.DefaultPosition)
404 self.__current_tag = None
405
406
408
410
411 kwargs['message'] = _("Today's KOrganizer appointments ...")
412 kwargs['button_defs'] = [
413 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
414 {'label': u''},
415 {'label': u''},
416 {'label': u''},
417 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')}
418 ]
419 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
420
421 self.fname = os.path.expanduser(os.path.join(gmTools.gmPaths().tmp_dir, 'korganizer2gnumed.csv'))
422 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
423
424
428
438
440 try: os.remove(self.fname)
441 except OSError: pass
442 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
443 try:
444 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace')
445 except IOError:
446 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
447 return
448
449 csv_lines = gmTools.unicode_csv_reader (
450 csv_file,
451 delimiter = ','
452 )
453
454 self._LCTRL_items.set_columns ([
455 _('Place'),
456 _('Start'),
457 u'',
458 u'',
459 _('Patient'),
460 _('Comment')
461 ])
462 items = []
463 data = []
464 for line in csv_lines:
465 items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
466 data.append([line[4], line[7]])
467
468 self._LCTRL_items.set_string_items(items = items)
469 self._LCTRL_items.set_column_widths()
470 self._LCTRL_items.set_data(data = data)
471 self._LCTRL_items.patient_key = 0
472
473
474
477
478
479
481
482 pat = gmPerson.gmCurrentPatient()
483 curr_jobs = pat.get_occupations()
484 if len(curr_jobs) > 0:
485 old_job = curr_jobs[0]['l10n_occupation']
486 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
487 else:
488 old_job = u''
489 update = u''
490
491 msg = _(
492 'Please enter the primary occupation of the patient.\n'
493 '\n'
494 'Currently recorded:\n'
495 '\n'
496 ' %s (last updated %s)'
497 ) % (old_job, update)
498
499 new_job = wx.GetTextFromUser (
500 message = msg,
501 caption = _('Editing primary occupation'),
502 default_value = old_job,
503 parent = None
504 )
505 if new_job.strip() == u'':
506 return
507
508 for job in curr_jobs:
509
510 if job['l10n_occupation'] != new_job:
511 pat.unlink_occupation(occupation = job['l10n_occupation'])
512
513 pat.link_occupation(occupation = new_job)
514
515
530
531
532
533
536
537
539
540 go_ahead = gmGuiHelpers.gm_show_question (
541 _('Are you sure you really, positively want\n'
542 'to disable the following person ?\n'
543 '\n'
544 ' %s %s %s\n'
545 ' born %s\n'
546 '\n'
547 '%s\n'
548 ) % (
549 identity['firstnames'],
550 identity['lastnames'],
551 identity['gender'],
552 identity.get_formatted_dob(),
553 gmTools.bool2subst (
554 identity.is_patient,
555 _('This patient DID receive care.'),
556 _('This person did NOT receive care.')
557 )
558 ),
559 _('Disabling person')
560 )
561 if not go_ahead:
562 return True
563
564
565 conn = gmAuthWidgets.get_dbowner_connection (
566 procedure = _('Disabling patient')
567 )
568
569 if conn is False:
570 return True
571
572 if conn is None:
573 return False
574
575
576 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}])
577
578 return True
579
580
581
582
597
599
601 query = u"""
602 (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
603 union
604 (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
605 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
606 mp.setThresholds(3, 5, 9)
607 gmPhraseWheel.cPhraseWheel.__init__ (
608 self,
609 *args,
610 **kwargs
611 )
612 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name)."))
613 self.capitalisation_mode = gmTools.CAPS_NAMES
614 self.matcher = mp
615
617
619 query = u"""
620 (SELECT distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
621 union
622 (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
623 union
624 (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
625 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
626 mp.setThresholds(3, 5, 9)
627 gmPhraseWheel.cPhraseWheel.__init__ (
628 self,
629 *args,
630 **kwargs
631 )
632 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
633
634
635 self.matcher = mp
636
638
640 query = u"SELECT distinct title, title from dem.identity where title %(fragment_condition)s"
641 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
642 mp.setThresholds(1, 3, 9)
643 gmPhraseWheel.cPhraseWheel.__init__ (
644 self,
645 *args,
646 **kwargs
647 )
648 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
649 self.matcher = mp
650
652 """Let user select a gender."""
653
654 _gender_map = None
655
657
658 if cGenderSelectionPhraseWheel._gender_map is None:
659 cmd = u"""
660 SELECT tag, l10n_label, sort_weight
661 from dem.v_gender_labels
662 order by sort_weight desc"""
663 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
664 cGenderSelectionPhraseWheel._gender_map = {}
665 for gender in rows:
666 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
667 'data': gender[idx['tag']],
668 'field_label': gender[idx['l10n_label']],
669 'list_label': gender[idx['l10n_label']],
670 'weight': gender[idx['sort_weight']]
671 }
672
673 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values())
674 mp.setThresholds(1, 1, 3)
675
676 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
677 self.selection_only = True
678 self.matcher = mp
679 self.picklist_delay = 50
680
682
684 query = u"""
685 SELECT DISTINCT ON (list_label)
686 pk AS data,
687 name AS field_label,
688 name || coalesce(' (' || issuer || ')', '') as list_label
689 FROM dem.enum_ext_id_types
690 WHERE name %(fragment_condition)s
691 ORDER BY list_label
692 LIMIT 25
693 """
694 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
695 mp.setThresholds(1, 3, 5)
696 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
697 self.SetToolTipString(_("Enter or select a type for the external ID."))
698 self.matcher = mp
699
704
719
720
721
722 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl
723
724 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
725 """An edit area for editing/creating external IDs.
726
727 Does NOT act on/listen to the current patient.
728 """
748
751
752
753
774
792
808
814
816 self._refresh_as_new()
817 self._PRW_issuer.SetText(self.data['issuer'])
818
824
825
826
828 """Set the issuer according to the selected type.
829
830 Matches are fetched from existing records in backend.
831 """
832 pk_curr_type = self._PRW_type.GetData()
833 if pk_curr_type is None:
834 return True
835 rows, idx = gmPG2.run_ro_queries(queries = [{
836 'cmd': u"SELECT issuer from dem.enum_ext_id_types where pk = %s",
837 'args': [pk_curr_type]
838 }])
839 if len(rows) == 0:
840 return True
841 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
842 return True
843
844
845
846
848 allow_empty_dob = gmGuiHelpers.gm_show_question (
849 _(
850 'Are you sure you want to leave this person\n'
851 'without a valid date of birth ?\n'
852 '\n'
853 'This can be useful for temporary staff members\n'
854 'but will provoke nag screens if this person\n'
855 'becomes a patient.\n'
856 ),
857 _('Validating date of birth')
858 )
859 return allow_empty_dob
860
862
863
864 if dob_prw.is_valid_timestamp(allow_empty = False):
865 dob = dob_prw.date
866
867 if (dob.year > 1899) and (dob < gmDateTime.pydt_now_here()):
868 return True
869
870 if dob.year < 1900:
871 msg = _(
872 'DOB: %s\n'
873 '\n'
874 'While this is a valid point in time Python does\n'
875 'not know how to deal with it.\n'
876 '\n'
877 'We suggest using January 1st 1901 instead and adding\n'
878 'the true date of birth to the patient comment.\n'
879 '\n'
880 'Sorry for the inconvenience %s'
881 ) % (dob, gmTools.u_frowning_face)
882 else:
883 msg = _(
884 'DOB: %s\n'
885 '\n'
886 'Date of birth in the future !'
887 ) % dob
888 gmGuiHelpers.gm_show_error (
889 msg,
890 _('Validating date of birth')
891 )
892 dob_prw.display_as_valid(False)
893 dob_prw.SetFocus()
894 return False
895
896
897 if dob_prw.GetValue().strip() != u'':
898 dob_prw.display_as_valid(False)
899 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of birth.'))
900 dob_prw.SetFocus()
901 return False
902
903
904 dob_prw.display_as_valid(False)
905 return True
906
907
909
910 val = ctrl.GetValue().strip()
911
912 if val == u'':
913 return True
914
915 converted, hours = gmTools.input2int(val[:2], 0, 23)
916 if not converted:
917 return False
918
919 converted, minutes = gmTools.input2int(val[3:5], 0, 59)
920 if not converted:
921 return False
922
923 return True
924
925
926 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
927
928 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
929 """An edit area for editing/creating title/gender/dob/dod etc."""
930
946
947
948
949
950
951
952
953
955
956 has_error = False
957
958 if self._PRW_gender.GetData() is None:
959 self._PRW_gender.SetFocus()
960 has_error = True
961
962 if self.data is not None:
963 if not _validate_dob_field(self._PRW_dob):
964 has_error = True
965
966
967 if _validate_tob_field(self._TCTRL_tob):
968 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = True)
969 else:
970 has_error = True
971 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = False)
972
973 if not self._PRW_dod.is_valid_timestamp(allow_empty = True):
974 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
975 self._PRW_dod.SetFocus()
976 has_error = True
977
978 return (has_error is False)
979
983
985
986 if self._PRW_dob.GetValue().strip() == u'':
987 if not _empty_dob_allowed():
988 return False
989 self.data['dob'] = None
990 else:
991 self.data['dob'] = self._PRW_dob.GetData()
992 self.data['dob_is_estimated'] = self._CHBOX_estimated_dob.GetValue()
993 val = self._TCTRL_tob.GetValue().strip()
994 if val == u'':
995 self.data['tob'] = None
996 else:
997 self.data['tob'] = pydt.time(int(val[:2]), int(val[3:5]))
998 self.data['gender'] = self._PRW_gender.GetData()
999 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
1000 self.data['deceased'] = self._PRW_dod.GetData()
1001 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1002
1003 self.data.save()
1004 return True
1005
1008
1042
1045
1046 from Gnumed.wxGladeWidgets import wxgPersonNameEAPnl
1047
1048 -class cPersonNameEAPnl(wxgPersonNameEAPnl.wxgPersonNameEAPnl, gmEditArea.cGenericEditAreaMixin):
1049 """An edit area for editing/creating names of people.
1050
1051 Does NOT act on/listen to the current patient.
1052 """
1073
1074
1075
1076
1077
1078
1079
1080
1082 validity = True
1083
1084 if self._PRW_lastname.GetValue().strip() == u'':
1085 validity = False
1086 self._PRW_lastname.display_as_valid(False)
1087 self._PRW_lastname.SetFocus()
1088 else:
1089 self._PRW_lastname.display_as_valid(True)
1090
1091 if self._PRW_firstname.GetValue().strip() == u'':
1092 validity = False
1093 self._PRW_firstname.display_as_valid(False)
1094 self._PRW_firstname.SetFocus()
1095 else:
1096 self._PRW_firstname.display_as_valid(True)
1097
1098 return validity
1099
1101
1102 first = self._PRW_firstname.GetValue().strip()
1103 last = self._PRW_lastname.GetValue().strip()
1104 active = self._CHBOX_active.GetValue()
1105
1106 try:
1107 data = self.__identity.add_name(first, last, active)
1108 except gmPG2.dbapi.IntegrityError as exc:
1109 _log.exception('cannot save new name')
1110 gmGuiHelpers.gm_show_error (
1111 aTitle = _('Adding name'),
1112 aMessage = _(
1113 'Cannot add this name to the patient !\n'
1114 '\n'
1115 ' %s'
1116 ) % str(exc)
1117 )
1118 return False
1119
1120 old_nick = self.__identity['active_name']['preferred']
1121 new_nick = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
1122 if active:
1123 data['preferred'] = gmTools.coalesce(new_nick, old_nick)
1124 else:
1125 data['preferred'] = new_nick
1126 data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1127 data.save()
1128
1129 self.data = data
1130 return True
1131
1133 """The knack here is that we can only update a few fields.
1134
1135 Otherwise we need to clone the name and update that.
1136 """
1137 first = self._PRW_firstname.GetValue().strip()
1138 last = self._PRW_lastname.GetValue().strip()
1139 active = self._CHBOX_active.GetValue()
1140
1141 current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip()
1142 new_name = first + last
1143
1144
1145 if new_name == current_name:
1146 self.data['active_name'] = self._CHBOX_active.GetValue()
1147 self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
1148 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1149 self.data.save()
1150
1151 else:
1152 try:
1153 name = self.__identity.add_name(first, last, active)
1154 except gmPG2.dbapi.IntegrityError as exc:
1155 _log.exception('cannot clone name when editing existing name')
1156 gmGuiHelpers.gm_show_error (
1157 aTitle = _('Editing name'),
1158 aMessage = _(
1159 'Cannot clone a copy of this name !\n'
1160 '\n'
1161 ' %s'
1162 ) % str(exc)
1163 )
1164 return False
1165 name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
1166 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1167 name.save()
1168 self.data = name
1169
1170 return True
1171
1180
1187
1196
1197
1198
1200 """A list for managing a person's names.
1201
1202 Does NOT act on/listen to the current patient.
1203 """
1221
1222
1223
1224 - def refresh(self, *args, **kwargs):
1241
1242
1243
1245 self._LCTRL_items.set_columns(columns = [
1246 _('Active'),
1247 _('Lastname'),
1248 _('Firstname(s)'),
1249 _('Preferred Name'),
1250 _('Comment')
1251 ])
1252
1263
1273
1275
1276 if len(self.__identity.get_names()) == 1:
1277 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1278 return False
1279
1280 if name['active_name']:
1281 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the active name of a person.'), beep = True)
1282 return False
1283
1284 go_ahead = gmGuiHelpers.gm_show_question (
1285 _( 'It is often advisable to keep old names around and\n'
1286 'just create a new "currently active" name.\n'
1287 '\n'
1288 'This allows finding the patient by both the old\n'
1289 'and the new name (think before/after marriage).\n'
1290 '\n'
1291 'Do you still want to really delete\n'
1292 "this name from the patient ?"
1293 ),
1294 _('Deleting name')
1295 )
1296 if not go_ahead:
1297 return False
1298
1299 self.__identity.delete_name(name = name)
1300 return True
1301
1302
1303
1305 return self.__identity
1306
1310
1311 identity = property(_get_identity, _set_identity)
1312
1314 """A list for managing a person's external IDs.
1315
1316 Does NOT act on/listen to the current patient.
1317 """
1335
1336
1337
1338 - def refresh(self, *args, **kwargs):
1355
1356
1357
1359 self._LCTRL_items.set_columns(columns = [
1360 _('ID type'),
1361 _('Value'),
1362 _('Issuer'),
1363 _('Comment')
1364 ])
1365
1376
1387
1389 go_ahead = gmGuiHelpers.gm_show_question (
1390 _( 'Do you really want to delete this\n'
1391 'external ID from the patient ?'),
1392 _('Deleting external ID')
1393 )
1394 if not go_ahead:
1395 return False
1396 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1397 return True
1398
1399
1400
1402 return self.__identity
1403
1407
1408 identity = property(_get_identity, _set_identity)
1409
1410
1411
1412 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
1413
1415 """A panel for editing identity data for a person.
1416
1417 - provides access to:
1418 - identity EA
1419 - name list manager
1420 - external IDs list manager
1421
1422 Does NOT act on/listen to the current patient.
1423 """
1430
1431
1432
1434 self._PNL_names.identity = self.__identity
1435 self._PNL_ids.identity = self.__identity
1436
1437 self._PNL_identity.mode = 'new'
1438 self._PNL_identity.data = self.__identity
1439 if self.__identity is not None:
1440 self._PNL_identity.mode = 'edit'
1441 self._PNL_identity._refresh_from_existing()
1442
1443
1444
1446 return self.__identity
1447
1451
1452 identity = property(_get_identity, _set_identity)
1453
1454
1455
1459
1460
1463
1464
1465 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
1466
1475
1476
1477
1479
1480 tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.')
1481
1482 if self.__identity is None:
1483 self._TCTRL_er_contact.SetValue(u'')
1484 self._TCTRL_person.person = None
1485 self._TCTRL_person.SetToolTipString(tt)
1486
1487 self._PRW_provider.SetText(value = u'', data = None)
1488 return
1489
1490 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u''))
1491 if self.__identity['pk_emergency_contact'] is not None:
1492 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact'])
1493 self._TCTRL_person.person = ident
1494 tt = u'%s\n\n%s\n\n%s' % (
1495 tt,
1496 ident['description_gender'],
1497 u'\n'.join([
1498 u'%s: %s%s' % (
1499 c['l10n_comm_type'],
1500 c['url'],
1501 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'')
1502 )
1503 for c in ident.get_comm_channels()
1504 ])
1505 )
1506 else:
1507 self._TCTRL_person.person = None
1508
1509 self._TCTRL_person.SetToolTipString(tt)
1510
1511 if self.__identity['pk_primary_provider'] is None:
1512 self._PRW_provider.SetText(value = u'', data = None)
1513 else:
1514 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1515
1516
1517
1519 return self.__identity
1520
1524
1525 identity = property(_get_identity, _set_identity)
1526
1527
1528
1543
1546
1557
1565
1566
1567
1568
1570 """Notebook displaying demographics editing pages:
1571
1572 - Identity (as per Jim/Rogerio 12/2011)
1573 - Contacts (addresses, phone numbers, etc)
1574 - Social network (significant others, GP, etc)
1575
1576 Does NOT act on/listen to the current patient.
1577 """
1578
1580
1581 wx.Notebook.__init__ (
1582 self,
1583 parent = parent,
1584 id = id,
1585 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1586 name = self.__class__.__name__
1587 )
1588
1589 self.__identity = None
1590 self.__do_layout()
1591 self.SetSelection(0)
1592
1593
1594
1596 """Populate fields in pages with data from model."""
1597 for page_idx in range(self.GetPageCount()):
1598 page = self.GetPage(page_idx)
1599 page.identity = self.__identity
1600
1601 return True
1602
1603
1604
1634
1635
1636
1638 return self.__identity
1639
1642
1643 identity = property(_get_identity, _set_identity)
1644
1645
1646
1647
1648
1649
1651 """Page containing patient occupations edition fields.
1652 """
1653 - def __init__(self, parent, id, ident=None):
1654 """
1655 Creates a new instance of BasicPatDetailsPage
1656 @param parent - The parent widget
1657 @type parent - A wx.Window instance
1658 @param id - The widget id
1659 @type id - An integer
1660 """
1661 wx.Panel.__init__(self, parent, id)
1662 self.__ident = ident
1663 self.__do_layout()
1664
1666 PNL_form = wx.Panel(self, -1)
1667
1668 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1669 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
1670 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
1671
1672 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
1673 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
1674
1675
1676 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
1677 SZR_input.AddGrowableCol(1)
1678 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1679 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1680 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
1681 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
1682 PNL_form.SetSizerAndFit(SZR_input)
1683
1684
1685 SZR_main = wx.BoxSizer(wx.VERTICAL)
1686 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1687 self.SetSizer(SZR_main)
1688
1691
1692 - def refresh(self, identity=None):
1693 if identity is not None:
1694 self.__ident = identity
1695 jobs = self.__ident.get_occupations()
1696 if len(jobs) > 0:
1697 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
1698 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
1699 return True
1700
1702 if self.PRW_occupation.IsModified():
1703 new_job = self.PRW_occupation.GetValue().strip()
1704 jobs = self.__ident.get_occupations()
1705 for job in jobs:
1706 if job['l10n_occupation'] == new_job:
1707 continue
1708 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
1709 self.__ident.link_occupation(occupation = new_job)
1710 return True
1711
1713 """Patient demographics plugin for main notebook.
1714
1715 Hosts another notebook with pages for Identity, Contacts, etc.
1716
1717 Acts on/listens to the currently active patient.
1718 """
1719
1725
1726
1727
1728
1729
1730
1732 """Arrange widgets."""
1733 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
1734
1735 szr_main = wx.BoxSizer(wx.VERTICAL)
1736 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
1737 self.SetSizerAndFit(szr_main)
1738
1739
1740
1742 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1743 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1744
1746 self._schedule_data_reget()
1747
1749 self._schedule_data_reget()
1750
1751
1761
1762
1763 if __name__ == "__main__":
1764
1765
1767 app = wx.PyWidgetTester(size = (600, 400))
1768 app.SetWidget(cKOrganizerSchedulePnl)
1769 app.MainLoop()
1770
1772 app = wx.PyWidgetTester(size = (600, 400))
1773 widget = cPersonNamesManagerPnl(app.frame, -1)
1774 widget.identity = activate_patient()
1775 app.frame.Show(True)
1776 app.MainLoop()
1777
1779 app = wx.PyWidgetTester(size = (600, 400))
1780 widget = cPersonIDsManagerPnl(app.frame, -1)
1781 widget.identity = activate_patient()
1782 app.frame.Show(True)
1783 app.MainLoop()
1784
1786 app = wx.PyWidgetTester(size = (600, 400))
1787 widget = cPersonIdentityManagerPnl(app.frame, -1)
1788 widget.identity = activate_patient()
1789 app.frame.Show(True)
1790 app.MainLoop()
1791
1796
1798 app = wx.PyWidgetTester(size = (600, 400))
1799 widget = cPersonDemographicsEditorNb(app.frame, -1)
1800 widget.identity = activate_patient()
1801 widget.refresh()
1802 app.frame.Show(True)
1803 app.MainLoop()
1804
1813
1814 if len(sys.argv) > 1 and sys.argv[1] == 'test':
1815
1816 gmI18N.activate_locale()
1817 gmI18N.install_domain(domain='gnumed')
1818 gmPG2.get_connection()
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830 test_person_ids_pnl()
1831
1832
1833
1834
1835
1836
1837