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 dlg.DestroyLater()
80 return False
81
94
95
96 def edit(tag_image=None):
97 return edit_tag_image(parent = parent, tag_image = tag_image, single_entry = (tag_image is not None))
98
99
100 def delete(tag):
101 if tag['is_in_use']:
102 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this tag. It is in use.'), beep = True)
103 return False
104
105 return gmDemographicRecord.delete_tag_image(tag_image = tag['pk_tag_image'])
106
107
108 def refresh(lctrl):
109 tags = gmDemographicRecord.get_tag_images(order_by = 'l10n_description')
110 items = [ [
111 t['l10n_description'],
112 gmTools.bool2subst(t['is_in_use'], 'X', ''),
113 '%s' % t['size'],
114 t['pk_tag_image']
115 ] for t in tags ]
116 lctrl.set_string_items(items)
117 lctrl.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE])
118 lctrl.set_data(tags)
119
120
121 msg = _('\nTags with images registered with GNUmed.\n')
122
123 tag = gmListWidgets.get_choices_from_list (
124 parent = parent,
125 msg = msg,
126 caption = _('Showing tags with images.'),
127 columns = [_('Tag name'), _('In use'), _('Image size'), '#'],
128 single_selection = True,
129 new_callback = edit,
130 edit_callback = edit,
131 delete_callback = delete,
132 refresh_callback = refresh,
133 left_extra_button = (_('WWW'), _('Go to www.openclipart.org for images.'), go_to_openclipart_org)
134 )
135
136 return tag
137
138 from Gnumed.wxGladeWidgets import wxgTagImageEAPnl
139
140 -class cTagImageEAPnl(wxgTagImageEAPnl.wxgTagImageEAPnl, gmEditArea.cGenericEditAreaMixin):
141
159
160
161
163
164 valid = True
165
166 if self.mode == 'new':
167 if self.__selected_image_file is None:
168 valid = False
169 self.StatusText = _('Must pick an image file for a new tag.')
170 self._BTN_pick_image.SetFocus()
171
172 if self.__selected_image_file is not None:
173 try:
174 open(self.__selected_image_file).close()
175 except Exception:
176 valid = False
177 self.__selected_image_file = None
178 self.StatusText = _('Cannot open the image file [%s].')
179 self._BTN_pick_image.SetFocus()
180
181 if self._TCTRL_description.GetValue().strip() == '':
182 valid = False
183 self.display_tctrl_as_valid(self._TCTRL_description, False)
184 self._TCTRL_description.SetFocus()
185 else:
186 self.display_tctrl_as_valid(self._TCTRL_description, True)
187
188 return (valid is True)
189
208
210
211
212
213 dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Updating tag with image'))
214 if dbo_conn is None:
215 return False
216 dbo_conn.close()
217
218 self.data['description'] = self._TCTRL_description.GetValue().strip()
219 self.data['filename'] = self._TCTRL_filename.GetValue().strip()
220 self.data.save()
221
222 if self.__selected_image_file is not None:
223 open(self.__selected_image_file).close()
224 self.data.update_image_from_file(filename = self.__selected_image_file)
225 self.__selected_image_file = None
226
227 return True
228
230 self._TCTRL_description.SetValue('')
231 self._TCTRL_filename.SetValue('')
232 self._BMP_image.SetBitmap(bitmap = wx.Bitmap(100, 100))
233
234 self.__selected_image_file = None
235
236 self._TCTRL_description.SetFocus()
237
239 self._refresh_as_new()
240
253
254
255
267
268
283
284 def delete(tag):
285 do_delete = gmGuiHelpers.gm_show_question (
286 title = _('Deleting patient tag'),
287 question = _('Do you really want to delete this patient tag ?')
288 )
289 if not do_delete:
290 return False
291 patient.remove_tag(tag = tag['pk_identity_tag'])
292 return True
293
294 def manage_available_tags(tag):
295 manage_tag_images(parent = parent)
296 return False
297
298 msg = _('Tags of patient: %s\n') % patient['description_gender']
299
300 return gmListWidgets.get_choices_from_list (
301 parent = parent,
302 msg = msg,
303 caption = _('Showing patient tags'),
304 columns = [_('Tag'), _('Comment')],
305 single_selection = False,
306 delete_callback = delete,
307 refresh_callback = refresh,
308 left_extra_button = (_('Manage'), _('Manage available tags.'), manage_available_tags)
309 )
310
311 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
312
314
316 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
317 self._SZR_bitmaps = self.GetSizer()
318 self.__bitmaps = []
319
320 self.__context_popup = wx.Menu()
321
322 item = self.__context_popup.Append(-1, _('&Edit comment'))
323 self.Bind(wx.EVT_MENU, self.__edit_tag, item)
324
325 item = self.__context_popup.Append(-1, _('&Remove tag'))
326 self.Bind(wx.EVT_MENU, self.__remove_tag, item)
327
328
329
331
332 self.clear()
333
334 for tag in patient.get_tags(order_by = 'l10n_description'):
335 fname = tag.export_image2file()
336 if fname is None:
337 _log.warning('cannot export image data of tag [%s]', tag['l10n_description'])
338 continue
339 img = gmGuiHelpers.file2scaled_image(filename = fname, height = 20)
340 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
341 bmp.SetToolTip('%s%s' % (
342 tag['l10n_description'],
343 gmTools.coalesce(tag['comment'], '', '\n\n%s')
344 ))
345 bmp.tag = tag
346 bmp.Bind(wx.EVT_RIGHT_UP, self._on_bitmap_rightclicked)
347
348 self._SZR_bitmaps.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 1)
349 self.__bitmaps.append(bmp)
350
351 self.GetParent().Layout()
352
353
355 while len(self._SZR_bitmaps.GetChildren()) > 0:
356 self._SZR_bitmaps.Detach(0)
357
358
359 for bmp in self.__bitmaps:
360 bmp.DestroyLater()
361 self.__bitmaps = []
362
363
364
372
374 if self.__current_tag is None:
375 return
376
377 msg = _('Edit the comment on tag [%s]') % self.__current_tag['l10n_description']
378 comment = wx.GetTextFromUser (
379 message = msg,
380 caption = _('Editing tag comment'),
381 default_value = gmTools.coalesce(self.__current_tag['comment'], ''),
382 parent = self
383 )
384
385 if comment == '':
386 return
387
388 if comment.strip() == self.__current_tag['comment']:
389 return
390
391 if comment == ' ':
392 self.__current_tag['comment'] = None
393 else:
394 self.__current_tag['comment'] = comment.strip()
395
396 self.__current_tag.save()
397
398
399
401 self.__current_tag = evt.GetEventObject().tag
402 self.PopupMenu(self.__context_popup, pos = wx.DefaultPosition)
403 self.__current_tag = None
404
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': ''},
415 {'label': ''},
416 {'label': ''},
417 {'label': '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 = io.open(self.fname , mode = 'rt', encoding = 'utf8', errors = 'replace')
445 except IOError:
446 gmDispatcher.send(signal = '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 '',
458 '',
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
480
482
483 pat = gmPerson.gmCurrentPatient()
484 curr_jobs = pat.get_occupations()
485 if len(curr_jobs) > 0:
486 old_job = curr_jobs[0]['l10n_occupation']
487 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
488 else:
489 old_job = ''
490 update = ''
491
492 msg = _(
493 'Please enter the primary occupation of the patient.\n'
494 '\n'
495 'Currently recorded:\n'
496 '\n'
497 ' %s (last updated %s)'
498 ) % (old_job, update)
499
500 new_job = wx.GetTextFromUser (
501 message = msg,
502 caption = _('Editing primary occupation'),
503 default_value = old_job,
504 parent = None
505 )
506 if new_job.strip() == '':
507 return
508
509 for job in curr_jobs:
510
511 if job['l10n_occupation'] != new_job:
512 pat.unlink_occupation(occupation = job['l10n_occupation'])
513
514 pat.link_occupation(occupation = new_job)
515
516
531
532
533
534
537
538
540
541
542 if identity['is_deleted']:
543 _log.debug('identity already deleted: %s', identity)
544 return True
545
546
547
548 prov = gmStaff.gmCurrentProvider()
549 if prov['pk_identity'] == identity['pk_identity']:
550 _log.warning('identity cannot delete itself while being logged on as staff member')
551 _log.debug('identity to delete: %s', identity)
552 _log.debug('logged on staff: %s', prov)
553 return False
554
555
556 go_ahead = gmGuiHelpers.gm_show_question (
557 _('Are you sure you really, positively want\n'
558 'to disable the following person ?\n'
559 '\n'
560 ' %s %s %s\n'
561 ' born %s\n'
562 '\n'
563 '%s\n'
564 ) % (
565 identity['firstnames'],
566 identity['lastnames'],
567 identity['gender'],
568 identity.get_formatted_dob(),
569 gmTools.bool2subst (
570 identity.is_patient,
571 _('This patient DID receive care here.'),
572 _('This person did NOT receive care here.')
573 )
574 ),
575 _('Disabling person')
576 )
577 if not go_ahead:
578 return False
579
580
581 conn = gmAuthWidgets.get_dbowner_connection (
582 procedure = _('Disabling person')
583 )
584
585 if conn is False:
586 return False
587
588 if conn is None:
589 return None
590
591
592 gmPerson.disable_identity(identity['pk_identity'])
593
594
595 from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient
596 wx.CallAfter(set_active_patient, patient = prov.identity)
597
598 return True
599
600
601
602
617
619
621 query = """
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.SetToolTip(_("Type or select a first name (forename/Christian name/given name)."))
633 self.capitalisation_mode = gmTools.CAPS_NAMES
634 self.matcher = mp
635
637
639 query = """
640 (SELECT distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
641 union
642 (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
643 union
644 (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
645 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
646 mp.setThresholds(3, 5, 9)
647 gmPhraseWheel.cPhraseWheel.__init__ (
648 self,
649 *args,
650 **kwargs
651 )
652 self.SetToolTip(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
653
654
655 self.matcher = mp
656
658
660 query = "SELECT distinct title, title from dem.identity where title %(fragment_condition)s"
661 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
662 mp.setThresholds(1, 3, 9)
663 gmPhraseWheel.cPhraseWheel.__init__ (
664 self,
665 *args,
666 **kwargs
667 )
668 self.SetToolTip(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
669 self.matcher = mp
670
672 """Let user select a gender."""
673
674 _gender_map = None
675
677
678 if cGenderSelectionPhraseWheel._gender_map is None:
679 cmd = """
680 SELECT tag, l10n_label, sort_weight
681 from dem.v_gender_labels
682 order by sort_weight desc"""
683 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
684 cGenderSelectionPhraseWheel._gender_map = {}
685 for gender in rows:
686 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
687 'data': gender[idx['tag']],
688 'field_label': gender[idx['l10n_label']],
689 'list_label': gender[idx['l10n_label']],
690 'weight': gender[idx['sort_weight']]
691 }
692
693 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = list(cGenderSelectionPhraseWheel._gender_map.values()))
694 mp.setThresholds(1, 1, 3)
695
696 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
697 self.selection_only = True
698 self.matcher = mp
699 self.picklist_delay = 50
700
702
704 query = """
705 SELECT DISTINCT ON (list_label)
706 pk AS data,
707 name AS field_label,
708 name || coalesce(' (' || issuer || ')', '') as list_label
709 FROM dem.enum_ext_id_types
710 WHERE name %(fragment_condition)s
711 ORDER BY list_label
712 LIMIT 25
713 """
714 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
715 mp.setThresholds(1, 3, 5)
716 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
717 self.SetToolTip(_("Enter or select a type for the external ID."))
718 self.matcher = mp
719
724
739
740
741
742 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl
743
744 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
745 """An edit area for editing/creating external IDs.
746
747 Does NOT act on/listen to the current patient.
748 """
768
771
772
773
794
812
828
834
836 self._refresh_as_new()
837 self._PRW_issuer.SetText(self.data['issuer'])
838
844
845
846
848 """Set the issuer according to the selected type.
849
850 Matches are fetched from existing records in backend.
851 """
852 pk_curr_type = self._PRW_type.GetData()
853 if pk_curr_type is None:
854 return True
855 rows, idx = gmPG2.run_ro_queries(queries = [{
856 'cmd': "SELECT issuer FROM dem.enum_ext_id_types WHERE pk = %s",
857 'args': [pk_curr_type]
858 }])
859 if len(rows) == 0:
860 return True
861 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
862 return True
863
864
865
866
868 allow_empty_dob = gmGuiHelpers.gm_show_question (
869 _(
870 'Are you sure you want to leave this person\n'
871 'without a valid date of birth ?\n'
872 '\n'
873 'This can be useful for temporary staff members\n'
874 'but will provoke nag screens if this person\n'
875 'becomes a patient.\n'
876 ),
877 _('Validating date of birth')
878 )
879 return allow_empty_dob
880
882
883
884 if dob_prw.is_valid_timestamp(empty_is_valid = False):
885 dob = dob_prw.date
886
887 if (dob.year > 1899) and (dob < gmDateTime.pydt_now_here()):
888 return True
889
890 if dob.year < 1900:
891 msg = _(
892 'DOB: %s\n'
893 '\n'
894 'While this is a valid point in time Python does\n'
895 'not know how to deal with it.\n'
896 '\n'
897 'We suggest using January 1st 1901 instead and adding\n'
898 'the true date of birth to the patient comment.\n'
899 '\n'
900 'Sorry for the inconvenience %s'
901 ) % (dob, gmTools.u_frowning_face)
902 else:
903 msg = _(
904 'DOB: %s\n'
905 '\n'
906 'Date of birth in the future !'
907 ) % dob
908 gmGuiHelpers.gm_show_error (
909 msg,
910 _('Validating date of birth')
911 )
912 dob_prw.display_as_valid(False)
913 dob_prw.SetFocus()
914 return False
915
916
917 if dob_prw.GetValue().strip() != '':
918 dob_prw.display_as_valid(False)
919 gmDispatcher.send(signal = 'statustext', msg = _('Invalid date of birth.'))
920 dob_prw.SetFocus()
921 return False
922
923
924 dob_prw.display_as_valid(False)
925 return True
926
927
929
930 val = ctrl.GetValue().strip()
931
932 if val == '':
933 return True
934
935 converted, hours = gmTools.input2int(val[:2], 0, 23)
936 if not converted:
937 return False
938
939 converted, minutes = gmTools.input2int(val[3:5], 0, 59)
940 if not converted:
941 return False
942
943 return True
944
945
946 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
947
948 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
949 """An edit area for editing/creating title/gender/dob/dod etc."""
950
966
967
968
969
970
971
972
973
975
976 has_error = False
977
978 if self._PRW_gender.GetData() is None:
979 self._PRW_gender.SetFocus()
980 has_error = True
981
982 if self.data is not None:
983 if not _validate_dob_field(self._PRW_dob):
984 has_error = True
985
986
987 if _validate_tob_field(self._TCTRL_tob):
988 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = True)
989 else:
990 has_error = True
991 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = False)
992
993 if not self._PRW_dod.is_valid_timestamp(empty_is_valid = True):
994 self.StatusText = _('Invalid date of death.')
995 self._PRW_dod.SetFocus()
996 has_error = True
997
998 return (has_error is False)
999
1003
1005
1006 if self._PRW_dob.GetValue().strip() == '':
1007 if not _empty_dob_allowed():
1008 return False
1009 self.data['dob'] = None
1010 else:
1011 self.data['dob'] = self._PRW_dob.GetData()
1012 self.data['dob_is_estimated'] = self._CHBOX_estimated_dob.GetValue()
1013 val = self._TCTRL_tob.GetValue().strip()
1014 if val == '':
1015 self.data['tob'] = None
1016 else:
1017 self.data['tob'] = pydt.time(int(val[:2]), int(val[3:5]))
1018 self.data['gender'] = self._PRW_gender.GetData()
1019 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), '')
1020 self.data['deceased'] = self._PRW_dod.GetData()
1021 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1022
1023 self.data.save()
1024 return True
1025
1028
1062
1065
1066 from Gnumed.wxGladeWidgets import wxgPersonNameEAPnl
1067
1068 -class cPersonNameEAPnl(wxgPersonNameEAPnl.wxgPersonNameEAPnl, gmEditArea.cGenericEditAreaMixin):
1069 """An edit area for editing/creating names of people.
1070
1071 Does NOT act on/listen to the current patient.
1072 """
1093
1094
1095
1096
1097
1098
1099
1100
1102 validity = True
1103
1104 if self._PRW_lastname.GetValue().strip() == '':
1105 validity = False
1106 self._PRW_lastname.display_as_valid(False)
1107 self._PRW_lastname.SetFocus()
1108 else:
1109 self._PRW_lastname.display_as_valid(True)
1110
1111 if self._PRW_firstname.GetValue().strip() == '':
1112 validity = False
1113 self._PRW_firstname.display_as_valid(False)
1114 self._PRW_firstname.SetFocus()
1115 else:
1116 self._PRW_firstname.display_as_valid(True)
1117
1118 return validity
1119
1121
1122 first = self._PRW_firstname.GetValue().strip()
1123 last = self._PRW_lastname.GetValue().strip()
1124 active = self._CHBOX_active.GetValue()
1125
1126 try:
1127 data = self.__identity.add_name(first, last, active)
1128 except gmPG2.dbapi.IntegrityError as exc:
1129 _log.exception('cannot save new name')
1130 gmGuiHelpers.gm_show_error (
1131 aTitle = _('Adding name'),
1132 aMessage = _(
1133 'Cannot add this name to the patient !\n'
1134 '\n'
1135 ' %s'
1136 ) % exc.pgerror
1137 )
1138 return False
1139
1140 old_nick = self.__identity['active_name']['preferred']
1141 new_nick = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1142 if active:
1143 data['preferred'] = gmTools.coalesce(new_nick, old_nick)
1144 else:
1145 data['preferred'] = new_nick
1146 data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1147 data.save()
1148
1149 self.data = data
1150 return True
1151
1153 """The knack here is that we can only update a few fields.
1154
1155 Otherwise we need to clone the name and update that.
1156 """
1157 first = self._PRW_firstname.GetValue().strip()
1158 last = self._PRW_lastname.GetValue().strip()
1159 active = self._CHBOX_active.GetValue()
1160
1161 current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip()
1162 new_name = first + last
1163
1164
1165 if new_name == current_name:
1166 self.data['active_name'] = self._CHBOX_active.GetValue()
1167 self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1168 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1169 self.data.save()
1170
1171 else:
1172 try:
1173 name = self.__identity.add_name(first, last, active)
1174 except gmPG2.dbapi.IntegrityError as exc:
1175 _log.exception('cannot clone name when editing existing name')
1176 gmGuiHelpers.gm_show_error (
1177 aTitle = _('Editing name'),
1178 aMessage = _(
1179 'Cannot clone a copy of this name !\n'
1180 '\n'
1181 ' %s'
1182 ) % exc.pgerror
1183 )
1184 return False
1185 name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1186 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1187 name.save()
1188 self.data = name
1189
1190 return True
1191
1200
1207
1216
1217
1218
1220 """A list for managing a person's names.
1221
1222 Does NOT act on/listen to the current patient.
1223 """
1241
1242
1243
1244 - def refresh(self, *args, **kwargs):
1261
1262
1263
1265 self._LCTRL_items.set_columns(columns = [
1266 _('Active'),
1267 _('Lastname'),
1268 _('Firstname(s)'),
1269 _('Preferred Name'),
1270 _('Comment')
1271 ])
1272
1274
1275 ea = cPersonNameEAPnl(self, -1, identity = self.__identity)
1276 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
1277 dlg.SetTitle(_('Adding new name'))
1278 if dlg.ShowModal() == wx.ID_OK:
1279 dlg.DestroyLater()
1280 return True
1281 dlg.DestroyLater()
1282 return False
1283
1285 ea = cPersonNameEAPnl(self, -1, name = name)
1286 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
1287 dlg.SetTitle(_('Editing name'))
1288 if dlg.ShowModal() == wx.ID_OK:
1289 dlg.DestroyLater()
1290 return True
1291 dlg.DestroyLater()
1292 return False
1293
1295
1296 if len(self.__identity.get_names()) == 1:
1297 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1298 return False
1299
1300 if name['active_name']:
1301 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the active name of a person.'), beep = True)
1302 return False
1303
1304 go_ahead = gmGuiHelpers.gm_show_question (
1305 _( 'It is often advisable to keep old names around and\n'
1306 'just create a new "currently active" name.\n'
1307 '\n'
1308 'This allows finding the patient by both the old\n'
1309 'and the new name (think before/after marriage).\n'
1310 '\n'
1311 'Do you still want to really delete\n'
1312 "this name from the patient ?"
1313 ),
1314 _('Deleting name')
1315 )
1316 if not go_ahead:
1317 return False
1318
1319 self.__identity.delete_name(name = name)
1320 return True
1321
1322
1323
1325 return self.__identity
1326
1330
1331 identity = property(_get_identity, _set_identity)
1332
1333
1335 """A list for managing a person's external IDs.
1336
1337 Does NOT act on/listen to the current patient.
1338 """
1356
1357
1358
1359 - def refresh(self, *args, **kwargs):
1376
1377
1378
1380 self._LCTRL_items.set_columns(columns = [
1381 _('ID type'),
1382 _('Value'),
1383 _('Issuer'),
1384 _('Comment')
1385 ])
1386
1388 ea = cExternalIDEditAreaPnl(self, -1)
1389 ea.id_holder = self.__identity
1390 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea)
1391 dlg.SetTitle(_('Adding new external ID'))
1392 if dlg.ShowModal() == wx.ID_OK:
1393 dlg.DestroyLater()
1394 return True
1395 dlg.DestroyLater()
1396 return False
1397
1399 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id)
1400 ea.id_holder = self.__identity
1401 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
1402 dlg.SetTitle(_('Editing external ID'))
1403 if dlg.ShowModal() == wx.ID_OK:
1404 dlg.DestroyLater()
1405 return True
1406 dlg.DestroyLater()
1407 return False
1408
1410 go_ahead = gmGuiHelpers.gm_show_question (
1411 _( 'Do you really want to delete this\n'
1412 'external ID from the patient ?'),
1413 _('Deleting external ID')
1414 )
1415 if not go_ahead:
1416 return False
1417 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1418 return True
1419
1420
1421
1423 return self.__identity
1424
1428
1429 identity = property(_get_identity, _set_identity)
1430
1431
1432
1433 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
1434
1436 """A panel for editing identity data for a person.
1437
1438 - provides access to:
1439 - identity EA
1440 - name list manager
1441 - external IDs list manager
1442
1443 Does NOT act on/listen to the current patient.
1444 """
1451
1452
1453
1455 self._PNL_names.identity = self.__identity
1456 self._PNL_ids.identity = self.__identity
1457
1458 self._PNL_identity.mode = 'new'
1459 self._PNL_identity.data = self.__identity
1460 if self.__identity is not None:
1461 self._PNL_identity.mode = 'edit'
1462 self._PNL_identity._refresh_from_existing()
1463
1464
1465
1467 return self.__identity
1468
1472
1473 identity = property(_get_identity, _set_identity)
1474
1475
1476
1480
1481
1484
1485
1486 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
1487
1496
1497
1498
1500
1501 tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.')
1502
1503 if self.__identity is None:
1504 self._TCTRL_er_contact.SetValue('')
1505 self._TCTRL_person.person = None
1506 self._TCTRL_person.SetToolTip(tt)
1507
1508 self._PRW_provider.SetText(value = '', data = None)
1509 return
1510
1511 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], ''))
1512 if self.__identity['pk_emergency_contact'] is not None:
1513 ident = gmPerson.cPerson(aPK_obj = self.__identity['pk_emergency_contact'])
1514 self._TCTRL_person.person = ident
1515 tt = '%s\n\n%s\n\n%s' % (
1516 tt,
1517 ident['description_gender'],
1518 '\n'.join([
1519 '%s: %s%s' % (
1520 c['l10n_comm_type'],
1521 c['url'],
1522 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), '', '')
1523 )
1524 for c in ident.get_comm_channels()
1525 ])
1526 )
1527 else:
1528 self._TCTRL_person.person = None
1529
1530 self._TCTRL_person.SetToolTip(tt)
1531
1532 if self.__identity['pk_primary_provider'] is None:
1533 self._PRW_provider.SetText(value = '', data = None)
1534 else:
1535 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1536
1537 self._PNL_external_care.identity = self.__identity
1538
1539
1540
1542 return self.__identity
1543
1547
1548 identity = property(_get_identity, _set_identity)
1549
1550
1551
1566
1569
1580
1588
1589
1590
1591
1593 """Notebook displaying demographics editing pages:
1594
1595 - Identity (as per Jim/Rogerio 12/2011)
1596 - Contacts (addresses, phone numbers, etc)
1597 - Social network (significant others, GP, etc)
1598
1599 Does NOT act on/listen to the current patient.
1600 """
1601
1603
1604 wx.Notebook.__init__ (
1605 self,
1606 parent = parent,
1607 id = id,
1608 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1609 name = self.__class__.__name__
1610 )
1611 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
1612
1613 self.__identity = None
1614 self.__do_layout()
1615 self.SetSelection(0)
1616
1617
1618
1620 """Populate fields in pages with data from model."""
1621 for page_idx in range(self.GetPageCount()):
1622 page = self.GetPage(page_idx)
1623 page.identity = self.__identity
1624
1625 return True
1626
1627
1628
1658
1659
1660
1662 return self.__identity
1663
1666
1667 identity = property(_get_identity, _set_identity)
1668
1669
1670
1671
1672
1673
1674
1676 """Page containing patient occupations edition fields.
1677 """
1678 - def __init__(self, parent, id, ident=None):
1679 """
1680 Creates a new instance of BasicPatDetailsPage
1681 @param parent - The parent widget
1682 @type parent - A wx.Window instance
1683 @param id - The widget id
1684 @type id - An integer
1685 """
1686 wx.Panel.__init__(self, parent, id)
1687 self.__ident = ident
1688 self.__do_layout()
1689
1691 PNL_form = wx.Panel(self, -1)
1692
1693 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1694 self.PRW_occupation = cOccupationPhraseWheel(PNL_form, -1)
1695 self.PRW_occupation.SetToolTip(_("primary occupation of the patient"))
1696
1697 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
1698 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
1699
1700
1701 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
1702 SZR_input.AddGrowableCol(1)
1703 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1704 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1705 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
1706 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
1707 PNL_form.SetSizerAndFit(SZR_input)
1708
1709
1710 SZR_main = wx.BoxSizer(wx.VERTICAL)
1711 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1712 self.SetSizer(SZR_main)
1713
1716
1717 - def refresh(self, identity=None):
1718 if identity is not None:
1719 self.__ident = identity
1720 jobs = self.__ident.get_occupations()
1721 if len(jobs) > 0:
1722 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
1723 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
1724 return True
1725
1727 if self.PRW_occupation.IsModified():
1728 new_job = self.PRW_occupation.GetValue().strip()
1729 jobs = self.__ident.get_occupations()
1730 for job in jobs:
1731 if job['l10n_occupation'] == new_job:
1732 continue
1733 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
1734 self.__ident.link_occupation(occupation = new_job)
1735 return True
1736
1737
1739 """Patient demographics plugin for main notebook.
1740
1741 Hosts another notebook with pages for Identity, Contacts, etc.
1742
1743 Acts on/listens to the currently active patient.
1744 """
1745
1751
1752
1753
1754
1755
1756
1758 """Arrange widgets."""
1759 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
1760
1761 szr_main = wx.BoxSizer(wx.VERTICAL)
1762 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
1763 self.SetSizerAndFit(szr_main)
1764
1765
1766
1768 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1769 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1770
1772 self.__patient_notebook.identity = None
1773 self.__patient_notebook.refresh()
1774
1776 self._schedule_data_reget()
1777
1778
1779
1789
1790
1791
1792 if __name__ == "__main__":
1793
1794
1796 app = wx.PyWidgetTester(size = (600, 400))
1797 app.SetWidget(cKOrganizerSchedulePnl)
1798 app.MainLoop()
1799
1801 app = wx.PyWidgetTester(size = (600, 400))
1802 widget = cPersonNamesManagerPnl(app.frame, -1)
1803 widget.identity = activate_patient()
1804 app.frame.Show(True)
1805 app.MainLoop()
1806
1808 app = wx.PyWidgetTester(size = (600, 400))
1809 widget = cPersonIDsManagerPnl(app.frame, -1)
1810 widget.identity = activate_patient()
1811 app.frame.Show(True)
1812 app.MainLoop()
1813
1815 app = wx.PyWidgetTester(size = (600, 400))
1816 widget = cPersonIdentityManagerPnl(app.frame, -1)
1817 widget.identity = activate_patient()
1818 app.frame.Show(True)
1819 app.MainLoop()
1820
1825
1827 app = wx.PyWidgetTester(size = (600, 400))
1828 widget = cPersonDemographicsEditorNb(app.frame, -1)
1829 widget.identity = activate_patient()
1830 widget.refresh()
1831 app.frame.Show(True)
1832 app.MainLoop()
1833
1842
1843 if len(sys.argv) > 1 and sys.argv[1] == 'test':
1844
1845 gmI18N.activate_locale()
1846 gmI18N.install_domain(domain='gnumed')
1847 gmPG2.get_connection()
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859 test_person_ids_pnl()
1860
1861
1862
1863
1864
1865
1866