1 """Widgets dealing with patient demographics."""
2
3 __version__ = "$Revision: 1.175 $"
4 __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>"
5 __license__ = 'GPL (details at http://www.gnu.org)'
6
7
8 import sys
9 import sys
10 import codecs
11 import re as regex
12 import logging
13 import webbrowser
14 import os
15
16
17 import wx
18 import wx.wizard
19 import wx.lib.imagebrowser as wx_imagebrowser
20 import wx.lib.statbmp as wx_genstatbmp
21
22
23
24 if __name__ == '__main__':
25 sys.path.insert(0, '../../')
26 from Gnumed.pycommon import gmDispatcher, gmI18N, gmMatchProvider, gmPG2, gmTools, gmCfg
27 from Gnumed.pycommon import gmDateTime, gmShellAPI
28
29 from Gnumed.business import gmDemographicRecord
30 from Gnumed.business import gmPersonSearch
31 from Gnumed.business import gmSurgery
32 from Gnumed.business import gmPerson
33
34 from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmDateTimeInput
35 from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea
36 from Gnumed.wxpython import gmAuthWidgets, gmPersonContactWidgets
37
38
39
40 _log = logging.getLogger('gm.ui')
41
42
43 try:
44 _('dummy-no-need-to-translate-but-make-epydoc-happy')
45 except NameError:
46 _ = lambda x:x
47
48
49
50
52 if tag_image is not None:
53 if tag_image['is_in_use']:
54 gmGuiHelpers.gm_show_info (
55 aTitle = _('Editing tag'),
56 aMessage = _(
57 'Cannot edit the image tag\n'
58 '\n'
59 ' "%s"\n'
60 '\n'
61 'because it is currently in use.\n'
62 ) % tag_image['l10n_description']
63 )
64 return False
65
66 ea = cTagImageEAPnl(parent = parent, id = -1)
67 ea.data = tag_image
68 ea.mode = gmTools.coalesce(tag_image, 'new', 'edit')
69 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
70 dlg.SetTitle(gmTools.coalesce(tag_image, _('Adding new tag'), _('Editing tag')))
71 if dlg.ShowModal() == wx.ID_OK:
72 dlg.Destroy()
73 return True
74 dlg.Destroy()
75 return False
76
78
79 if parent is None:
80 parent = wx.GetApp().GetTopWindow()
81
82 def go_to_openclipart_org(tag_image):
83 webbrowser.open (
84 url = u'http://www.openclipart.org',
85 new = False,
86 autoraise = True
87 )
88 webbrowser.open (
89 url = u'http://www.google.com',
90 new = False,
91 autoraise = True
92 )
93 return True
94
95 def edit(tag_image=None):
96 return edit_tag_image(parent = parent, tag_image = tag_image, single_entry = (tag_image is not None))
97
98 def delete(tag):
99 if tag['is_in_use']:
100 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this tag. It is in use.'), beep = True)
101 return False
102
103 return gmDemographicRecord.delete_tag_image(tag_image = tag['pk_tag_image'])
104
105 def refresh(lctrl):
106 tags = gmDemographicRecord.get_tag_images(order_by = u'l10n_description')
107 items = [ [
108 t['l10n_description'],
109 gmTools.bool2subst(t['is_in_use'], u'X', u''),
110 u'%s' % t['size'],
111 t['pk_tag_image']
112 ] for t in tags ]
113 lctrl.set_string_items(items)
114 lctrl.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE])
115 lctrl.set_data(tags)
116
117 msg = _('\nTags with images registered with GNUmed.\n')
118
119 tag = gmListWidgets.get_choices_from_list (
120 parent = parent,
121 msg = msg,
122 caption = _('Showing tags with images.'),
123 columns = [_('Tag name'), _('In use'), _('Image size'), u'#'],
124 single_selection = True,
125 new_callback = edit,
126 edit_callback = edit,
127 delete_callback = delete,
128 refresh_callback = refresh,
129 left_extra_button = (_('WWW'), _('Go to www.openclipart.org for images.'), go_to_openclipart_org)
130 )
131
132 return tag
133
134 from Gnumed.wxGladeWidgets import wxgTagImageEAPnl
135
136 -class cTagImageEAPnl(wxgTagImageEAPnl.wxgTagImageEAPnl, gmEditArea.cGenericEditAreaMixin):
137
155
156
157
159
160 valid = True
161
162 if self.mode == u'new':
163 if self.__selected_image_file is None:
164 valid = False
165 gmDispatcher.send(signal = 'statustext', msg = _('Must pick an image file for a new tag.'), beep = True)
166 self._BTN_pick_image.SetFocus()
167
168 if self.__selected_image_file is not None:
169 try:
170 open(self.__selected_image_file).close()
171 except StandardError:
172 valid = False
173 self.__selected_image_file = None
174 gmDispatcher.send(signal = 'statustext', msg = _('Cannot open the image file [%s].') % self.__selected_image_file, beep = True)
175 self._BTN_pick_image.SetFocus()
176
177 if self._TCTRL_description.GetValue().strip() == u'':
178 valid = False
179 self.display_tctrl_as_valid(self._TCTRL_description, False)
180 self._TCTRL_description.SetFocus()
181 else:
182 self.display_tctrl_as_valid(self._TCTRL_description, True)
183
184 return (valid is True)
185
198
200
201 self.data['description'] = self._TCTRL_description.GetValue().strip()
202 self.data['filename'] = self._TCTRL_filename.GetValue().strip()
203 self.data.save()
204
205 if self.__selected_image_file is not None:
206 open(self.__selected_image_file).close()
207 self.data.update_image_from_file(filename = self.__selected_image_file)
208 self.__selected_image_file = None
209
210 return True
211
213 self._TCTRL_description.SetValue(u'')
214 self._TCTRL_filename.SetValue(u'')
215 self._BMP_image.SetBitmap(bitmap = wx.EmptyBitmap(100, 100))
216
217 self.__selected_image_file = None
218
219 self._TCTRL_description.SetFocus()
220
222 self._refresh_as_new()
223
236
237
238
250
251
252 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
253
255
257 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
258 self._SZR_bitmaps = self.GetSizer()
259 self.__bitmaps = []
260
261 self.__context_popup = wx.Menu()
262
263 item = self.__context_popup.Append(-1, _('&Edit comment'))
264 self.Bind(wx.EVT_MENU, self.__edit_tag, item)
265
266 item = self.__context_popup.Append(-1, _('&Remove tag'))
267 self.Bind(wx.EVT_MENU, self.__remove_tag, item)
268
269
270
272
273 self.clear()
274
275 for tag in patient.get_tags(order_by = u'l10n_description'):
276 fname = tag.export_image2file()
277 if fname is None:
278 _log.warning('cannot export image data of tag [%s]', tag['l10n_description'])
279 continue
280 img = gmGuiHelpers.file2scaled_image(filename = fname, height = 20)
281 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
282 bmp.SetToolTipString(u'%s%s' % (
283 tag['l10n_description'],
284 gmTools.coalesce(tag['comment'], u'', u'\n\n%s')
285 ))
286 bmp.tag = tag
287 bmp.Bind(wx.EVT_RIGHT_UP, self._on_bitmap_rightclicked)
288
289 self._SZR_bitmaps.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 1)
290 self.__bitmaps.append(bmp)
291
292 self.GetParent().Layout()
293
295 while self._SZR_bitmaps.Detach(0):
296 pass
297 for bmp in self.__bitmaps:
298 bmp.Destroy()
299 self.__bitmaps = []
300
301
302
310
312 if self.__current_tag is None:
313 return
314
315 msg = _('Edit the comment on tag [%s]') % self.__current_tag['l10n_description']
316 comment = wx.GetTextFromUser (
317 message = msg,
318 caption = _('Editing tag comment'),
319 default_value = gmTools.coalesce(self.__current_tag['comment'], u''),
320 parent = self
321 )
322
323 if comment == u'':
324 return
325
326 if comment.strip() == self.__current_tag['comment']:
327 return
328
329 if comment == u' ':
330 self.__current_tag['comment'] = None
331 else:
332 self.__current_tag['comment'] = comment.strip()
333
334 self.__current_tag.save()
335
336
337
339 self.__current_tag = evt.GetEventObject().tag
340 self.PopupMenu(self.__context_popup, pos = wx.DefaultPosition)
341 self.__current_tag = None
342
343
345
347
348 kwargs['message'] = _("Today's KOrganizer appointments ...")
349 kwargs['button_defs'] = [
350 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
351 {'label': u''},
352 {'label': u''},
353 {'label': u''},
354 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')}
355 ]
356 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
357
358 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv'))
359 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
360
361
365
375
377 try: os.remove(self.fname)
378 except OSError: pass
379 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
380 try:
381 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace')
382 except IOError:
383 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
384 return
385
386 csv_lines = gmTools.unicode_csv_reader (
387 csv_file,
388 delimiter = ','
389 )
390
391 self._LCTRL_items.set_columns ([
392 _('Place'),
393 _('Start'),
394 u'',
395 u'',
396 _('Patient'),
397 _('Comment')
398 ])
399 items = []
400 data = []
401 for line in csv_lines:
402 items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
403 data.append([line[4], line[7]])
404
405 self._LCTRL_items.set_string_items(items = items)
406 self._LCTRL_items.set_column_widths()
407 self._LCTRL_items.set_data(data = data)
408 self._LCTRL_items.patient_key = 0
409
410
411
414
415
416
418
419 pat = gmPerson.gmCurrentPatient()
420 curr_jobs = pat.get_occupations()
421 if len(curr_jobs) > 0:
422 old_job = curr_jobs[0]['l10n_occupation']
423 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
424 else:
425 old_job = u''
426 update = u''
427
428 msg = _(
429 'Please enter the primary occupation of the patient.\n'
430 '\n'
431 'Currently recorded:\n'
432 '\n'
433 ' %s (last updated %s)'
434 ) % (old_job, update)
435
436 new_job = wx.GetTextFromUser (
437 message = msg,
438 caption = _('Editing primary occupation'),
439 default_value = old_job,
440 parent = None
441 )
442 if new_job.strip() == u'':
443 return
444
445 for job in curr_jobs:
446
447 if job['l10n_occupation'] != new_job:
448 pat.unlink_occupation(occupation = job['l10n_occupation'])
449
450 pat.link_occupation(occupation = new_job)
451
452
467
468
469
470
472
473 go_ahead = gmGuiHelpers.gm_show_question (
474 _('Are you sure you really, positively want\n'
475 'to disable the following person ?\n'
476 '\n'
477 ' %s %s %s\n'
478 ' born %s\n'
479 '\n'
480 '%s\n'
481 ) % (
482 identity['firstnames'],
483 identity['lastnames'],
484 identity['gender'],
485 identity['dob'],
486 gmTools.bool2subst (
487 identity.is_patient,
488 _('This patient DID receive care.'),
489 _('This person did NOT receive care.')
490 )
491 ),
492 _('Disabling person')
493 )
494 if not go_ahead:
495 return True
496
497
498 conn = gmAuthWidgets.get_dbowner_connection (
499 procedure = _('Disabling patient')
500 )
501
502 if conn is False:
503 return True
504
505 if conn is None:
506 return False
507
508
509 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}])
510
511 return True
512
513
514
515
530
532
534 query = u"""
535 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
536 union
537 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
538 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
539 mp.setThresholds(3, 5, 9)
540 gmPhraseWheel.cPhraseWheel.__init__ (
541 self,
542 *args,
543 **kwargs
544 )
545 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name)."))
546 self.capitalisation_mode = gmTools.CAPS_NAMES
547 self.matcher = mp
548
550
552 query = u"""
553 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
554 union
555 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
556 union
557 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
558 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
559 mp.setThresholds(3, 5, 9)
560 gmPhraseWheel.cPhraseWheel.__init__ (
561 self,
562 *args,
563 **kwargs
564 )
565 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
566
567
568 self.matcher = mp
569
571
573 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s"
574 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
575 mp.setThresholds(1, 3, 9)
576 gmPhraseWheel.cPhraseWheel.__init__ (
577 self,
578 *args,
579 **kwargs
580 )
581 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
582 self.matcher = mp
583
585 """Let user select a gender."""
586
587 _gender_map = None
588
590
591 if cGenderSelectionPhraseWheel._gender_map is None:
592 cmd = u"""
593 select tag, l10n_label, sort_weight
594 from dem.v_gender_labels
595 order by sort_weight desc"""
596 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
597 cGenderSelectionPhraseWheel._gender_map = {}
598 for gender in rows:
599 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
600 'data': gender[idx['tag']],
601 'label': gender[idx['l10n_label']],
602 'weight': gender[idx['sort_weight']]
603 }
604
605 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values())
606 mp.setThresholds(1, 1, 3)
607
608 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
609 self.selection_only = True
610 self.matcher = mp
611 self.picklist_delay = 50
612
614
616 query = u"""
617 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label
618 from dem.enum_ext_id_types
619 where name %%(fragment_condition)s
620 order by label limit 25""" % _('issued by')
621 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
622 mp.setThresholds(1, 3, 5)
623 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
624 self.SetToolTipString(_("Enter or select a type for the external ID."))
625 self.matcher = mp
626
641
642
643
644 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl
645
647 """An edit area for editing/creating external IDs.
648
649 Does NOT act on/listen to the current patient.
650 """
666
667
668
670 if ext_id is not None:
671 self.ext_id = ext_id
672
673 if self.ext_id is not None:
674 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type'])
675 self._TCTRL_value.SetValue(self.ext_id['value'])
676 self._PRW_issuer.SetText(self.ext_id['issuer'])
677 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
678
679
680
681
683
684 if not self.__valid_for_save():
685 return False
686
687
688 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0]
689
690
691 if self.ext_id is None:
692 self.identity.add_external_id (
693 type_name = type,
694 value = self._TCTRL_value.GetValue().strip(),
695 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
696 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
697 )
698
699 else:
700 self.identity.update_external_id (
701 pk_id = self.ext_id['pk_id'],
702 type = type,
703 value = self._TCTRL_value.GetValue().strip(),
704 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
705 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
706 )
707
708 return True
709
710
711
714
716 """Set the issuer according to the selected type.
717
718 Matches are fetched from existing records in backend.
719 """
720 pk_curr_type = self._PRW_type.GetData()
721 if pk_curr_type is None:
722 return True
723 rows, idx = gmPG2.run_ro_queries(queries = [{
724 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s",
725 'args': [pk_curr_type]
726 }])
727 if len(rows) == 0:
728 return True
729 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
730 return True
731
733
734 no_errors = True
735
736
737
738
739 if self._PRW_type.GetValue().strip() == u'':
740 self._PRW_type.SetBackgroundColour('pink')
741 self._PRW_type.SetFocus()
742 self._PRW_type.Refresh()
743 else:
744 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
745 self._PRW_type.Refresh()
746
747 if self._TCTRL_value.GetValue().strip() == u'':
748 self._TCTRL_value.SetBackgroundColour('pink')
749 self._TCTRL_value.SetFocus()
750 self._TCTRL_value.Refresh()
751 no_errors = False
752 else:
753 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
754 self._TCTRL_value.Refresh()
755
756 return no_errors
757
758 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
759
760 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
761 """An edit area for editing/creating title/gender/dob/dod etc."""
762
778
779
780
781
782
783
784
785
787
788 has_error = False
789
790 if self._PRW_gender.GetData() is None:
791 self._PRW_gender.SetFocus()
792 has_error = True
793
794 if not self._PRW_dob.is_valid_timestamp():
795 val = self._PRW_dob.GetValue().strip()
796 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
797 self._PRW_dob.SetBackgroundColour('pink')
798 self._PRW_dob.Refresh()
799 self._PRW_dob.SetFocus()
800 has_error = True
801 else:
802 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
803 self._PRW_dob.Refresh()
804
805 if not self._DP_dod.is_valid_timestamp(allow_none = True, invalid_as_none = True):
806 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
807 self._DP_dod.SetFocus()
808 has_error = True
809
810 return (has_error is False)
811
815
831
834
850
853
854
855 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl
856
858 """An edit area for editing/creating name/gender/dob.
859
860 Does NOT act on/listen to the current patient.
861 """
872
873
874
891
892
893
894
896
897 if not self.__valid_for_save():
898 return False
899
900 self.__identity['gender'] = self._PRW_gender.GetData()
901 if self._PRW_dob.GetValue().strip() == u'':
902 self.__identity['dob'] = None
903 else:
904 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt()
905 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
906 self.__identity['deceased'] = self._PRW_dod.GetData()
907 self.__identity.save_payload()
908
909 active = self._CHBOX_active.GetValue()
910 first = self._PRW_firstname.GetValue().strip()
911 last = self._PRW_lastname.GetValue().strip()
912 old_nick = self.__name['preferred']
913
914
915 old_name = self.__name['firstnames'] + self.__name['lastnames']
916 if (first + last) != old_name:
917 self.__name = self.__identity.add_name(first, last, active)
918
919 self.__name['active_name'] = active
920 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
921 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
922
923 self.__name.save_payload()
924
925 return True
926
927
928
931
933 """Set the gender according to entered firstname.
934
935 Matches are fetched from existing records in backend.
936 """
937 firstname = self._PRW_firstname.GetValue().strip()
938 if firstname == u'':
939 return True
940 rows, idx = gmPG2.run_ro_queries(queries = [{
941 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
942 'args': [firstname]
943 }])
944 if len(rows) == 0:
945 return True
946 wx.CallAfter(self._PRW_gender.SetData, rows[0][0])
947 return True
948
949
950
994
995
996
998 """A list for managing a person's names.
999
1000 Does NOT act on/listen to the current patient.
1001 """
1019
1020
1021
1022 - def refresh(self, *args, **kwargs):
1039
1040
1041
1043 self._LCTRL_items.set_columns(columns = [
1044 _('Active'),
1045 _('Lastname'),
1046 _('Firstname(s)'),
1047 _('Preferred Name'),
1048 _('Comment')
1049 ])
1050 self._BTN_edit.SetLabel(_('Clone and &edit'))
1051
1061
1071
1073
1074 if len(self.__identity.get_names()) == 1:
1075 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1076 return False
1077
1078 go_ahead = gmGuiHelpers.gm_show_question (
1079 _( 'It is often advisable to keep old names around and\n'
1080 'just create a new "currently active" name.\n'
1081 '\n'
1082 'This allows finding the patient by both the old\n'
1083 'and the new name (think before/after marriage).\n'
1084 '\n'
1085 'Do you still want to really delete\n'
1086 "this name from the patient ?"
1087 ),
1088 _('Deleting name')
1089 )
1090 if not go_ahead:
1091 return False
1092
1093 self.__identity.delete_name(name = name)
1094 return True
1095
1096
1097
1099 return self.__identity
1100
1104
1105 identity = property(_get_identity, _set_identity)
1106
1108 """A list for managing a person's external IDs.
1109
1110 Does NOT act on/listen to the current patient.
1111 """
1129
1130
1131
1132 - def refresh(self, *args, **kwargs):
1149
1150
1151
1153 self._LCTRL_items.set_columns(columns = [
1154 _('ID type'),
1155 _('Value'),
1156 _('Issuer'),
1157 _('Comment')
1158 ])
1159
1170
1181
1183 go_ahead = gmGuiHelpers.gm_show_question (
1184 _( 'Do you really want to delete this\n'
1185 'external ID from the patient ?'),
1186 _('Deleting external ID')
1187 )
1188 if not go_ahead:
1189 return False
1190 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1191 return True
1192
1193
1194
1196 return self.__identity
1197
1201
1202 identity = property(_get_identity, _set_identity)
1203
1204
1205
1206 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
1207
1209 """A panel for editing identity data for a person.
1210
1211 - provides access to:
1212 - name
1213 - external IDs
1214
1215 Does NOT act on/listen to the current patient.
1216 """
1223
1224
1225
1227 self._PNL_names.identity = self.__identity
1228 self._PNL_ids.identity = self.__identity
1229
1230 self._PNL_identity.mode = 'new'
1231 self._PNL_identity.data = self.__identity
1232 if self.__identity is not None:
1233 self._PNL_identity.mode = 'edit'
1234
1235
1236
1238 return self.__identity
1239
1243
1244 identity = property(_get_identity, _set_identity)
1245
1246
1247
1251
1254
1255
1256 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
1257
1266
1267
1268
1270
1271 tt = _("Link another person in this database as the emergency contact.")
1272
1273 if self.__identity is None:
1274 self._TCTRL_er_contact.SetValue(u'')
1275 self._TCTRL_person.person = None
1276 self._TCTRL_person.SetToolTipString(tt)
1277
1278 self._PRW_provider.SetText(value = u'', data = None)
1279 return
1280
1281 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u''))
1282 if self.__identity['pk_emergency_contact'] is not None:
1283 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact'])
1284 self._TCTRL_person.person = ident
1285 tt = u'%s\n\n%s\n\n%s' % (
1286 tt,
1287 ident['description_gender'],
1288 u'\n'.join([
1289 u'%s: %s%s' % (
1290 c['l10n_comm_type'],
1291 c['url'],
1292 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'')
1293 )
1294 for c in ident.get_comm_channels()
1295 ])
1296 )
1297 else:
1298 self._TCTRL_person.person = None
1299
1300 self._TCTRL_person.SetToolTipString(tt)
1301
1302 if self.__identity['pk_primary_provider'] is None:
1303 self._PRW_provider.SetText(value = u'', data = None)
1304 else:
1305 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1306
1307
1308
1310 return self.__identity
1311
1315
1316 identity = property(_get_identity, _set_identity)
1317
1318
1319
1333
1344
1352
1353
1354
1356
1357 dbcfg = gmCfg.cCfgSQL()
1358
1359 def_region = dbcfg.get2 (
1360 option = u'person.create.default_region',
1361 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1362 bias = u'user'
1363 )
1364 def_country = None
1365
1366 if def_region is None:
1367 def_country = dbcfg.get2 (
1368 option = u'person.create.default_country',
1369 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1370 bias = u'user'
1371 )
1372 else:
1373 countries = gmDemographicRecord.get_country_for_region(region = def_region)
1374 if len(countries) == 1:
1375 def_country = countries[0]['l10n_country']
1376
1377 if parent is None:
1378 parent = wx.GetApp().GetTopWindow()
1379
1380 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
1381 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
1382 dlg.SetTitle(_('Adding new person'))
1383 ea._PRW_lastname.SetFocus()
1384 result = dlg.ShowModal()
1385 pat = ea.data
1386 dlg.Destroy()
1387
1388 if result != wx.ID_OK:
1389 return False
1390
1391 _log.debug('created new person [%s]', pat.ID)
1392
1393 if activate:
1394 from Gnumed.wxpython import gmPatSearchWidgets
1395 gmPatSearchWidgets.set_active_patient(patient = pat)
1396
1397 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
1398
1399 return True
1400
1401 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
1402
1403 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1404
1406
1407 try:
1408 self.default_region = kwargs['region']
1409 del kwargs['region']
1410 except KeyError:
1411 self.default_region = None
1412
1413 try:
1414 self.default_country = kwargs['country']
1415 del kwargs['country']
1416 except KeyError:
1417 self.default_country = None
1418
1419 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
1420 gmEditArea.cGenericEditAreaMixin.__init__(self)
1421
1422 self.mode = 'new'
1423 self.data = None
1424 self._address = None
1425
1426 self.__init_ui()
1427 self.__register_interests()
1428
1429
1430
1432 self._PRW_lastname.final_regex = '.+'
1433 self._PRW_firstnames.final_regex = '.+'
1434 self._PRW_address_searcher.selection_only = False
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445 if self.default_country is not None:
1446 self._PRW_country.SetText(value = self.default_country)
1447
1448 if self.default_region is not None:
1449 self._PRW_region.SetText(value = self.default_region)
1450
1452
1453 adr = self._PRW_address_searcher.get_address()
1454 if adr is None:
1455 return True
1456
1457 if ctrl.GetValue().strip() != adr[field]:
1458 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
1459 return True
1460
1461 return False
1462
1464 adr = self._PRW_address_searcher.get_address()
1465 if adr is None:
1466 return True
1467
1468 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
1469
1470 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
1471 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
1472
1473 self._TCTRL_number.SetValue(adr['number'])
1474
1475 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
1476 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
1477
1478 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
1479 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
1480
1481 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
1482 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
1483
1485 error = False
1486
1487
1488 if self._PRW_lastname.GetValue().strip() == u'':
1489 error = True
1490 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
1491 self._PRW_lastname.display_as_valid(False)
1492 else:
1493 self._PRW_lastname.display_as_valid(True)
1494
1495 if self._PRW_firstnames.GetValue().strip() == '':
1496 error = True
1497 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
1498 self._PRW_firstnames.display_as_valid(False)
1499 else:
1500 self._PRW_firstnames.display_as_valid(True)
1501
1502
1503 if self._PRW_gender.GetData() is None:
1504 error = True
1505 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
1506 self._PRW_gender.display_as_valid(False)
1507 else:
1508 self._PRW_gender.display_as_valid(True)
1509
1510
1511 dob = self._DP_dob.GetValue(as_pydt = False, invalid_as_none = True)
1512
1513 if self._DP_dob.is_valid_timestamp(allow_none = False):
1514
1515 msg = None
1516 if (dob.GetYear() < 1900):
1517 msg = _(
1518 'DOB: %s\n'
1519 '\n'
1520 'While this is a valid point in time Python does\n'
1521 'not know how to deal with it.\n'
1522 '\n'
1523 'We suggest using January 1st 1901 instead and adding\n'
1524 'the true date of birth to the patient comment.\n'
1525 '\n'
1526 'Sorry for the inconvenience %s'
1527 ) % (dob, gmTools.u_frowning_face)
1528 elif dob > gmDateTime.wx_now_here(wx = wx):
1529 msg = _(
1530 'DOB: %s\n'
1531 '\n'
1532 'Date of birth in the future !'
1533 ) % dob
1534
1535 if msg is not None:
1536 error = True
1537 gmGuiHelpers.gm_show_error (
1538 msg,
1539 _('Registering new person')
1540 )
1541 self._DP_dob.display_as_valid(False)
1542 self._DP_dob.SetFocus()
1543
1544
1545 else:
1546 allow_empty_dob = gmGuiHelpers.gm_show_question (
1547 _(
1548 'Are you sure you want to register this person\n'
1549 'without a valid date of birth ?\n'
1550 '\n'
1551 'This can be useful for temporary staff members\n'
1552 'but will provoke nag screens if this person\n'
1553 'becomes a patient.\n'
1554 ),
1555 _('Registering new person')
1556 )
1557 if allow_empty_dob:
1558 self._DP_dob.display_as_valid(True)
1559 else:
1560 error = True
1561 self._DP_dob.SetFocus()
1562
1563
1564
1565
1566 return (not error)
1567
1569
1570
1571 if self._PRW_address_searcher.GetData() is not None:
1572 wx.CallAfter(self.__set_fields_from_address_searcher)
1573 return True
1574
1575
1576 fields_to_fill = (
1577 self._TCTRL_number,
1578 self._PRW_zip,
1579 self._PRW_street,
1580 self._PRW_urb,
1581 self._PRW_region,
1582 self._PRW_country
1583 )
1584 no_of_filled_fields = 0
1585
1586 for field in fields_to_fill:
1587 if field.GetValue().strip() != u'':
1588 no_of_filled_fields += 1
1589 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1590 field.Refresh()
1591
1592
1593 if no_of_filled_fields == 0:
1594 if empty_address_is_valid:
1595 return True
1596 else:
1597 return None
1598
1599
1600 if no_of_filled_fields != len(fields_to_fill):
1601 for field in fields_to_fill:
1602 if field.GetValue().strip() == u'':
1603 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1604 field.SetFocus()
1605 field.Refresh()
1606 msg = _('To properly create an address, all the related fields must be filled in.')
1607 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1608 return False
1609
1610
1611
1612
1613 strict_fields = (
1614 self._PRW_region,
1615 self._PRW_country
1616 )
1617 error = False
1618 for field in strict_fields:
1619 if field.GetData() is None:
1620 error = True
1621 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1622 field.SetFocus()
1623 else:
1624 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1625 field.Refresh()
1626
1627 if error:
1628 msg = _('This field must contain an item selected from the dropdown list.')
1629 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1630 return False
1631
1632 return True
1633
1650
1651
1652
1654 """Set the gender according to entered firstname.
1655
1656 Matches are fetched from existing records in backend.
1657 """
1658
1659
1660 if self._PRW_gender.GetData() is not None:
1661 return True
1662
1663 firstname = self._PRW_firstnames.GetValue().strip()
1664 if firstname == u'':
1665 return True
1666
1667 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
1668 if gender is None:
1669 return True
1670
1671 wx.CallAfter(self._PRW_gender.SetData, gender)
1672 return True
1673
1675 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
1676
1677 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
1678 self._PRW_street.set_context(context = u'zip', val = zip_code)
1679 self._PRW_urb.set_context(context = u'zip', val = zip_code)
1680 self._PRW_region.set_context(context = u'zip', val = zip_code)
1681 self._PRW_country.set_context(context = u'zip', val = zip_code)
1682
1683 return True
1684
1686 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
1687
1688 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
1689 self._PRW_region.set_context(context = u'country', val = country)
1690
1691 return True
1692
1694 mapping = [
1695 (self._PRW_street, 'street'),
1696 (self._TCTRL_number, 'number'),
1697 (self._PRW_urb, 'urb'),
1698 (self._PRW_region, 'l10n_state')
1699 ]
1700
1701
1702 for ctrl, field in mapping:
1703 if self.__perhaps_invalidate_address_searcher(ctrl, field):
1704 return True
1705
1706 return True
1707
1709 adr = self._PRW_address_searcher.get_address()
1710 if adr is None:
1711 return True
1712
1713 wx.CallAfter(self.__set_fields_from_address_searcher)
1714 return True
1715
1716
1717
1719 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
1720
1722
1723
1724 new_identity = gmPerson.create_identity (
1725 gender = self._PRW_gender.GetData(),
1726 dob = self._DP_dob.get_pydt(),
1727 lastnames = self._PRW_lastname.GetValue().strip(),
1728 firstnames = self._PRW_firstnames.GetValue().strip()
1729 )
1730 _log.debug('identity created: %s' % new_identity)
1731
1732 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
1733 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
1734
1735 new_identity.save()
1736
1737 name = new_identity.get_active_name()
1738 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1739 name.save()
1740
1741
1742 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
1743 if is_valid is True:
1744
1745
1746 try:
1747 new_identity.link_address (
1748 number = self._TCTRL_number.GetValue().strip(),
1749 street = self._PRW_street.GetValue().strip(),
1750 postcode = self._PRW_zip.GetValue().strip(),
1751 urb = self._PRW_urb.GetValue().strip(),
1752 state = self._PRW_region.GetData(),
1753 country = self._PRW_country.GetData()
1754 )
1755 except gmPG2.dbapi.InternalError:
1756 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
1757 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
1758 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
1759 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
1760 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
1761 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
1762 _log.exception('cannot link address')
1763 gmGuiHelpers.gm_show_error (
1764 aTitle = _('Saving address'),
1765 aMessage = _(
1766 'Cannot save this address.\n'
1767 '\n'
1768 'You will have to add it via the Demographics plugin.\n'
1769 )
1770 )
1771 elif is_valid is False:
1772 gmGuiHelpers.gm_show_error (
1773 aTitle = _('Saving address'),
1774 aMessage = _(
1775 'Address not saved.\n'
1776 '\n'
1777 'You will have to add it via the Demographics plugin.\n'
1778 )
1779 )
1780
1781
1782
1783 new_identity.link_comm_channel (
1784 comm_medium = u'homephone',
1785 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
1786 is_confidential = False
1787 )
1788
1789
1790 pk_type = self._PRW_external_id_type.GetData()
1791 id_value = self._TCTRL_external_id_value.GetValue().strip()
1792 if (pk_type is not None) and (id_value != u''):
1793 new_identity.add_external_id(value = id_value, pk_type = pk_type)
1794
1795
1796 new_identity.link_occupation (
1797 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
1798 )
1799
1800 self.data = new_identity
1801 return True
1802
1804 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1805
1809
1812
1814 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1815
1816
1817
1818
1820 """Notebook displaying demographics editing pages:
1821
1822 - Identity
1823 - Contacts (addresses, phone numbers, etc)
1824 - Social network (significant others, GP, etc)
1825
1826 Does NOT act on/listen to the current patient.
1827 """
1828
1830
1831 wx.Notebook.__init__ (
1832 self,
1833 parent = parent,
1834 id = id,
1835 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1836 name = self.__class__.__name__
1837 )
1838
1839 self.__identity = None
1840 self.__do_layout()
1841 self.SetSelection(0)
1842
1843
1844
1846 """Populate fields in pages with data from model."""
1847 for page_idx in range(self.GetPageCount()):
1848 page = self.GetPage(page_idx)
1849 page.identity = self.__identity
1850
1851 return True
1852
1853
1854
1884
1885
1886
1888 return self.__identity
1889
1892
1893 identity = property(_get_identity, _set_identity)
1894
1895
1896
1897
1898
1899
1901 """Page containing patient occupations edition fields.
1902 """
1903 - def __init__(self, parent, id, ident=None):
1904 """
1905 Creates a new instance of BasicPatDetailsPage
1906 @param parent - The parent widget
1907 @type parent - A wx.Window instance
1908 @param id - The widget id
1909 @type id - An integer
1910 """
1911 wx.Panel.__init__(self, parent, id)
1912 self.__ident = ident
1913 self.__do_layout()
1914
1916 PNL_form = wx.Panel(self, -1)
1917
1918 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1919 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
1920 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
1921
1922 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
1923 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
1924
1925
1926 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
1927 SZR_input.AddGrowableCol(1)
1928 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1929 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1930 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
1931 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
1932 PNL_form.SetSizerAndFit(SZR_input)
1933
1934
1935 SZR_main = wx.BoxSizer(wx.VERTICAL)
1936 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1937 self.SetSizer(SZR_main)
1938
1941
1942 - def refresh(self, identity=None):
1943 if identity is not None:
1944 self.__ident = identity
1945 jobs = self.__ident.get_occupations()
1946 if len(jobs) > 0:
1947 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
1948 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
1949 return True
1950
1952 if self.PRW_occupation.IsModified():
1953 new_job = self.PRW_occupation.GetValue().strip()
1954 jobs = self.__ident.get_occupations()
1955 for job in jobs:
1956 if job['l10n_occupation'] == new_job:
1957 continue
1958 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
1959 self.__ident.link_occupation(occupation = new_job)
1960 return True
1961
1963 """Patient demographics plugin for main notebook.
1964
1965 Hosts another notebook with pages for Identity, Contacts, etc.
1966
1967 Acts on/listens to the currently active patient.
1968 """
1969
1975
1976
1977
1978
1979
1980
1982 """Arrange widgets."""
1983 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
1984
1985 szr_main = wx.BoxSizer(wx.VERTICAL)
1986 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
1987 self.SetSizerAndFit(szr_main)
1988
1989
1990
1992 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1993 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1994
1996 self._schedule_data_reget()
1997
1999 self._schedule_data_reget()
2000
2001
2002
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2022 """
2023 Wizard page for entering patient's basic demographic information
2024 """
2025
2026 form_fields = (
2027 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
2028 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
2029 )
2030
2031 - def __init__(self, parent, title):
2032 """
2033 Creates a new instance of BasicPatDetailsPage
2034 @param parent - The parent widget
2035 @type parent - A wx.Window instance
2036 @param tile - The title of the page
2037 @type title - A StringType instance
2038 """
2039 wx.wizard.WizardPageSimple.__init__(self, parent)
2040 self.__title = title
2041 self.__do_layout()
2042 self.__register_interests()
2043
2044 - def __do_layout(self):
2045 PNL_form = wx.Panel(self, -1)
2046
2047
2048 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
2049 STT_lastname.SetForegroundColour('red')
2050 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
2051 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
2052
2053
2054 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
2055 STT_firstname.SetForegroundColour('red')
2056 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
2057 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
2058
2059
2060 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
2061 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
2062
2063
2064 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
2065 STT_dob.SetForegroundColour('red')
2066 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
2067 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
2068
2069
2070 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
2071 STT_gender.SetForegroundColour('red')
2072 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
2073 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
2074
2075
2076 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
2077 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
2078
2079
2080 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
2081 STT_zip_code.SetForegroundColour('orange')
2082 self.PRW_zip_code = gmPersonContactWidgets.cZipcodePhraseWheel(parent = PNL_form, id = -1)
2083 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
2084
2085
2086 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
2087 STT_street.SetForegroundColour('orange')
2088 self.PRW_street = gmPersonContactWidgets.cStreetPhraseWheel(parent = PNL_form, id = -1)
2089 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
2090
2091
2092 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
2093 STT_address_number.SetForegroundColour('orange')
2094 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
2095 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
2096
2097
2098 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
2099 STT_town.SetForegroundColour('orange')
2100 self.PRW_town = gmPersonContactWidgets.cUrbPhraseWheel(parent = PNL_form, id = -1)
2101 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
2102
2103
2104 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
2105 STT_state.SetForegroundColour('orange')
2106 self.PRW_state = gmPersonContactWidgets.cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
2107 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
2108
2109
2110 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
2111 STT_country.SetForegroundColour('orange')
2112 self.PRW_country = gmPersonContactWidgets.cCountryPhraseWheel(parent = PNL_form, id = -1)
2113 self.PRW_country.SetToolTipString(_("primary/home address: country"))
2114
2115
2116 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
2117 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
2118 self.TTC_phone.SetToolTipString(_("phone number at home"))
2119
2120
2121 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2122 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2123
2124
2125 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
2126 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
2127 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
2128
2129
2130 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
2131 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
2132
2133
2134 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
2135 SZR_input.AddGrowableCol(1)
2136 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
2137 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
2138 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
2139 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
2140 SZR_input.Add(STT_nick, 0, wx.SHAPED)
2141 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
2142 SZR_input.Add(STT_dob, 0, wx.SHAPED)
2143 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
2144 SZR_input.Add(STT_gender, 0, wx.SHAPED)
2145 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
2146 SZR_input.Add(STT_title, 0, wx.SHAPED)
2147 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
2148 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
2149 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
2150 SZR_input.Add(STT_street, 0, wx.SHAPED)
2151 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
2152 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
2153 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
2154 SZR_input.Add(STT_town, 0, wx.SHAPED)
2155 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
2156 SZR_input.Add(STT_state, 0, wx.SHAPED)
2157 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
2158 SZR_input.Add(STT_country, 0, wx.SHAPED)
2159 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
2160 SZR_input.Add(STT_phone, 0, wx.SHAPED)
2161 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
2162 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2163 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2164 SZR_input.Add(STT_comment, 0, wx.SHAPED)
2165 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
2166
2167 PNL_form.SetSizerAndFit(SZR_input)
2168
2169
2170 SZR_main = makePageTitle(self, self.__title)
2171 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2172
2173
2174
2179
2180 - def on_country_selected(self, data):
2181 """Set the states according to entered country."""
2182 self.PRW_state.set_context(context=u'country', val=data)
2183 return True
2184
2185 - def on_name_set(self):
2186 """Set the gender according to entered firstname.
2187
2188 Matches are fetched from existing records in backend.
2189 """
2190 firstname = self.PRW_firstname.GetValue().strip()
2191 rows, idx = gmPG2.run_ro_queries(queries = [{
2192 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
2193 'args': [firstname]
2194 }])
2195 if len(rows) == 0:
2196 return True
2197 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
2198 return True
2199
2200 - def on_zip_set(self):
2201 """Set the street, town, state and country according to entered zip code."""
2202 zip_code = self.PRW_zip_code.GetValue().strip()
2203 self.PRW_street.set_context(context=u'zip', val=zip_code)
2204 self.PRW_town.set_context(context=u'zip', val=zip_code)
2205 self.PRW_state.set_context(context=u'zip', val=zip_code)
2206 self.PRW_country.set_context(context=u'zip', val=zip_code)
2207 return True
2208
2209 -def makePageTitle(wizPg, title):
2210 """
2211 Utility function to create the main sizer of a wizard's page.
2212
2213 @param wizPg The wizard page widget
2214 @type wizPg A wx.WizardPageSimple instance
2215 @param title The wizard page's descriptive title
2216 @type title A StringType instance
2217 """
2218 sizer = wx.BoxSizer(wx.VERTICAL)
2219 wizPg.SetSizer(sizer)
2220 title = wx.StaticText(wizPg, -1, title)
2221 title.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD))
2222 sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 2)
2223 sizer.Add(wx.StaticLine(wizPg, -1), 0, wx.EXPAND|wx.ALL, 2)
2224 return sizer
2225
2227 """
2228 Wizard to create a new patient.
2229
2230 TODO:
2231 - write pages for different "themes" of patient creation
2232 - make it configurable which pages are loaded
2233 - make available sets of pages that apply to a country
2234 - make loading of some pages depend upon values in earlier pages, eg
2235 when the patient is female and older than 13 include a page about
2236 "female" data (number of kids etc)
2237
2238 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
2239 """
2240
2241 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2242 """
2243 Creates a new instance of NewPatientWizard
2244 @param parent - The parent widget
2245 @type parent - A wx.Window instance
2246 """
2247 id_wiz = wx.NewId()
2248 wx.wizard.Wizard.__init__(self, parent, id_wiz, title)
2249 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
2250 self.__subtitle = subtitle
2251 self.__do_layout()
2252
2254 """Create new patient.
2255
2256 activate, too, if told to do so (and patient successfully created)
2257 """
2258 while True:
2259
2260 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
2261 return False
2262
2263 try:
2264
2265 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
2266 except:
2267 _log.exception('cannot add new patient - missing identity fields')
2268 gmGuiHelpers.gm_show_error (
2269 _('Cannot create new patient.\n'
2270 'Missing parts of the identity.'
2271 ),
2272 _('Adding new patient')
2273 )
2274 continue
2275
2276 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2277
2278 try:
2279 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2280 except:
2281 _log.exception('cannot finalize new patient - missing address fields')
2282 gmGuiHelpers.gm_show_error (
2283 _('Cannot add address for the new patient.\n'
2284 'You must either enter all of the address fields or\n'
2285 'none at all. The relevant fields are marked in yellow.\n'
2286 '\n'
2287 'You will need to add the address details in the\n'
2288 'demographics module.'
2289 ),
2290 _('Adding new patient')
2291 )
2292 break
2293
2294 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2295
2296 break
2297
2298 if activate:
2299 from Gnumed.wxpython import gmPatSearchWidgets
2300 gmPatSearchWidgets.set_active_patient(patient = ident)
2301
2302 return ident
2303
2304
2305
2307 """Arrange widgets.
2308 """
2309
2310 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2311 self.FitToPage(self.basic_pat_details)
2312
2313
2315 """
2316 This validator is used to ensure that the user has entered all
2317 the required conditional values in the page (eg., to properly
2318 create an address, all the related fields must be filled).
2319 """
2320
2321 - def __init__(self, dtd):
2322 """
2323 Validator initialization.
2324 @param dtd The object containing the data model.
2325 @type dtd A cFormDTD instance
2326 """
2327
2328 wx.PyValidator.__init__(self)
2329
2330 self.form_DTD = dtd
2331
2333 """
2334 Standard cloner.
2335 Note that every validator must implement the Clone() method.
2336 """
2337 return cBasicPatDetailsPageValidator(dtd = self.form_DTD)
2338
2339 - def Validate(self, parent = None):
2340 """
2341 Validate the contents of the given text control.
2342 """
2343 _pnl_form = self.GetWindow().GetParent()
2344
2345 error = False
2346
2347
2348 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2349 error = True
2350 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2351 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2352 _pnl_form.PRW_lastname.Refresh()
2353 else:
2354 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2355 _pnl_form.PRW_lastname.Refresh()
2356
2357 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2358 error = True
2359 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2360 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2361 _pnl_form.PRW_firstname.Refresh()
2362 else:
2363 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2364 _pnl_form.PRW_firstname.Refresh()
2365
2366
2367 if _pnl_form.PRW_gender.GetData() is None:
2368 error = True
2369 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2370 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2371 _pnl_form.PRW_gender.Refresh()
2372 else:
2373 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2374 _pnl_form.PRW_gender.Refresh()
2375
2376
2377 if (
2378 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2379 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2380 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2381 ):
2382 error = True
2383 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
2384 gmDispatcher.send(signal = 'statustext', msg = msg)
2385 _pnl_form.PRW_dob.SetBackgroundColour('pink')
2386 _pnl_form.PRW_dob.Refresh()
2387 else:
2388 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2389 _pnl_form.PRW_dob.Refresh()
2390
2391
2392 is_any_field_filled = False
2393 address_fields = (
2394 _pnl_form.TTC_address_number,
2395 _pnl_form.PRW_zip_code,
2396 _pnl_form.PRW_street,
2397 _pnl_form.PRW_town
2398 )
2399 for field in address_fields:
2400 if field.GetValue().strip() == u'':
2401 if is_any_field_filled:
2402 error = True
2403 msg = _('To properly create an address, all the related fields must be filled in.')
2404 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2405 field.SetBackgroundColour('pink')
2406 field.SetFocus()
2407 field.Refresh()
2408 else:
2409 is_any_field_filled = True
2410 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2411 field.Refresh()
2412
2413 address_fields = (
2414 _pnl_form.PRW_state,
2415 _pnl_form.PRW_country
2416 )
2417 for field in address_fields:
2418 if field.GetData() is None:
2419 if is_any_field_filled:
2420 error = True
2421 msg = _('To properly create an address, all the related fields must be filled in.')
2422 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2423 field.SetBackgroundColour('pink')
2424 field.SetFocus()
2425 field.Refresh()
2426 else:
2427 is_any_field_filled = True
2428 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2429 field.Refresh()
2430
2431 return (not error)
2432
2433 - def TransferToWindow(self):
2434 """
2435 Transfer data from validator to window.
2436 The default implementation returns False, indicating that an error
2437 occurred. We simply return True, as we don't do any data transfer.
2438 """
2439 _pnl_form = self.GetWindow().GetParent()
2440
2441 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
2442 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
2443 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
2444 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
2445 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
2446 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
2447 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
2448 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
2449 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
2450 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
2451 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
2452 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
2453 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
2454 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
2455 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
2456 return True
2457
2459 """
2460 Transfer data from window to validator.
2461 The default implementation returns False, indicating that an error
2462 occurred. We simply return True, as we don't do any data transfer.
2463 """
2464
2465 if not self.GetWindow().GetParent().Validate():
2466 return False
2467 try:
2468 _pnl_form = self.GetWindow().GetParent()
2469
2470 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
2471 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
2472
2473 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
2474 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
2475 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
2476 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
2477
2478 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
2479
2480 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
2481 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
2482 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
2483 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
2484 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
2485 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
2486
2487 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
2488
2489 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
2490 except:
2491 return False
2492 return True
2493
2495 """
2496 Utility class to test the new patient wizard.
2497 """
2498
2500 """
2501 Create a new instance of TestPanel.
2502 @param parent The parent widget
2503 @type parent A wx.Window instance
2504 """
2505 wx.Panel.__init__(self, parent, id)
2506 wizard = cNewPatientWizard(self)
2507 print wizard.RunWizard()
2508
2509 if __name__ == "__main__":
2510
2511
2513 app = wx.PyWidgetTester(size = (600, 400))
2514 app.SetWidget(cKOrganizerSchedulePnl)
2515 app.MainLoop()
2516
2518 app = wx.PyWidgetTester(size = (600, 400))
2519 widget = cPersonNamesManagerPnl(app.frame, -1)
2520 widget.identity = activate_patient()
2521 app.frame.Show(True)
2522 app.MainLoop()
2523
2525 app = wx.PyWidgetTester(size = (600, 400))
2526 widget = cPersonIDsManagerPnl(app.frame, -1)
2527 widget.identity = activate_patient()
2528 app.frame.Show(True)
2529 app.MainLoop()
2530
2532 app = wx.PyWidgetTester(size = (600, 400))
2533 widget = cPersonIdentityManagerPnl(app.frame, -1)
2534 widget.identity = activate_patient()
2535 app.frame.Show(True)
2536 app.MainLoop()
2537
2542
2549
2551 app = wx.PyWidgetTester(size = (600, 400))
2552 widget = cPersonDemographicsEditorNb(app.frame, -1)
2553 widget.identity = activate_patient()
2554 widget.refresh()
2555 app.frame.Show(True)
2556 app.MainLoop()
2557
2566
2567 if len(sys.argv) > 1 and sys.argv[1] == 'test':
2568
2569 gmI18N.activate_locale()
2570 gmI18N.install_domain(domain='gnumed')
2571 gmPG2.get_connection()
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588 test_urb_prw()
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606