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.Destroy()
78 return True
79 dlg.Destroy()
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 gmDispatcher.send(signal = 'statustext', msg = _('Must pick an image file for a new tag.'), beep = True)
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 gmDispatcher.send(signal = 'statustext', msg = _('Cannot open the image file [%s].') % self.__selected_image_file, beep = True)
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.Destroy()
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 gmDispatcher.send(signal = 'statustext', msg = _('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 exc = gmPG2.make_pg_exception_fields_unicode(exc)
1131 gmGuiHelpers.gm_show_error (
1132 aTitle = _('Adding name'),
1133 aMessage = _(
1134 'Cannot add this name to the patient !\n'
1135 '\n'
1136 ' %s'
1137 ) % exc.u_pgerror
1138 )
1139 return False
1140
1141 old_nick = self.__identity['active_name']['preferred']
1142 new_nick = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1143 if active:
1144 data['preferred'] = gmTools.coalesce(new_nick, old_nick)
1145 else:
1146 data['preferred'] = new_nick
1147 data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1148 data.save()
1149
1150 self.data = data
1151 return True
1152
1154 """The knack here is that we can only update a few fields.
1155
1156 Otherwise we need to clone the name and update that.
1157 """
1158 first = self._PRW_firstname.GetValue().strip()
1159 last = self._PRW_lastname.GetValue().strip()
1160 active = self._CHBOX_active.GetValue()
1161
1162 current_name = self.data['firstnames'].strip() + self.data['lastnames'].strip()
1163 new_name = first + last
1164
1165
1166 if new_name == current_name:
1167 self.data['active_name'] = self._CHBOX_active.GetValue()
1168 self.data['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1169 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1170 self.data.save()
1171
1172 else:
1173 try:
1174 name = self.__identity.add_name(first, last, active)
1175 except gmPG2.dbapi.IntegrityError as exc:
1176 _log.exception('cannot clone name when editing existing name')
1177 exc = gmPG2.make_pg_exception_fields_unicode(exc)
1178 gmGuiHelpers.gm_show_error (
1179 aTitle = _('Editing name'),
1180 aMessage = _(
1181 'Cannot clone a copy of this name !\n'
1182 '\n'
1183 ' %s'
1184 ) % exc.u_pgerror
1185
1186 )
1187 return False
1188 name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), '')
1189 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), '')
1190 name.save()
1191 self.data = name
1192
1193 return True
1194
1203
1210
1219
1220
1221
1223 """A list for managing a person's names.
1224
1225 Does NOT act on/listen to the current patient.
1226 """
1244
1245
1246
1247 - def refresh(self, *args, **kwargs):
1264
1265
1266
1268 self._LCTRL_items.set_columns(columns = [
1269 _('Active'),
1270 _('Lastname'),
1271 _('Firstname(s)'),
1272 _('Preferred Name'),
1273 _('Comment')
1274 ])
1275
1286
1296
1298
1299 if len(self.__identity.get_names()) == 1:
1300 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1301 return False
1302
1303 if name['active_name']:
1304 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete the active name of a person.'), beep = True)
1305 return False
1306
1307 go_ahead = gmGuiHelpers.gm_show_question (
1308 _( 'It is often advisable to keep old names around and\n'
1309 'just create a new "currently active" name.\n'
1310 '\n'
1311 'This allows finding the patient by both the old\n'
1312 'and the new name (think before/after marriage).\n'
1313 '\n'
1314 'Do you still want to really delete\n'
1315 "this name from the patient ?"
1316 ),
1317 _('Deleting name')
1318 )
1319 if not go_ahead:
1320 return False
1321
1322 self.__identity.delete_name(name = name)
1323 return True
1324
1325
1326
1328 return self.__identity
1329
1333
1334 identity = property(_get_identity, _set_identity)
1335
1336
1338 """A list for managing a person's external IDs.
1339
1340 Does NOT act on/listen to the current patient.
1341 """
1359
1360
1361
1362 - def refresh(self, *args, **kwargs):
1379
1380
1381
1383 self._LCTRL_items.set_columns(columns = [
1384 _('ID type'),
1385 _('Value'),
1386 _('Issuer'),
1387 _('Comment')
1388 ])
1389
1400
1402 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id)
1403 ea.id_holder = self.__identity
1404 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True)
1405 dlg.SetTitle(_('Editing external ID'))
1406 if dlg.ShowModal() == wx.ID_OK:
1407 dlg.Destroy()
1408 return True
1409 dlg.Destroy()
1410 return False
1411
1413 go_ahead = gmGuiHelpers.gm_show_question (
1414 _( 'Do you really want to delete this\n'
1415 'external ID from the patient ?'),
1416 _('Deleting external ID')
1417 )
1418 if not go_ahead:
1419 return False
1420 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1421 return True
1422
1423
1424
1426 return self.__identity
1427
1431
1432 identity = property(_get_identity, _set_identity)
1433
1434
1435
1436 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
1437
1439 """A panel for editing identity data for a person.
1440
1441 - provides access to:
1442 - identity EA
1443 - name list manager
1444 - external IDs list manager
1445
1446 Does NOT act on/listen to the current patient.
1447 """
1454
1455
1456
1458 self._PNL_names.identity = self.__identity
1459 self._PNL_ids.identity = self.__identity
1460
1461 self._PNL_identity.mode = 'new'
1462 self._PNL_identity.data = self.__identity
1463 if self.__identity is not None:
1464 self._PNL_identity.mode = 'edit'
1465 self._PNL_identity._refresh_from_existing()
1466
1467
1468
1470 return self.__identity
1471
1475
1476 identity = property(_get_identity, _set_identity)
1477
1478
1479
1483
1484
1487
1488
1489 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
1490
1499
1500
1501
1503
1504 tt = _('Link another person in this database as the emergency contact:\n\nEnter person name part or identifier and hit <enter>.')
1505
1506 if self.__identity is None:
1507 self._TCTRL_er_contact.SetValue('')
1508 self._TCTRL_person.person = None
1509 self._TCTRL_person.SetToolTip(tt)
1510
1511 self._PRW_provider.SetText(value = '', data = None)
1512 return
1513
1514 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], ''))
1515 if self.__identity['pk_emergency_contact'] is not None:
1516 ident = gmPerson.cPerson(aPK_obj = self.__identity['pk_emergency_contact'])
1517 self._TCTRL_person.person = ident
1518 tt = '%s\n\n%s\n\n%s' % (
1519 tt,
1520 ident['description_gender'],
1521 '\n'.join([
1522 '%s: %s%s' % (
1523 c['l10n_comm_type'],
1524 c['url'],
1525 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), '', '')
1526 )
1527 for c in ident.get_comm_channels()
1528 ])
1529 )
1530 else:
1531 self._TCTRL_person.person = None
1532
1533 self._TCTRL_person.SetToolTip(tt)
1534
1535 if self.__identity['pk_primary_provider'] is None:
1536 self._PRW_provider.SetText(value = '', data = None)
1537 else:
1538 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1539
1540 self._PNL_external_care.identity = self.__identity
1541
1542
1543
1545 return self.__identity
1546
1550
1551 identity = property(_get_identity, _set_identity)
1552
1553
1554
1569
1572
1583
1591
1592
1593
1594
1596 """Notebook displaying demographics editing pages:
1597
1598 - Identity (as per Jim/Rogerio 12/2011)
1599 - Contacts (addresses, phone numbers, etc)
1600 - Social network (significant others, GP, etc)
1601
1602 Does NOT act on/listen to the current patient.
1603 """
1604
1606
1607 wx.Notebook.__init__ (
1608 self,
1609 parent = parent,
1610 id = id,
1611 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1612 name = self.__class__.__name__
1613 )
1614 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
1615
1616 self.__identity = None
1617 self.__do_layout()
1618 self.SetSelection(0)
1619
1620
1621
1623 """Populate fields in pages with data from model."""
1624 for page_idx in range(self.GetPageCount()):
1625 page = self.GetPage(page_idx)
1626 page.identity = self.__identity
1627
1628 return True
1629
1630
1631
1661
1662
1663
1665 return self.__identity
1666
1669
1670 identity = property(_get_identity, _set_identity)
1671
1672
1673
1674
1675
1676
1677
1679 """Page containing patient occupations edition fields.
1680 """
1681 - def __init__(self, parent, id, ident=None):
1682 """
1683 Creates a new instance of BasicPatDetailsPage
1684 @param parent - The parent widget
1685 @type parent - A wx.Window instance
1686 @param id - The widget id
1687 @type id - An integer
1688 """
1689 wx.Panel.__init__(self, parent, id)
1690 self.__ident = ident
1691 self.__do_layout()
1692
1694 PNL_form = wx.Panel(self, -1)
1695
1696 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1697 self.PRW_occupation = cOccupationPhraseWheel(PNL_form, -1)
1698 self.PRW_occupation.SetToolTip(_("primary occupation of the patient"))
1699
1700 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
1701 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
1702
1703
1704 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
1705 SZR_input.AddGrowableCol(1)
1706 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1707 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1708 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
1709 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
1710 PNL_form.SetSizerAndFit(SZR_input)
1711
1712
1713 SZR_main = wx.BoxSizer(wx.VERTICAL)
1714 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1715 self.SetSizer(SZR_main)
1716
1719
1720 - def refresh(self, identity=None):
1721 if identity is not None:
1722 self.__ident = identity
1723 jobs = self.__ident.get_occupations()
1724 if len(jobs) > 0:
1725 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
1726 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
1727 return True
1728
1730 if self.PRW_occupation.IsModified():
1731 new_job = self.PRW_occupation.GetValue().strip()
1732 jobs = self.__ident.get_occupations()
1733 for job in jobs:
1734 if job['l10n_occupation'] == new_job:
1735 continue
1736 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
1737 self.__ident.link_occupation(occupation = new_job)
1738 return True
1739
1740
1742 """Patient demographics plugin for main notebook.
1743
1744 Hosts another notebook with pages for Identity, Contacts, etc.
1745
1746 Acts on/listens to the currently active patient.
1747 """
1748
1754
1755
1756
1757
1758
1759
1761 """Arrange widgets."""
1762 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
1763
1764 szr_main = wx.BoxSizer(wx.VERTICAL)
1765 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
1766 self.SetSizerAndFit(szr_main)
1767
1768
1769
1771 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1772 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1773
1775 self.__patient_notebook.identity = None
1776 self.__patient_notebook.refresh()
1777
1779 self._schedule_data_reget()
1780
1781
1782
1792
1793
1794
1795 if __name__ == "__main__":
1796
1797
1799 app = wx.PyWidgetTester(size = (600, 400))
1800 app.SetWidget(cKOrganizerSchedulePnl)
1801 app.MainLoop()
1802
1804 app = wx.PyWidgetTester(size = (600, 400))
1805 widget = cPersonNamesManagerPnl(app.frame, -1)
1806 widget.identity = activate_patient()
1807 app.frame.Show(True)
1808 app.MainLoop()
1809
1811 app = wx.PyWidgetTester(size = (600, 400))
1812 widget = cPersonIDsManagerPnl(app.frame, -1)
1813 widget.identity = activate_patient()
1814 app.frame.Show(True)
1815 app.MainLoop()
1816
1818 app = wx.PyWidgetTester(size = (600, 400))
1819 widget = cPersonIdentityManagerPnl(app.frame, -1)
1820 widget.identity = activate_patient()
1821 app.frame.Show(True)
1822 app.MainLoop()
1823
1828
1830 app = wx.PyWidgetTester(size = (600, 400))
1831 widget = cPersonDemographicsEditorNb(app.frame, -1)
1832 widget.identity = activate_patient()
1833 widget.refresh()
1834 app.frame.Show(True)
1835 app.MainLoop()
1836
1845
1846 if len(sys.argv) > 1 and sys.argv[1] == 'test':
1847
1848 gmI18N.activate_locale()
1849 gmI18N.install_domain(domain='gnumed')
1850 gmPG2.get_connection()
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862 test_person_ids_pnl()
1863
1864
1865
1866
1867
1868
1869