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