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
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):
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)
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():
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):
852
853
854
856 self._LCTRL_items.set_columns(columns = [
857 _('ID type'),
858 _('Value'),
859 _('Issuer'),
860 _('Context'),
861 _('Comment')
862 ])
863
874
885
887 go_ahead = gmGuiHelpers.gm_show_question (
888 _( 'Do you really want to delete this\n'
889 'external ID from the patient ?'),
890 _('Deleting external ID')
891 )
892 if not go_ahead:
893 return False
894 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
895 return True
896
897
898
900 return self.__identity
901
905
906 identity = property(_get_identity, _set_identity)
907
908
909
910 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl
911
913 """A panel for editing identity data for a person.
914
915 - provides access to:
916 - name
917 - external IDs
918
919 Does NOT act on/listen to the current patient.
920 """
927
928
929
931 self._PNL_names.identity = self.__identity
932 self._PNL_ids.identity = self.__identity
933
934 self._PNL_identity.mode = 'new'
935 self._PNL_identity.data = self.__identity
936 if self.__identity is not None:
937 self._PNL_identity.mode = 'edit'
938
939
940
942 return self.__identity
943
947
948 identity = property(_get_identity, _set_identity)
949
950
951
955
958
959
960 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl
961
969
970
971
973
974 tt = _("Link another person in this database as the emergency contact.")
975
976 if self.__identity is None:
977 self._TCTRL_er_contact.SetValue(u'')
978 self._TCTRL_person.person = None
979 self._TCTRL_person.SetToolTipString(tt)
980 return
981
982 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u''))
983 if self.__identity['pk_emergency_contact'] is not None:
984 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact'])
985 self._TCTRL_person.person = ident
986 tt = u'%s\n\n%s\n\n%s' % (
987 tt,
988 ident['description_gender'],
989 u'\n'.join([
990 u'%s: %s%s' % (
991 c['l10n_comm_type'],
992 c['url'],
993 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'')
994 )
995 for c in ident.get_comm_channels()
996 ])
997 )
998 else:
999 self._TCTRL_person.person = None
1000
1001 self._TCTRL_person.SetToolTipString(tt)
1002
1003
1004
1006 return self.__identity
1007
1011
1012 identity = property(_get_identity, _set_identity)
1013
1014
1015
1024
1035
1043
1044
1045
1047
1048 dbcfg = gmCfg.cCfgSQL()
1049
1050 def_region = dbcfg.get2 (
1051 option = u'person.create.default_region',
1052 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1053 bias = u'user'
1054 )
1055 def_country = None
1056
1057 if def_region is None:
1058 def_country = dbcfg.get2 (
1059 option = u'person.create.default_country',
1060 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1061 bias = u'user'
1062 )
1063 else:
1064 countries = gmDemographicRecord.get_country_for_region(region = def_region)
1065 if len(countries) == 1:
1066 def_country = countries[0]['l10n_country']
1067
1068 if parent is None:
1069 parent = wx.GetApp().GetTopWindow()
1070
1071 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
1072 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
1073 dlg.SetTitle(_('Adding new person'))
1074 ea._PRW_lastname.SetFocus()
1075 result = dlg.ShowModal()
1076 pat = ea.data
1077 dlg.Destroy()
1078
1079 if result != wx.ID_OK:
1080 return False
1081
1082 _log.debug('created new person [%s]', pat.ID)
1083
1084 if activate:
1085 from Gnumed.wxpython import gmPatSearchWidgets
1086 gmPatSearchWidgets.set_active_patient(patient = pat)
1087
1088 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
1089
1090 return True
1091
1092 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
1093
1094 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1095
1097
1098 try:
1099 self.default_region = kwargs['region']
1100 del kwargs['region']
1101 except KeyError:
1102 self.default_region = None
1103
1104 try:
1105 self.default_country = kwargs['country']
1106 del kwargs['country']
1107 except KeyError:
1108 self.default_country = None
1109
1110 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
1111 gmEditArea.cGenericEditAreaMixin.__init__(self)
1112
1113 self.mode = 'new'
1114 self.data = None
1115 self._address = None
1116
1117 self.__init_ui()
1118 self.__register_interests()
1119
1120
1121
1123 self._PRW_lastname.final_regex = '.+'
1124 self._PRW_firstnames.final_regex = '.+'
1125 self._PRW_address_searcher.selection_only = False
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136 if self.default_country is not None:
1137 self._PRW_country.SetText(value = self.default_country)
1138
1139 if self.default_region is not None:
1140 self._PRW_region.SetText(value = self.default_region)
1141
1143
1144 adr = self._PRW_address_searcher.get_address()
1145 if adr is None:
1146 return True
1147
1148 if ctrl.GetValue().strip() != adr[field]:
1149 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
1150 return True
1151
1152 return False
1153
1155 adr = self._PRW_address_searcher.get_address()
1156 if adr is None:
1157 return True
1158
1159 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
1160
1161 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
1162 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
1163
1164 self._TCTRL_number.SetValue(adr['number'])
1165
1166 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
1167 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
1168
1169 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
1170 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
1171
1172 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
1173 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
1174
1176 error = False
1177
1178
1179 if self._PRW_lastname.GetValue().strip() == u'':
1180 error = True
1181 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
1182 self._PRW_lastname.display_as_valid(False)
1183 else:
1184 self._PRW_lastname.display_as_valid(True)
1185
1186 if self._PRW_firstnames.GetValue().strip() == '':
1187 error = True
1188 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
1189 self._PRW_firstnames.display_as_valid(False)
1190 else:
1191 self._PRW_firstnames.display_as_valid(True)
1192
1193
1194 if self._PRW_gender.GetData() is None:
1195 error = True
1196 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
1197 self._PRW_gender.display_as_valid(False)
1198 else:
1199 self._PRW_gender.display_as_valid(True)
1200
1201
1202 dob = self._DP_dob.GetValue(as_pydt = False, invalid_as_none = True)
1203
1204 if self._DP_dob.is_valid_timestamp(allow_none = False):
1205
1206 msg = None
1207 if (dob.GetYear() < 1900):
1208 msg = _(
1209 'DOB: %s\n'
1210 '\n'
1211 'While this is a valid point in time Python does\n'
1212 'not know how to deal with it.\n'
1213 '\n'
1214 'We suggest using January 1st 1901 instead and adding\n'
1215 'the true date of birth to the patient comment.\n'
1216 '\n'
1217 'Sorry for the inconvenience %s'
1218 ) % (dob, gmTools.u_frowning_face)
1219 elif dob > gmDateTime.wx_now_here(wx = wx):
1220 msg = _(
1221 'DOB: %s\n'
1222 '\n'
1223 'Date of birth in the future !'
1224 ) % dob
1225
1226 if msg is not None:
1227 error = True
1228 gmGuiHelpers.gm_show_error (
1229 msg,
1230 _('Registering new person')
1231 )
1232 self._DP_dob.display_as_valid(False)
1233 self._DP_dob.SetFocus()
1234
1235
1236 else:
1237 allow_empty_dob = gmGuiHelpers.gm_show_question (
1238 _(
1239 'Are you sure you want to register this person\n'
1240 'without a valid date of birth ?\n'
1241 '\n'
1242 'This can be useful for temporary staff members\n'
1243 'but will provoke nag screens if this person\n'
1244 'becomes a patient.\n'
1245 ),
1246 _('Registering new person')
1247 )
1248 if allow_empty_dob:
1249 self._DP_dob.display_as_valid(True)
1250 else:
1251 error = True
1252 self._DP_dob.SetFocus()
1253
1254
1255
1256
1257 return (not error)
1258
1260
1261
1262 if self._PRW_address_searcher.GetData() is not None:
1263 wx.CallAfter(self.__set_fields_from_address_searcher)
1264 return True
1265
1266
1267 fields_to_fill = (
1268 self._TCTRL_number,
1269 self._PRW_zip,
1270 self._PRW_street,
1271 self._PRW_urb,
1272 self._PRW_region,
1273 self._PRW_country
1274 )
1275 no_of_filled_fields = 0
1276
1277 for field in fields_to_fill:
1278 if field.GetValue().strip() != u'':
1279 no_of_filled_fields += 1
1280 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1281 field.Refresh()
1282
1283
1284 if no_of_filled_fields == 0:
1285 if empty_address_is_valid:
1286 return True
1287 else:
1288 return None
1289
1290
1291 if no_of_filled_fields != len(fields_to_fill):
1292 for field in fields_to_fill:
1293 if field.GetValue().strip() == u'':
1294 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1295 field.SetFocus()
1296 field.Refresh()
1297 msg = _('To properly create an address, all the related fields must be filled in.')
1298 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1299 return False
1300
1301
1302
1303
1304 strict_fields = (
1305 self._PRW_region,
1306 self._PRW_country
1307 )
1308 error = False
1309 for field in strict_fields:
1310 if field.GetData() is None:
1311 error = True
1312 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1313 field.SetFocus()
1314 else:
1315 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1316 field.Refresh()
1317
1318 if error:
1319 msg = _('This field must contain an item selected from the dropdown list.')
1320 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1321 return False
1322
1323 return True
1324
1341
1342
1343
1345 """Set the gender according to entered firstname.
1346
1347 Matches are fetched from existing records in backend.
1348 """
1349
1350
1351 if self._PRW_gender.GetData() is not None:
1352 return True
1353
1354 firstname = self._PRW_firstnames.GetValue().strip()
1355 if firstname == u'':
1356 return True
1357
1358 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
1359 if gender is None:
1360 return True
1361
1362 wx.CallAfter(self._PRW_gender.SetData, gender)
1363 return True
1364
1366 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
1367
1368 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
1369 self._PRW_street.set_context(context = u'zip', val = zip_code)
1370 self._PRW_urb.set_context(context = u'zip', val = zip_code)
1371 self._PRW_region.set_context(context = u'zip', val = zip_code)
1372 self._PRW_country.set_context(context = u'zip', val = zip_code)
1373
1374 return True
1375
1377 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
1378
1379 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
1380 self._PRW_region.set_context(context = u'country', val = country)
1381
1382 return True
1383
1385 mapping = [
1386 (self._PRW_street, 'street'),
1387 (self._TCTRL_number, 'number'),
1388 (self._PRW_urb, 'urb'),
1389 (self._PRW_region, 'l10n_state')
1390 ]
1391
1392
1393 for ctrl, field in mapping:
1394 if self.__perhaps_invalidate_address_searcher(ctrl, field):
1395 return True
1396
1397 return True
1398
1400 adr = self._PRW_address_searcher.get_address()
1401 if adr is None:
1402 return True
1403
1404 wx.CallAfter(self.__set_fields_from_address_searcher)
1405 return True
1406
1407
1408
1410 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
1411
1413
1414
1415 new_identity = gmPerson.create_identity (
1416 gender = self._PRW_gender.GetData(),
1417 dob = self._DP_dob.get_pydt(),
1418 lastnames = self._PRW_lastname.GetValue().strip(),
1419 firstnames = self._PRW_firstnames.GetValue().strip()
1420 )
1421 _log.debug('identity created: %s' % new_identity)
1422
1423 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
1424 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
1425
1426 new_identity.save()
1427
1428 name = new_identity.get_active_name()
1429 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1430 name.save()
1431
1432
1433 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
1434 if is_valid is True:
1435
1436
1437 try:
1438 new_identity.link_address (
1439 number = self._TCTRL_number.GetValue().strip(),
1440 street = self._PRW_street.GetValue().strip(),
1441 postcode = self._PRW_zip.GetValue().strip(),
1442 urb = self._PRW_urb.GetValue().strip(),
1443 state = self._PRW_region.GetData(),
1444 country = self._PRW_country.GetData()
1445 )
1446 except gmPG2.dbapi.InternalError:
1447 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
1448 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
1449 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
1450 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
1451 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
1452 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
1453 _log.exception('cannot link address')
1454 gmGuiHelpers.gm_show_error (
1455 aTitle = _('Saving address'),
1456 aMessage = _(
1457 'Cannot save this address.\n'
1458 '\n'
1459 'You will have to add it via the Demographics plugin.\n'
1460 )
1461 )
1462 elif is_valid is False:
1463 gmGuiHelpers.gm_show_error (
1464 aTitle = _('Saving address'),
1465 aMessage = _(
1466 'Address not saved.\n'
1467 '\n'
1468 'You will have to add it via the Demographics plugin.\n'
1469 )
1470 )
1471
1472
1473
1474 new_identity.link_comm_channel (
1475 comm_medium = u'homephone',
1476 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
1477 is_confidential = False
1478 )
1479
1480
1481 pk_type = self._PRW_external_id_type.GetData()
1482 id_value = self._TCTRL_external_id_value.GetValue().strip()
1483 if (pk_type is not None) and (id_value != u''):
1484 new_identity.add_external_id(value = id_value, pk_type = pk_type)
1485
1486
1487 new_identity.link_occupation (
1488 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
1489 )
1490
1491 self.data = new_identity
1492 return True
1493
1495 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1496
1500
1503
1505 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1506
1507
1508
1509 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
1510 """
1511 Wizard page for entering patient's basic demographic information
1512 """
1513
1514 form_fields = (
1515 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
1516 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
1517 )
1518
1519 - def __init__(self, parent, title):
1520 """
1521 Creates a new instance of BasicPatDetailsPage
1522 @param parent - The parent widget
1523 @type parent - A wx.Window instance
1524 @param tile - The title of the page
1525 @type title - A StringType instance
1526 """
1527 wx.wizard.WizardPageSimple.__init__(self, parent)
1528 self.__title = title
1529 self.__do_layout()
1530 self.__register_interests()
1531
1532 - def __do_layout(self):
1533 PNL_form = wx.Panel(self, -1)
1534
1535
1536 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
1537 STT_lastname.SetForegroundColour('red')
1538 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
1539 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
1540
1541
1542 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
1543 STT_firstname.SetForegroundColour('red')
1544 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
1545 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
1546
1547
1548 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
1549 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
1550
1551
1552 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
1553 STT_dob.SetForegroundColour('red')
1554 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
1555 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
1556
1557
1558 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
1559 STT_gender.SetForegroundColour('red')
1560 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
1561 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
1562
1563
1564 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
1565 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
1566
1567
1568 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
1569 STT_zip_code.SetForegroundColour('orange')
1570 self.PRW_zip_code = gmPersonContactWidgets.cZipcodePhraseWheel(parent = PNL_form, id = -1)
1571 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
1572
1573
1574 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
1575 STT_street.SetForegroundColour('orange')
1576 self.PRW_street = gmPersonContactWidgets.cStreetPhraseWheel(parent = PNL_form, id = -1)
1577 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
1578
1579
1580 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
1581 STT_address_number.SetForegroundColour('orange')
1582 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
1583 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
1584
1585
1586 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
1587 STT_town.SetForegroundColour('orange')
1588 self.PRW_town = gmPersonContactWidgets.cUrbPhraseWheel(parent = PNL_form, id = -1)
1589 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
1590
1591
1592 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
1593 STT_state.SetForegroundColour('orange')
1594 self.PRW_state = gmPersonContactWidgets.cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
1595 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
1596
1597
1598 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
1599 STT_country.SetForegroundColour('orange')
1600 self.PRW_country = gmPersonContactWidgets.cCountryPhraseWheel(parent = PNL_form, id = -1)
1601 self.PRW_country.SetToolTipString(_("primary/home address: country"))
1602
1603
1604 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
1605 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
1606 self.TTC_phone.SetToolTipString(_("phone number at home"))
1607
1608
1609 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
1610 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
1611
1612
1613 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
1614 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
1615 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
1616
1617
1618 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
1619 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
1620
1621
1622 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
1623 SZR_input.AddGrowableCol(1)
1624 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
1625 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
1626 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
1627 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
1628 SZR_input.Add(STT_nick, 0, wx.SHAPED)
1629 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
1630 SZR_input.Add(STT_dob, 0, wx.SHAPED)
1631 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
1632 SZR_input.Add(STT_gender, 0, wx.SHAPED)
1633 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
1634 SZR_input.Add(STT_title, 0, wx.SHAPED)
1635 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
1636 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
1637 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
1638 SZR_input.Add(STT_street, 0, wx.SHAPED)
1639 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
1640 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
1641 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
1642 SZR_input.Add(STT_town, 0, wx.SHAPED)
1643 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
1644 SZR_input.Add(STT_state, 0, wx.SHAPED)
1645 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
1646 SZR_input.Add(STT_country, 0, wx.SHAPED)
1647 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
1648 SZR_input.Add(STT_phone, 0, wx.SHAPED)
1649 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
1650 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
1651 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
1652 SZR_input.Add(STT_comment, 0, wx.SHAPED)
1653 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
1654
1655 PNL_form.SetSizerAndFit(SZR_input)
1656
1657
1658 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title)
1659 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1660
1661
1662
1667
1668 - def on_country_selected(self, data):
1669 """Set the states according to entered country."""
1670 self.PRW_state.set_context(context=u'country', val=data)
1671 return True
1672
1673 - def on_name_set(self):
1674 """Set the gender according to entered firstname.
1675
1676 Matches are fetched from existing records in backend.
1677 """
1678 firstname = self.PRW_firstname.GetValue().strip()
1679 rows, idx = gmPG2.run_ro_queries(queries = [{
1680 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
1681 'args': [firstname]
1682 }])
1683 if len(rows) == 0:
1684 return True
1685 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
1686 return True
1687
1688 - def on_zip_set(self):
1689 """Set the street, town, state and country according to entered zip code."""
1690 zip_code = self.PRW_zip_code.GetValue().strip()
1691 self.PRW_street.set_context(context=u'zip', val=zip_code)
1692 self.PRW_town.set_context(context=u'zip', val=zip_code)
1693 self.PRW_state.set_context(context=u'zip', val=zip_code)
1694 self.PRW_country.set_context(context=u'zip', val=zip_code)
1695 return True
1696
1698 """
1699 Wizard to create a new patient.
1700
1701 TODO:
1702 - write pages for different "themes" of patient creation
1703 - make it configurable which pages are loaded
1704 - make available sets of pages that apply to a country
1705 - make loading of some pages depend upon values in earlier pages, eg
1706 when the patient is female and older than 13 include a page about
1707 "female" data (number of kids etc)
1708
1709 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
1710 """
1711
1712 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
1713 """
1714 Creates a new instance of NewPatientWizard
1715 @param parent - The parent widget
1716 @type parent - A wx.Window instance
1717 """
1718 id_wiz = wx.NewId()
1719 wx.wizard.Wizard.__init__(self, parent, id_wiz, title)
1720 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
1721 self.__subtitle = subtitle
1722 self.__do_layout()
1723
1725 """Create new patient.
1726
1727 activate, too, if told to do so (and patient successfully created)
1728 """
1729 while True:
1730
1731 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
1732 return False
1733
1734 try:
1735
1736 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
1737 except:
1738 _log.exception('cannot add new patient - missing identity fields')
1739 gmGuiHelpers.gm_show_error (
1740 _('Cannot create new patient.\n'
1741 'Missing parts of the identity.'
1742 ),
1743 _('Adding new patient')
1744 )
1745 continue
1746
1747 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
1748
1749 try:
1750 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
1751 except:
1752 _log.exception('cannot finalize new patient - missing address fields')
1753 gmGuiHelpers.gm_show_error (
1754 _('Cannot add address for the new patient.\n'
1755 'You must either enter all of the address fields or\n'
1756 'none at all. The relevant fields are marked in yellow.\n'
1757 '\n'
1758 'You will need to add the address details in the\n'
1759 'demographics module.'
1760 ),
1761 _('Adding new patient')
1762 )
1763 break
1764
1765 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
1766
1767 break
1768
1769 if activate:
1770 from Gnumed.wxpython import gmPatSearchWidgets
1771 gmPatSearchWidgets.set_active_patient(patient = ident)
1772
1773 return ident
1774
1775
1776
1778 """Arrange widgets.
1779 """
1780
1781 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
1782 self.FitToPage(self.basic_pat_details)
1783
1784
1786 """
1787 This validator is used to ensure that the user has entered all
1788 the required conditional values in the page (eg., to properly
1789 create an address, all the related fields must be filled).
1790 """
1791
1792 - def __init__(self, dtd):
1793 """
1794 Validator initialization.
1795 @param dtd The object containing the data model.
1796 @type dtd A cFormDTD instance
1797 """
1798
1799 wx.PyValidator.__init__(self)
1800
1801 self.form_DTD = dtd
1802
1804 """
1805 Standard cloner.
1806 Note that every validator must implement the Clone() method.
1807 """
1808 return cBasicPatDetailsPageValidator(dtd = self.form_DTD)
1809
1810 - def Validate(self, parent = None):
1811 """
1812 Validate the contents of the given text control.
1813 """
1814 _pnl_form = self.GetWindow().GetParent()
1815
1816 error = False
1817
1818
1819 if _pnl_form.PRW_lastname.GetValue().strip() == '':
1820 error = True
1821 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
1822 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
1823 _pnl_form.PRW_lastname.Refresh()
1824 else:
1825 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1826 _pnl_form.PRW_lastname.Refresh()
1827
1828 if _pnl_form.PRW_firstname.GetValue().strip() == '':
1829 error = True
1830 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
1831 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
1832 _pnl_form.PRW_firstname.Refresh()
1833 else:
1834 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1835 _pnl_form.PRW_firstname.Refresh()
1836
1837
1838 if _pnl_form.PRW_gender.GetData() is None:
1839 error = True
1840 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
1841 _pnl_form.PRW_gender.SetBackgroundColour('pink')
1842 _pnl_form.PRW_gender.Refresh()
1843 else:
1844 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1845 _pnl_form.PRW_gender.Refresh()
1846
1847
1848 if (
1849 (_pnl_form.PRW_dob.GetValue().strip() == u'')
1850 or (not _pnl_form.PRW_dob.is_valid_timestamp())
1851 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
1852 ):
1853 error = True
1854 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
1855 gmDispatcher.send(signal = 'statustext', msg = msg)
1856 _pnl_form.PRW_dob.SetBackgroundColour('pink')
1857 _pnl_form.PRW_dob.Refresh()
1858 else:
1859 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1860 _pnl_form.PRW_dob.Refresh()
1861
1862
1863 is_any_field_filled = False
1864 address_fields = (
1865 _pnl_form.TTC_address_number,
1866 _pnl_form.PRW_zip_code,
1867 _pnl_form.PRW_street,
1868 _pnl_form.PRW_town
1869 )
1870 for field in address_fields:
1871 if field.GetValue().strip() == u'':
1872 if is_any_field_filled:
1873 error = True
1874 msg = _('To properly create an address, all the related fields must be filled in.')
1875 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1876 field.SetBackgroundColour('pink')
1877 field.SetFocus()
1878 field.Refresh()
1879 else:
1880 is_any_field_filled = True
1881 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1882 field.Refresh()
1883
1884 address_fields = (
1885 _pnl_form.PRW_state,
1886 _pnl_form.PRW_country
1887 )
1888 for field in address_fields:
1889 if field.GetData() is None:
1890 if is_any_field_filled:
1891 error = True
1892 msg = _('To properly create an address, all the related fields must be filled in.')
1893 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
1894 field.SetBackgroundColour('pink')
1895 field.SetFocus()
1896 field.Refresh()
1897 else:
1898 is_any_field_filled = True
1899 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1900 field.Refresh()
1901
1902 return (not error)
1903
1904 - def TransferToWindow(self):
1905 """
1906 Transfer data from validator to window.
1907 The default implementation returns False, indicating that an error
1908 occurred. We simply return True, as we don't do any data transfer.
1909 """
1910 _pnl_form = self.GetWindow().GetParent()
1911
1912 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
1913 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
1914 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
1915 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
1916 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
1917 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
1918 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
1919 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
1920 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
1921 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
1922 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
1923 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
1924 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
1925 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
1926 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
1927 return True
1928
1930 """
1931 Transfer data from window to validator.
1932 The default implementation returns False, indicating that an error
1933 occurred. We simply return True, as we don't do any data transfer.
1934 """
1935
1936 if not self.GetWindow().GetParent().Validate():
1937 return False
1938 try:
1939 _pnl_form = self.GetWindow().GetParent()
1940
1941 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
1942 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
1943
1944 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
1945 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
1946 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
1947 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
1948
1949 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
1950
1951 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
1952 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
1953 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
1954 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
1955 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
1956 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
1957
1958 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
1959
1960 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
1961 except:
1962 return False
1963 return True
1964
1965
1966
1968 """Notebook displaying demographics editing pages:
1969
1970 - Identity
1971 - Contacts (addresses, phone numbers, etc)
1972 - Social Network (significant others, GP, etc)
1973
1974 Does NOT act on/listen to the current patient.
1975 """
1976
1978
1979 wx.Notebook.__init__ (
1980 self,
1981 parent = parent,
1982 id = id,
1983 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1984 name = self.__class__.__name__
1985 )
1986
1987 self.__identity = None
1988 self.__do_layout()
1989 self.SetSelection(0)
1990
1991
1992
1994 """Populate fields in pages with data from model."""
1995 for page_idx in range(self.GetPageCount()):
1996 page = self.GetPage(page_idx)
1997 page.identity = self.__identity
1998
1999 return True
2000
2001
2002
2032
2033
2034
2036 return self.__identity
2037
2040
2041 identity = property(_get_identity, _set_identity)
2042
2043
2044
2045
2046
2047
2049 """Page containing patient occupations edition fields.
2050 """
2051 - def __init__(self, parent, id, ident=None):
2052 """
2053 Creates a new instance of BasicPatDetailsPage
2054 @param parent - The parent widget
2055 @type parent - A wx.Window instance
2056 @param id - The widget id
2057 @type id - An integer
2058 """
2059 wx.Panel.__init__(self, parent, id)
2060 self.__ident = ident
2061 self.__do_layout()
2062
2064 PNL_form = wx.Panel(self, -1)
2065
2066 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2067 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2068 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
2069
2070 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
2071 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
2072
2073
2074 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
2075 SZR_input.AddGrowableCol(1)
2076 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2077 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2078 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
2079 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
2080 PNL_form.SetSizerAndFit(SZR_input)
2081
2082
2083 SZR_main = wx.BoxSizer(wx.VERTICAL)
2084 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2085 self.SetSizer(SZR_main)
2086
2089
2090 - def refresh(self, identity=None):
2091 if identity is not None:
2092 self.__ident = identity
2093 jobs = self.__ident.get_occupations()
2094 if len(jobs) > 0:
2095 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
2096 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
2097 return True
2098
2100 if self.PRW_occupation.IsModified():
2101 new_job = self.PRW_occupation.GetValue().strip()
2102 jobs = self.__ident.get_occupations()
2103 for job in jobs:
2104 if job['l10n_occupation'] == new_job:
2105 continue
2106 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
2107 self.__ident.link_occupation(occupation = new_job)
2108 return True
2109
2111 """Patient demographics plugin for main notebook.
2112
2113 Hosts another notebook with pages for Identity, Contacts, etc.
2114
2115 Acts on/listens to the currently active patient.
2116 """
2117
2123
2124
2125
2126
2127
2128
2130 """Arrange widgets."""
2131 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
2132
2133 szr_main = wx.BoxSizer(wx.VERTICAL)
2134 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
2135 self.SetSizerAndFit(szr_main)
2136
2137
2138
2140 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
2141 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
2142
2144 self._schedule_data_reget()
2145
2147 self._schedule_data_reget()
2148
2149
2150
2160
2162 """
2163 Utility class to test the new patient wizard.
2164 """
2165
2167 """
2168 Create a new instance of TestPanel.
2169 @param parent The parent widget
2170 @type parent A wx.Window instance
2171 """
2172 wx.Panel.__init__(self, parent, id)
2173 wizard = cNewPatientWizard(self)
2174 print wizard.RunWizard()
2175
2176 if __name__ == "__main__":
2177
2178
2180 app = wx.PyWidgetTester(size = (600, 400))
2181 app.SetWidget(cKOrganizerSchedulePnl)
2182 app.MainLoop()
2183
2185 app = wx.PyWidgetTester(size = (600, 400))
2186 widget = cPersonNamesManagerPnl(app.frame, -1)
2187 widget.identity = activate_patient()
2188 app.frame.Show(True)
2189 app.MainLoop()
2190
2192 app = wx.PyWidgetTester(size = (600, 400))
2193 widget = cPersonIDsManagerPnl(app.frame, -1)
2194 widget.identity = activate_patient()
2195 app.frame.Show(True)
2196 app.MainLoop()
2197
2199 app = wx.PyWidgetTester(size = (600, 400))
2200 widget = cPersonIdentityManagerPnl(app.frame, -1)
2201 widget.identity = activate_patient()
2202 app.frame.Show(True)
2203 app.MainLoop()
2204
2209
2216
2218 app = wx.PyWidgetTester(size = (600, 400))
2219 widget = cPersonDemographicsEditorNb(app.frame, -1)
2220 widget.identity = activate_patient()
2221 widget.refresh()
2222 app.frame.Show(True)
2223 app.MainLoop()
2224
2233
2234 if len(sys.argv) > 1 and sys.argv[1] == 'test':
2235
2236 gmI18N.activate_locale()
2237 gmI18N.install_domain(domain='gnumed')
2238 gmPG2.get_connection()
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255 test_urb_prw()
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273