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