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 io
10 import re as regex
11 import logging
12 import os
13 import datetime as pydt
14
15
16 import wx
17 import wx.lib.imagebrowser as wx_imagebrowser
18 import wx.lib.statbmp as wx_genstatbmp
19
20
21
22 if __name__ == '__main__':
23 sys.path.insert(0, '../../')
24 from Gnumed.pycommon import gmDispatcher
25 from Gnumed.pycommon import gmI18N
26 from Gnumed.pycommon import gmMatchProvider
27 from Gnumed.pycommon import gmPG2
28 from Gnumed.pycommon import gmTools
29 from Gnumed.pycommon import gmCfg
30 from Gnumed.pycommon import gmDateTime
31 from Gnumed.pycommon import gmShellAPI
32 from Gnumed.pycommon import gmNetworkTools
33
34 from Gnumed.business import gmDemographicRecord
35 from Gnumed.business import gmPersonSearch
36 from Gnumed.business import gmPerson
37 from Gnumed.business import gmStaff
38
39 from Gnumed.wxpython import gmPhraseWheel
40 from Gnumed.wxpython import gmRegetMixin
41 from Gnumed.wxpython import gmAuthWidgets
42 from Gnumed.wxpython import gmPersonContactWidgets
43 from Gnumed.wxpython import gmEditArea
44 from Gnumed.wxpython import gmListWidgets
45 from Gnumed.wxpython import gmDateTimeInput
46 from Gnumed.wxpython import gmDataMiningWidgets
47 from Gnumed.wxpython import gmGuiHelpers
48
49
50
51 _log = logging.getLogger('gm.ui')
52
53
54
55
57 if tag_image is not None:
58 if tag_image['is_in_use']:
59 gmGuiHelpers.gm_show_info (
60 aTitle = _('Editing tag'),
61 aMessage = _(
62 'Cannot edit the image tag\n'
63 '\n'
64 ' "%s"\n'
65 '\n'
66 'because it is currently in use.\n'
67 ) % tag_image['l10n_description']
68 )
69 return False
70
71 ea = cTagImageEAPnl(parent, -1)
72 ea.data = tag_image
73 ea.mode = gmTools.coalesce(tag_image, 'new', 'edit')
74 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
75 dlg.SetTitle(gmTools.coalesce(tag_image, _('Adding new tag'), _('Editing tag')))
76 if dlg.ShowModal() == wx.ID_OK:
77 dlg.DestroyLater()
78 return True
79
80 dlg.DestroyLater()
81 return False
82
83
96
97
98 def edit(tag_image=None):
99 return edit_tag_image(parent = parent, tag_image = tag_image, single_entry = (tag_image is not None))
100
101
102 def delete(tag):
103 if tag['is_in_use']:
104 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this tag. It is in use.'), beep = True)
105 return False
106
107 return gmDemographicRecord.delete_tag_image(tag_image = tag['pk_tag_image'])
108
109
110 def refresh(lctrl):
111 tags = gmDemographicRecord.get_tag_images(order_by = 'l10n_description')
112 items = [ [
113 t['l10n_description'],
114 gmTools.bool2subst(t['is_in_use'], 'X', ''),
115 '%s' % t['size'],
116 t['pk_tag_image']
117 ] for t in tags ]
118 lctrl.set_string_items(items)
119 lctrl.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE])
120 lctrl.set_data(tags)
121
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'), '#'],
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
141 from Gnumed.wxGladeWidgets import wxgTagImageEAPnl
142
143 -class cTagImageEAPnl(wxgTagImageEAPnl.wxgTagImageEAPnl, gmEditArea.cGenericEditAreaMixin):
144
162
163
164
165
167
168 valid = True
169
170 if self.mode == 'new':
171 if self.__selected_image_file is None:
172 valid = False
173 self.StatusText = _('Must pick an image file for a new tag.')
174 self._BTN_pick_image.SetFocus()
175
176 if self.__selected_image_file is not None:
177 try:
178 open(self.__selected_image_file).close()
179 except Exception:
180 valid = False
181 self.__selected_image_file = None
182 self.StatusText = _('Cannot open the image file [%s].')
183 self._BTN_pick_image.SetFocus()
184
185 if self._TCTRL_description.GetValue().strip() == '':
186 valid = False
187 self.display_tctrl_as_valid(self._TCTRL_description, False)
188 self._TCTRL_description.SetFocus()
189 else:
190 self.display_tctrl_as_valid(self._TCTRL_description, True)
191
192 return (valid is True)
193
212
214
215
216
217 dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Updating tag with image'))
218 if dbo_conn is None:
219 return False
220 dbo_conn.close()
221
222 self.data['description'] = self._TCTRL_description.GetValue().strip()
223 self.data['filename'] = self._TCTRL_filename.GetValue().strip()
224 self.data.save()
225
226 if self.__selected_image_file is not None:
227 open(self.__selected_image_file).close()
228 self.data.update_image_from_file(filename = self.__selected_image_file)
229 self.__selected_image_file = None
230
231 return True
232
234 self._TCTRL_description.SetValue('')
235 self._TCTRL_filename.SetValue('')
236 self._BMP_image.SetBitmap(bitmap = wx.Bitmap(wx.Image(100, 100, clear = True)))
237
238 self.__selected_image_file = None
239
240 self._TCTRL_description.SetFocus()
241
243 self._refresh_as_new()
244
257
258
259
271
272
287
288 def delete(tag):
289 do_delete = gmGuiHelpers.gm_show_question (
290 title = _('Deleting patient tag'),
291 question = _('Do you really want to delete this patient tag ?')
292 )
293 if not do_delete:
294 return False
295 patient.remove_tag(tag = tag['pk_identity_tag'])
296 return True
297
298 def manage_available_tags(tag):
299 manage_tag_images(parent = parent)
300 return False
301
302 msg = _('Tags of patient: %s\n') % patient['description_gender']
303
304 return gmListWidgets.get_choices_from_list (
305 parent = parent,
306 msg = msg,
307 caption = _('Showing patient tags'),
308 columns = [_('Tag'), _('Comment')],
309 single_selection = False,
310 delete_callback = delete,
311 refresh_callback = refresh,
312 left_extra_button = (_('Manage'), _('Manage available tags.'), manage_available_tags)
313 )
314
315 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
316
318
320 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
321 self._SZR_bitmaps = self.GetSizer()
322 self.__bitmaps = []
323
324 self.__context_popup = wx.Menu()
325
326 item = self.__context_popup.Append(-1, _('&Edit comment'))
327 self.Bind(wx.EVT_MENU, self.__edit_tag, item)
328
329 item = self.__context_popup.Append(-1, _('&Remove tag'))
330 self.Bind(wx.EVT_MENU, self.__remove_tag, item)
331
332
333
335
336 self.clear()
337
338 for tag in patient.get_tags(order_by = 'l10n_description'):
339 fname = tag.export_image2file()
340 if fname is None:
341 _log.warning('cannot export image data of tag [%s]', tag['l10n_description'])
342 continue
343 img = gmGuiHelpers.file2scaled_image(filename = fname, height = 20)
344 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
345 bmp.SetToolTip('%s%s' % (
346 tag['l10n_description'],
347 gmTools.coalesce(tag['comment'], '', '\n\n%s')
348 ))
349 bmp.tag = tag
350 bmp.Bind(wx.EVT_RIGHT_UP, self._on_bitmap_rightclicked)
351
352 self._SZR_bitmaps.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 1)
353 self.__bitmaps.append(bmp)
354
355 self.GetParent().Layout()
356
357
359 while len(self._SZR_bitmaps.GetChildren()) > 0:
360 self._SZR_bitmaps.Detach(0)
361
362
363 for bmp in self.__bitmaps:
364 bmp.DestroyLater()
365 self.__bitmaps = []
366
367
368
376
378 if self.__current_tag is None:
379 return
380
381 msg = _('Edit the comment on tag [%s]') % self.__current_tag['l10n_description']
382 comment = wx.GetTextFromUser (
383 message = msg,
384 caption = _('Editing tag comment'),
385 default_value = gmTools.coalesce(self.__current_tag['comment'], ''),
386 parent = self
387 )
388
389 if comment == '':
390 return
391
392 if comment.strip() == self.__current_tag['comment']:
393 return
394
395 if comment == ' ':
396 self.__current_tag['comment'] = None
397 else:
398 self.__current_tag['comment'] = comment.strip()
399
400 self.__current_tag.save()
401
402
403
405 self.__current_tag = evt.GetEventObject().tag
406 self.PopupMenu(self.__context_popup, pos = wx.DefaultPosition)
407 self.__current_tag = None
408
409
410
412
414
415 kwargs['message'] = _("Today's KOrganizer appointments ...")
416 kwargs['button_defs'] = [
417 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
418 {'label': ''},
419 {'label': ''},
420 {'label': ''},
421 {'label': 'KOrganizer', 'tooltip': _('Launch KOrganizer')}
422 ]
423 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
424
425 self.fname = os.path.expanduser(os.path.join(gmTools.gmPaths().tmp_dir, 'korganizer2gnumed.csv'))
426 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
427
428
432
442
444 try: os.remove(self.fname)
445 except OSError: pass
446 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
447 try:
448 csv_file = io.open(self.fname , mode = 'rt', encoding = 'utf8', errors = 'replace')
449 except IOError:
450 gmDispatcher.send(signal = 'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
451 return
452
453 csv_lines = gmTools.unicode_csv_reader (
454 csv_file,
455 delimiter = ','
456 )
457
458 self._LCTRL_items.set_columns ([
459 _('Place'),
460 _('Start'),
461 '',
462 '',
463 _('Patient'),
464 _('Comment')
465 ])
466 items = []
467 data = []
468 for line in csv_lines:
469 items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
470 data.append([line[4], line[7]])
471
472 self._LCTRL_items.set_string_items(items = items)
473 self._LCTRL_items.set_column_widths()
474 self._LCTRL_items.set_data(data = data)
475 self._LCTRL_items.patient_key = 0
476
477
478
481
482
483
484
486
487 pat = gmPerson.gmCurrentPatient()
488 curr_jobs = pat.get_occupations()
489 if len(curr_jobs) > 0:
490 old_job = curr_jobs[0]['l10n_occupation']
491 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
492 else:
493 old_job = ''
494 update = ''
495
496 msg = _(
497 'Please enter the primary occupation of the patient.\n'
498 '\n'
499 'Currently recorded:\n'
500 '\n'
501 ' %s (last updated %s)'
502 ) % (old_job, update)
503
504 new_job = wx.GetTextFromUser (
505 message = msg,
506 caption = _('Editing primary occupation'),
507 default_value = old_job,
508 parent = None
509 )
510 if new_job.strip() == '':
511 return
512
513 for job in curr_jobs:
514
515 if job['l10n_occupation'] != new_job:
516 pat.unlink_occupation(occupation = job['l10n_occupation'])
517
518 pat.link_occupation(occupation = new_job)
519
520
535
536
537
538
541
542
544
545
546 if identity['is_deleted']:
547 _log.debug('identity already deleted: %s', identity)
548 return True
549
550
551
552 prov = gmStaff.gmCurrentProvider()
553 if prov['pk_identity'] == identity['pk_identity']:
554 _log.warning('identity cannot delete itself while being logged on as staff member')
555 _log.debug('identity to delete: %s', identity)
556 _log.debug('logged on staff: %s', prov)
557 return False
558
559
560 go_ahead = gmGuiHelpers.gm_show_question (
561 _('Are you sure you really, positively want\n'
562 'to disable the following person ?\n'
563 '\n'
564 ' %s %s %s\n'
565 ' born %s\n'
566 '\n'
567 '%s\n'
568 ) % (
569 identity['firstnames'],
570 identity['lastnames'],
571 identity['gender'],
572 identity.get_formatted_dob(),
573 gmTools.bool2subst (
574 identity.is_patient,
575 _('This patient DID receive care here.'),
576 _('This person did NOT receive care here.')
577 )
578 ),
579 _('Disabling person')
580 )
581 if not go_ahead:
582 return False
583
584
585 conn = gmAuthWidgets.get_dbowner_connection (
586 procedure = _('Disabling person')
587 )
588
589 if conn is False:
590 return False
591
592 if conn is None:
593 return None
594
595
596 gmPerson.disable_identity(identity['pk_identity'])
597
598
599 from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient
600 wx.CallAfter(set_active_patient, patient = prov.identity)
601
602 return True
603
604
605
606
621
623
625 query = """
626 (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
627 union
628 (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
629 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
630 mp.setThresholds(3, 5, 9)
631 gmPhraseWheel.cPhraseWheel.__init__ (
632 self,
633 *args,
634 **kwargs
635 )
636 self.SetToolTip(_("Type or select a first name (forename/Christian name/given name)."))
637 self.capitalisation_mode = gmTools.CAPS_NAMES
638 self.matcher = mp
639
641
643 query = """
644 (SELECT distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
645 union
646 (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
647 union
648 (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
649 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
650 mp.setThresholds(3, 5, 9)
651 gmPhraseWheel.cPhraseWheel.__init__ (
652 self,
653 *args,
654 **kwargs
655 )
656 self.SetToolTip(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
657
658
659 self.matcher = mp
660
662
664 query = "SELECT distinct title, title from dem.identity where title %(fragment_condition)s"
665 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
666 mp.setThresholds(1, 3, 9)
667 gmPhraseWheel.cPhraseWheel.__init__ (
668 self,
669 *args,
670 **kwargs
671 )
672 self.SetToolTip(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
673 self.matcher = mp
674
676 """Let user select a gender."""
677
678 _gender_map = None
679
681
682 if cGenderSelectionPhraseWheel._gender_map is None:
683 cmd = """
684 SELECT tag, l10n_label, sort_weight
685 from dem.v_gender_labels
686 order by sort_weight desc"""
687 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
688 cGenderSelectionPhraseWheel._gender_map = {}
689 for gender in rows:
690 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
691 'data': gender[idx['tag']],
692 'field_label': gender[idx['l10n_label']],
693 'list_label': gender[idx['l10n_label']],
694 'weight': gender[idx['sort_weight']]
695 }
696
697 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = list(cGenderSelectionPhraseWheel._gender_map.values()))
698 mp.setThresholds(1, 1, 3)
699
700 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
701 self.selection_only = True
702 self.matcher = mp
703 self.picklist_delay = 50
704
706
708 query = """
709 SELECT DISTINCT ON (list_label)
710 pk AS data,
711 name AS field_label,
712 name || coalesce(' (' || issuer || ')', '') as list_label
713 FROM dem.enum_ext_id_types
714 WHERE name %(fragment_condition)s
715 ORDER BY list_label
716 LIMIT 25
717 """
718 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
719 mp.setThresholds(1, 3, 5)
720 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
721 self.SetToolTip(_("Enter or select a type for the external ID."))
722 self.matcher = mp
723
728
743
744
745
746 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl
747
748 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
749 """An edit area for editing/creating external IDs.
750
751 Does NOT act on/listen to the current patient.
752 """
772
775
776
777
798
816
832
838
840 self._refresh_as_new()
841 self._PRW_issuer.SetText(self.data['issuer'])
842
848
849
850
852 """Set the issuer according to the selected type.
853
854 Matches are fetched from existing records in backend.
855 """
856 pk_curr_type = self._PRW_type.GetData()
857 if pk_curr_type is None:
858 return True
859 rows, idx = gmPG2.run_ro_queries(queries = [{
860 'cmd': "SELECT issuer FROM dem.enum_ext_id_types WHERE pk = %s",
861 'args': [pk_curr_type]
862 }])
863 if len(rows) == 0:
864 return True
865 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
866 return True
867
868
869
870
872 allow_empty_dob = gmGuiHelpers.gm_show_question (
873 _(
874 'Are you sure you want to leave this person\n'
875 'without a valid date of birth ?\n'
876 '\n'
877 'This can be useful for temporary staff members\n'
878 'but will provoke nag screens if this person\n'
879 'becomes a patient.\n'
880 ),
881 _('Validating date of birth')
882 )
883 return allow_empty_dob
884
886
887
888 if dob_prw.is_valid_timestamp(empty_is_valid = False):
889 dob = dob_prw.date
890
891 if (dob.year > 1899) and (dob < gmDateTime.pydt_now_here()):
892 return True
893
894 if dob.year < 1900:
895 msg = _(
896 'DOB: %s\n'
897 '\n'
898 'While this is a valid point in time Python does\n'
899 'not know how to deal with it.\n'
900 '\n'
901 'We suggest using January 1st 1901 instead and adding\n'
902 'the true date of birth to the patient comment.\n'
903 '\n'
904 'Sorry for the inconvenience %s'
905 ) % (dob, gmTools.u_frowning_face)
906 else:
907 msg = _(
908 'DOB: %s\n'
909 '\n'
910 'Date of birth in the future !'
911 ) % dob
912 gmGuiHelpers.gm_show_error (
913 msg,
914 _('Validating date of birth')
915 )
916 dob_prw.display_as_valid(False)
917 dob_prw.SetFocus()
918 return False
919
920
921 if dob_prw.GetValue().strip() != '':
922 dob_prw.display_as_valid(False)
923 gmDispatcher.send(signal = 'statustext', msg = _('Invalid date of birth.'))
924 dob_prw.SetFocus()
925 return False
926
927
928 dob_prw.display_as_valid(False)
929 return True
930
931
933
934 val = ctrl.GetValue().strip()
935
936 if val == '':
937 return True
938
939 converted, hours = gmTools.input2int(val[:2], 0, 23)
940 if not converted:
941 return False
942
943 converted, minutes = gmTools.input2int(val[3:5], 0, 59)
944 if not converted:
945 return False
946
947 return True
948
949
950 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
951
952 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
953 """An edit area for editing/creating title/gender/dob/dod etc."""
954
970
971
972
973
974
975
976
977
979
980 has_error = False
981
982 if self._PRW_gender.GetData() is None:
983 self._PRW_gender.SetFocus()
984 has_error = True
985
986 if self.data is not None:
987 if not _validate_dob_field(self._PRW_dob):
988 has_error = True
989
990
991 if _validate_tob_field(self._TCTRL_tob):
992 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = True)
993 else:
994 has_error = True
995 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = False)
996
997 if not self._PRW_dod.is_valid_timestamp(empty_is_valid = True):
998 self.StatusText = _('Invalid date of death.')
999 self._PRW_dod.SetFocus()
1000 has_error = True
1001
1002 return (has_error is False)
1003
1007
1009
1010 if self._PRW_dob.GetValue().strip() == '':
1011 if not _empty_dob_allowed():
1012 return False
1013 self.data['dob'] = None
1014 else:
1015 self.data['dob'] = self._PRW_dob.GetData()
1016 self.data['dob_is_estimated'] = self._CHBOX_estimated_dob.GetValue()
1017 val = self._TCTRL_tob.GetValue().strip()
1018 if val == '':
1019 self.data['tob'] = None
1020 else:
1021 self.data['tob'] = pydt.time(int(val[:2]), int(val[3:5]))
1022 self.data['gender'] = self._PRW_gender.GetData()
1023 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), '')
1024 self.data['deceased'] = self._PRW_dod.GetData()
1025 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1026
1027 self.data.save()
1028 return True
1029
1032
1066
1069
1070 from Gnumed.wxGladeWidgets import wxgPersonNameEAPnl
1071
1072 -class cPersonNameEAPnl(wxgPersonNameEAPnl.wxgPersonNameEAPnl, gmEditArea.cGenericEditAreaMixin):
1073 """An edit area for editing/creating names of people.
1074
1075 Does NOT act on/listen to the current patient.
1076 """
1097
1098
1099
1100
1101
1102
1103
1104
1106 validity = True
1107
1108 if self._PRW_lastname.GetValue().strip() == '':
1109 validity = False
1110 self._PRW_lastname.display_as_valid(False)
1111 self._PRW_lastname.SetFocus()
1112 else:
1113 self._PRW_lastname.display_as_valid(True)
1114
1115 if self._PRW_firstname.GetValue().strip() == '':
1116 validity = False
1117 self._PRW_firstname.display_as_valid(False)
1118 self._PRW_firstname.SetFocus()
1119 else:
1120 self._PRW_firstname.display_as_valid(True)
1121
1122 return validity
1123
1125
1126 first = self._PRW_firstname.GetValue().strip()
1127 last = self._PRW_lastname.GetValue().strip()
1128 active = self._CHBOX_active.GetValue()
1129
1130 try:
1131 data = self.__identity.add_name(first, last, active)
1132 except gmPG2.dbapi.IntegrityError as exc:
1133 _log.exception('cannot save new name')
1134 gmGuiHelpers.gm_show_error (
1135 aTitle = _('Adding name'),
1136 aMessage = _(
1137 'Cannot add this name to the patient !\n'
1138 '\n'
1139 ' %s'
1140 ) % exc.pgerror
1141 )
1142 return False
1143
1144 old_nick = self.__identity['active_name']['preferred']
1145 new_nick = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1146 if active:
1147 data['preferred'] = gmTools.coalesce(new_nick, old_nick)
1148 else:
1149 data['preferred'] = new_nick
1150 data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1151 data.save()
1152
1153 self.data = data
1154 return True
1155
1157 """The knack here is that we can only update a few fields.
1158
1159 Otherwise we need to clone the name and update that.
1160 """
1161 first = self._PRW_firstname.GetValue().strip()
1162 last = self._PRW_lastname.GetValue().strip()
1163 active = self._CHBOX_active.GetValue()
1164
1165 current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip()
1166 new_name = first + last
1167
1168
1169 if new_name == current_name:
1170 self.data['active_name'] = self._CHBOX_active.GetValue()
1171 self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1172 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1173 self.data.save()
1174
1175 else:
1176 try:
1177 name = self.__identity.add_name(first, last, active)
1178 except gmPG2.dbapi.IntegrityError as exc:
1179 _log.exception('cannot clone name when editing existing name')
1180 gmGuiHelpers.gm_show_error (
1181 aTitle = _('Editing name'),
1182 aMessage = _(
1183 'Cannot clone a copy of this name !\n'
1184 '\n'
1185 ' %s'
1186 ) % exc.pgerror
1187 )
1188 return False
1189 name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1190 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1191 name.save()
1192 self.data = name
1193
1194 return True
1195
1204
1211
1220
1221
1222
1224 """A list for managing a person's names.
1225
1226 Does NOT act on/listen to the current patient.
1227 """
1245
1246
1247
1248 - def refresh(self, *args, **kwargs):
1265
1266
1267
1269 self._LCTRL_items.set_columns(columns = [
1270 _('Active'),
1271 _('Lastname'),
1272 _('Firstname(s)'),
1273 _('Preferred Name'),
1274 _('Comment')
1275 ])
1276
1278
1279 ea = cPersonNameEAPnl(self, -1, identity = self.__identity)
1280 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
1281 dlg.SetTitle(_('Adding new name'))
1282 if dlg.ShowModal() == wx.ID_OK:
1283 dlg.DestroyLater()
1284 return True
1285 dlg.DestroyLater()
1286 return False
1287
1297
1299
1300 if len(self.__identity.get_names()) == 1:
1301 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1302 return False
1303
1304 if name['active_name']:
1305 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the active name of a person.'), beep = True)
1306 return False
1307
1308 go_ahead = gmGuiHelpers.gm_show_question (
1309 _( 'It is often advisable to keep old names around and\n'
1310 'just create a new "currently active" name.\n'
1311 '\n'
1312 'This allows finding the patient by both the old\n'
1313 'and the new name (think before/after marriage).\n'
1314 '\n'
1315 'Do you still want to really delete\n'
1316 "this name from the patient ?"
1317 ),
1318 _('Deleting name')
1319 )
1320 if not go_ahead:
1321 return False
1322
1323 self.__identity.delete_name(name = name)
1324 return True
1325
1326
1327
1329 return self.__identity
1330
1334
1335 identity = property(_get_identity, _set_identity)
1336
1337
1339 """A list for managing a person's external IDs.
1340
1341 Does NOT act on/listen to the current patient.
1342 """
1360
1361
1362
1363 - def refresh(self, *args, **kwargs):
1380
1381
1382
1384 self._LCTRL_items.set_columns(columns = [
1385 _('ID type'),
1386 _('Value'),
1387 _('Issuer'),
1388 _('Comment')
1389 ])
1390
1392 ea = cExternalIDEditAreaPnl(self, -1)
1393 ea.id_holder = self.__identity
1394 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea)
1395 dlg.SetTitle(_('Adding new external ID'))
1396 if dlg.ShowModal() == wx.ID_OK:
1397 dlg.DestroyLater()
1398 return True
1399 dlg.DestroyLater()
1400 return False
1401
1403 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id)
1404 ea.id_holder = self.__identity
1405 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
1406 dlg.SetTitle(_('Editing external ID'))
1407 if dlg.ShowModal() == wx.ID_OK:
1408 dlg.DestroyLater()
1409 return True
1410 dlg.DestroyLater()
1411 return False
1412
1414 go_ahead = gmGuiHelpers.gm_show_question (
1415 _( 'Do you really want to delete this\n'
1416 'external ID from the patient ?'),
1417 _('Deleting external ID')
1418 )
1419 if not go_ahead:
1420 return False
1421 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1422 return True
1423
1424
1425
1427 return self.__identity
1428
1432
1433 identity = property(_get_identity, _set_identity)
1434
1435
1436
1437 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
1438
1440 """A panel for editing identity data for a person.
1441
1442 - provides access to:
1443 - identity EA
1444 - name list manager
1445 - external IDs list manager
1446
1447 Does NOT act on/listen to the current patient.
1448 """
1455
1456
1457
1459 self._PNL_names.identity = self.__identity
1460 self._PNL_ids.identity = self.__identity
1461
1462 self._PNL_identity.mode = 'new'
1463 self._PNL_identity.data = self.__identity
1464 if self.__identity is not None:
1465 self._PNL_identity.mode = 'edit'
1466 self._PNL_identity._refresh_from_existing()
1467
1468
1469
1471 return self.__identity
1472
1476
1477 identity = property(_get_identity, _set_identity)
1478
1479
1480
1484
1485
1488
1489
1490 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
1491
1500
1501
1502
1504
1505 tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.')
1506
1507 if self.__identity is None:
1508 self._TCTRL_er_contact.SetValue('')
1509 self._TCTRL_person.person = None
1510 self._TCTRL_person.SetToolTip(tt)
1511
1512 self._PRW_provider.SetText(value = '', data = None)
1513 return
1514
1515 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], ''))
1516 if self.__identity['pk_emergency_contact'] is not None:
1517 ident = gmPerson.cPerson(aPK_obj = self.__identity['pk_emergency_contact'])
1518 self._TCTRL_person.person = ident
1519 tt = '%s\n\n%s\n\n%s' % (
1520 tt,
1521 ident['description_gender'],
1522 '\n'.join([
1523 '%s: %s%s' % (
1524 c['l10n_comm_type'],
1525 c['url'],
1526 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), '', '')
1527 )
1528 for c in ident.get_comm_channels()
1529 ])
1530 )
1531 else:
1532 self._TCTRL_person.person = None
1533
1534 self._TCTRL_person.SetToolTip(tt)
1535
1536 if self.__identity['pk_primary_provider'] is None:
1537 self._PRW_provider.SetText(value = '', data = None)
1538 else:
1539 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1540
1541 self._PNL_external_care.identity = self.__identity
1542
1543
1544
1546 return self.__identity
1547
1551
1552 identity = property(_get_identity, _set_identity)
1553
1554
1555
1570
1573
1584
1592
1593
1594
1595
1597 """Notebook displaying demographics editing pages:
1598
1599 - Identity (as per Jim/Rogerio 12/2011)
1600 - Contacts (addresses, phone numbers, etc)
1601 - Social network (significant others, GP, etc)
1602
1603 Does NOT act on/listen to the current patient.
1604 """
1605
1607
1608 wx.Notebook.__init__ (
1609 self,
1610 parent = parent,
1611 id = id,
1612 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1613 name = self.__class__.__name__
1614 )
1615 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
1616
1617 self.__identity = None
1618 self.__do_layout()
1619 self.SetSelection(0)
1620
1621
1622
1624 """Populate fields in pages with data from model."""
1625 for page_idx in range(self.GetPageCount()):
1626 page = self.GetPage(page_idx)
1627 page.identity = self.__identity
1628
1629 return True
1630
1631
1632
1662
1663
1664
1666 return self.__identity
1667
1670
1671 identity = property(_get_identity, _set_identity)
1672
1673
1674
1675
1676
1677
1678
1680 """Page containing patient occupations edition fields.
1681 """
1682 - def __init__(self, parent, id, ident=None):
1683 """
1684 Creates a new instance of BasicPatDetailsPage
1685 @param parent - The parent widget
1686 @type parent - A wx.Window instance
1687 @param id - The widget id
1688 @type id - An integer
1689 """
1690 wx.Panel.__init__(self, parent, id)
1691 self.__ident = ident
1692 self.__do_layout()
1693
1695 PNL_form = wx.Panel(self, -1)
1696
1697 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1698 self.PRW_occupation = cOccupationPhraseWheel(PNL_form, -1)
1699 self.PRW_occupation.SetToolTip(_("primary occupation of the patient"))
1700
1701 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
1702 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
1703
1704
1705 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
1706 SZR_input.AddGrowableCol(1)
1707 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1708 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1709 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
1710 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
1711 PNL_form.SetSizerAndFit(SZR_input)
1712
1713
1714 SZR_main = wx.BoxSizer(wx.VERTICAL)
1715 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1716 self.SetSizer(SZR_main)
1717
1720
1721 - def refresh(self, identity=None):
1722 if identity is not None:
1723 self.__ident = identity
1724 jobs = self.__ident.get_occupations()
1725 if len(jobs) > 0:
1726 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
1727 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
1728 return True
1729
1731 if self.PRW_occupation.IsModified():
1732 new_job = self.PRW_occupation.GetValue().strip()
1733 jobs = self.__ident.get_occupations()
1734 for job in jobs:
1735 if job['l10n_occupation'] == new_job:
1736 continue
1737 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
1738 self.__ident.link_occupation(occupation = new_job)
1739 return True
1740
1741
1743 """Patient demographics plugin for main notebook.
1744
1745 Hosts another notebook with pages for Identity, Contacts, etc.
1746
1747 Acts on/listens to the currently active patient.
1748 """
1749
1755
1756
1757
1758
1759
1760
1762 """Arrange widgets."""
1763 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
1764
1765 szr_main = wx.BoxSizer(wx.VERTICAL)
1766 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
1767 self.SetSizerAndFit(szr_main)
1768
1769
1770
1772 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1773 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1774
1776 self.__patient_notebook.identity = None
1777 self.__patient_notebook.refresh()
1778
1780 self._schedule_data_reget()
1781
1782
1783
1793
1794
1795
1796 if __name__ == "__main__":
1797
1798
1800 app = wx.PyWidgetTester(size = (600, 400))
1801 app.SetWidget(cKOrganizerSchedulePnl)
1802 app.MainLoop()
1803
1805 app = wx.PyWidgetTester(size = (600, 400))
1806 widget = cPersonNamesManagerPnl(app.frame, -1)
1807 widget.identity = activate_patient()
1808 app.frame.Show(True)
1809 app.MainLoop()
1810
1812 app = wx.PyWidgetTester(size = (600, 400))
1813 widget = cPersonIDsManagerPnl(app.frame, -1)
1814 widget.identity = activate_patient()
1815 app.frame.Show(True)
1816 app.MainLoop()
1817
1819 app = wx.PyWidgetTester(size = (600, 400))
1820 widget = cPersonIdentityManagerPnl(app.frame, -1)
1821 widget.identity = activate_patient()
1822 app.frame.Show(True)
1823 app.MainLoop()
1824
1829
1831 app = wx.PyWidgetTester(size = (600, 400))
1832 widget = cPersonDemographicsEditorNb(app.frame, -1)
1833 widget.identity = activate_patient()
1834 widget.refresh()
1835 app.frame.Show(True)
1836 app.MainLoop()
1837
1846
1847 if len(sys.argv) > 1 and sys.argv[1] == 'test':
1848
1849 gmI18N.activate_locale()
1850 gmI18N.install_domain(domain='gnumed')
1851 gmPG2.get_connection()
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863 test_person_ids_pnl()
1864
1865
1866
1867
1868
1869
1870