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 try:
55 _('dummy-no-need-to-translate-but-make-epydoc-happy')
56 except NameError:
57 _ = lambda x:x
58
59
60
61
63 if tag_image is not None:
64 if tag_image['is_in_use']:
65 gmGuiHelpers.gm_show_info (
66 aTitle = _('Editing tag'),
67 aMessage = _(
68 'Cannot edit the image tag\n'
69 '\n'
70 ' "%s"\n'
71 '\n'
72 'because it is currently in use.\n'
73 ) % tag_image['l10n_description']
74 )
75 return False
76
77 ea = cTagImageEAPnl(parent, -1)
78 ea.data = tag_image
79 ea.mode = gmTools.coalesce(tag_image, 'new', 'edit')
80 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
81 dlg.SetTitle(gmTools.coalesce(tag_image, _('Adding new tag'), _('Editing tag')))
82 if dlg.ShowModal() == wx.ID_OK:
83 dlg.Destroy()
84 return True
85 dlg.Destroy()
86 return False
87
100
101
102 def edit(tag_image=None):
103 return edit_tag_image(parent = parent, tag_image = tag_image, single_entry = (tag_image is not None))
104
105
106 def delete(tag):
107 if tag['is_in_use']:
108 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this tag. It is in use.'), beep = True)
109 return False
110
111 return gmDemographicRecord.delete_tag_image(tag_image = tag['pk_tag_image'])
112
113
114 def refresh(lctrl):
115 tags = gmDemographicRecord.get_tag_images(order_by = 'l10n_description')
116 items = [ [
117 t['l10n_description'],
118 gmTools.bool2subst(t['is_in_use'], 'X', ''),
119 '%s' % t['size'],
120 t['pk_tag_image']
121 ] for t in tags ]
122 lctrl.set_string_items(items)
123 lctrl.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE])
124 lctrl.set_data(tags)
125
126
127 msg = _('\nTags with images registered with GNUmed.\n')
128
129 tag = gmListWidgets.get_choices_from_list (
130 parent = parent,
131 msg = msg,
132 caption = _('Showing tags with images.'),
133 columns = [_('Tag name'), _('In use'), _('Image size'), '#'],
134 single_selection = True,
135 new_callback = edit,
136 edit_callback = edit,
137 delete_callback = delete,
138 refresh_callback = refresh,
139 left_extra_button = (_('WWW'), _('Go to www.openclipart.org for images.'), go_to_openclipart_org)
140 )
141
142 return tag
143
144 from Gnumed.wxGladeWidgets import wxgTagImageEAPnl
145
146 -class cTagImageEAPnl(wxgTagImageEAPnl.wxgTagImageEAPnl, gmEditArea.cGenericEditAreaMixin):
147
165
166
167
169
170 valid = True
171
172 if self.mode == 'new':
173 if self.__selected_image_file is None:
174 valid = False
175 gmDispatcher.send(signal = 'statustext', msg = _('Must pick an image file for a new tag.'), beep = True)
176 self._BTN_pick_image.SetFocus()
177
178 if self.__selected_image_file is not None:
179 try:
180 open(self.__selected_image_file).close()
181 except Exception:
182 valid = False
183 self.__selected_image_file = None
184 gmDispatcher.send(signal = 'statustext', msg = _('Cannot open the image file [%s].') % self.__selected_image_file, beep = True)
185 self._BTN_pick_image.SetFocus()
186
187 if self._TCTRL_description.GetValue().strip() == '':
188 valid = False
189 self.display_tctrl_as_valid(self._TCTRL_description, False)
190 self._TCTRL_description.SetFocus()
191 else:
192 self.display_tctrl_as_valid(self._TCTRL_description, True)
193
194 return (valid is True)
195
214
216
217
218
219 dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Updating tag with image'))
220 if dbo_conn is None:
221 return False
222 dbo_conn.close()
223
224 self.data['description'] = self._TCTRL_description.GetValue().strip()
225 self.data['filename'] = self._TCTRL_filename.GetValue().strip()
226 self.data.save()
227
228 if self.__selected_image_file is not None:
229 open(self.__selected_image_file).close()
230 self.data.update_image_from_file(filename = self.__selected_image_file)
231 self.__selected_image_file = None
232
233 return True
234
236 self._TCTRL_description.SetValue('')
237 self._TCTRL_filename.SetValue('')
238 self._BMP_image.SetBitmap(bitmap = wx.Bitmap(100, 100))
239
240 self.__selected_image_file = None
241
242 self._TCTRL_description.SetFocus()
243
245 self._refresh_as_new()
246
259
260
261
273
274
289
290 def delete(tag):
291 do_delete = gmGuiHelpers.gm_show_question (
292 title = _('Deleting patient tag'),
293 question = _('Do you really want to delete this patient tag ?')
294 )
295 if not do_delete:
296 return False
297 patient.remove_tag(tag = tag['pk_identity_tag'])
298 return True
299
300 def manage_available_tags(tag):
301 manage_tag_images(parent = parent)
302 return False
303
304 msg = _('Tags of patient: %s\n') % patient['description_gender']
305
306 return gmListWidgets.get_choices_from_list (
307 parent = parent,
308 msg = msg,
309 caption = _('Showing patient tags'),
310 columns = [_('Tag'), _('Comment')],
311 single_selection = False,
312 delete_callback = delete,
313 refresh_callback = refresh,
314 left_extra_button = (_('Manage'), _('Manage available tags.'), manage_available_tags)
315 )
316
317 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
318
320
322 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
323 self._SZR_bitmaps = self.GetSizer()
324 self.__bitmaps = []
325
326 self.__context_popup = wx.Menu()
327
328 item = self.__context_popup.Append(-1, _('&Edit comment'))
329 self.Bind(wx.EVT_MENU, self.__edit_tag, item)
330
331 item = self.__context_popup.Append(-1, _('&Remove tag'))
332 self.Bind(wx.EVT_MENU, self.__remove_tag, item)
333
334
335
337
338 self.clear()
339
340 for tag in patient.get_tags(order_by = 'l10n_description'):
341 fname = tag.export_image2file()
342 if fname is None:
343 _log.warning('cannot export image data of tag [%s]', tag['l10n_description'])
344 continue
345 img = gmGuiHelpers.file2scaled_image(filename = fname, height = 20)
346 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
347 bmp.SetToolTip('%s%s' % (
348 tag['l10n_description'],
349 gmTools.coalesce(tag['comment'], '', '\n\n%s')
350 ))
351 bmp.tag = tag
352 bmp.Bind(wx.EVT_RIGHT_UP, self._on_bitmap_rightclicked)
353
354 self._SZR_bitmaps.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 1)
355 self.__bitmaps.append(bmp)
356
357 self.GetParent().Layout()
358
359
361 while len(self._SZR_bitmaps.GetChildren()) > 0:
362 self._SZR_bitmaps.Detach(0)
363
364
365 for bmp in self.__bitmaps:
366 bmp.Destroy()
367 self.__bitmaps = []
368
369
370
378
380 if self.__current_tag is None:
381 return
382
383 msg = _('Edit the comment on tag [%s]') % self.__current_tag['l10n_description']
384 comment = wx.GetTextFromUser (
385 message = msg,
386 caption = _('Editing tag comment'),
387 default_value = gmTools.coalesce(self.__current_tag['comment'], ''),
388 parent = self
389 )
390
391 if comment == '':
392 return
393
394 if comment.strip() == self.__current_tag['comment']:
395 return
396
397 if comment == ' ':
398 self.__current_tag['comment'] = None
399 else:
400 self.__current_tag['comment'] = comment.strip()
401
402 self.__current_tag.save()
403
404
405
407 self.__current_tag = evt.GetEventObject().tag
408 self.PopupMenu(self.__context_popup, pos = wx.DefaultPosition)
409 self.__current_tag = None
410
411
412
414
416
417 kwargs['message'] = _("Today's KOrganizer appointments ...")
418 kwargs['button_defs'] = [
419 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
420 {'label': ''},
421 {'label': ''},
422 {'label': ''},
423 {'label': 'KOrganizer', 'tooltip': _('Launch KOrganizer')}
424 ]
425 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
426
427 self.fname = os.path.expanduser(os.path.join(gmTools.gmPaths().tmp_dir, 'korganizer2gnumed.csv'))
428 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
429
430
434
444
446 try: os.remove(self.fname)
447 except OSError: pass
448 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
449 try:
450 csv_file = io.open(self.fname , mode = 'rt', encoding = 'utf8', errors = 'replace')
451 except IOError:
452 gmDispatcher.send(signal = 'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
453 return
454
455 csv_lines = gmTools.unicode_csv_reader (
456 csv_file,
457 delimiter = ','
458 )
459
460 self._LCTRL_items.set_columns ([
461 _('Place'),
462 _('Start'),
463 '',
464 '',
465 _('Patient'),
466 _('Comment')
467 ])
468 items = []
469 data = []
470 for line in csv_lines:
471 items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
472 data.append([line[4], line[7]])
473
474 self._LCTRL_items.set_string_items(items = items)
475 self._LCTRL_items.set_column_widths()
476 self._LCTRL_items.set_data(data = data)
477 self._LCTRL_items.patient_key = 0
478
479
480
483
484
485
486
488
489 pat = gmPerson.gmCurrentPatient()
490 curr_jobs = pat.get_occupations()
491 if len(curr_jobs) > 0:
492 old_job = curr_jobs[0]['l10n_occupation']
493 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
494 else:
495 old_job = ''
496 update = ''
497
498 msg = _(
499 'Please enter the primary occupation of the patient.\n'
500 '\n'
501 'Currently recorded:\n'
502 '\n'
503 ' %s (last updated %s)'
504 ) % (old_job, update)
505
506 new_job = wx.GetTextFromUser (
507 message = msg,
508 caption = _('Editing primary occupation'),
509 default_value = old_job,
510 parent = None
511 )
512 if new_job.strip() == '':
513 return
514
515 for job in curr_jobs:
516
517 if job['l10n_occupation'] != new_job:
518 pat.unlink_occupation(occupation = job['l10n_occupation'])
519
520 pat.link_occupation(occupation = new_job)
521
522
537
538
539
540
543
544
546
547
548 if identity['is_deleted']:
549 _log.debug('identity already deleted: %s', identity)
550 return True
551
552
553
554 prov = gmStaff.gmCurrentProvider()
555 if prov['pk_identity'] == identity['pk_identity']:
556 _log.warning('identity cannot delete itself while being logged on as staff member')
557 _log.debug('identity to delete: %s', identity)
558 _log.debug('logged on staff: %s', prov)
559 return False
560
561
562 go_ahead = gmGuiHelpers.gm_show_question (
563 _('Are you sure you really, positively want\n'
564 'to disable the following person ?\n'
565 '\n'
566 ' %s %s %s\n'
567 ' born %s\n'
568 '\n'
569 '%s\n'
570 ) % (
571 identity['firstnames'],
572 identity['lastnames'],
573 identity['gender'],
574 identity.get_formatted_dob(),
575 gmTools.bool2subst (
576 identity.is_patient,
577 _('This patient DID receive care here.'),
578 _('This person did NOT receive care here.')
579 )
580 ),
581 _('Disabling person')
582 )
583 if not go_ahead:
584 return False
585
586
587 conn = gmAuthWidgets.get_dbowner_connection (
588 procedure = _('Disabling person')
589 )
590
591 if conn is False:
592 return False
593
594 if conn is None:
595 return None
596
597
598 gmPerson.disable_identity(identity['pk_identity'])
599
600
601 from Gnumed.wxpython.gmPatSearchWidgets import set_active_patient
602 wx.CallAfter(set_active_patient, patient = prov.identity)
603
604 return True
605
606
607
608
623
625
627 query = """
628 (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
629 union
630 (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
631 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
632 mp.setThresholds(3, 5, 9)
633 gmPhraseWheel.cPhraseWheel.__init__ (
634 self,
635 *args,
636 **kwargs
637 )
638 self.SetToolTip(_("Type or select a first name (forename/Christian name/given name)."))
639 self.capitalisation_mode = gmTools.CAPS_NAMES
640 self.matcher = mp
641
643
645 query = """
646 (SELECT distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
647 union
648 (SELECT distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
649 union
650 (SELECT distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
651 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
652 mp.setThresholds(3, 5, 9)
653 gmPhraseWheel.cPhraseWheel.__init__ (
654 self,
655 *args,
656 **kwargs
657 )
658 self.SetToolTip(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
659
660
661 self.matcher = mp
662
664
666 query = "SELECT distinct title, title from dem.identity where title %(fragment_condition)s"
667 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
668 mp.setThresholds(1, 3, 9)
669 gmPhraseWheel.cPhraseWheel.__init__ (
670 self,
671 *args,
672 **kwargs
673 )
674 self.SetToolTip(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
675 self.matcher = mp
676
678 """Let user select a gender."""
679
680 _gender_map = None
681
683
684 if cGenderSelectionPhraseWheel._gender_map is None:
685 cmd = """
686 SELECT tag, l10n_label, sort_weight
687 from dem.v_gender_labels
688 order by sort_weight desc"""
689 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
690 cGenderSelectionPhraseWheel._gender_map = {}
691 for gender in rows:
692 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
693 'data': gender[idx['tag']],
694 'field_label': gender[idx['l10n_label']],
695 'list_label': gender[idx['l10n_label']],
696 'weight': gender[idx['sort_weight']]
697 }
698
699 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = list(cGenderSelectionPhraseWheel._gender_map.values()))
700 mp.setThresholds(1, 1, 3)
701
702 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
703 self.selection_only = True
704 self.matcher = mp
705 self.picklist_delay = 50
706
708
710 query = """
711 SELECT DISTINCT ON (list_label)
712 pk AS data,
713 name AS field_label,
714 name || coalesce(' (' || issuer || ')', '') as list_label
715 FROM dem.enum_ext_id_types
716 WHERE name %(fragment_condition)s
717 ORDER BY list_label
718 LIMIT 25
719 """
720 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
721 mp.setThresholds(1, 3, 5)
722 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
723 self.SetToolTip(_("Enter or select a type for the external ID."))
724 self.matcher = mp
725
730
745
746
747
748 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl
749
750 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
751 """An edit area for editing/creating external IDs.
752
753 Does NOT act on/listen to the current patient.
754 """
774
777
778
779
800
818
834
840
842 self._refresh_as_new()
843 self._PRW_issuer.SetText(self.data['issuer'])
844
850
851
852
854 """Set the issuer according to the selected type.
855
856 Matches are fetched from existing records in backend.
857 """
858 pk_curr_type = self._PRW_type.GetData()
859 if pk_curr_type is None:
860 return True
861 rows, idx = gmPG2.run_ro_queries(queries = [{
862 'cmd': "SELECT issuer FROM dem.enum_ext_id_types WHERE pk = %s",
863 'args': [pk_curr_type]
864 }])
865 if len(rows) == 0:
866 return True
867 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
868 return True
869
870
871
872
874 allow_empty_dob = gmGuiHelpers.gm_show_question (
875 _(
876 'Are you sure you want to leave this person\n'
877 'without a valid date of birth ?\n'
878 '\n'
879 'This can be useful for temporary staff members\n'
880 'but will provoke nag screens if this person\n'
881 'becomes a patient.\n'
882 ),
883 _('Validating date of birth')
884 )
885 return allow_empty_dob
886
888
889
890 if dob_prw.is_valid_timestamp(empty_is_valid = False):
891 dob = dob_prw.date
892
893 if (dob.year > 1899) and (dob < gmDateTime.pydt_now_here()):
894 return True
895
896 if dob.year < 1900:
897 msg = _(
898 'DOB: %s\n'
899 '\n'
900 'While this is a valid point in time Python does\n'
901 'not know how to deal with it.\n'
902 '\n'
903 'We suggest using January 1st 1901 instead and adding\n'
904 'the true date of birth to the patient comment.\n'
905 '\n'
906 'Sorry for the inconvenience %s'
907 ) % (dob, gmTools.u_frowning_face)
908 else:
909 msg = _(
910 'DOB: %s\n'
911 '\n'
912 'Date of birth in the future !'
913 ) % dob
914 gmGuiHelpers.gm_show_error (
915 msg,
916 _('Validating date of birth')
917 )
918 dob_prw.display_as_valid(False)
919 dob_prw.SetFocus()
920 return False
921
922
923 if dob_prw.GetValue().strip() != '':
924 dob_prw.display_as_valid(False)
925 gmDispatcher.send(signal = 'statustext', msg = _('Invalid date of birth.'))
926 dob_prw.SetFocus()
927 return False
928
929
930 dob_prw.display_as_valid(False)
931 return True
932
933
935
936 val = ctrl.GetValue().strip()
937
938 if val == '':
939 return True
940
941 converted, hours = gmTools.input2int(val[:2], 0, 23)
942 if not converted:
943 return False
944
945 converted, minutes = gmTools.input2int(val[3:5], 0, 59)
946 if not converted:
947 return False
948
949 return True
950
951
952 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
953
954 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
955 """An edit area for editing/creating title/gender/dob/dod etc."""
956
972
973
974
975
976
977
978
979
981
982 has_error = False
983
984 if self._PRW_gender.GetData() is None:
985 self._PRW_gender.SetFocus()
986 has_error = True
987
988 if self.data is not None:
989 if not _validate_dob_field(self._PRW_dob):
990 has_error = True
991
992
993 if _validate_tob_field(self._TCTRL_tob):
994 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = True)
995 else:
996 has_error = True
997 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = False)
998
999 if not self._PRW_dod.is_valid_timestamp(empty_is_valid = True):
1000 gmDispatcher.send(signal = 'statustext', msg = _('Invalid date of death.'))
1001 self._PRW_dod.SetFocus()
1002 has_error = True
1003
1004 return (has_error is False)
1005
1009
1011
1012 if self._PRW_dob.GetValue().strip() == '':
1013 if not _empty_dob_allowed():
1014 return False
1015 self.data['dob'] = None
1016 else:
1017 self.data['dob'] = self._PRW_dob.GetData()
1018 self.data['dob_is_estimated'] = self._CHBOX_estimated_dob.GetValue()
1019 val = self._TCTRL_tob.GetValue().strip()
1020 if val == '':
1021 self.data['tob'] = None
1022 else:
1023 self.data['tob'] = pydt.time(int(val[:2]), int(val[3:5]))
1024 self.data['gender'] = self._PRW_gender.GetData()
1025 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), '')
1026 self.data['deceased'] = self._PRW_dod.GetData()
1027 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1028
1029 self.data.save()
1030 return True
1031
1034
1068
1071
1072 from Gnumed.wxGladeWidgets import wxgPersonNameEAPnl
1073
1074 -class cPersonNameEAPnl(wxgPersonNameEAPnl.wxgPersonNameEAPnl, gmEditArea.cGenericEditAreaMixin):
1075 """An edit area for editing/creating names of people.
1076
1077 Does NOT act on/listen to the current patient.
1078 """
1099
1100
1101
1102
1103
1104
1105
1106
1108 validity = True
1109
1110 if self._PRW_lastname.GetValue().strip() == '':
1111 validity = False
1112 self._PRW_lastname.display_as_valid(False)
1113 self._PRW_lastname.SetFocus()
1114 else:
1115 self._PRW_lastname.display_as_valid(True)
1116
1117 if self._PRW_firstname.GetValue().strip() == '':
1118 validity = False
1119 self._PRW_firstname.display_as_valid(False)
1120 self._PRW_firstname.SetFocus()
1121 else:
1122 self._PRW_firstname.display_as_valid(True)
1123
1124 return validity
1125
1158
1160 """The knack here is that we can only update a few fields.
1161
1162 Otherwise we need to clone the name and update that.
1163 """
1164 first = self._PRW_firstname.GetValue().strip()
1165 last = self._PRW_lastname.GetValue().strip()
1166 active = self._CHBOX_active.GetValue()
1167
1168 current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip()
1169 new_name = first + last
1170
1171
1172 if new_name == current_name:
1173 self.data['active_name'] = self._CHBOX_active.GetValue()
1174 self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1175 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1176 self.data.save()
1177
1178 else:
1179 try:
1180 name = self.__identity.add_name(first, last, active)
1181 except gmPG2.dbapi.IntegrityError as exc:
1182 _log.exception('cannot clone name when editing existing name')
1183 exc = gmPG2.make_pg_exception_fields_unicode(exc)
1184 gmGuiHelpers.gm_show_error (
1185 aTitle = _('Editing name'),
1186 aMessage = _(
1187 'Cannot clone a copy of this name !\n'
1188 '\n'
1189 ' %s'
1190 ) % exc.u_pgerror
1191
1192 )
1193 return False
1194 name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1195 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1196 name.save()
1197 self.data = name
1198
1199 return True
1200
1209
1216
1225
1226
1227
1229 """A list for managing a person's names.
1230
1231 Does NOT act on/listen to the current patient.
1232 """
1250
1251
1252
1253 - def refresh(self, *args, **kwargs):
1270
1271
1272
1274 self._LCTRL_items.set_columns(columns = [
1275 _('Active'),
1276 _('Lastname'),
1277 _('Firstname(s)'),
1278 _('Preferred Name'),
1279 _('Comment')
1280 ])
1281
1292
1302
1304
1305 if len(self.__identity.get_names()) == 1:
1306 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1307 return False
1308
1309 if name['active_name']:
1310 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the active name of a person.'), beep = True)
1311 return False
1312
1313 go_ahead = gmGuiHelpers.gm_show_question (
1314 _( 'It is often advisable to keep old names around and\n'
1315 'just create a new "currently active" name.\n'
1316 '\n'
1317 'This allows finding the patient by both the old\n'
1318 'and the new name (think before/after marriage).\n'
1319 '\n'
1320 'Do you still want to really delete\n'
1321 "this name from the patient ?"
1322 ),
1323 _('Deleting name')
1324 )
1325 if not go_ahead:
1326 return False
1327
1328 self.__identity.delete_name(name = name)
1329 return True
1330
1331
1332
1334 return self.__identity
1335
1339
1340 identity = property(_get_identity, _set_identity)
1341
1342
1344 """A list for managing a person's external IDs.
1345
1346 Does NOT act on/listen to the current patient.
1347 """
1365
1366
1367
1368 - def refresh(self, *args, **kwargs):
1385
1386
1387
1389 self._LCTRL_items.set_columns(columns = [
1390 _('ID type'),
1391 _('Value'),
1392 _('Issuer'),
1393 _('Comment')
1394 ])
1395
1406
1408 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id)
1409 ea.id_holder = self.__identity
1410 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
1411 dlg.SetTitle(_('Editing external ID'))
1412 if dlg.ShowModal() == wx.ID_OK:
1413 dlg.Destroy()
1414 return True
1415 dlg.Destroy()
1416 return False
1417
1419 go_ahead = gmGuiHelpers.gm_show_question (
1420 _( 'Do you really want to delete this\n'
1421 'external ID from the patient ?'),
1422 _('Deleting external ID')
1423 )
1424 if not go_ahead:
1425 return False
1426 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1427 return True
1428
1429
1430
1432 return self.__identity
1433
1437
1438 identity = property(_get_identity, _set_identity)
1439
1440
1441
1442 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
1443
1445 """A panel for editing identity data for a person.
1446
1447 - provides access to:
1448 - identity EA
1449 - name list manager
1450 - external IDs list manager
1451
1452 Does NOT act on/listen to the current patient.
1453 """
1460
1461
1462
1464 self._PNL_names.identity = self.__identity
1465 self._PNL_ids.identity = self.__identity
1466
1467 self._PNL_identity.mode = 'new'
1468 self._PNL_identity.data = self.__identity
1469 if self.__identity is not None:
1470 self._PNL_identity.mode = 'edit'
1471 self._PNL_identity._refresh_from_existing()
1472
1473
1474
1476 return self.__identity
1477
1481
1482 identity = property(_get_identity, _set_identity)
1483
1484
1485
1489
1490
1493
1494
1495 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
1496
1505
1506
1507
1509
1510 tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.')
1511
1512 if self.__identity is None:
1513 self._TCTRL_er_contact.SetValue('')
1514 self._TCTRL_person.person = None
1515 self._TCTRL_person.SetToolTip(tt)
1516
1517 self._PRW_provider.SetText(value = '', data = None)
1518 return
1519
1520 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], ''))
1521 if self.__identity['pk_emergency_contact'] is not None:
1522 ident = gmPerson.cPerson(aPK_obj = self.__identity['pk_emergency_contact'])
1523 self._TCTRL_person.person = ident
1524 tt = '%s\n\n%s\n\n%s' % (
1525 tt,
1526 ident['description_gender'],
1527 '\n'.join([
1528 '%s: %s%s' % (
1529 c['l10n_comm_type'],
1530 c['url'],
1531 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), '', '')
1532 )
1533 for c in ident.get_comm_channels()
1534 ])
1535 )
1536 else:
1537 self._TCTRL_person.person = None
1538
1539 self._TCTRL_person.SetToolTip(tt)
1540
1541 if self.__identity['pk_primary_provider'] is None:
1542 self._PRW_provider.SetText(value = '', data = None)
1543 else:
1544 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1545
1546 self._PNL_external_care.identity = self.__identity
1547
1548
1549
1551 return self.__identity
1552
1556
1557 identity = property(_get_identity, _set_identity)
1558
1559
1560
1575
1578
1589
1597
1598
1599
1600
1602 """Notebook displaying demographics editing pages:
1603
1604 - Identity (as per Jim/Rogerio 12/2011)
1605 - Contacts (addresses, phone numbers, etc)
1606 - Social network (significant others, GP, etc)
1607
1608 Does NOT act on/listen to the current patient.
1609 """
1610
1612
1613 wx.Notebook.__init__ (
1614 self,
1615 parent = parent,
1616 id = id,
1617 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1618 name = self.__class__.__name__
1619 )
1620 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
1621
1622 self.__identity = None
1623 self.__do_layout()
1624 self.SetSelection(0)
1625
1626
1627
1629 """Populate fields in pages with data from model."""
1630 for page_idx in range(self.GetPageCount()):
1631 page = self.GetPage(page_idx)
1632 page.identity = self.__identity
1633
1634 return True
1635
1636
1637
1667
1668
1669
1671 return self.__identity
1672
1675
1676 identity = property(_get_identity, _set_identity)
1677
1678
1679
1680
1681
1682
1683
1685 """Page containing patient occupations edition fields.
1686 """
1687 - def __init__(self, parent, id, ident=None):
1688 """
1689 Creates a new instance of BasicPatDetailsPage
1690 @param parent - The parent widget
1691 @type parent - A wx.Window instance
1692 @param id - The widget id
1693 @type id - An integer
1694 """
1695 wx.Panel.__init__(self, parent, id)
1696 self.__ident = ident
1697 self.__do_layout()
1698
1700 PNL_form = wx.Panel(self, -1)
1701
1702 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1703 self.PRW_occupation = cOccupationPhraseWheel(PNL_form, -1)
1704 self.PRW_occupation.SetToolTip(_("primary occupation of the patient"))
1705
1706 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
1707 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
1708
1709
1710 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
1711 SZR_input.AddGrowableCol(1)
1712 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1713 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1714 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
1715 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
1716 PNL_form.SetSizerAndFit(SZR_input)
1717
1718
1719 SZR_main = wx.BoxSizer(wx.VERTICAL)
1720 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1721 self.SetSizer(SZR_main)
1722
1725
1726 - def refresh(self, identity=None):
1727 if identity is not None:
1728 self.__ident = identity
1729 jobs = self.__ident.get_occupations()
1730 if len(jobs) > 0:
1731 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
1732 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
1733 return True
1734
1736 if self.PRW_occupation.IsModified():
1737 new_job = self.PRW_occupation.GetValue().strip()
1738 jobs = self.__ident.get_occupations()
1739 for job in jobs:
1740 if job['l10n_occupation'] == new_job:
1741 continue
1742 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
1743 self.__ident.link_occupation(occupation = new_job)
1744 return True
1745
1746
1748 """Patient demographics plugin for main notebook.
1749
1750 Hosts another notebook with pages for Identity, Contacts, etc.
1751
1752 Acts on/listens to the currently active patient.
1753 """
1754
1760
1761
1762
1763
1764
1765
1767 """Arrange widgets."""
1768 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
1769
1770 szr_main = wx.BoxSizer(wx.VERTICAL)
1771 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
1772 self.SetSizerAndFit(szr_main)
1773
1774
1775
1777 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1778 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1779
1781 self.__patient_notebook.identity = None
1782 self.__patient_notebook.refresh()
1783
1785 self._schedule_data_reget()
1786
1787
1788
1798
1799
1800
1801 if __name__ == "__main__":
1802
1803
1805 app = wx.PyWidgetTester(size = (600, 400))
1806 app.SetWidget(cKOrganizerSchedulePnl)
1807 app.MainLoop()
1808
1810 app = wx.PyWidgetTester(size = (600, 400))
1811 widget = cPersonNamesManagerPnl(app.frame, -1)
1812 widget.identity = activate_patient()
1813 app.frame.Show(True)
1814 app.MainLoop()
1815
1817 app = wx.PyWidgetTester(size = (600, 400))
1818 widget = cPersonIDsManagerPnl(app.frame, -1)
1819 widget.identity = activate_patient()
1820 app.frame.Show(True)
1821 app.MainLoop()
1822
1824 app = wx.PyWidgetTester(size = (600, 400))
1825 widget = cPersonIdentityManagerPnl(app.frame, -1)
1826 widget.identity = activate_patient()
1827 app.frame.Show(True)
1828 app.MainLoop()
1829
1834
1836 app = wx.PyWidgetTester(size = (600, 400))
1837 widget = cPersonDemographicsEditorNb(app.frame, -1)
1838 widget.identity = activate_patient()
1839 widget.refresh()
1840 app.frame.Show(True)
1841 app.MainLoop()
1842
1851
1852 if len(sys.argv) > 1 and sys.argv[1] == 'test':
1853
1854 gmI18N.activate_locale()
1855 gmI18N.install_domain(domain='gnumed')
1856 gmPG2.get_connection()
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868 test_person_ids_pnl()
1869
1870
1871
1872
1873
1874
1875