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, os, codecs, re as regex, logging
9
10
11 import wx
12 import wx.wizard
13
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmDispatcher, gmI18N, gmMatchProvider, gmPG2, gmTools, gmCfg
19 from Gnumed.pycommon import gmDateTime, gmShellAPI
20 from Gnumed.business import gmDemographicRecord, gmPerson, gmSurgery, gmPersonSearch
21 from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmDateTimeInput
22 from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea
23 from Gnumed.wxpython import gmAuthWidgets, gmPersonContactWidgets
24
25
26
27 _log = logging.getLogger('gm.ui')
28
29
30 try:
31 _('dummy-no-need-to-translate-but-make-epydoc-happy')
32 except NameError:
33 _ = lambda x:x
34
35
36
38
40
41 kwargs['message'] = _("Today's KOrganizer appointments ...")
42 kwargs['button_defs'] = [
43 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
44 {'label': u''},
45 {'label': u''},
46 {'label': u''},
47 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')}
48 ]
49 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
50
51 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv'))
52 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
53
54
58
68
70 try: os.remove(self.fname)
71 except OSError: pass
72 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
73 try:
74 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace')
75 except IOError:
76 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
77 return
78
79 csv_lines = gmTools.unicode_csv_reader (
80 csv_file,
81 delimiter = ','
82 )
83
84 self._LCTRL_items.set_columns ([
85 _('Place'),
86 _('Start'),
87 u'',
88 u'',
89 _('Patient'),
90 _('Comment')
91 ])
92 items = []
93 data = []
94 for line in csv_lines:
95 items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
96 data.append([line[4], line[7]])
97
98 self._LCTRL_items.set_string_items(items = items)
99 self._LCTRL_items.set_column_widths()
100 self._LCTRL_items.set_data(data = data)
101 self._LCTRL_items.patient_key = 0
102
103
104
107
108
109
111
112 pat = gmPerson.gmCurrentPatient()
113 curr_jobs = pat.get_occupations()
114 if len(curr_jobs) > 0:
115 old_job = curr_jobs[0]['l10n_occupation']
116 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
117 else:
118 old_job = u''
119 update = u''
120
121 msg = _(
122 'Please enter the primary occupation of the patient.\n'
123 '\n'
124 'Currently recorded:\n'
125 '\n'
126 ' %s (last updated %s)'
127 ) % (old_job, update)
128
129 new_job = wx.GetTextFromUser (
130 message = msg,
131 caption = _('Editing primary occupation'),
132 default_value = old_job,
133 parent = None
134 )
135 if new_job.strip() == u'':
136 return
137
138 for job in curr_jobs:
139
140 if job['l10n_occupation'] != new_job:
141 pat.unlink_occupation(occupation = job['l10n_occupation'])
142
143 pat.link_occupation(occupation = new_job)
144
145
160
161
162
163
165
166 go_ahead = gmGuiHelpers.gm_show_question (
167 _('Are you sure you really, positively want\n'
168 'to disable the following person ?\n'
169 '\n'
170 ' %s %s %s\n'
171 ' born %s\n'
172 '\n'
173 '%s\n'
174 ) % (
175 identity['firstnames'],
176 identity['lastnames'],
177 identity['gender'],
178 identity['dob'],
179 gmTools.bool2subst (
180 identity.is_patient,
181 _('This patient DID receive care.'),
182 _('This person did NOT receive care.')
183 )
184 ),
185 _('Disabling person')
186 )
187 if not go_ahead:
188 return True
189
190
191 conn = gmAuthWidgets.get_dbowner_connection (
192 procedure = _('Disabling patient')
193 )
194
195 if conn is False:
196 return True
197
198 if conn is None:
199 return False
200
201
202 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}])
203
204 return True
205
206
207
208
223
225
227 query = u"""
228 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
229 union
230 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
231 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
232 mp.setThresholds(3, 5, 9)
233 gmPhraseWheel.cPhraseWheel.__init__ (
234 self,
235 *args,
236 **kwargs
237 )
238 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name)."))
239 self.capitalisation_mode = gmTools.CAPS_NAMES
240 self.matcher = mp
241
243
245 query = u"""
246 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
247 union
248 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
249 union
250 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
251 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
252 mp.setThresholds(3, 5, 9)
253 gmPhraseWheel.cPhraseWheel.__init__ (
254 self,
255 *args,
256 **kwargs
257 )
258 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
259
260
261 self.matcher = mp
262
264
266 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s"
267 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
268 mp.setThresholds(1, 3, 9)
269 gmPhraseWheel.cPhraseWheel.__init__ (
270 self,
271 *args,
272 **kwargs
273 )
274 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
275 self.matcher = mp
276
278 """Let user select a gender."""
279
280 _gender_map = None
281
283
284 if cGenderSelectionPhraseWheel._gender_map is None:
285 cmd = u"""
286 select tag, l10n_label, sort_weight
287 from dem.v_gender_labels
288 order by sort_weight desc"""
289 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
290 cGenderSelectionPhraseWheel._gender_map = {}
291 for gender in rows:
292 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
293 'data': gender[idx['tag']],
294 'label': gender[idx['l10n_label']],
295 'weight': gender[idx['sort_weight']]
296 }
297
298 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values())
299 mp.setThresholds(1, 1, 3)
300
301 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
302 self.selection_only = True
303 self.matcher = mp
304 self.picklist_delay = 50
305
307
309 query = u"""
310 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label
311 from dem.enum_ext_id_types
312 where name %%(fragment_condition)s
313 order by label limit 25""" % _('issued by')
314 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
315 mp.setThresholds(1, 3, 5)
316 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
317 self.SetToolTipString(_("Enter or select a type for the external ID."))
318 self.matcher = mp
319
334
335
336
337 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl
338
340 """An edit area for editing/creating external IDs.
341
342 Does NOT act on/listen to the current patient.
343 """
359
360
361
363 if ext_id is not None:
364 self.ext_id = ext_id
365
366 if self.ext_id is not None:
367 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type'])
368 self._TCTRL_value.SetValue(self.ext_id['value'])
369 self._PRW_issuer.SetText(self.ext_id['issuer'])
370 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
371
372
373
374
376
377 if not self.__valid_for_save():
378 return False
379
380
381 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0]
382
383
384 if self.ext_id is None:
385 self.identity.add_external_id (
386 type_name = type,
387 value = self._TCTRL_value.GetValue().strip(),
388 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
389 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
390 )
391
392 else:
393 self.identity.update_external_id (
394 pk_id = self.ext_id['pk_id'],
395 type = type,
396 value = self._TCTRL_value.GetValue().strip(),
397 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
398 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
399 )
400
401 return True
402
403
404
407
409 """Set the issuer according to the selected type.
410
411 Matches are fetched from existing records in backend.
412 """
413 pk_curr_type = self._PRW_type.GetData()
414 if pk_curr_type is None:
415 return True
416 rows, idx = gmPG2.run_ro_queries(queries = [{
417 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s",
418 'args': [pk_curr_type]
419 }])
420 if len(rows) == 0:
421 return True
422 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
423 return True
424
426
427 no_errors = True
428
429
430
431
432 if self._PRW_type.GetValue().strip() == u'':
433 self._PRW_type.SetBackgroundColour('pink')
434 self._PRW_type.SetFocus()
435 self._PRW_type.Refresh()
436 else:
437 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
438 self._PRW_type.Refresh()
439
440 if self._TCTRL_value.GetValue().strip() == u'':
441 self._TCTRL_value.SetBackgroundColour('pink')
442 self._TCTRL_value.SetFocus()
443 self._TCTRL_value.Refresh()
444 no_errors = False
445 else:
446 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
447 self._TCTRL_value.Refresh()
448
449 return no_errors
450
451 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl
452
453 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
454 """An edit area for editing/creating title/gender/dob/dod etc."""
455
471
472
473
474
475
476
477
478
480
481 has_error = False
482
483 if self._PRW_gender.GetData() is None:
484 self._PRW_gender.SetFocus()
485 has_error = True
486
487 if not self._PRW_dob.is_valid_timestamp():
488 val = self._PRW_dob.GetValue().strip()
489 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
490 self._PRW_dob.SetBackgroundColour('pink')
491 self._PRW_dob.Refresh()
492 self._PRW_dob.SetFocus()
493 has_error = True
494 else:
495 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
496 self._PRW_dob.Refresh()
497
498 if not self._DP_dod.is_valid_timestamp(allow_none = True, invalid_as_none = True):
499 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
500 self._DP_dod.SetFocus()
501 has_error = True
502
503 return (has_error is False)
504
508
524
527
543
546
547
548 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl
549
551 """An edit area for editing/creating name/gender/dob.
552
553 Does NOT act on/listen to the current patient.
554 """
565
566
567
584
585
586
587
589
590 if not self.__valid_for_save():
591 return False
592
593 self.__identity['gender'] = self._PRW_gender.GetData()
594 if self._PRW_dob.GetValue().strip() == u'':
595 self.__identity['dob'] = None
596 else:
597 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt()
598 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
599 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True, invalid_as_none = True)
600 self.__identity.save_payload()
601
602 active = self._CHBOX_active.GetValue()
603 first = self._PRW_firstname.GetValue().strip()
604 last = self._PRW_lastname.GetValue().strip()
605 old_nick = self.__name['preferred']
606
607
608 old_name = self.__name['firstnames'] + self.__name['lastnames']
609 if (first + last) != old_name:
610 self.__name = self.__identity.add_name(first, last, active)
611
612 self.__name['active_name'] = active
613 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
614 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
615
616 self.__name.save_payload()
617
618 return True
619
620
621
624
626 """Set the gender according to entered firstname.
627
628 Matches are fetched from existing records in backend.
629 """
630 firstname = self._PRW_firstname.GetValue().strip()
631 if firstname == u'':
632 return True
633 rows, idx = gmPG2.run_ro_queries(queries = [{
634 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
635 'args': [firstname]
636 }])
637 if len(rows) == 0:
638 return True
639 wx.CallAfter(self._PRW_gender.SetData, rows[0][0])
640 return True
641
642
643
645
646 has_error = False
647
648 if self._PRW_gender.GetData() is None:
649 self._PRW_gender.SetBackgroundColour('pink')
650 self._PRW_gender.Refresh()
651 self._PRW_gender.SetFocus()
652 has_error = True
653 else:
654 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
655 self._PRW_gender.Refresh()
656
657 if not self._PRW_dob.is_valid_timestamp():
658 val = self._PRW_dob.GetValue().strip()
659 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
660 self._PRW_dob.SetBackgroundColour('pink')
661 self._PRW_dob.Refresh()
662 self._PRW_dob.SetFocus()
663 has_error = True
664 else:
665 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
666 self._PRW_dob.Refresh()
667
668 if not self._DP_dod.is_valid_timestamp(allow_none = True, invalid_as_none = True):
669 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.'))
670 self._DP_dod.SetBackgroundColour('pink')
671 self._DP_dod.Refresh()
672 self._DP_dod.SetFocus()
673 has_error = True
674 else:
675 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
676 self._DP_dod.Refresh()
677
678 if self._PRW_lastname.GetValue().strip() == u'':
679 self._PRW_lastname.SetBackgroundColour('pink')
680 self._PRW_lastname.Refresh()
681 self._PRW_lastname.SetFocus()
682 has_error = True
683 else:
684 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
685 self._PRW_lastname.Refresh()
686
687 if self._PRW_firstname.GetValue().strip() == u'':
688 self._PRW_firstname.SetBackgroundColour('pink')
689 self._PRW_firstname.Refresh()
690 self._PRW_firstname.SetFocus()
691 has_error = True
692 else:
693 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
694 self._PRW_firstname.Refresh()
695
696 return (has_error is False)
697
698
699
701 """A list for managing a person's names.
702
703 Does NOT act on/listen to the current patient.
704 """
722
723
724
725 - def refresh(self, *args, **kwargs):
742
743
744
746 self._LCTRL_items.set_columns(columns = [
747 _('Active'),
748 _('Lastname'),
749 _('Firstname(s)'),
750 _('Preferred Name'),
751 _('Comment')
752 ])
753
763
773
775
776 if len(self.__identity.get_names()) == 1:
777 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
778 return False
779
780 go_ahead = gmGuiHelpers.gm_show_question (
781 _( 'It is often advisable to keep old names around and\n'
782 'just create a new "currently active" name.\n'
783 '\n'
784 'This allows finding the patient by both the old\n'
785 'and the new name (think before/after marriage).\n'
786 '\n'
787 'Do you still want to really delete\n'
788 "this name from the patient ?"
789 ),
790 _('Deleting name')
791 )
792 if not go_ahead:
793 return False
794
795 self.__identity.delete_name(name = name)
796 return True
797
798
799
801 return self.__identity
802
806
807 identity = property(_get_identity, _set_identity)
808
810 """A list for managing a person's external IDs.
811
812 Does NOT act on/listen to the current patient.
813 """
831
832
833
834 - def refresh(self, *args, **kwargs):
851
852
853
855 self._LCTRL_items.set_columns(columns = [
856 _('ID type'),
857 _('Value'),
858 _('Issuer'),
859 _('Comment')
860 ])
861
872
883
885 go_ahead = gmGuiHelpers.gm_show_question (
886 _( 'Do you really want to delete this\n'
887 'external ID from the patient ?'),
888 _('Deleting external ID')
889 )
890 if not go_ahead:
891 return False
892 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
893 return True
894
895
896
898 return self.__identity
899
903
904 identity = property(_get_identity, _set_identity)
905
906
907
908 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
909
911 """A panel for editing identity data for a person.
912
913 - provides access to:
914 - name
915 - external IDs
916
917 Does NOT act on/listen to the current patient.
918 """
925
926
927
929 self._PNL_names.identity = self.__identity
930 self._PNL_ids.identity = self.__identity
931
932 self._PNL_identity.mode = 'new'
933 self._PNL_identity.data = self.__identity
934 if self.__identity is not None:
935 self._PNL_identity.mode = 'edit'
936
937
938
940 return self.__identity
941
945
946 identity = property(_get_identity, _set_identity)
947
948
949
953
956
957
958 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
959
968
969
970
972
973 tt = _("Link another person in this database as the emergency contact.")
974
975 if self.__identity is None:
976 self._TCTRL_er_contact.SetValue(u'')
977 self._TCTRL_person.person = None
978 self._TCTRL_person.SetToolTipString(tt)
979
980 self._PRW_provider.SetText(value = u'', data = None)
981 return
982
983 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u''))
984 if self.__identity['pk_emergency_contact'] is not None:
985 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact'])
986 self._TCTRL_person.person = ident
987 tt = u'%s\n\n%s\n\n%s' % (
988 tt,
989 ident['description_gender'],
990 u'\n'.join([
991 u'%s: %s%s' % (
992 c['l10n_comm_type'],
993 c['url'],
994 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'')
995 )
996 for c in ident.get_comm_channels()
997 ])
998 )
999 else:
1000 self._TCTRL_person.person = None
1001
1002 self._TCTRL_person.SetToolTipString(tt)
1003
1004 if self.__identity['pk_primary_provider'] is None:
1005 self._PRW_provider.SetText(value = u'', data = None)
1006 else:
1007 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1008
1009
1010
1012 return self.__identity
1013
1017
1018 identity = property(_get_identity, _set_identity)
1019
1020
1021
1035
1046
1054
1055
1056
1058
1059 dbcfg = gmCfg.cCfgSQL()
1060
1061 def_region = dbcfg.get2 (
1062 option = u'person.create.default_region',
1063 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1064 bias = u'user'
1065 )
1066 def_country = None
1067
1068 if def_region is None:
1069 def_country = dbcfg.get2 (
1070 option = u'person.create.default_country',
1071 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1072 bias = u'user'
1073 )
1074 else:
1075 countries = gmDemographicRecord.get_country_for_region(region = def_region)
1076 if len(countries) == 1:
1077 def_country = countries[0]['l10n_country']
1078
1079 if parent is None:
1080 parent = wx.GetApp().GetTopWindow()
1081
1082 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
1083 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
1084 dlg.SetTitle(_('Adding new person'))
1085 ea._PRW_lastname.SetFocus()
1086 result = dlg.ShowModal()
1087 pat = ea.data
1088 dlg.Destroy()
1089
1090 if result != wx.ID_OK:
1091 return False
1092
1093 _log.debug('created new person [%s]', pat.ID)
1094
1095 if activate:
1096 from Gnumed.wxpython import gmPatSearchWidgets
1097 gmPatSearchWidgets.set_active_patient(patient = pat)
1098
1099 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
1100
1101 return True
1102
1103 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
1104
1105 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1106
1108
1109 try:
1110 self.default_region = kwargs['region']
1111 del kwargs['region']
1112 except KeyError:
1113 self.default_region = None
1114
1115 try:
1116 self.default_country = kwargs['country']
1117 del kwargs['country']
1118 except KeyError:
1119 self.default_country = None
1120
1121 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
1122 gmEditArea.cGenericEditAreaMixin.__init__(self)
1123
1124 self.mode = 'new'
1125 self.data = None
1126 self._address = None
1127
1128 self.__init_ui()
1129 self.__register_interests()
1130
1131
1132
1134 self._PRW_lastname.final_regex = '.+'
1135 self._PRW_firstnames.final_regex = '.+'
1136 self._PRW_address_searcher.selection_only = False
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147 if self.default_country is not None:
1148 self._PRW_country.SetText(value = self.default_country)
1149
1150 if self.default_region is not None:
1151 self._PRW_region.SetText(value = self.default_region)
1152
1154
1155 adr = self._PRW_address_searcher.get_address()
1156 if adr is None:
1157 return True
1158
1159 if ctrl.GetValue().strip() != adr[field]:
1160 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
1161 return True
1162
1163 return False
1164
1166 adr = self._PRW_address_searcher.get_address()
1167 if adr is None:
1168 return True
1169
1170 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
1171
1172 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
1173 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
1174
1175 self._TCTRL_number.SetValue(adr['number'])
1176
1177 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
1178 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
1179
1180 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
1181 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
1182
1183 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
1184 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
1185
1187 error = False
1188
1189
1190 if self._PRW_lastname.GetValue().strip() == u'':
1191 error = True
1192 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
1193 self._PRW_lastname.display_as_valid(False)
1194 else:
1195 self._PRW_lastname.display_as_valid(True)
1196
1197 if self._PRW_firstnames.GetValue().strip() == '':
1198 error = True
1199 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
1200 self._PRW_firstnames.display_as_valid(False)
1201 else:
1202 self._PRW_firstnames.display_as_valid(True)
1203
1204
1205 if self._PRW_gender.GetData() is None:
1206 error = True
1207 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
1208 self._PRW_gender.display_as_valid(False)
1209 else:
1210 self._PRW_gender.display_as_valid(True)
1211
1212
1213 dob = self._DP_dob.GetValue(as_pydt = False, invalid_as_none = True)
1214
1215 if self._DP_dob.is_valid_timestamp(allow_none = False):
1216
1217 msg = None
1218 if (dob.GetYear() < 1900):
1219 msg = _(
1220 'DOB: %s\n'
1221 '\n'
1222 'While this is a valid point in time Python does\n'
1223 'not know how to deal with it.\n'
1224 '\n'
1225 'We suggest using January 1st 1901 instead and adding\n'
1226 'the true date of birth to the patient comment.\n'
1227 '\n'
1228 'Sorry for the inconvenience %s'
1229 ) % (dob, gmTools.u_frowning_face)
1230 elif dob > gmDateTime.wx_now_here(wx = wx):
1231 msg = _(
1232 'DOB: %s\n'
1233 '\n'
1234 'Date of birth in the future !'
1235 ) % dob
1236
1237 if msg is not None:
1238 error = True
1239 gmGuiHelpers.gm_show_error (
1240 msg,
1241 _('Registering new person')
1242 )
1243 self._DP_dob.display_as_valid(False)
1244 self._DP_dob.SetFocus()
1245
1246
1247 else:
1248 allow_empty_dob = gmGuiHelpers.gm_show_question (
1249 _(
1250 'Are you sure you want to register this person\n'
1251 'without a valid date of birth ?\n'
1252 '\n'
1253 'This can be useful for temporary staff members\n'
1254 'but will provoke nag screens if this person\n'
1255 'becomes a patient.\n'
1256 ),
1257 _('Registering new person')
1258 )
1259 if allow_empty_dob:
1260 self._DP_dob.display_as_valid(True)
1261 else:
1262 error = True
1263 self._DP_dob.SetFocus()
1264
1265
1266
1267
1268 return (not error)
1269
1271
1272
1273 if self._PRW_address_searcher.GetData() is not None:
1274 wx.CallAfter(self.__set_fields_from_address_searcher)
1275 return True
1276
1277
1278 fields_to_fill = (
1279 self._TCTRL_number,
1280 self._PRW_zip,
1281 self._PRW_street,
1282 self._PRW_urb,
1283 self._PRW_region,
1284 self._PRW_country
1285 )
1286 no_of_filled_fields = 0
1287
1288 for field in fields_to_fill:
1289 if field.GetValue().strip() != u'':
1290 no_of_filled_fields += 1
1291 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1292 field.Refresh()
1293
1294
1295 if no_of_filled_fields == 0:
1296 if empty_address_is_valid:
1297 return True
1298 else:
1299 return None
1300
1301
1302 if no_of_filled_fields != len(fields_to_fill):
1303 for field in fields_to_fill:
1304 if field.GetValue().strip() == u'':
1305 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1306 field.SetFocus()
1307 field.Refresh()
1308 msg = _('To properly create an address, all the related fields must be filled in.')
1309 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1310 return False
1311
1312
1313
1314
1315 strict_fields = (
1316 self._PRW_region,
1317 self._PRW_country
1318 )
1319 error = False
1320 for field in strict_fields:
1321 if field.GetData() is None:
1322 error = True
1323 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1324 field.SetFocus()
1325 else:
1326 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1327 field.Refresh()
1328
1329 if error:
1330 msg = _('This field must contain an item selected from the dropdown list.')
1331 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1332 return False
1333
1334 return True
1335
1352
1353
1354
1356 """Set the gender according to entered firstname.
1357
1358 Matches are fetched from existing records in backend.
1359 """
1360
1361
1362 if self._PRW_gender.GetData() is not None:
1363 return True
1364
1365 firstname = self._PRW_firstnames.GetValue().strip()
1366 if firstname == u'':
1367 return True
1368
1369 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
1370 if gender is None:
1371 return True
1372
1373 wx.CallAfter(self._PRW_gender.SetData, gender)
1374 return True
1375
1377 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
1378
1379 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
1380 self._PRW_street.set_context(context = u'zip', val = zip_code)
1381 self._PRW_urb.set_context(context = u'zip', val = zip_code)
1382 self._PRW_region.set_context(context = u'zip', val = zip_code)
1383 self._PRW_country.set_context(context = u'zip', val = zip_code)
1384
1385 return True
1386
1388 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
1389
1390 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
1391 self._PRW_region.set_context(context = u'country', val = country)
1392
1393 return True
1394
1396 mapping = [
1397 (self._PRW_street, 'street'),
1398 (self._TCTRL_number, 'number'),
1399 (self._PRW_urb, 'urb'),
1400 (self._PRW_region, 'l10n_state')
1401 ]
1402
1403
1404 for ctrl, field in mapping:
1405 if self.__perhaps_invalidate_address_searcher(ctrl, field):
1406 return True
1407
1408 return True
1409
1411 adr = self._PRW_address_searcher.get_address()
1412 if adr is None:
1413 return True
1414
1415 wx.CallAfter(self.__set_fields_from_address_searcher)
1416 return True
1417
1418
1419
1421 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
1422
1424
1425
1426 new_identity = gmPerson.create_identity (
1427 gender = self._PRW_gender.GetData(),
1428 dob = self._DP_dob.get_pydt(),
1429 lastnames = self._PRW_lastname.GetValue().strip(),
1430 firstnames = self._PRW_firstnames.GetValue().strip()
1431 )
1432 _log.debug('identity created: %s' % new_identity)
1433
1434 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
1435 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
1436
1437 new_identity.save()
1438
1439 name = new_identity.get_active_name()
1440 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1441 name.save()
1442
1443
1444 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
1445 if is_valid is True:
1446
1447
1448 try:
1449 new_identity.link_address (
1450 number = self._TCTRL_number.GetValue().strip(),
1451 street = self._PRW_street.GetValue().strip(),
1452 postcode = self._PRW_zip.GetValue().strip(),
1453 urb = self._PRW_urb.GetValue().strip(),
1454 state = self._PRW_region.GetData(),
1455 country = self._PRW_country.GetData()
1456 )
1457 except gmPG2.dbapi.InternalError:
1458 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
1459 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
1460 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
1461 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
1462 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
1463 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
1464 _log.exception('cannot link address')
1465 gmGuiHelpers.gm_show_error (
1466 aTitle = _('Saving address'),
1467 aMessage = _(
1468 'Cannot save this address.\n'
1469 '\n'
1470 'You will have to add it via the Demographics plugin.\n'
1471 )
1472 )
1473 elif is_valid is False:
1474 gmGuiHelpers.gm_show_error (
1475 aTitle = _('Saving address'),
1476 aMessage = _(
1477 'Address not saved.\n'
1478 '\n'
1479 'You will have to add it via the Demographics plugin.\n'
1480 )
1481 )
1482
1483
1484
1485 new_identity.link_comm_channel (
1486 comm_medium = u'homephone',
1487 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
1488 is_confidential = False
1489 )
1490
1491
1492 pk_type = self._PRW_external_id_type.GetData()
1493 id_value = self._TCTRL_external_id_value.GetValue().strip()
1494 if (pk_type is not None) and (id_value != u''):
1495 new_identity.add_external_id(value = id_value, pk_type = pk_type)
1496
1497
1498 new_identity.link_occupation (
1499 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
1500 )
1501
1502 self.data = new_identity
1503 return True
1504
1506 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1507
1511
1514
1516 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1517
1518
1519
1520
1522 """Notebook displaying demographics editing pages:
1523
1524 - Identity
1525 - Contacts (addresses, phone numbers, etc)
1526 - Social Network (significant others, GP, etc)
1527
1528 Does NOT act on/listen to the current patient.
1529 """
1530
1532
1533 wx.Notebook.__init__ (
1534 self,
1535 parent = parent,
1536 id = id,
1537 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1538 name = self.__class__.__name__
1539 )
1540
1541 self.__identity = None
1542 self.__do_layout()
1543 self.SetSelection(0)
1544
1545
1546
1548 """Populate fields in pages with data from model."""
1549 for page_idx in range(self.GetPageCount()):
1550 page = self.GetPage(page_idx)
1551 page.identity = self.__identity
1552
1553 return True
1554
1555
1556
1586
1587
1588
1590 return self.__identity
1591
1594
1595 identity = property(_get_identity, _set_identity)
1596
1597
1598
1599
1600
1601
1603 """Page containing patient occupations edition fields.
1604 """
1605 - def __init__(self, parent, id, ident=None):
1606 """
1607 Creates a new instance of BasicPatDetailsPage
1608 @param parent - The parent widget
1609 @type parent - A wx.Window instance
1610 @param id - The widget id
1611 @type id - An integer
1612 """
1613 wx.Panel.__init__(self, parent, id)
1614 self.__ident = ident
1615 self.__do_layout()
1616
1618 PNL_form = wx.Panel(self, -1)
1619
1620 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1621 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
1622 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
1623
1624 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
1625 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
1626
1627
1628 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
1629 SZR_input.AddGrowableCol(1)
1630 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1631 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1632 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
1633 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
1634 PNL_form.SetSizerAndFit(SZR_input)
1635
1636
1637 SZR_main = wx.BoxSizer(wx.VERTICAL)
1638 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1639 self.SetSizer(SZR_main)
1640
1643
1644 - def refresh(self, identity=None):
1645 if identity is not None:
1646 self.__ident = identity
1647 jobs = self.__ident.get_occupations()
1648 if len(jobs) > 0:
1649 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
1650 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
1651 return True
1652
1654 if self.PRW_occupation.IsModified():
1655 new_job = self.PRW_occupation.GetValue().strip()
1656 jobs = self.__ident.get_occupations()
1657 for job in jobs:
1658 if job['l10n_occupation'] == new_job:
1659 continue
1660 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
1661 self.__ident.link_occupation(occupation = new_job)
1662 return True
1663
1665 """Patient demographics plugin for main notebook.
1666
1667 Hosts another notebook with pages for Identity, Contacts, etc.
1668
1669 Acts on/listens to the currently active patient.
1670 """
1671
1677
1678
1679
1680
1681
1682
1684 """Arrange widgets."""
1685 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
1686
1687 szr_main = wx.BoxSizer(wx.VERTICAL)
1688 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
1689 self.SetSizerAndFit(szr_main)
1690
1691
1692
1694 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1695 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1696
1698 self._schedule_data_reget()
1699
1701 self._schedule_data_reget()
1702
1703
1704
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
1724 """
1725 Wizard page for entering patient's basic demographic information
1726 """
1727
1728 form_fields = (
1729 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
1730 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
1731 )
1732
1733 - def __init__(self, parent, title):
1734 """
1735 Creates a new instance of BasicPatDetailsPage
1736 @param parent - The parent widget
1737 @type parent - A wx.Window instance
1738 @param tile - The title of the page
1739 @type title - A StringType instance
1740 """
1741 wx.wizard.WizardPageSimple.__init__(self, parent)
1742 self.__title = title
1743 self.__do_layout()
1744 self.__register_interests()
1745
1746 - def __do_layout(self):
1747 PNL_form = wx.Panel(self, -1)
1748
1749
1750 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
1751 STT_lastname.SetForegroundColour('red')
1752 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
1753 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
1754
1755
1756 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
1757 STT_firstname.SetForegroundColour('red')
1758 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
1759 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
1760
1761
1762 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
1763 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
1764
1765
1766 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
1767 STT_dob.SetForegroundColour('red')
1768 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
1769 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
1770
1771
1772 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
1773 STT_gender.SetForegroundColour('red')
1774 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
1775 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
1776
1777
1778 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
1779 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
1780
1781
1782 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
1783 STT_zip_code.SetForegroundColour('orange')
1784 self.PRW_zip_code = gmPersonContactWidgets.cZipcodePhraseWheel(parent = PNL_form, id = -1)
1785 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
1786
1787
1788 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
1789 STT_street.SetForegroundColour('orange')
1790 self.PRW_street = gmPersonContactWidgets.cStreetPhraseWheel(parent = PNL_form, id = -1)
1791 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
1792
1793
1794 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
1795 STT_address_number.SetForegroundColour('orange')
1796 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
1797 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
1798
1799
1800 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
1801 STT_town.SetForegroundColour('orange')
1802 self.PRW_town = gmPersonContactWidgets.cUrbPhraseWheel(parent = PNL_form, id = -1)
1803 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
1804
1805
1806 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
1807 STT_state.SetForegroundColour('orange')
1808 self.PRW_state = gmPersonContactWidgets.cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
1809 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
1810
1811
1812 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
1813 STT_country.SetForegroundColour('orange')
1814 self.PRW_country = gmPersonContactWidgets.cCountryPhraseWheel(parent = PNL_form, id = -1)
1815 self.PRW_country.SetToolTipString(_("primary/home address: country"))
1816
1817
1818 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
1819 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
1820 self.TTC_phone.SetToolTipString(_("phone number at home"))
1821
1822
1823 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1824 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
1825
1826
1827 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
1828 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
1829 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
1830
1831
1832 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
1833 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
1834
1835
1836 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
1837 SZR_input.AddGrowableCol(1)
1838 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
1839 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
1840 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
1841 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
1842 SZR_input.Add(STT_nick, 0, wx.SHAPED)
1843 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
1844 SZR_input.Add(STT_dob, 0, wx.SHAPED)
1845 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
1846 SZR_input.Add(STT_gender, 0, wx.SHAPED)
1847 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
1848 SZR_input.Add(STT_title, 0, wx.SHAPED)
1849 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
1850 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
1851 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
1852 SZR_input.Add(STT_street, 0, wx.SHAPED)
1853 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
1854 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
1855 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
1856 SZR_input.Add(STT_town, 0, wx.SHAPED)
1857 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
1858 SZR_input.Add(STT_state, 0, wx.SHAPED)
1859 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
1860 SZR_input.Add(STT_country, 0, wx.SHAPED)
1861 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
1862 SZR_input.Add(STT_phone, 0, wx.SHAPED)
1863 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
1864 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1865 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1866 SZR_input.Add(STT_comment, 0, wx.SHAPED)
1867 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
1868
1869 PNL_form.SetSizerAndFit(SZR_input)
1870
1871
1872 SZR_main = makePageTitle(self, self.__title)
1873 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1874
1875
1876
1881
1882 - def on_country_selected(self, data):
1883 """Set the states according to entered country."""
1884 self.PRW_state.set_context(context=u'country', val=data)
1885 return True
1886
1887 - def on_name_set(self):
1888 """Set the gender according to entered firstname.
1889
1890 Matches are fetched from existing records in backend.
1891 """
1892 firstname = self.PRW_firstname.GetValue().strip()
1893 rows, idx = gmPG2.run_ro_queries(queries = [{
1894 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
1895 'args': [firstname]
1896 }])
1897 if len(rows) == 0:
1898 return True
1899 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
1900 return True
1901
1902 - def on_zip_set(self):
1903 """Set the street, town, state and country according to entered zip code."""
1904 zip_code = self.PRW_zip_code.GetValue().strip()
1905 self.PRW_street.set_context(context=u'zip', val=zip_code)
1906 self.PRW_town.set_context(context=u'zip', val=zip_code)
1907 self.PRW_state.set_context(context=u'zip', val=zip_code)
1908 self.PRW_country.set_context(context=u'zip', val=zip_code)
1909 return True
1910
1911 -def makePageTitle(wizPg, title):
1912 """
1913 Utility function to create the main sizer of a wizard's page.
1914
1915 @param wizPg The wizard page widget
1916 @type wizPg A wx.WizardPageSimple instance
1917 @param title The wizard page's descriptive title
1918 @type title A StringType instance
1919 """
1920 sizer = wx.BoxSizer(wx.VERTICAL)
1921 wizPg.SetSizer(sizer)
1922 title = wx.StaticText(wizPg, -1, title)
1923 title.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD))
1924 sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 2)
1925 sizer.Add(wx.StaticLine(wizPg, -1), 0, wx.EXPAND|wx.ALL, 2)
1926 return sizer
1927
1929 """
1930 Wizard to create a new patient.
1931
1932 TODO:
1933 - write pages for different "themes" of patient creation
1934 - make it configurable which pages are loaded
1935 - make available sets of pages that apply to a country
1936 - make loading of some pages depend upon values in earlier pages, eg
1937 when the patient is female and older than 13 include a page about
1938 "female" data (number of kids etc)
1939
1940 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
1941 """
1942
1943 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
1944 """
1945 Creates a new instance of NewPatientWizard
1946 @param parent - The parent widget
1947 @type parent - A wx.Window instance
1948 """
1949 id_wiz = wx.NewId()
1950 wx.wizard.Wizard.__init__(self, parent, id_wiz, title)
1951 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
1952 self.__subtitle = subtitle
1953 self.__do_layout()
1954
1956 """Create new patient.
1957
1958 activate, too, if told to do so (and patient successfully created)
1959 """
1960 while True:
1961
1962 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
1963 return False
1964
1965 try:
1966
1967 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
1968 except:
1969 _log.exception('cannot add new patient - missing identity fields')
1970 gmGuiHelpers.gm_show_error (
1971 _('Cannot create new patient.\n'
1972 'Missing parts of the identity.'
1973 ),
1974 _('Adding new patient')
1975 )
1976 continue
1977
1978 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
1979
1980 try:
1981 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
1982 except:
1983 _log.exception('cannot finalize new patient - missing address fields')
1984 gmGuiHelpers.gm_show_error (
1985 _('Cannot add address for the new patient.\n'
1986 'You must either enter all of the address fields or\n'
1987 'none at all. The relevant fields are marked in yellow.\n'
1988 '\n'
1989 'You will need to add the address details in the\n'
1990 'demographics module.'
1991 ),
1992 _('Adding new patient')
1993 )
1994 break
1995
1996 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
1997
1998 break
1999
2000 if activate:
2001 from Gnumed.wxpython import gmPatSearchWidgets
2002 gmPatSearchWidgets.set_active_patient(patient = ident)
2003
2004 return ident
2005
2006
2007
2009 """Arrange widgets.
2010 """
2011
2012 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2013 self.FitToPage(self.basic_pat_details)
2014
2015
2017 """
2018 This validator is used to ensure that the user has entered all
2019 the required conditional values in the page (eg., to properly
2020 create an address, all the related fields must be filled).
2021 """
2022
2023 - def __init__(self, dtd):
2024 """
2025 Validator initialization.
2026 @param dtd The object containing the data model.
2027 @type dtd A cFormDTD instance
2028 """
2029
2030 wx.PyValidator.__init__(self)
2031
2032 self.form_DTD = dtd
2033
2035 """
2036 Standard cloner.
2037 Note that every validator must implement the Clone() method.
2038 """
2039 return cBasicPatDetailsPageValidator(dtd = self.form_DTD)
2040
2041 - def Validate(self, parent = None):
2042 """
2043 Validate the contents of the given text control.
2044 """
2045 _pnl_form = self.GetWindow().GetParent()
2046
2047 error = False
2048
2049
2050 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2051 error = True
2052 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2053 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2054 _pnl_form.PRW_lastname.Refresh()
2055 else:
2056 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2057 _pnl_form.PRW_lastname.Refresh()
2058
2059 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2060 error = True
2061 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2062 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2063 _pnl_form.PRW_firstname.Refresh()
2064 else:
2065 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2066 _pnl_form.PRW_firstname.Refresh()
2067
2068
2069 if _pnl_form.PRW_gender.GetData() is None:
2070 error = True
2071 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2072 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2073 _pnl_form.PRW_gender.Refresh()
2074 else:
2075 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2076 _pnl_form.PRW_gender.Refresh()
2077
2078
2079 if (
2080 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2081 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2082 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2083 ):
2084 error = True
2085 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
2086 gmDispatcher.send(signal = 'statustext', msg = msg)
2087 _pnl_form.PRW_dob.SetBackgroundColour('pink')
2088 _pnl_form.PRW_dob.Refresh()
2089 else:
2090 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2091 _pnl_form.PRW_dob.Refresh()
2092
2093
2094 is_any_field_filled = False
2095 address_fields = (
2096 _pnl_form.TTC_address_number,
2097 _pnl_form.PRW_zip_code,
2098 _pnl_form.PRW_street,
2099 _pnl_form.PRW_town
2100 )
2101 for field in address_fields:
2102 if field.GetValue().strip() == u'':
2103 if is_any_field_filled:
2104 error = True
2105 msg = _('To properly create an address, all the related fields must be filled in.')
2106 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2107 field.SetBackgroundColour('pink')
2108 field.SetFocus()
2109 field.Refresh()
2110 else:
2111 is_any_field_filled = True
2112 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2113 field.Refresh()
2114
2115 address_fields = (
2116 _pnl_form.PRW_state,
2117 _pnl_form.PRW_country
2118 )
2119 for field in address_fields:
2120 if field.GetData() is None:
2121 if is_any_field_filled:
2122 error = True
2123 msg = _('To properly create an address, all the related fields must be filled in.')
2124 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2125 field.SetBackgroundColour('pink')
2126 field.SetFocus()
2127 field.Refresh()
2128 else:
2129 is_any_field_filled = True
2130 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2131 field.Refresh()
2132
2133 return (not error)
2134
2135 - def TransferToWindow(self):
2136 """
2137 Transfer data from validator to window.
2138 The default implementation returns False, indicating that an error
2139 occurred. We simply return True, as we don't do any data transfer.
2140 """
2141 _pnl_form = self.GetWindow().GetParent()
2142
2143 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
2144 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
2145 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
2146 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
2147 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
2148 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
2149 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
2150 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
2151 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
2152 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
2153 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
2154 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
2155 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
2156 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
2157 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
2158 return True
2159
2161 """
2162 Transfer data from window to validator.
2163 The default implementation returns False, indicating that an error
2164 occurred. We simply return True, as we don't do any data transfer.
2165 """
2166
2167 if not self.GetWindow().GetParent().Validate():
2168 return False
2169 try:
2170 _pnl_form = self.GetWindow().GetParent()
2171
2172 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
2173 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
2174
2175 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
2176 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
2177 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
2178 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
2179
2180 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
2181
2182 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
2183 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
2184 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
2185 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
2186 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
2187 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
2188
2189 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
2190
2191 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
2192 except:
2193 return False
2194 return True
2195
2197 """
2198 Utility class to test the new patient wizard.
2199 """
2200
2202 """
2203 Create a new instance of TestPanel.
2204 @param parent The parent widget
2205 @type parent A wx.Window instance
2206 """
2207 wx.Panel.__init__(self, parent, id)
2208 wizard = cNewPatientWizard(self)
2209 print wizard.RunWizard()
2210
2211 if __name__ == "__main__":
2212
2213
2215 app = wx.PyWidgetTester(size = (600, 400))
2216 app.SetWidget(cKOrganizerSchedulePnl)
2217 app.MainLoop()
2218
2220 app = wx.PyWidgetTester(size = (600, 400))
2221 widget = cPersonNamesManagerPnl(app.frame, -1)
2222 widget.identity = activate_patient()
2223 app.frame.Show(True)
2224 app.MainLoop()
2225
2227 app = wx.PyWidgetTester(size = (600, 400))
2228 widget = cPersonIDsManagerPnl(app.frame, -1)
2229 widget.identity = activate_patient()
2230 app.frame.Show(True)
2231 app.MainLoop()
2232
2234 app = wx.PyWidgetTester(size = (600, 400))
2235 widget = cPersonIdentityManagerPnl(app.frame, -1)
2236 widget.identity = activate_patient()
2237 app.frame.Show(True)
2238 app.MainLoop()
2239
2244
2251
2253 app = wx.PyWidgetTester(size = (600, 400))
2254 widget = cPersonDemographicsEditorNb(app.frame, -1)
2255 widget.identity = activate_patient()
2256 widget.refresh()
2257 app.frame.Show(True)
2258 app.MainLoop()
2259
2268
2269 if len(sys.argv) > 1 and sys.argv[1] == 'test':
2270
2271 gmI18N.activate_locale()
2272 gmI18N.install_domain(domain='gnumed')
2273 gmPG2.get_connection()
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290 test_urb_prw()
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308